extjs.js 5.3 MB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412204132041420415204162041720418204192042020421204222042320424204252042620427204282042920430204312043220433204342043520436204372043820439204402044120442204432044420445204462044720448204492045020451204522045320454204552045620457204582045920460204612046220463204642046520466204672046820469204702047120472204732047420475204762047720478204792048020481204822048320484204852048620487204882048920490204912049220493204942049520496204972049820499205002050120502205032050420505205062050720508205092051020511205122051320514205152051620517205182051920520205212052220523205242052520526205272052820529205302053120532205332053420535205362053720538205392054020541205422054320544205452054620547205482054920550205512055220553205542055520556205572055820559205602056120562205632056420565205662056720568205692057020571205722057320574205752057620577205782057920580205812058220583205842058520586205872058820589205902059120592205932059420595205962059720598205992060020601206022060320604206052060620607206082060920610206112061220613206142061520616206172061820619206202062120622206232062420625206262062720628206292063020631206322063320634206352063620637206382063920640206412064220643206442064520646206472064820649206502065120652206532065420655206562065720658206592066020661206622066320664206652066620667206682066920670206712067220673206742067520676206772067820679206802068120682206832068420685206862068720688206892069020691206922069320694206952069620697206982069920700207012070220703207042070520706207072070820709207102071120712207132071420715207162071720718207192072020721207222072320724207252072620727207282072920730207312073220733207342073520736207372073820739207402074120742207432074420745207462074720748207492075020751207522075320754207552075620757207582075920760207612076220763207642076520766207672076820769207702077120772207732077420775207762077720778207792078020781207822078320784207852078620787207882078920790207912079220793207942079520796207972079820799208002080120802208032080420805208062080720808208092081020811208122081320814208152081620817208182081920820208212082220823208242082520826208272082820829208302083120832208332083420835208362083720838208392084020841208422084320844208452084620847208482084920850208512085220853208542085520856208572085820859208602086120862208632086420865208662086720868208692087020871208722087320874208752087620877208782087920880208812088220883208842088520886208872088820889208902089120892208932089420895208962089720898208992090020901209022090320904209052090620907209082090920910209112091220913209142091520916209172091820919209202092120922209232092420925209262092720928209292093020931209322093320934209352093620937209382093920940209412094220943209442094520946209472094820949209502095120952209532095420955209562095720958209592096020961209622096320964209652096620967209682096920970209712097220973209742097520976209772097820979209802098120982209832098420985209862098720988209892099020991209922099320994209952099620997209982099921000210012100221003210042100521006210072100821009210102101121012210132101421015210162101721018210192102021021210222102321024210252102621027210282102921030210312103221033210342103521036210372103821039210402104121042210432104421045210462104721048210492105021051210522105321054210552105621057210582105921060210612106221063210642106521066210672106821069210702107121072210732107421075210762107721078210792108021081210822108321084210852108621087210882108921090210912109221093210942109521096210972109821099211002110121102211032110421105211062110721108211092111021111211122111321114211152111621117211182111921120211212112221123211242112521126211272112821129211302113121132211332113421135211362113721138211392114021141211422114321144211452114621147211482114921150211512115221153211542115521156211572115821159211602116121162211632116421165211662116721168211692117021171211722117321174211752117621177211782117921180211812118221183211842118521186211872118821189211902119121192211932119421195211962119721198211992120021201212022120321204212052120621207212082120921210212112121221213212142121521216212172121821219212202122121222212232122421225212262122721228212292123021231212322123321234212352123621237212382123921240212412124221243212442124521246212472124821249212502125121252212532125421255212562125721258212592126021261212622126321264212652126621267212682126921270212712127221273212742127521276212772127821279212802128121282212832128421285212862128721288212892129021291212922129321294212952129621297212982129921300213012130221303213042130521306213072130821309213102131121312213132131421315213162131721318213192132021321213222132321324213252132621327213282132921330213312133221333213342133521336213372133821339213402134121342213432134421345213462134721348213492135021351213522135321354213552135621357213582135921360213612136221363213642136521366213672136821369213702137121372213732137421375213762137721378213792138021381213822138321384213852138621387213882138921390213912139221393213942139521396213972139821399214002140121402214032140421405214062140721408214092141021411214122141321414214152141621417214182141921420214212142221423214242142521426214272142821429214302143121432214332143421435214362143721438214392144021441214422144321444214452144621447214482144921450214512145221453214542145521456214572145821459214602146121462214632146421465214662146721468214692147021471214722147321474214752147621477214782147921480214812148221483214842148521486214872148821489214902149121492214932149421495214962149721498214992150021501215022150321504215052150621507215082150921510215112151221513215142151521516215172151821519215202152121522215232152421525215262152721528215292153021531215322153321534215352153621537215382153921540215412154221543215442154521546215472154821549215502155121552215532155421555215562155721558215592156021561215622156321564215652156621567215682156921570215712157221573215742157521576215772157821579215802158121582215832158421585215862158721588215892159021591215922159321594215952159621597215982159921600216012160221603216042160521606216072160821609216102161121612216132161421615216162161721618216192162021621216222162321624216252162621627216282162921630216312163221633216342163521636216372163821639216402164121642216432164421645216462164721648216492165021651216522165321654216552165621657216582165921660216612166221663216642166521666216672166821669216702167121672216732167421675216762167721678216792168021681216822168321684216852168621687216882168921690216912169221693216942169521696216972169821699217002170121702217032170421705217062170721708217092171021711217122171321714217152171621717217182171921720217212172221723217242172521726217272172821729217302173121732217332173421735217362173721738217392174021741217422174321744217452174621747217482174921750217512175221753217542175521756217572175821759217602176121762217632176421765217662176721768217692177021771217722177321774217752177621777217782177921780217812178221783217842178521786217872178821789217902179121792217932179421795217962179721798217992180021801218022180321804218052180621807218082180921810218112181221813218142181521816218172181821819218202182121822218232182421825218262182721828218292183021831218322183321834218352183621837218382183921840218412184221843218442184521846218472184821849218502185121852218532185421855218562185721858218592186021861218622186321864218652186621867218682186921870218712187221873218742187521876218772187821879218802188121882218832188421885218862188721888218892189021891218922189321894218952189621897218982189921900219012190221903219042190521906219072190821909219102191121912219132191421915219162191721918219192192021921219222192321924219252192621927219282192921930219312193221933219342193521936219372193821939219402194121942219432194421945219462194721948219492195021951219522195321954219552195621957219582195921960219612196221963219642196521966219672196821969219702197121972219732197421975219762197721978219792198021981219822198321984219852198621987219882198921990219912199221993219942199521996219972199821999220002200122002220032200422005220062200722008220092201022011220122201322014220152201622017220182201922020220212202222023220242202522026220272202822029220302203122032220332203422035220362203722038220392204022041220422204322044220452204622047220482204922050220512205222053220542205522056220572205822059220602206122062220632206422065220662206722068220692207022071220722207322074220752207622077220782207922080220812208222083220842208522086220872208822089220902209122092220932209422095220962209722098220992210022101221022210322104221052210622107221082210922110221112211222113221142211522116221172211822119221202212122122221232212422125221262212722128221292213022131221322213322134221352213622137221382213922140221412214222143221442214522146221472214822149221502215122152221532215422155221562215722158221592216022161221622216322164221652216622167221682216922170221712217222173221742217522176221772217822179221802218122182221832218422185221862218722188221892219022191221922219322194221952219622197221982219922200222012220222203222042220522206222072220822209222102221122212222132221422215222162221722218222192222022221222222222322224222252222622227222282222922230222312223222233222342223522236222372223822239222402224122242222432224422245222462224722248222492225022251222522225322254222552225622257222582225922260222612226222263222642226522266222672226822269222702227122272222732227422275222762227722278222792228022281222822228322284222852228622287222882228922290222912229222293222942229522296222972229822299223002230122302223032230422305223062230722308223092231022311223122231322314223152231622317223182231922320223212232222323223242232522326223272232822329223302233122332223332233422335223362233722338223392234022341223422234322344223452234622347223482234922350223512235222353223542235522356223572235822359223602236122362223632236422365223662236722368223692237022371223722237322374223752237622377223782237922380223812238222383223842238522386223872238822389223902239122392223932239422395223962239722398223992240022401224022240322404224052240622407224082240922410224112241222413224142241522416224172241822419224202242122422224232242422425224262242722428224292243022431224322243322434224352243622437224382243922440224412244222443224442244522446224472244822449224502245122452224532245422455224562245722458224592246022461224622246322464224652246622467224682246922470224712247222473224742247522476224772247822479224802248122482224832248422485224862248722488224892249022491224922249322494224952249622497224982249922500225012250222503225042250522506225072250822509225102251122512225132251422515225162251722518225192252022521225222252322524225252252622527225282252922530225312253222533225342253522536225372253822539225402254122542225432254422545225462254722548225492255022551225522255322554225552255622557225582255922560225612256222563225642256522566225672256822569225702257122572225732257422575225762257722578225792258022581225822258322584225852258622587225882258922590225912259222593225942259522596225972259822599226002260122602226032260422605226062260722608226092261022611226122261322614226152261622617226182261922620226212262222623226242262522626226272262822629226302263122632226332263422635226362263722638226392264022641226422264322644226452264622647226482264922650226512265222653226542265522656226572265822659226602266122662226632266422665226662266722668226692267022671226722267322674226752267622677226782267922680226812268222683226842268522686226872268822689226902269122692226932269422695226962269722698226992270022701227022270322704227052270622707227082270922710227112271222713227142271522716227172271822719227202272122722227232272422725227262272722728227292273022731227322273322734227352273622737227382273922740227412274222743227442274522746227472274822749227502275122752227532275422755227562275722758227592276022761227622276322764227652276622767227682276922770227712277222773227742277522776227772277822779227802278122782227832278422785227862278722788227892279022791227922279322794227952279622797227982279922800228012280222803228042280522806228072280822809228102281122812228132281422815228162281722818228192282022821228222282322824228252282622827228282282922830228312283222833228342283522836228372283822839228402284122842228432284422845228462284722848228492285022851228522285322854228552285622857228582285922860228612286222863228642286522866228672286822869228702287122872228732287422875228762287722878228792288022881228822288322884228852288622887228882288922890228912289222893228942289522896228972289822899229002290122902229032290422905229062290722908229092291022911229122291322914229152291622917229182291922920229212292222923229242292522926229272292822929229302293122932229332293422935229362293722938229392294022941229422294322944229452294622947229482294922950229512295222953229542295522956229572295822959229602296122962229632296422965229662296722968229692297022971229722297322974229752297622977229782297922980229812298222983229842298522986229872298822989229902299122992229932299422995229962299722998229992300023001230022300323004230052300623007230082300923010230112301223013230142301523016230172301823019230202302123022230232302423025230262302723028230292303023031230322303323034230352303623037230382303923040230412304223043230442304523046230472304823049230502305123052230532305423055230562305723058230592306023061230622306323064230652306623067230682306923070230712307223073230742307523076230772307823079230802308123082230832308423085230862308723088230892309023091230922309323094230952309623097230982309923100231012310223103231042310523106231072310823109231102311123112231132311423115231162311723118231192312023121231222312323124231252312623127231282312923130231312313223133231342313523136231372313823139231402314123142231432314423145231462314723148231492315023151231522315323154231552315623157231582315923160231612316223163231642316523166231672316823169231702317123172231732317423175231762317723178231792318023181231822318323184231852318623187231882318923190231912319223193231942319523196231972319823199232002320123202232032320423205232062320723208232092321023211232122321323214232152321623217232182321923220232212322223223232242322523226232272322823229232302323123232232332323423235232362323723238232392324023241232422324323244232452324623247232482324923250232512325223253232542325523256232572325823259232602326123262232632326423265232662326723268232692327023271232722327323274232752327623277232782327923280232812328223283232842328523286232872328823289232902329123292232932329423295232962329723298232992330023301233022330323304233052330623307233082330923310233112331223313233142331523316233172331823319233202332123322233232332423325233262332723328233292333023331233322333323334233352333623337233382333923340233412334223343233442334523346233472334823349233502335123352233532335423355233562335723358233592336023361233622336323364233652336623367233682336923370233712337223373233742337523376233772337823379233802338123382233832338423385233862338723388233892339023391233922339323394233952339623397233982339923400234012340223403234042340523406234072340823409234102341123412234132341423415234162341723418234192342023421234222342323424234252342623427234282342923430234312343223433234342343523436234372343823439234402344123442234432344423445234462344723448234492345023451234522345323454234552345623457234582345923460234612346223463234642346523466234672346823469234702347123472234732347423475234762347723478234792348023481234822348323484234852348623487234882348923490234912349223493234942349523496234972349823499235002350123502235032350423505235062350723508235092351023511235122351323514235152351623517235182351923520235212352223523235242352523526235272352823529235302353123532235332353423535235362353723538235392354023541235422354323544235452354623547235482354923550235512355223553235542355523556235572355823559235602356123562235632356423565235662356723568235692357023571235722357323574235752357623577235782357923580235812358223583235842358523586235872358823589235902359123592235932359423595235962359723598235992360023601236022360323604236052360623607236082360923610236112361223613236142361523616236172361823619236202362123622236232362423625236262362723628236292363023631236322363323634236352363623637236382363923640236412364223643236442364523646236472364823649236502365123652236532365423655236562365723658236592366023661236622366323664236652366623667236682366923670236712367223673236742367523676236772367823679236802368123682236832368423685236862368723688236892369023691236922369323694236952369623697236982369923700237012370223703237042370523706237072370823709237102371123712237132371423715237162371723718237192372023721237222372323724237252372623727237282372923730237312373223733237342373523736237372373823739237402374123742237432374423745237462374723748237492375023751237522375323754237552375623757237582375923760237612376223763237642376523766237672376823769237702377123772237732377423775237762377723778237792378023781237822378323784237852378623787237882378923790237912379223793237942379523796237972379823799238002380123802238032380423805238062380723808238092381023811238122381323814238152381623817238182381923820238212382223823238242382523826238272382823829238302383123832238332383423835238362383723838238392384023841238422384323844238452384623847238482384923850238512385223853238542385523856238572385823859238602386123862238632386423865238662386723868238692387023871238722387323874238752387623877238782387923880238812388223883238842388523886238872388823889238902389123892238932389423895238962389723898238992390023901239022390323904239052390623907239082390923910239112391223913239142391523916239172391823919239202392123922239232392423925239262392723928239292393023931239322393323934239352393623937239382393923940239412394223943239442394523946239472394823949239502395123952239532395423955239562395723958239592396023961239622396323964239652396623967239682396923970239712397223973239742397523976239772397823979239802398123982239832398423985239862398723988239892399023991239922399323994239952399623997239982399924000240012400224003240042400524006240072400824009240102401124012240132401424015240162401724018240192402024021240222402324024240252402624027240282402924030240312403224033240342403524036240372403824039240402404124042240432404424045240462404724048240492405024051240522405324054240552405624057240582405924060240612406224063240642406524066240672406824069240702407124072240732407424075240762407724078240792408024081240822408324084240852408624087240882408924090240912409224093240942409524096240972409824099241002410124102241032410424105241062410724108241092411024111241122411324114241152411624117241182411924120241212412224123241242412524126241272412824129241302413124132241332413424135241362413724138241392414024141241422414324144241452414624147241482414924150241512415224153241542415524156241572415824159241602416124162241632416424165241662416724168241692417024171241722417324174241752417624177241782417924180241812418224183241842418524186241872418824189241902419124192241932419424195241962419724198241992420024201242022420324204242052420624207242082420924210242112421224213242142421524216242172421824219242202422124222242232422424225242262422724228242292423024231242322423324234242352423624237242382423924240242412424224243242442424524246242472424824249242502425124252242532425424255242562425724258242592426024261242622426324264242652426624267242682426924270242712427224273242742427524276242772427824279242802428124282242832428424285242862428724288242892429024291242922429324294242952429624297242982429924300243012430224303243042430524306243072430824309243102431124312243132431424315243162431724318243192432024321243222432324324243252432624327243282432924330243312433224333243342433524336243372433824339243402434124342243432434424345243462434724348243492435024351243522435324354243552435624357243582435924360243612436224363243642436524366243672436824369243702437124372243732437424375243762437724378243792438024381243822438324384243852438624387243882438924390243912439224393243942439524396243972439824399244002440124402244032440424405244062440724408244092441024411244122441324414244152441624417244182441924420244212442224423244242442524426244272442824429244302443124432244332443424435244362443724438244392444024441244422444324444244452444624447244482444924450244512445224453244542445524456244572445824459244602446124462244632446424465244662446724468244692447024471244722447324474244752447624477244782447924480244812448224483244842448524486244872448824489244902449124492244932449424495244962449724498244992450024501245022450324504245052450624507245082450924510245112451224513245142451524516245172451824519245202452124522245232452424525245262452724528245292453024531245322453324534245352453624537245382453924540245412454224543245442454524546245472454824549245502455124552245532455424555245562455724558245592456024561245622456324564245652456624567245682456924570245712457224573245742457524576245772457824579245802458124582245832458424585245862458724588245892459024591245922459324594245952459624597245982459924600246012460224603246042460524606246072460824609246102461124612246132461424615246162461724618246192462024621246222462324624246252462624627246282462924630246312463224633246342463524636246372463824639246402464124642246432464424645246462464724648246492465024651246522465324654246552465624657246582465924660246612466224663246642466524666246672466824669246702467124672246732467424675246762467724678246792468024681246822468324684246852468624687246882468924690246912469224693246942469524696246972469824699247002470124702247032470424705247062470724708247092471024711247122471324714247152471624717247182471924720247212472224723247242472524726247272472824729247302473124732247332473424735247362473724738247392474024741247422474324744247452474624747247482474924750247512475224753247542475524756247572475824759247602476124762247632476424765247662476724768247692477024771247722477324774247752477624777247782477924780247812478224783247842478524786247872478824789247902479124792247932479424795247962479724798247992480024801248022480324804248052480624807248082480924810248112481224813248142481524816248172481824819248202482124822248232482424825248262482724828248292483024831248322483324834248352483624837248382483924840248412484224843248442484524846248472484824849248502485124852248532485424855248562485724858248592486024861248622486324864248652486624867248682486924870248712487224873248742487524876248772487824879248802488124882248832488424885248862488724888248892489024891248922489324894248952489624897248982489924900249012490224903249042490524906249072490824909249102491124912249132491424915249162491724918249192492024921249222492324924249252492624927249282492924930249312493224933249342493524936249372493824939249402494124942249432494424945249462494724948249492495024951249522495324954249552495624957249582495924960249612496224963249642496524966249672496824969249702497124972249732497424975249762497724978249792498024981249822498324984249852498624987249882498924990249912499224993249942499524996249972499824999250002500125002250032500425005250062500725008250092501025011250122501325014250152501625017250182501925020250212502225023250242502525026250272502825029250302503125032250332503425035250362503725038250392504025041250422504325044250452504625047250482504925050250512505225053250542505525056250572505825059250602506125062250632506425065250662506725068250692507025071250722507325074250752507625077250782507925080250812508225083250842508525086250872508825089250902509125092250932509425095250962509725098250992510025101251022510325104251052510625107251082510925110251112511225113251142511525116251172511825119251202512125122251232512425125251262512725128251292513025131251322513325134251352513625137251382513925140251412514225143251442514525146251472514825149251502515125152251532515425155251562515725158251592516025161251622516325164251652516625167251682516925170251712517225173251742517525176251772517825179251802518125182251832518425185251862518725188251892519025191251922519325194251952519625197251982519925200252012520225203252042520525206252072520825209252102521125212252132521425215252162521725218252192522025221252222522325224252252522625227252282522925230252312523225233252342523525236252372523825239252402524125242252432524425245252462524725248252492525025251252522525325254252552525625257252582525925260252612526225263252642526525266252672526825269252702527125272252732527425275252762527725278252792528025281252822528325284252852528625287252882528925290252912529225293252942529525296252972529825299253002530125302253032530425305253062530725308253092531025311253122531325314253152531625317253182531925320253212532225323253242532525326253272532825329253302533125332253332533425335253362533725338253392534025341253422534325344253452534625347253482534925350253512535225353253542535525356253572535825359253602536125362253632536425365253662536725368253692537025371253722537325374253752537625377253782537925380253812538225383253842538525386253872538825389253902539125392253932539425395253962539725398253992540025401254022540325404254052540625407254082540925410254112541225413254142541525416254172541825419254202542125422254232542425425254262542725428254292543025431254322543325434254352543625437254382543925440254412544225443254442544525446254472544825449254502545125452254532545425455254562545725458254592546025461254622546325464254652546625467254682546925470254712547225473254742547525476254772547825479254802548125482254832548425485254862548725488254892549025491254922549325494254952549625497254982549925500255012550225503255042550525506255072550825509255102551125512255132551425515255162551725518255192552025521255222552325524255252552625527255282552925530255312553225533255342553525536255372553825539255402554125542255432554425545255462554725548255492555025551255522555325554255552555625557255582555925560255612556225563255642556525566255672556825569255702557125572255732557425575255762557725578255792558025581255822558325584255852558625587255882558925590255912559225593255942559525596255972559825599256002560125602256032560425605256062560725608256092561025611256122561325614256152561625617256182561925620256212562225623256242562525626256272562825629256302563125632256332563425635256362563725638256392564025641256422564325644256452564625647256482564925650256512565225653256542565525656256572565825659256602566125662256632566425665256662566725668256692567025671256722567325674256752567625677256782567925680256812568225683256842568525686256872568825689256902569125692256932569425695256962569725698256992570025701257022570325704257052570625707257082570925710257112571225713257142571525716257172571825719257202572125722257232572425725257262572725728257292573025731257322573325734257352573625737257382573925740257412574225743257442574525746257472574825749257502575125752257532575425755257562575725758257592576025761257622576325764257652576625767257682576925770257712577225773257742577525776257772577825779257802578125782257832578425785257862578725788257892579025791257922579325794257952579625797257982579925800258012580225803258042580525806258072580825809258102581125812258132581425815258162581725818258192582025821258222582325824258252582625827258282582925830258312583225833258342583525836258372583825839258402584125842258432584425845258462584725848258492585025851258522585325854258552585625857258582585925860258612586225863258642586525866258672586825869258702587125872258732587425875258762587725878258792588025881258822588325884258852588625887258882588925890258912589225893258942589525896258972589825899259002590125902259032590425905259062590725908259092591025911259122591325914259152591625917259182591925920259212592225923259242592525926259272592825929259302593125932259332593425935259362593725938259392594025941259422594325944259452594625947259482594925950259512595225953259542595525956259572595825959259602596125962259632596425965259662596725968259692597025971259722597325974259752597625977259782597925980259812598225983259842598525986259872598825989259902599125992259932599425995259962599725998259992600026001260022600326004260052600626007260082600926010260112601226013260142601526016260172601826019260202602126022260232602426025260262602726028260292603026031260322603326034260352603626037260382603926040260412604226043260442604526046260472604826049260502605126052260532605426055260562605726058260592606026061260622606326064260652606626067260682606926070260712607226073260742607526076260772607826079260802608126082260832608426085260862608726088260892609026091260922609326094260952609626097260982609926100261012610226103261042610526106261072610826109261102611126112261132611426115261162611726118261192612026121261222612326124261252612626127261282612926130261312613226133261342613526136261372613826139261402614126142261432614426145261462614726148261492615026151261522615326154261552615626157261582615926160261612616226163261642616526166261672616826169261702617126172261732617426175261762617726178261792618026181261822618326184261852618626187261882618926190261912619226193261942619526196261972619826199262002620126202262032620426205262062620726208262092621026211262122621326214262152621626217262182621926220262212622226223262242622526226262272622826229262302623126232262332623426235262362623726238262392624026241262422624326244262452624626247262482624926250262512625226253262542625526256262572625826259262602626126262262632626426265262662626726268262692627026271262722627326274262752627626277262782627926280262812628226283262842628526286262872628826289262902629126292262932629426295262962629726298262992630026301263022630326304263052630626307263082630926310263112631226313263142631526316263172631826319263202632126322263232632426325263262632726328263292633026331263322633326334263352633626337263382633926340263412634226343263442634526346263472634826349263502635126352263532635426355263562635726358263592636026361263622636326364263652636626367263682636926370263712637226373263742637526376263772637826379263802638126382263832638426385263862638726388263892639026391263922639326394263952639626397263982639926400264012640226403264042640526406264072640826409264102641126412264132641426415264162641726418264192642026421264222642326424264252642626427264282642926430264312643226433264342643526436264372643826439264402644126442264432644426445264462644726448264492645026451264522645326454264552645626457264582645926460264612646226463264642646526466264672646826469264702647126472264732647426475264762647726478264792648026481264822648326484264852648626487264882648926490264912649226493264942649526496264972649826499265002650126502265032650426505265062650726508265092651026511265122651326514265152651626517265182651926520265212652226523265242652526526265272652826529265302653126532265332653426535265362653726538265392654026541265422654326544265452654626547265482654926550265512655226553265542655526556265572655826559265602656126562265632656426565265662656726568265692657026571265722657326574265752657626577265782657926580265812658226583265842658526586265872658826589265902659126592265932659426595265962659726598265992660026601266022660326604266052660626607266082660926610266112661226613266142661526616266172661826619266202662126622266232662426625266262662726628266292663026631266322663326634266352663626637266382663926640266412664226643266442664526646266472664826649266502665126652266532665426655266562665726658266592666026661266622666326664266652666626667266682666926670266712667226673266742667526676266772667826679266802668126682266832668426685266862668726688266892669026691266922669326694266952669626697266982669926700267012670226703267042670526706267072670826709267102671126712267132671426715267162671726718267192672026721267222672326724267252672626727267282672926730267312673226733267342673526736267372673826739267402674126742267432674426745267462674726748267492675026751267522675326754267552675626757267582675926760267612676226763267642676526766267672676826769267702677126772267732677426775267762677726778267792678026781267822678326784267852678626787267882678926790267912679226793267942679526796267972679826799268002680126802268032680426805268062680726808268092681026811268122681326814268152681626817268182681926820268212682226823268242682526826268272682826829268302683126832268332683426835268362683726838268392684026841268422684326844268452684626847268482684926850268512685226853268542685526856268572685826859268602686126862268632686426865268662686726868268692687026871268722687326874268752687626877268782687926880268812688226883268842688526886268872688826889268902689126892268932689426895268962689726898268992690026901269022690326904269052690626907269082690926910269112691226913269142691526916269172691826919269202692126922269232692426925269262692726928269292693026931269322693326934269352693626937269382693926940269412694226943269442694526946269472694826949269502695126952269532695426955269562695726958269592696026961269622696326964269652696626967269682696926970269712697226973269742697526976269772697826979269802698126982269832698426985269862698726988269892699026991269922699326994269952699626997269982699927000270012700227003270042700527006270072700827009270102701127012270132701427015270162701727018270192702027021270222702327024270252702627027270282702927030270312703227033270342703527036270372703827039270402704127042270432704427045270462704727048270492705027051270522705327054270552705627057270582705927060270612706227063270642706527066270672706827069270702707127072270732707427075270762707727078270792708027081270822708327084270852708627087270882708927090270912709227093270942709527096270972709827099271002710127102271032710427105271062710727108271092711027111271122711327114271152711627117271182711927120271212712227123271242712527126271272712827129271302713127132271332713427135271362713727138271392714027141271422714327144271452714627147271482714927150271512715227153271542715527156271572715827159271602716127162271632716427165271662716727168271692717027171271722717327174271752717627177271782717927180271812718227183271842718527186271872718827189271902719127192271932719427195271962719727198271992720027201272022720327204272052720627207272082720927210272112721227213272142721527216272172721827219272202722127222272232722427225272262722727228272292723027231272322723327234272352723627237272382723927240272412724227243272442724527246272472724827249272502725127252272532725427255272562725727258272592726027261272622726327264272652726627267272682726927270272712727227273272742727527276272772727827279272802728127282272832728427285272862728727288272892729027291272922729327294272952729627297272982729927300273012730227303273042730527306273072730827309273102731127312273132731427315273162731727318273192732027321273222732327324273252732627327273282732927330273312733227333273342733527336273372733827339273402734127342273432734427345273462734727348273492735027351273522735327354273552735627357273582735927360273612736227363273642736527366273672736827369273702737127372273732737427375273762737727378273792738027381273822738327384273852738627387273882738927390273912739227393273942739527396273972739827399274002740127402274032740427405274062740727408274092741027411274122741327414274152741627417274182741927420274212742227423274242742527426274272742827429274302743127432274332743427435274362743727438274392744027441274422744327444274452744627447274482744927450274512745227453274542745527456274572745827459274602746127462274632746427465274662746727468274692747027471274722747327474274752747627477274782747927480274812748227483274842748527486274872748827489274902749127492274932749427495274962749727498274992750027501275022750327504275052750627507275082750927510275112751227513275142751527516275172751827519275202752127522275232752427525275262752727528275292753027531275322753327534275352753627537275382753927540275412754227543275442754527546275472754827549275502755127552275532755427555275562755727558275592756027561275622756327564275652756627567275682756927570275712757227573275742757527576275772757827579275802758127582275832758427585275862758727588275892759027591275922759327594275952759627597275982759927600276012760227603276042760527606276072760827609276102761127612276132761427615276162761727618276192762027621276222762327624276252762627627276282762927630276312763227633276342763527636276372763827639276402764127642276432764427645276462764727648276492765027651276522765327654276552765627657276582765927660276612766227663276642766527666276672766827669276702767127672276732767427675276762767727678276792768027681276822768327684276852768627687276882768927690276912769227693276942769527696276972769827699277002770127702277032770427705277062770727708277092771027711277122771327714277152771627717277182771927720277212772227723277242772527726277272772827729277302773127732277332773427735277362773727738277392774027741277422774327744277452774627747277482774927750277512775227753277542775527756277572775827759277602776127762277632776427765277662776727768277692777027771277722777327774277752777627777277782777927780277812778227783277842778527786277872778827789277902779127792277932779427795277962779727798277992780027801278022780327804278052780627807278082780927810278112781227813278142781527816278172781827819278202782127822278232782427825278262782727828278292783027831278322783327834278352783627837278382783927840278412784227843278442784527846278472784827849278502785127852278532785427855278562785727858278592786027861278622786327864278652786627867278682786927870278712787227873278742787527876278772787827879278802788127882278832788427885278862788727888278892789027891278922789327894278952789627897278982789927900279012790227903279042790527906279072790827909279102791127912279132791427915279162791727918279192792027921279222792327924279252792627927279282792927930279312793227933279342793527936279372793827939279402794127942279432794427945279462794727948279492795027951279522795327954279552795627957279582795927960279612796227963279642796527966279672796827969279702797127972279732797427975279762797727978279792798027981279822798327984279852798627987279882798927990279912799227993279942799527996279972799827999280002800128002280032800428005280062800728008280092801028011280122801328014280152801628017280182801928020280212802228023280242802528026280272802828029280302803128032280332803428035280362803728038280392804028041280422804328044280452804628047280482804928050280512805228053280542805528056280572805828059280602806128062280632806428065280662806728068280692807028071280722807328074280752807628077280782807928080280812808228083280842808528086280872808828089280902809128092280932809428095280962809728098280992810028101281022810328104281052810628107281082810928110281112811228113281142811528116281172811828119281202812128122281232812428125281262812728128281292813028131281322813328134281352813628137281382813928140281412814228143281442814528146281472814828149281502815128152281532815428155281562815728158281592816028161281622816328164281652816628167281682816928170281712817228173281742817528176281772817828179281802818128182281832818428185281862818728188281892819028191281922819328194281952819628197281982819928200282012820228203282042820528206282072820828209282102821128212282132821428215282162821728218282192822028221282222822328224282252822628227282282822928230282312823228233282342823528236282372823828239282402824128242282432824428245282462824728248282492825028251282522825328254282552825628257282582825928260282612826228263282642826528266282672826828269282702827128272282732827428275282762827728278282792828028281282822828328284282852828628287282882828928290282912829228293282942829528296282972829828299283002830128302283032830428305283062830728308283092831028311283122831328314283152831628317283182831928320283212832228323283242832528326283272832828329283302833128332283332833428335283362833728338283392834028341283422834328344283452834628347283482834928350283512835228353283542835528356283572835828359283602836128362283632836428365283662836728368283692837028371283722837328374283752837628377283782837928380283812838228383283842838528386283872838828389283902839128392283932839428395283962839728398283992840028401284022840328404284052840628407284082840928410284112841228413284142841528416284172841828419284202842128422284232842428425284262842728428284292843028431284322843328434284352843628437284382843928440284412844228443284442844528446284472844828449284502845128452284532845428455284562845728458284592846028461284622846328464284652846628467284682846928470284712847228473284742847528476284772847828479284802848128482284832848428485284862848728488284892849028491284922849328494284952849628497284982849928500285012850228503285042850528506285072850828509285102851128512285132851428515285162851728518285192852028521285222852328524285252852628527285282852928530285312853228533285342853528536285372853828539285402854128542285432854428545285462854728548285492855028551285522855328554285552855628557285582855928560285612856228563285642856528566285672856828569285702857128572285732857428575285762857728578285792858028581285822858328584285852858628587285882858928590285912859228593285942859528596285972859828599286002860128602286032860428605286062860728608286092861028611286122861328614286152861628617286182861928620286212862228623286242862528626286272862828629286302863128632286332863428635286362863728638286392864028641286422864328644286452864628647286482864928650286512865228653286542865528656286572865828659286602866128662286632866428665286662866728668286692867028671286722867328674286752867628677286782867928680286812868228683286842868528686286872868828689286902869128692286932869428695286962869728698286992870028701287022870328704287052870628707287082870928710287112871228713287142871528716287172871828719287202872128722287232872428725287262872728728287292873028731287322873328734287352873628737287382873928740287412874228743287442874528746287472874828749287502875128752287532875428755287562875728758287592876028761287622876328764287652876628767287682876928770287712877228773287742877528776287772877828779287802878128782287832878428785287862878728788287892879028791287922879328794287952879628797287982879928800288012880228803288042880528806288072880828809288102881128812288132881428815288162881728818288192882028821288222882328824288252882628827288282882928830288312883228833288342883528836288372883828839288402884128842288432884428845288462884728848288492885028851288522885328854288552885628857288582885928860288612886228863288642886528866288672886828869288702887128872288732887428875288762887728878288792888028881288822888328884288852888628887288882888928890288912889228893288942889528896288972889828899289002890128902289032890428905289062890728908289092891028911289122891328914289152891628917289182891928920289212892228923289242892528926289272892828929289302893128932289332893428935289362893728938289392894028941289422894328944289452894628947289482894928950289512895228953289542895528956289572895828959289602896128962289632896428965289662896728968289692897028971289722897328974289752897628977289782897928980289812898228983289842898528986289872898828989289902899128992289932899428995289962899728998289992900029001290022900329004290052900629007290082900929010290112901229013290142901529016290172901829019290202902129022290232902429025290262902729028290292903029031290322903329034290352903629037290382903929040290412904229043290442904529046290472904829049290502905129052290532905429055290562905729058290592906029061290622906329064290652906629067290682906929070290712907229073290742907529076290772907829079290802908129082290832908429085290862908729088290892909029091290922909329094290952909629097290982909929100291012910229103291042910529106291072910829109291102911129112291132911429115291162911729118291192912029121291222912329124291252912629127291282912929130291312913229133291342913529136291372913829139291402914129142291432914429145291462914729148291492915029151291522915329154291552915629157291582915929160291612916229163291642916529166291672916829169291702917129172291732917429175291762917729178291792918029181291822918329184291852918629187291882918929190291912919229193291942919529196291972919829199292002920129202292032920429205292062920729208292092921029211292122921329214292152921629217292182921929220292212922229223292242922529226292272922829229292302923129232292332923429235292362923729238292392924029241292422924329244292452924629247292482924929250292512925229253292542925529256292572925829259292602926129262292632926429265292662926729268292692927029271292722927329274292752927629277292782927929280292812928229283292842928529286292872928829289292902929129292292932929429295292962929729298292992930029301293022930329304293052930629307293082930929310293112931229313293142931529316293172931829319293202932129322293232932429325293262932729328293292933029331293322933329334293352933629337293382933929340293412934229343293442934529346293472934829349293502935129352293532935429355293562935729358293592936029361293622936329364293652936629367293682936929370293712937229373293742937529376293772937829379293802938129382293832938429385293862938729388293892939029391293922939329394293952939629397293982939929400294012940229403294042940529406294072940829409294102941129412294132941429415294162941729418294192942029421294222942329424294252942629427294282942929430294312943229433294342943529436294372943829439294402944129442294432944429445294462944729448294492945029451294522945329454294552945629457294582945929460294612946229463294642946529466294672946829469294702947129472294732947429475294762947729478294792948029481294822948329484294852948629487294882948929490294912949229493294942949529496294972949829499295002950129502295032950429505295062950729508295092951029511295122951329514295152951629517295182951929520295212952229523295242952529526295272952829529295302953129532295332953429535295362953729538295392954029541295422954329544295452954629547295482954929550295512955229553295542955529556295572955829559295602956129562295632956429565295662956729568295692957029571295722957329574295752957629577295782957929580295812958229583295842958529586295872958829589295902959129592295932959429595295962959729598295992960029601296022960329604296052960629607296082960929610296112961229613296142961529616296172961829619296202962129622296232962429625296262962729628296292963029631296322963329634296352963629637296382963929640296412964229643296442964529646296472964829649296502965129652296532965429655296562965729658296592966029661296622966329664296652966629667296682966929670296712967229673296742967529676296772967829679296802968129682296832968429685296862968729688296892969029691296922969329694296952969629697296982969929700297012970229703297042970529706297072970829709297102971129712297132971429715297162971729718297192972029721297222972329724297252972629727297282972929730297312973229733297342973529736297372973829739297402974129742297432974429745297462974729748297492975029751297522975329754297552975629757297582975929760297612976229763297642976529766297672976829769297702977129772297732977429775297762977729778297792978029781297822978329784297852978629787297882978929790297912979229793297942979529796297972979829799298002980129802298032980429805298062980729808298092981029811298122981329814298152981629817298182981929820298212982229823298242982529826298272982829829298302983129832298332983429835298362983729838298392984029841298422984329844298452984629847298482984929850298512985229853298542985529856298572985829859298602986129862298632986429865298662986729868298692987029871298722987329874298752987629877298782987929880298812988229883298842988529886298872988829889298902989129892298932989429895298962989729898298992990029901299022990329904299052990629907299082990929910299112991229913299142991529916299172991829919299202992129922299232992429925299262992729928299292993029931299322993329934299352993629937299382993929940299412994229943299442994529946299472994829949299502995129952299532995429955299562995729958299592996029961299622996329964299652996629967299682996929970299712997229973299742997529976299772997829979299802998129982299832998429985299862998729988299892999029991299922999329994299952999629997299982999930000300013000230003300043000530006300073000830009300103001130012300133001430015300163001730018300193002030021300223002330024300253002630027300283002930030300313003230033300343003530036300373003830039300403004130042300433004430045300463004730048300493005030051300523005330054300553005630057300583005930060300613006230063300643006530066300673006830069300703007130072300733007430075300763007730078300793008030081300823008330084300853008630087300883008930090300913009230093300943009530096300973009830099301003010130102301033010430105301063010730108301093011030111301123011330114301153011630117301183011930120301213012230123301243012530126301273012830129301303013130132301333013430135301363013730138301393014030141301423014330144301453014630147301483014930150301513015230153301543015530156301573015830159301603016130162301633016430165301663016730168301693017030171301723017330174301753017630177301783017930180301813018230183301843018530186301873018830189301903019130192301933019430195301963019730198301993020030201302023020330204302053020630207302083020930210302113021230213302143021530216302173021830219302203022130222302233022430225302263022730228302293023030231302323023330234302353023630237302383023930240302413024230243302443024530246302473024830249302503025130252302533025430255302563025730258302593026030261302623026330264302653026630267302683026930270302713027230273302743027530276302773027830279302803028130282302833028430285302863028730288302893029030291302923029330294302953029630297302983029930300303013030230303303043030530306303073030830309303103031130312303133031430315303163031730318303193032030321303223032330324303253032630327303283032930330303313033230333303343033530336303373033830339303403034130342303433034430345303463034730348303493035030351303523035330354303553035630357303583035930360303613036230363303643036530366303673036830369303703037130372303733037430375303763037730378303793038030381303823038330384303853038630387303883038930390303913039230393303943039530396303973039830399304003040130402304033040430405304063040730408304093041030411304123041330414304153041630417304183041930420304213042230423304243042530426304273042830429304303043130432304333043430435304363043730438304393044030441304423044330444304453044630447304483044930450304513045230453304543045530456304573045830459304603046130462304633046430465304663046730468304693047030471304723047330474304753047630477304783047930480304813048230483304843048530486304873048830489304903049130492304933049430495304963049730498304993050030501305023050330504305053050630507305083050930510305113051230513305143051530516305173051830519305203052130522305233052430525305263052730528305293053030531305323053330534305353053630537305383053930540305413054230543305443054530546305473054830549305503055130552305533055430555305563055730558305593056030561305623056330564305653056630567305683056930570305713057230573305743057530576305773057830579305803058130582305833058430585305863058730588305893059030591305923059330594305953059630597305983059930600306013060230603306043060530606306073060830609306103061130612306133061430615306163061730618306193062030621306223062330624306253062630627306283062930630306313063230633306343063530636306373063830639306403064130642306433064430645306463064730648306493065030651306523065330654306553065630657306583065930660306613066230663306643066530666306673066830669306703067130672306733067430675306763067730678306793068030681306823068330684306853068630687306883068930690306913069230693306943069530696306973069830699307003070130702307033070430705307063070730708307093071030711307123071330714307153071630717307183071930720307213072230723307243072530726307273072830729307303073130732307333073430735307363073730738307393074030741307423074330744307453074630747307483074930750307513075230753307543075530756307573075830759307603076130762307633076430765307663076730768307693077030771307723077330774307753077630777307783077930780307813078230783307843078530786307873078830789307903079130792307933079430795307963079730798307993080030801308023080330804308053080630807308083080930810308113081230813308143081530816308173081830819308203082130822308233082430825308263082730828308293083030831308323083330834308353083630837308383083930840308413084230843308443084530846308473084830849308503085130852308533085430855308563085730858308593086030861308623086330864308653086630867308683086930870308713087230873308743087530876308773087830879308803088130882308833088430885308863088730888308893089030891308923089330894308953089630897308983089930900309013090230903309043090530906309073090830909309103091130912309133091430915309163091730918309193092030921309223092330924309253092630927309283092930930309313093230933309343093530936309373093830939309403094130942309433094430945309463094730948309493095030951309523095330954309553095630957309583095930960309613096230963309643096530966309673096830969309703097130972309733097430975309763097730978309793098030981309823098330984309853098630987309883098930990309913099230993309943099530996309973099830999310003100131002310033100431005310063100731008310093101031011310123101331014310153101631017310183101931020310213102231023310243102531026310273102831029310303103131032310333103431035310363103731038310393104031041310423104331044310453104631047310483104931050310513105231053310543105531056310573105831059310603106131062310633106431065310663106731068310693107031071310723107331074310753107631077310783107931080310813108231083310843108531086310873108831089310903109131092310933109431095310963109731098310993110031101311023110331104311053110631107311083110931110311113111231113311143111531116311173111831119311203112131122311233112431125311263112731128311293113031131311323113331134311353113631137311383113931140311413114231143311443114531146311473114831149311503115131152311533115431155311563115731158311593116031161311623116331164311653116631167311683116931170311713117231173311743117531176311773117831179311803118131182311833118431185311863118731188311893119031191311923119331194311953119631197311983119931200312013120231203312043120531206312073120831209312103121131212312133121431215312163121731218312193122031221312223122331224312253122631227312283122931230312313123231233312343123531236312373123831239312403124131242312433124431245312463124731248312493125031251312523125331254312553125631257312583125931260312613126231263312643126531266312673126831269312703127131272312733127431275312763127731278312793128031281312823128331284312853128631287312883128931290312913129231293312943129531296312973129831299313003130131302313033130431305313063130731308313093131031311313123131331314313153131631317313183131931320313213132231323313243132531326313273132831329313303133131332313333133431335313363133731338313393134031341313423134331344313453134631347313483134931350313513135231353313543135531356313573135831359313603136131362313633136431365313663136731368313693137031371313723137331374313753137631377313783137931380313813138231383313843138531386313873138831389313903139131392313933139431395313963139731398313993140031401314023140331404314053140631407314083140931410314113141231413314143141531416314173141831419314203142131422314233142431425314263142731428314293143031431314323143331434314353143631437314383143931440314413144231443314443144531446314473144831449314503145131452314533145431455314563145731458314593146031461314623146331464314653146631467314683146931470314713147231473314743147531476314773147831479314803148131482314833148431485314863148731488314893149031491314923149331494314953149631497314983149931500315013150231503315043150531506315073150831509315103151131512315133151431515315163151731518315193152031521315223152331524315253152631527315283152931530315313153231533315343153531536315373153831539315403154131542315433154431545315463154731548315493155031551315523155331554315553155631557315583155931560315613156231563315643156531566315673156831569315703157131572315733157431575315763157731578315793158031581315823158331584315853158631587315883158931590315913159231593315943159531596315973159831599316003160131602316033160431605316063160731608316093161031611316123161331614316153161631617316183161931620316213162231623316243162531626316273162831629316303163131632316333163431635316363163731638316393164031641316423164331644316453164631647316483164931650316513165231653316543165531656316573165831659316603166131662316633166431665316663166731668316693167031671316723167331674316753167631677316783167931680316813168231683316843168531686316873168831689316903169131692316933169431695316963169731698316993170031701317023170331704317053170631707317083170931710317113171231713317143171531716317173171831719317203172131722317233172431725317263172731728317293173031731317323173331734317353173631737317383173931740317413174231743317443174531746317473174831749317503175131752317533175431755317563175731758317593176031761317623176331764317653176631767317683176931770317713177231773317743177531776317773177831779317803178131782317833178431785317863178731788317893179031791317923179331794317953179631797317983179931800318013180231803318043180531806318073180831809318103181131812318133181431815318163181731818318193182031821318223182331824318253182631827318283182931830318313183231833318343183531836318373183831839318403184131842318433184431845318463184731848318493185031851318523185331854318553185631857318583185931860318613186231863318643186531866318673186831869318703187131872318733187431875318763187731878318793188031881318823188331884318853188631887318883188931890318913189231893318943189531896318973189831899319003190131902319033190431905319063190731908319093191031911319123191331914319153191631917319183191931920319213192231923319243192531926319273192831929319303193131932319333193431935319363193731938319393194031941319423194331944319453194631947319483194931950319513195231953319543195531956319573195831959319603196131962319633196431965319663196731968319693197031971319723197331974319753197631977319783197931980319813198231983319843198531986319873198831989319903199131992319933199431995319963199731998319993200032001320023200332004320053200632007320083200932010320113201232013320143201532016320173201832019320203202132022320233202432025320263202732028320293203032031320323203332034320353203632037320383203932040320413204232043320443204532046320473204832049320503205132052320533205432055320563205732058320593206032061320623206332064320653206632067320683206932070320713207232073320743207532076320773207832079320803208132082320833208432085320863208732088320893209032091320923209332094320953209632097320983209932100321013210232103321043210532106321073210832109321103211132112321133211432115321163211732118321193212032121321223212332124321253212632127321283212932130321313213232133321343213532136321373213832139321403214132142321433214432145321463214732148321493215032151321523215332154321553215632157321583215932160321613216232163321643216532166321673216832169321703217132172321733217432175321763217732178321793218032181321823218332184321853218632187321883218932190321913219232193321943219532196321973219832199322003220132202322033220432205322063220732208322093221032211322123221332214322153221632217322183221932220322213222232223322243222532226322273222832229322303223132232322333223432235322363223732238322393224032241322423224332244322453224632247322483224932250322513225232253322543225532256322573225832259322603226132262322633226432265322663226732268322693227032271322723227332274322753227632277322783227932280322813228232283322843228532286322873228832289322903229132292322933229432295322963229732298322993230032301323023230332304323053230632307323083230932310323113231232313323143231532316323173231832319323203232132322323233232432325323263232732328323293233032331323323233332334323353233632337323383233932340323413234232343323443234532346323473234832349323503235132352323533235432355323563235732358323593236032361323623236332364323653236632367323683236932370323713237232373323743237532376323773237832379323803238132382323833238432385323863238732388323893239032391323923239332394323953239632397323983239932400324013240232403324043240532406324073240832409324103241132412324133241432415324163241732418324193242032421324223242332424324253242632427324283242932430324313243232433324343243532436324373243832439324403244132442324433244432445324463244732448324493245032451324523245332454324553245632457324583245932460324613246232463324643246532466324673246832469324703247132472324733247432475324763247732478324793248032481324823248332484324853248632487324883248932490324913249232493324943249532496324973249832499325003250132502325033250432505325063250732508325093251032511325123251332514325153251632517325183251932520325213252232523325243252532526325273252832529325303253132532325333253432535325363253732538325393254032541325423254332544325453254632547325483254932550325513255232553325543255532556325573255832559325603256132562325633256432565325663256732568325693257032571325723257332574325753257632577325783257932580325813258232583325843258532586325873258832589325903259132592325933259432595325963259732598325993260032601326023260332604326053260632607326083260932610326113261232613326143261532616326173261832619326203262132622326233262432625326263262732628326293263032631326323263332634326353263632637326383263932640326413264232643326443264532646326473264832649326503265132652326533265432655326563265732658326593266032661326623266332664326653266632667326683266932670326713267232673326743267532676326773267832679326803268132682326833268432685326863268732688326893269032691326923269332694326953269632697326983269932700327013270232703327043270532706327073270832709327103271132712327133271432715327163271732718327193272032721327223272332724327253272632727327283272932730327313273232733327343273532736327373273832739327403274132742327433274432745327463274732748327493275032751327523275332754327553275632757327583275932760327613276232763327643276532766327673276832769327703277132772327733277432775327763277732778327793278032781327823278332784327853278632787327883278932790327913279232793327943279532796327973279832799328003280132802328033280432805328063280732808328093281032811328123281332814328153281632817328183281932820328213282232823328243282532826328273282832829328303283132832328333283432835328363283732838328393284032841328423284332844328453284632847328483284932850328513285232853328543285532856328573285832859328603286132862328633286432865328663286732868328693287032871328723287332874328753287632877328783287932880328813288232883328843288532886328873288832889328903289132892328933289432895328963289732898328993290032901329023290332904329053290632907329083290932910329113291232913329143291532916329173291832919329203292132922329233292432925329263292732928329293293032931329323293332934329353293632937329383293932940329413294232943329443294532946329473294832949329503295132952329533295432955329563295732958329593296032961329623296332964329653296632967329683296932970329713297232973329743297532976329773297832979329803298132982329833298432985329863298732988329893299032991329923299332994329953299632997329983299933000330013300233003330043300533006330073300833009330103301133012330133301433015330163301733018330193302033021330223302333024330253302633027330283302933030330313303233033330343303533036330373303833039330403304133042330433304433045330463304733048330493305033051330523305333054330553305633057330583305933060330613306233063330643306533066330673306833069330703307133072330733307433075330763307733078330793308033081330823308333084330853308633087330883308933090330913309233093330943309533096330973309833099331003310133102331033310433105331063310733108331093311033111331123311333114331153311633117331183311933120331213312233123331243312533126331273312833129331303313133132331333313433135331363313733138331393314033141331423314333144331453314633147331483314933150331513315233153331543315533156331573315833159331603316133162331633316433165331663316733168331693317033171331723317333174331753317633177331783317933180331813318233183331843318533186331873318833189331903319133192331933319433195331963319733198331993320033201332023320333204332053320633207332083320933210332113321233213332143321533216332173321833219332203322133222332233322433225332263322733228332293323033231332323323333234332353323633237332383323933240332413324233243332443324533246332473324833249332503325133252332533325433255332563325733258332593326033261332623326333264332653326633267332683326933270332713327233273332743327533276332773327833279332803328133282332833328433285332863328733288332893329033291332923329333294332953329633297332983329933300333013330233303333043330533306333073330833309333103331133312333133331433315333163331733318333193332033321333223332333324333253332633327333283332933330333313333233333333343333533336333373333833339333403334133342333433334433345333463334733348333493335033351333523335333354333553335633357333583335933360333613336233363333643336533366333673336833369333703337133372333733337433375333763337733378333793338033381333823338333384333853338633387333883338933390333913339233393333943339533396333973339833399334003340133402334033340433405334063340733408334093341033411334123341333414334153341633417334183341933420334213342233423334243342533426334273342833429334303343133432334333343433435334363343733438334393344033441334423344333444334453344633447334483344933450334513345233453334543345533456334573345833459334603346133462334633346433465334663346733468334693347033471334723347333474334753347633477334783347933480334813348233483334843348533486334873348833489334903349133492334933349433495334963349733498334993350033501335023350333504335053350633507335083350933510335113351233513335143351533516335173351833519335203352133522335233352433525335263352733528335293353033531335323353333534335353353633537335383353933540335413354233543335443354533546335473354833549335503355133552335533355433555335563355733558335593356033561335623356333564335653356633567335683356933570335713357233573335743357533576335773357833579335803358133582335833358433585335863358733588335893359033591335923359333594335953359633597335983359933600336013360233603336043360533606336073360833609336103361133612336133361433615336163361733618336193362033621336223362333624336253362633627336283362933630336313363233633336343363533636336373363833639336403364133642336433364433645336463364733648336493365033651336523365333654336553365633657336583365933660336613366233663336643366533666336673366833669336703367133672336733367433675336763367733678336793368033681336823368333684336853368633687336883368933690336913369233693336943369533696336973369833699337003370133702337033370433705337063370733708337093371033711337123371333714337153371633717337183371933720337213372233723337243372533726337273372833729337303373133732337333373433735337363373733738337393374033741337423374333744337453374633747337483374933750337513375233753337543375533756337573375833759337603376133762337633376433765337663376733768337693377033771337723377333774337753377633777337783377933780337813378233783337843378533786337873378833789337903379133792337933379433795337963379733798337993380033801338023380333804338053380633807338083380933810338113381233813338143381533816338173381833819338203382133822338233382433825338263382733828338293383033831338323383333834338353383633837338383383933840338413384233843338443384533846338473384833849338503385133852338533385433855338563385733858338593386033861338623386333864338653386633867338683386933870338713387233873338743387533876338773387833879338803388133882338833388433885338863388733888338893389033891338923389333894338953389633897338983389933900339013390233903339043390533906339073390833909339103391133912339133391433915339163391733918339193392033921339223392333924339253392633927339283392933930339313393233933339343393533936339373393833939339403394133942339433394433945339463394733948339493395033951339523395333954339553395633957339583395933960339613396233963339643396533966339673396833969339703397133972339733397433975339763397733978339793398033981339823398333984339853398633987339883398933990339913399233993339943399533996339973399833999340003400134002340033400434005340063400734008340093401034011340123401334014340153401634017340183401934020340213402234023340243402534026340273402834029340303403134032340333403434035340363403734038340393404034041340423404334044340453404634047340483404934050340513405234053340543405534056340573405834059340603406134062340633406434065340663406734068340693407034071340723407334074340753407634077340783407934080340813408234083340843408534086340873408834089340903409134092340933409434095340963409734098340993410034101341023410334104341053410634107341083410934110341113411234113341143411534116341173411834119341203412134122341233412434125341263412734128341293413034131341323413334134341353413634137341383413934140341413414234143341443414534146341473414834149341503415134152341533415434155341563415734158341593416034161341623416334164341653416634167341683416934170341713417234173341743417534176341773417834179341803418134182341833418434185341863418734188341893419034191341923419334194341953419634197341983419934200342013420234203342043420534206342073420834209342103421134212342133421434215342163421734218342193422034221342223422334224342253422634227342283422934230342313423234233342343423534236342373423834239342403424134242342433424434245342463424734248342493425034251342523425334254342553425634257342583425934260342613426234263342643426534266342673426834269342703427134272342733427434275342763427734278342793428034281342823428334284342853428634287342883428934290342913429234293342943429534296342973429834299343003430134302343033430434305343063430734308343093431034311343123431334314343153431634317343183431934320343213432234323343243432534326343273432834329343303433134332343333433434335343363433734338343393434034341343423434334344343453434634347343483434934350343513435234353343543435534356343573435834359343603436134362343633436434365343663436734368343693437034371343723437334374343753437634377343783437934380343813438234383343843438534386343873438834389343903439134392343933439434395343963439734398343993440034401344023440334404344053440634407344083440934410344113441234413344143441534416344173441834419344203442134422344233442434425344263442734428344293443034431344323443334434344353443634437344383443934440344413444234443344443444534446344473444834449344503445134452344533445434455344563445734458344593446034461344623446334464344653446634467344683446934470344713447234473344743447534476344773447834479344803448134482344833448434485344863448734488344893449034491344923449334494344953449634497344983449934500345013450234503345043450534506345073450834509345103451134512345133451434515345163451734518345193452034521345223452334524345253452634527345283452934530345313453234533345343453534536345373453834539345403454134542345433454434545345463454734548345493455034551345523455334554345553455634557345583455934560345613456234563345643456534566345673456834569345703457134572345733457434575345763457734578345793458034581345823458334584345853458634587345883458934590345913459234593345943459534596345973459834599346003460134602346033460434605346063460734608346093461034611346123461334614346153461634617346183461934620346213462234623346243462534626346273462834629346303463134632346333463434635346363463734638346393464034641346423464334644346453464634647346483464934650346513465234653346543465534656346573465834659346603466134662346633466434665346663466734668346693467034671346723467334674346753467634677346783467934680346813468234683346843468534686346873468834689346903469134692346933469434695346963469734698346993470034701347023470334704347053470634707347083470934710347113471234713347143471534716347173471834719347203472134722347233472434725347263472734728347293473034731347323473334734347353473634737347383473934740347413474234743347443474534746347473474834749347503475134752347533475434755347563475734758347593476034761347623476334764347653476634767347683476934770347713477234773347743477534776347773477834779347803478134782347833478434785347863478734788347893479034791347923479334794347953479634797347983479934800348013480234803348043480534806348073480834809348103481134812348133481434815348163481734818348193482034821348223482334824348253482634827348283482934830348313483234833348343483534836348373483834839348403484134842348433484434845348463484734848348493485034851348523485334854348553485634857348583485934860348613486234863348643486534866348673486834869348703487134872348733487434875348763487734878348793488034881348823488334884348853488634887348883488934890348913489234893348943489534896348973489834899349003490134902349033490434905349063490734908349093491034911349123491334914349153491634917349183491934920349213492234923349243492534926349273492834929349303493134932349333493434935349363493734938349393494034941349423494334944349453494634947349483494934950349513495234953349543495534956349573495834959349603496134962349633496434965349663496734968349693497034971349723497334974349753497634977349783497934980349813498234983349843498534986349873498834989349903499134992349933499434995349963499734998349993500035001350023500335004350053500635007350083500935010350113501235013350143501535016350173501835019350203502135022350233502435025350263502735028350293503035031350323503335034350353503635037350383503935040350413504235043350443504535046350473504835049350503505135052350533505435055350563505735058350593506035061350623506335064350653506635067350683506935070350713507235073350743507535076350773507835079350803508135082350833508435085350863508735088350893509035091350923509335094350953509635097350983509935100351013510235103351043510535106351073510835109351103511135112351133511435115351163511735118351193512035121351223512335124351253512635127351283512935130351313513235133351343513535136351373513835139351403514135142351433514435145351463514735148351493515035151351523515335154351553515635157351583515935160351613516235163351643516535166351673516835169351703517135172351733517435175351763517735178351793518035181351823518335184351853518635187351883518935190351913519235193351943519535196351973519835199352003520135202352033520435205352063520735208352093521035211352123521335214352153521635217352183521935220352213522235223352243522535226352273522835229352303523135232352333523435235352363523735238352393524035241352423524335244352453524635247352483524935250352513525235253352543525535256352573525835259352603526135262352633526435265352663526735268352693527035271352723527335274352753527635277352783527935280352813528235283352843528535286352873528835289352903529135292352933529435295352963529735298352993530035301353023530335304353053530635307353083530935310353113531235313353143531535316353173531835319353203532135322353233532435325353263532735328353293533035331353323533335334353353533635337353383533935340353413534235343353443534535346353473534835349353503535135352353533535435355353563535735358353593536035361353623536335364353653536635367353683536935370353713537235373353743537535376353773537835379353803538135382353833538435385353863538735388353893539035391353923539335394353953539635397353983539935400354013540235403354043540535406354073540835409354103541135412354133541435415354163541735418354193542035421354223542335424354253542635427354283542935430354313543235433354343543535436354373543835439354403544135442354433544435445354463544735448354493545035451354523545335454354553545635457354583545935460354613546235463354643546535466354673546835469354703547135472354733547435475354763547735478354793548035481354823548335484354853548635487354883548935490354913549235493354943549535496354973549835499355003550135502355033550435505355063550735508355093551035511355123551335514355153551635517355183551935520355213552235523355243552535526355273552835529355303553135532355333553435535355363553735538355393554035541355423554335544355453554635547355483554935550355513555235553355543555535556355573555835559355603556135562355633556435565355663556735568355693557035571355723557335574355753557635577355783557935580355813558235583355843558535586355873558835589355903559135592355933559435595355963559735598355993560035601356023560335604356053560635607356083560935610356113561235613356143561535616356173561835619356203562135622356233562435625356263562735628356293563035631356323563335634356353563635637356383563935640356413564235643356443564535646356473564835649356503565135652356533565435655356563565735658356593566035661356623566335664356653566635667356683566935670356713567235673356743567535676356773567835679356803568135682356833568435685356863568735688356893569035691356923569335694356953569635697356983569935700357013570235703357043570535706357073570835709357103571135712357133571435715357163571735718357193572035721357223572335724357253572635727357283572935730357313573235733357343573535736357373573835739357403574135742357433574435745357463574735748357493575035751357523575335754357553575635757357583575935760357613576235763357643576535766357673576835769357703577135772357733577435775357763577735778357793578035781357823578335784357853578635787357883578935790357913579235793357943579535796357973579835799358003580135802358033580435805358063580735808358093581035811358123581335814358153581635817358183581935820358213582235823358243582535826358273582835829358303583135832358333583435835358363583735838358393584035841358423584335844358453584635847358483584935850358513585235853358543585535856358573585835859358603586135862358633586435865358663586735868358693587035871358723587335874358753587635877358783587935880358813588235883358843588535886358873588835889358903589135892358933589435895358963589735898358993590035901359023590335904359053590635907359083590935910359113591235913359143591535916359173591835919359203592135922359233592435925359263592735928359293593035931359323593335934359353593635937359383593935940359413594235943359443594535946359473594835949359503595135952359533595435955359563595735958359593596035961359623596335964359653596635967359683596935970359713597235973359743597535976359773597835979359803598135982359833598435985359863598735988359893599035991359923599335994359953599635997359983599936000360013600236003360043600536006360073600836009360103601136012360133601436015360163601736018360193602036021360223602336024360253602636027360283602936030360313603236033360343603536036360373603836039360403604136042360433604436045360463604736048360493605036051360523605336054360553605636057360583605936060360613606236063360643606536066360673606836069360703607136072360733607436075360763607736078360793608036081360823608336084360853608636087360883608936090360913609236093360943609536096360973609836099361003610136102361033610436105361063610736108361093611036111361123611336114361153611636117361183611936120361213612236123361243612536126361273612836129361303613136132361333613436135361363613736138361393614036141361423614336144361453614636147361483614936150361513615236153361543615536156361573615836159361603616136162361633616436165361663616736168361693617036171361723617336174361753617636177361783617936180361813618236183361843618536186361873618836189361903619136192361933619436195361963619736198361993620036201362023620336204362053620636207362083620936210362113621236213362143621536216362173621836219362203622136222362233622436225362263622736228362293623036231362323623336234362353623636237362383623936240362413624236243362443624536246362473624836249362503625136252362533625436255362563625736258362593626036261362623626336264362653626636267362683626936270362713627236273362743627536276362773627836279362803628136282362833628436285362863628736288362893629036291362923629336294362953629636297362983629936300363013630236303363043630536306363073630836309363103631136312363133631436315363163631736318363193632036321363223632336324363253632636327363283632936330363313633236333363343633536336363373633836339363403634136342363433634436345363463634736348363493635036351363523635336354363553635636357363583635936360363613636236363363643636536366363673636836369363703637136372363733637436375363763637736378363793638036381363823638336384363853638636387363883638936390363913639236393363943639536396363973639836399364003640136402364033640436405364063640736408364093641036411364123641336414364153641636417364183641936420364213642236423364243642536426364273642836429364303643136432364333643436435364363643736438364393644036441364423644336444364453644636447364483644936450364513645236453364543645536456364573645836459364603646136462364633646436465364663646736468364693647036471364723647336474364753647636477364783647936480364813648236483364843648536486364873648836489364903649136492364933649436495364963649736498364993650036501365023650336504365053650636507365083650936510365113651236513365143651536516365173651836519365203652136522365233652436525365263652736528365293653036531365323653336534365353653636537365383653936540365413654236543365443654536546365473654836549365503655136552365533655436555365563655736558365593656036561365623656336564365653656636567365683656936570365713657236573365743657536576365773657836579365803658136582365833658436585365863658736588365893659036591365923659336594365953659636597365983659936600366013660236603366043660536606366073660836609366103661136612366133661436615366163661736618366193662036621366223662336624366253662636627366283662936630366313663236633366343663536636366373663836639366403664136642366433664436645366463664736648366493665036651366523665336654366553665636657366583665936660366613666236663366643666536666366673666836669366703667136672366733667436675366763667736678366793668036681366823668336684366853668636687366883668936690366913669236693366943669536696366973669836699367003670136702367033670436705367063670736708367093671036711367123671336714367153671636717367183671936720367213672236723367243672536726367273672836729367303673136732367333673436735367363673736738367393674036741367423674336744367453674636747367483674936750367513675236753367543675536756367573675836759367603676136762367633676436765367663676736768367693677036771367723677336774367753677636777367783677936780367813678236783367843678536786367873678836789367903679136792367933679436795367963679736798367993680036801368023680336804368053680636807368083680936810368113681236813368143681536816368173681836819368203682136822368233682436825368263682736828368293683036831368323683336834368353683636837368383683936840368413684236843368443684536846368473684836849368503685136852368533685436855368563685736858368593686036861368623686336864368653686636867368683686936870368713687236873368743687536876368773687836879368803688136882368833688436885368863688736888368893689036891368923689336894368953689636897368983689936900369013690236903369043690536906369073690836909369103691136912369133691436915369163691736918369193692036921369223692336924369253692636927369283692936930369313693236933369343693536936369373693836939369403694136942369433694436945369463694736948369493695036951369523695336954369553695636957369583695936960369613696236963369643696536966369673696836969369703697136972369733697436975369763697736978369793698036981369823698336984369853698636987369883698936990369913699236993369943699536996369973699836999370003700137002370033700437005370063700737008370093701037011370123701337014370153701637017370183701937020370213702237023370243702537026370273702837029370303703137032370333703437035370363703737038370393704037041370423704337044370453704637047370483704937050370513705237053370543705537056370573705837059370603706137062370633706437065370663706737068370693707037071370723707337074370753707637077370783707937080370813708237083370843708537086370873708837089370903709137092370933709437095370963709737098370993710037101371023710337104371053710637107371083710937110371113711237113371143711537116371173711837119371203712137122371233712437125371263712737128371293713037131371323713337134371353713637137371383713937140371413714237143371443714537146371473714837149371503715137152371533715437155371563715737158371593716037161371623716337164371653716637167371683716937170371713717237173371743717537176371773717837179371803718137182371833718437185371863718737188371893719037191371923719337194371953719637197371983719937200372013720237203372043720537206372073720837209372103721137212372133721437215372163721737218372193722037221372223722337224372253722637227372283722937230372313723237233372343723537236372373723837239372403724137242372433724437245372463724737248372493725037251372523725337254372553725637257372583725937260372613726237263372643726537266372673726837269372703727137272372733727437275372763727737278372793728037281372823728337284372853728637287372883728937290372913729237293372943729537296372973729837299373003730137302373033730437305373063730737308373093731037311373123731337314373153731637317373183731937320373213732237323373243732537326373273732837329373303733137332373333733437335373363733737338373393734037341373423734337344373453734637347373483734937350373513735237353373543735537356373573735837359373603736137362373633736437365373663736737368373693737037371373723737337374373753737637377373783737937380373813738237383373843738537386373873738837389373903739137392373933739437395373963739737398373993740037401374023740337404374053740637407374083740937410374113741237413374143741537416374173741837419374203742137422374233742437425374263742737428374293743037431374323743337434374353743637437374383743937440374413744237443374443744537446374473744837449374503745137452374533745437455374563745737458374593746037461374623746337464374653746637467374683746937470374713747237473374743747537476374773747837479374803748137482374833748437485374863748737488374893749037491374923749337494374953749637497374983749937500375013750237503375043750537506375073750837509375103751137512375133751437515375163751737518375193752037521375223752337524375253752637527375283752937530375313753237533375343753537536375373753837539375403754137542375433754437545375463754737548375493755037551375523755337554375553755637557375583755937560375613756237563375643756537566375673756837569375703757137572375733757437575375763757737578375793758037581375823758337584375853758637587375883758937590375913759237593375943759537596375973759837599376003760137602376033760437605376063760737608376093761037611376123761337614376153761637617376183761937620376213762237623376243762537626376273762837629376303763137632376333763437635376363763737638376393764037641376423764337644376453764637647376483764937650376513765237653376543765537656376573765837659376603766137662376633766437665376663766737668376693767037671376723767337674376753767637677376783767937680376813768237683376843768537686376873768837689376903769137692376933769437695376963769737698376993770037701377023770337704377053770637707377083770937710377113771237713377143771537716377173771837719377203772137722377233772437725377263772737728377293773037731377323773337734377353773637737377383773937740377413774237743377443774537746377473774837749377503775137752377533775437755377563775737758377593776037761377623776337764377653776637767377683776937770377713777237773377743777537776377773777837779377803778137782377833778437785377863778737788377893779037791377923779337794377953779637797377983779937800378013780237803378043780537806378073780837809378103781137812378133781437815378163781737818378193782037821378223782337824378253782637827378283782937830378313783237833378343783537836378373783837839378403784137842378433784437845378463784737848378493785037851378523785337854378553785637857378583785937860378613786237863378643786537866378673786837869378703787137872378733787437875378763787737878378793788037881378823788337884378853788637887378883788937890378913789237893378943789537896378973789837899379003790137902379033790437905379063790737908379093791037911379123791337914379153791637917379183791937920379213792237923379243792537926379273792837929379303793137932379333793437935379363793737938379393794037941379423794337944379453794637947379483794937950379513795237953379543795537956379573795837959379603796137962379633796437965379663796737968379693797037971379723797337974379753797637977379783797937980379813798237983379843798537986379873798837989379903799137992379933799437995379963799737998379993800038001380023800338004380053800638007380083800938010380113801238013380143801538016380173801838019380203802138022380233802438025380263802738028380293803038031380323803338034380353803638037380383803938040380413804238043380443804538046380473804838049380503805138052380533805438055380563805738058380593806038061380623806338064380653806638067380683806938070380713807238073380743807538076380773807838079380803808138082380833808438085380863808738088380893809038091380923809338094380953809638097380983809938100381013810238103381043810538106381073810838109381103811138112381133811438115381163811738118381193812038121381223812338124381253812638127381283812938130381313813238133381343813538136381373813838139381403814138142381433814438145381463814738148381493815038151381523815338154381553815638157381583815938160381613816238163381643816538166381673816838169381703817138172381733817438175381763817738178381793818038181381823818338184381853818638187381883818938190381913819238193381943819538196381973819838199382003820138202382033820438205382063820738208382093821038211382123821338214382153821638217382183821938220382213822238223382243822538226382273822838229382303823138232382333823438235382363823738238382393824038241382423824338244382453824638247382483824938250382513825238253382543825538256382573825838259382603826138262382633826438265382663826738268382693827038271382723827338274382753827638277382783827938280382813828238283382843828538286382873828838289382903829138292382933829438295382963829738298382993830038301383023830338304383053830638307383083830938310383113831238313383143831538316383173831838319383203832138322383233832438325383263832738328383293833038331383323833338334383353833638337383383833938340383413834238343383443834538346383473834838349383503835138352383533835438355383563835738358383593836038361383623836338364383653836638367383683836938370383713837238373383743837538376383773837838379383803838138382383833838438385383863838738388383893839038391383923839338394383953839638397383983839938400384013840238403384043840538406384073840838409384103841138412384133841438415384163841738418384193842038421384223842338424384253842638427384283842938430384313843238433384343843538436384373843838439384403844138442384433844438445384463844738448384493845038451384523845338454384553845638457384583845938460384613846238463384643846538466384673846838469384703847138472384733847438475384763847738478384793848038481384823848338484384853848638487384883848938490384913849238493384943849538496384973849838499385003850138502385033850438505385063850738508385093851038511385123851338514385153851638517385183851938520385213852238523385243852538526385273852838529385303853138532385333853438535385363853738538385393854038541385423854338544385453854638547385483854938550385513855238553385543855538556385573855838559385603856138562385633856438565385663856738568385693857038571385723857338574385753857638577385783857938580385813858238583385843858538586385873858838589385903859138592385933859438595385963859738598385993860038601386023860338604386053860638607386083860938610386113861238613386143861538616386173861838619386203862138622386233862438625386263862738628386293863038631386323863338634386353863638637386383863938640386413864238643386443864538646386473864838649386503865138652386533865438655386563865738658386593866038661386623866338664386653866638667386683866938670386713867238673386743867538676386773867838679386803868138682386833868438685386863868738688386893869038691386923869338694386953869638697386983869938700387013870238703387043870538706387073870838709387103871138712387133871438715387163871738718387193872038721387223872338724387253872638727387283872938730387313873238733387343873538736387373873838739387403874138742387433874438745387463874738748387493875038751387523875338754387553875638757387583875938760387613876238763387643876538766387673876838769387703877138772387733877438775387763877738778387793878038781387823878338784387853878638787387883878938790387913879238793387943879538796387973879838799388003880138802388033880438805388063880738808388093881038811388123881338814388153881638817388183881938820388213882238823388243882538826388273882838829388303883138832388333883438835388363883738838388393884038841388423884338844388453884638847388483884938850388513885238853388543885538856388573885838859388603886138862388633886438865388663886738868388693887038871388723887338874388753887638877388783887938880388813888238883388843888538886388873888838889388903889138892388933889438895388963889738898388993890038901389023890338904389053890638907389083890938910389113891238913389143891538916389173891838919389203892138922389233892438925389263892738928389293893038931389323893338934389353893638937389383893938940389413894238943389443894538946389473894838949389503895138952389533895438955389563895738958389593896038961389623896338964389653896638967389683896938970389713897238973389743897538976389773897838979389803898138982389833898438985389863898738988389893899038991389923899338994389953899638997389983899939000390013900239003390043900539006390073900839009390103901139012390133901439015390163901739018390193902039021390223902339024390253902639027390283902939030390313903239033390343903539036390373903839039390403904139042390433904439045390463904739048390493905039051390523905339054390553905639057390583905939060390613906239063390643906539066390673906839069390703907139072390733907439075390763907739078390793908039081390823908339084390853908639087390883908939090390913909239093390943909539096390973909839099391003910139102391033910439105391063910739108391093911039111391123911339114391153911639117391183911939120391213912239123391243912539126391273912839129391303913139132391333913439135391363913739138391393914039141391423914339144391453914639147391483914939150391513915239153391543915539156391573915839159391603916139162391633916439165391663916739168391693917039171391723917339174391753917639177391783917939180391813918239183391843918539186391873918839189391903919139192391933919439195391963919739198391993920039201392023920339204392053920639207392083920939210392113921239213392143921539216392173921839219392203922139222392233922439225392263922739228392293923039231392323923339234392353923639237392383923939240392413924239243392443924539246392473924839249392503925139252392533925439255392563925739258392593926039261392623926339264392653926639267392683926939270392713927239273392743927539276392773927839279392803928139282392833928439285392863928739288392893929039291392923929339294392953929639297392983929939300393013930239303393043930539306393073930839309393103931139312393133931439315393163931739318393193932039321393223932339324393253932639327393283932939330393313933239333393343933539336393373933839339393403934139342393433934439345393463934739348393493935039351393523935339354393553935639357393583935939360393613936239363393643936539366393673936839369393703937139372393733937439375393763937739378393793938039381393823938339384393853938639387393883938939390393913939239393393943939539396393973939839399394003940139402394033940439405394063940739408394093941039411394123941339414394153941639417394183941939420394213942239423394243942539426394273942839429394303943139432394333943439435394363943739438394393944039441394423944339444394453944639447394483944939450394513945239453394543945539456394573945839459394603946139462394633946439465394663946739468394693947039471394723947339474394753947639477394783947939480394813948239483394843948539486394873948839489394903949139492394933949439495394963949739498394993950039501395023950339504395053950639507395083950939510395113951239513395143951539516395173951839519395203952139522395233952439525395263952739528395293953039531395323953339534395353953639537395383953939540395413954239543395443954539546395473954839549395503955139552395533955439555395563955739558395593956039561395623956339564395653956639567395683956939570395713957239573395743957539576395773957839579395803958139582395833958439585395863958739588395893959039591395923959339594395953959639597395983959939600396013960239603396043960539606396073960839609396103961139612396133961439615396163961739618396193962039621396223962339624396253962639627396283962939630396313963239633396343963539636396373963839639396403964139642396433964439645396463964739648396493965039651396523965339654396553965639657396583965939660396613966239663396643966539666396673966839669396703967139672396733967439675396763967739678396793968039681396823968339684396853968639687396883968939690396913969239693396943969539696396973969839699397003970139702397033970439705397063970739708397093971039711397123971339714397153971639717397183971939720397213972239723397243972539726397273972839729397303973139732397333973439735397363973739738397393974039741397423974339744397453974639747397483974939750397513975239753397543975539756397573975839759397603976139762397633976439765397663976739768397693977039771397723977339774397753977639777397783977939780397813978239783397843978539786397873978839789397903979139792397933979439795397963979739798397993980039801398023980339804398053980639807398083980939810398113981239813398143981539816398173981839819398203982139822398233982439825398263982739828398293983039831398323983339834398353983639837398383983939840398413984239843398443984539846398473984839849398503985139852398533985439855398563985739858398593986039861398623986339864398653986639867398683986939870398713987239873398743987539876398773987839879398803988139882398833988439885398863988739888398893989039891398923989339894398953989639897398983989939900399013990239903399043990539906399073990839909399103991139912399133991439915399163991739918399193992039921399223992339924399253992639927399283992939930399313993239933399343993539936399373993839939399403994139942399433994439945399463994739948399493995039951399523995339954399553995639957399583995939960399613996239963399643996539966399673996839969399703997139972399733997439975399763997739978399793998039981399823998339984399853998639987399883998939990399913999239993399943999539996399973999839999400004000140002400034000440005400064000740008400094001040011400124001340014400154001640017400184001940020400214002240023400244002540026400274002840029400304003140032400334003440035400364003740038400394004040041400424004340044400454004640047400484004940050400514005240053400544005540056400574005840059400604006140062400634006440065400664006740068400694007040071400724007340074400754007640077400784007940080400814008240083400844008540086400874008840089400904009140092400934009440095400964009740098400994010040101401024010340104401054010640107401084010940110401114011240113401144011540116401174011840119401204012140122401234012440125401264012740128401294013040131401324013340134401354013640137401384013940140401414014240143401444014540146401474014840149401504015140152401534015440155401564015740158401594016040161401624016340164401654016640167401684016940170401714017240173401744017540176401774017840179401804018140182401834018440185401864018740188401894019040191401924019340194401954019640197401984019940200402014020240203402044020540206402074020840209402104021140212402134021440215402164021740218402194022040221402224022340224402254022640227402284022940230402314023240233402344023540236402374023840239402404024140242402434024440245402464024740248402494025040251402524025340254402554025640257402584025940260402614026240263402644026540266402674026840269402704027140272402734027440275402764027740278402794028040281402824028340284402854028640287402884028940290402914029240293402944029540296402974029840299403004030140302403034030440305403064030740308403094031040311403124031340314403154031640317403184031940320403214032240323403244032540326403274032840329403304033140332403334033440335403364033740338403394034040341403424034340344403454034640347403484034940350403514035240353403544035540356403574035840359403604036140362403634036440365403664036740368403694037040371403724037340374403754037640377403784037940380403814038240383403844038540386403874038840389403904039140392403934039440395403964039740398403994040040401404024040340404404054040640407404084040940410404114041240413404144041540416404174041840419404204042140422404234042440425404264042740428404294043040431404324043340434404354043640437404384043940440404414044240443404444044540446404474044840449404504045140452404534045440455404564045740458404594046040461404624046340464404654046640467404684046940470404714047240473404744047540476404774047840479404804048140482404834048440485404864048740488404894049040491404924049340494404954049640497404984049940500405014050240503405044050540506405074050840509405104051140512405134051440515405164051740518405194052040521405224052340524405254052640527405284052940530405314053240533405344053540536405374053840539405404054140542405434054440545405464054740548405494055040551405524055340554405554055640557405584055940560405614056240563405644056540566405674056840569405704057140572405734057440575405764057740578405794058040581405824058340584405854058640587405884058940590405914059240593405944059540596405974059840599406004060140602406034060440605406064060740608406094061040611406124061340614406154061640617406184061940620406214062240623406244062540626406274062840629406304063140632406334063440635406364063740638406394064040641406424064340644406454064640647406484064940650406514065240653406544065540656406574065840659406604066140662406634066440665406664066740668406694067040671406724067340674406754067640677406784067940680406814068240683406844068540686406874068840689406904069140692406934069440695406964069740698406994070040701407024070340704407054070640707407084070940710407114071240713407144071540716407174071840719407204072140722407234072440725407264072740728407294073040731407324073340734407354073640737407384073940740407414074240743407444074540746407474074840749407504075140752407534075440755407564075740758407594076040761407624076340764407654076640767407684076940770407714077240773407744077540776407774077840779407804078140782407834078440785407864078740788407894079040791407924079340794407954079640797407984079940800408014080240803408044080540806408074080840809408104081140812408134081440815408164081740818408194082040821408224082340824408254082640827408284082940830408314083240833408344083540836408374083840839408404084140842408434084440845408464084740848408494085040851408524085340854408554085640857408584085940860408614086240863408644086540866408674086840869408704087140872408734087440875408764087740878408794088040881408824088340884408854088640887408884088940890408914089240893408944089540896408974089840899409004090140902409034090440905409064090740908409094091040911409124091340914409154091640917409184091940920409214092240923409244092540926409274092840929409304093140932409334093440935409364093740938409394094040941409424094340944409454094640947409484094940950409514095240953409544095540956409574095840959409604096140962409634096440965409664096740968409694097040971409724097340974409754097640977409784097940980409814098240983409844098540986409874098840989409904099140992409934099440995409964099740998409994100041001410024100341004410054100641007410084100941010410114101241013410144101541016410174101841019410204102141022410234102441025410264102741028410294103041031410324103341034410354103641037410384103941040410414104241043410444104541046410474104841049410504105141052410534105441055410564105741058410594106041061410624106341064410654106641067410684106941070410714107241073410744107541076410774107841079410804108141082410834108441085410864108741088410894109041091410924109341094410954109641097410984109941100411014110241103411044110541106411074110841109411104111141112411134111441115411164111741118411194112041121411224112341124411254112641127411284112941130411314113241133411344113541136411374113841139411404114141142411434114441145411464114741148411494115041151411524115341154411554115641157411584115941160411614116241163411644116541166411674116841169411704117141172411734117441175411764117741178411794118041181411824118341184411854118641187411884118941190411914119241193411944119541196411974119841199412004120141202412034120441205412064120741208412094121041211412124121341214412154121641217412184121941220412214122241223412244122541226412274122841229412304123141232412334123441235412364123741238412394124041241412424124341244412454124641247412484124941250412514125241253412544125541256412574125841259412604126141262412634126441265412664126741268412694127041271412724127341274412754127641277412784127941280412814128241283412844128541286412874128841289412904129141292412934129441295412964129741298412994130041301413024130341304413054130641307413084130941310413114131241313413144131541316413174131841319413204132141322413234132441325413264132741328413294133041331413324133341334413354133641337413384133941340413414134241343413444134541346413474134841349413504135141352413534135441355413564135741358413594136041361413624136341364413654136641367413684136941370413714137241373413744137541376413774137841379413804138141382413834138441385413864138741388413894139041391413924139341394413954139641397413984139941400414014140241403414044140541406414074140841409414104141141412414134141441415414164141741418414194142041421414224142341424414254142641427414284142941430414314143241433414344143541436414374143841439414404144141442414434144441445414464144741448414494145041451414524145341454414554145641457414584145941460414614146241463414644146541466414674146841469414704147141472414734147441475414764147741478414794148041481414824148341484414854148641487414884148941490414914149241493414944149541496414974149841499415004150141502415034150441505415064150741508415094151041511415124151341514415154151641517415184151941520415214152241523415244152541526415274152841529415304153141532415334153441535415364153741538415394154041541415424154341544415454154641547415484154941550415514155241553415544155541556415574155841559415604156141562415634156441565415664156741568415694157041571415724157341574415754157641577415784157941580415814158241583415844158541586415874158841589415904159141592415934159441595415964159741598415994160041601416024160341604416054160641607416084160941610416114161241613416144161541616416174161841619416204162141622416234162441625416264162741628416294163041631416324163341634416354163641637416384163941640416414164241643416444164541646416474164841649416504165141652416534165441655416564165741658416594166041661416624166341664416654166641667416684166941670416714167241673416744167541676416774167841679416804168141682416834168441685416864168741688416894169041691416924169341694416954169641697416984169941700417014170241703417044170541706417074170841709417104171141712417134171441715417164171741718417194172041721417224172341724417254172641727417284172941730417314173241733417344173541736417374173841739417404174141742417434174441745417464174741748417494175041751417524175341754417554175641757417584175941760417614176241763417644176541766417674176841769417704177141772417734177441775417764177741778417794178041781417824178341784417854178641787417884178941790417914179241793417944179541796417974179841799418004180141802418034180441805418064180741808418094181041811418124181341814418154181641817418184181941820418214182241823418244182541826418274182841829418304183141832418334183441835418364183741838418394184041841418424184341844418454184641847418484184941850418514185241853418544185541856418574185841859418604186141862418634186441865418664186741868418694187041871418724187341874418754187641877418784187941880418814188241883418844188541886418874188841889418904189141892418934189441895418964189741898418994190041901419024190341904419054190641907419084190941910419114191241913419144191541916419174191841919419204192141922419234192441925419264192741928419294193041931419324193341934419354193641937419384193941940419414194241943419444194541946419474194841949419504195141952419534195441955419564195741958419594196041961419624196341964419654196641967419684196941970419714197241973419744197541976419774197841979419804198141982419834198441985419864198741988419894199041991419924199341994419954199641997419984199942000420014200242003420044200542006420074200842009420104201142012420134201442015420164201742018420194202042021420224202342024420254202642027420284202942030420314203242033420344203542036420374203842039420404204142042420434204442045420464204742048420494205042051420524205342054420554205642057420584205942060420614206242063420644206542066420674206842069420704207142072420734207442075420764207742078420794208042081420824208342084420854208642087420884208942090420914209242093420944209542096420974209842099421004210142102421034210442105421064210742108421094211042111421124211342114421154211642117421184211942120421214212242123421244212542126421274212842129421304213142132421334213442135421364213742138421394214042141421424214342144421454214642147421484214942150421514215242153421544215542156421574215842159421604216142162421634216442165421664216742168421694217042171421724217342174421754217642177421784217942180421814218242183421844218542186421874218842189421904219142192421934219442195421964219742198421994220042201422024220342204422054220642207422084220942210422114221242213422144221542216422174221842219422204222142222422234222442225422264222742228422294223042231422324223342234422354223642237422384223942240422414224242243422444224542246422474224842249422504225142252422534225442255422564225742258422594226042261422624226342264422654226642267422684226942270422714227242273422744227542276422774227842279422804228142282422834228442285422864228742288422894229042291422924229342294422954229642297422984229942300423014230242303423044230542306423074230842309423104231142312423134231442315423164231742318423194232042321423224232342324423254232642327423284232942330423314233242333423344233542336423374233842339423404234142342423434234442345423464234742348423494235042351423524235342354423554235642357423584235942360423614236242363423644236542366423674236842369423704237142372423734237442375423764237742378423794238042381423824238342384423854238642387423884238942390423914239242393423944239542396423974239842399424004240142402424034240442405424064240742408424094241042411424124241342414424154241642417424184241942420424214242242423424244242542426424274242842429424304243142432424334243442435424364243742438424394244042441424424244342444424454244642447424484244942450424514245242453424544245542456424574245842459424604246142462424634246442465424664246742468424694247042471424724247342474424754247642477424784247942480424814248242483424844248542486424874248842489424904249142492424934249442495424964249742498424994250042501425024250342504425054250642507425084250942510425114251242513425144251542516425174251842519425204252142522425234252442525425264252742528425294253042531425324253342534425354253642537425384253942540425414254242543425444254542546425474254842549425504255142552425534255442555425564255742558425594256042561425624256342564425654256642567425684256942570425714257242573425744257542576425774257842579425804258142582425834258442585425864258742588425894259042591425924259342594425954259642597425984259942600426014260242603426044260542606426074260842609426104261142612426134261442615426164261742618426194262042621426224262342624426254262642627426284262942630426314263242633426344263542636426374263842639426404264142642426434264442645426464264742648426494265042651426524265342654426554265642657426584265942660426614266242663426644266542666426674266842669426704267142672426734267442675426764267742678426794268042681426824268342684426854268642687426884268942690426914269242693426944269542696426974269842699427004270142702427034270442705427064270742708427094271042711427124271342714427154271642717427184271942720427214272242723427244272542726427274272842729427304273142732427334273442735427364273742738427394274042741427424274342744427454274642747427484274942750427514275242753427544275542756427574275842759427604276142762427634276442765427664276742768427694277042771427724277342774427754277642777427784277942780427814278242783427844278542786427874278842789427904279142792427934279442795427964279742798427994280042801428024280342804428054280642807428084280942810428114281242813428144281542816428174281842819428204282142822428234282442825428264282742828428294283042831428324283342834428354283642837428384283942840428414284242843428444284542846428474284842849428504285142852428534285442855428564285742858428594286042861428624286342864428654286642867428684286942870428714287242873428744287542876428774287842879428804288142882428834288442885428864288742888428894289042891428924289342894428954289642897428984289942900429014290242903429044290542906429074290842909429104291142912429134291442915429164291742918429194292042921429224292342924429254292642927429284292942930429314293242933429344293542936429374293842939429404294142942429434294442945429464294742948429494295042951429524295342954429554295642957429584295942960429614296242963429644296542966429674296842969429704297142972429734297442975429764297742978429794298042981429824298342984429854298642987429884298942990429914299242993429944299542996429974299842999430004300143002430034300443005430064300743008430094301043011430124301343014430154301643017430184301943020430214302243023430244302543026430274302843029430304303143032430334303443035430364303743038430394304043041430424304343044430454304643047430484304943050430514305243053430544305543056430574305843059430604306143062430634306443065430664306743068430694307043071430724307343074430754307643077430784307943080430814308243083430844308543086430874308843089430904309143092430934309443095430964309743098430994310043101431024310343104431054310643107431084310943110431114311243113431144311543116431174311843119431204312143122431234312443125431264312743128431294313043131431324313343134431354313643137431384313943140431414314243143431444314543146431474314843149431504315143152431534315443155431564315743158431594316043161431624316343164431654316643167431684316943170431714317243173431744317543176431774317843179431804318143182431834318443185431864318743188431894319043191431924319343194431954319643197431984319943200432014320243203432044320543206432074320843209432104321143212432134321443215432164321743218432194322043221432224322343224432254322643227432284322943230432314323243233432344323543236432374323843239432404324143242432434324443245432464324743248432494325043251432524325343254432554325643257432584325943260432614326243263432644326543266432674326843269432704327143272432734327443275432764327743278432794328043281432824328343284432854328643287432884328943290432914329243293432944329543296432974329843299433004330143302433034330443305433064330743308433094331043311433124331343314433154331643317433184331943320433214332243323433244332543326433274332843329433304333143332433334333443335433364333743338433394334043341433424334343344433454334643347433484334943350433514335243353433544335543356433574335843359433604336143362433634336443365433664336743368433694337043371433724337343374433754337643377433784337943380433814338243383433844338543386433874338843389433904339143392433934339443395433964339743398433994340043401434024340343404434054340643407434084340943410434114341243413434144341543416434174341843419434204342143422434234342443425434264342743428434294343043431434324343343434434354343643437434384343943440434414344243443434444344543446434474344843449434504345143452434534345443455434564345743458434594346043461434624346343464434654346643467434684346943470434714347243473434744347543476434774347843479434804348143482434834348443485434864348743488434894349043491434924349343494434954349643497434984349943500435014350243503435044350543506435074350843509435104351143512435134351443515435164351743518435194352043521435224352343524435254352643527435284352943530435314353243533435344353543536435374353843539435404354143542435434354443545435464354743548435494355043551435524355343554435554355643557435584355943560435614356243563435644356543566435674356843569435704357143572435734357443575435764357743578435794358043581435824358343584435854358643587435884358943590435914359243593435944359543596435974359843599436004360143602436034360443605436064360743608436094361043611436124361343614436154361643617436184361943620436214362243623436244362543626436274362843629436304363143632436334363443635436364363743638436394364043641436424364343644436454364643647436484364943650436514365243653436544365543656436574365843659436604366143662436634366443665436664366743668436694367043671436724367343674436754367643677436784367943680436814368243683436844368543686436874368843689436904369143692436934369443695436964369743698436994370043701437024370343704437054370643707437084370943710437114371243713437144371543716437174371843719437204372143722437234372443725437264372743728437294373043731437324373343734437354373643737437384373943740437414374243743437444374543746437474374843749437504375143752437534375443755437564375743758437594376043761437624376343764437654376643767437684376943770437714377243773437744377543776437774377843779437804378143782437834378443785437864378743788437894379043791437924379343794437954379643797437984379943800438014380243803438044380543806438074380843809438104381143812438134381443815438164381743818438194382043821438224382343824438254382643827438284382943830438314383243833438344383543836438374383843839438404384143842438434384443845438464384743848438494385043851438524385343854438554385643857438584385943860438614386243863438644386543866438674386843869438704387143872438734387443875438764387743878438794388043881438824388343884438854388643887438884388943890438914389243893438944389543896438974389843899439004390143902439034390443905439064390743908439094391043911439124391343914439154391643917439184391943920439214392243923439244392543926439274392843929439304393143932439334393443935439364393743938439394394043941439424394343944439454394643947439484394943950439514395243953439544395543956439574395843959439604396143962439634396443965439664396743968439694397043971439724397343974439754397643977439784397943980439814398243983439844398543986439874398843989439904399143992439934399443995439964399743998439994400044001440024400344004440054400644007440084400944010440114401244013440144401544016440174401844019440204402144022440234402444025440264402744028440294403044031440324403344034440354403644037440384403944040440414404244043440444404544046440474404844049440504405144052440534405444055440564405744058440594406044061440624406344064440654406644067440684406944070440714407244073440744407544076440774407844079440804408144082440834408444085440864408744088440894409044091440924409344094440954409644097440984409944100441014410244103441044410544106441074410844109441104411144112441134411444115441164411744118441194412044121441224412344124441254412644127441284412944130441314413244133441344413544136441374413844139441404414144142441434414444145441464414744148441494415044151441524415344154441554415644157441584415944160441614416244163441644416544166441674416844169441704417144172441734417444175441764417744178441794418044181441824418344184441854418644187441884418944190441914419244193441944419544196441974419844199442004420144202442034420444205442064420744208442094421044211442124421344214442154421644217442184421944220442214422244223442244422544226442274422844229442304423144232442334423444235442364423744238442394424044241442424424344244442454424644247442484424944250442514425244253442544425544256442574425844259442604426144262442634426444265442664426744268442694427044271442724427344274442754427644277442784427944280442814428244283442844428544286442874428844289442904429144292442934429444295442964429744298442994430044301443024430344304443054430644307443084430944310443114431244313443144431544316443174431844319443204432144322443234432444325443264432744328443294433044331443324433344334443354433644337443384433944340443414434244343443444434544346443474434844349443504435144352443534435444355443564435744358443594436044361443624436344364443654436644367443684436944370443714437244373443744437544376443774437844379443804438144382443834438444385443864438744388443894439044391443924439344394443954439644397443984439944400444014440244403444044440544406444074440844409444104441144412444134441444415444164441744418444194442044421444224442344424444254442644427444284442944430444314443244433444344443544436444374443844439444404444144442444434444444445444464444744448444494445044451444524445344454444554445644457444584445944460444614446244463444644446544466444674446844469444704447144472444734447444475444764447744478444794448044481444824448344484444854448644487444884448944490444914449244493444944449544496444974449844499445004450144502445034450444505445064450744508445094451044511445124451344514445154451644517445184451944520445214452244523445244452544526445274452844529445304453144532445334453444535445364453744538445394454044541445424454344544445454454644547445484454944550445514455244553445544455544556445574455844559445604456144562445634456444565445664456744568445694457044571445724457344574445754457644577445784457944580445814458244583445844458544586445874458844589445904459144592445934459444595445964459744598445994460044601446024460344604446054460644607446084460944610446114461244613446144461544616446174461844619446204462144622446234462444625446264462744628446294463044631446324463344634446354463644637446384463944640446414464244643446444464544646446474464844649446504465144652446534465444655446564465744658446594466044661446624466344664446654466644667446684466944670446714467244673446744467544676446774467844679446804468144682446834468444685446864468744688446894469044691446924469344694446954469644697446984469944700447014470244703447044470544706447074470844709447104471144712447134471444715447164471744718447194472044721447224472344724447254472644727447284472944730447314473244733447344473544736447374473844739447404474144742447434474444745447464474744748447494475044751447524475344754447554475644757447584475944760447614476244763447644476544766447674476844769447704477144772447734477444775447764477744778447794478044781447824478344784447854478644787447884478944790447914479244793447944479544796447974479844799448004480144802448034480444805448064480744808448094481044811448124481344814448154481644817448184481944820448214482244823448244482544826448274482844829448304483144832448334483444835448364483744838448394484044841448424484344844448454484644847448484484944850448514485244853448544485544856448574485844859448604486144862448634486444865448664486744868448694487044871448724487344874448754487644877448784487944880448814488244883448844488544886448874488844889448904489144892448934489444895448964489744898448994490044901449024490344904449054490644907449084490944910449114491244913449144491544916449174491844919449204492144922449234492444925449264492744928449294493044931449324493344934449354493644937449384493944940449414494244943449444494544946449474494844949449504495144952449534495444955449564495744958449594496044961449624496344964449654496644967449684496944970449714497244973449744497544976449774497844979449804498144982449834498444985449864498744988449894499044991449924499344994449954499644997449984499945000450014500245003450044500545006450074500845009450104501145012450134501445015450164501745018450194502045021450224502345024450254502645027450284502945030450314503245033450344503545036450374503845039450404504145042450434504445045450464504745048450494505045051450524505345054450554505645057450584505945060450614506245063450644506545066450674506845069450704507145072450734507445075450764507745078450794508045081450824508345084450854508645087450884508945090450914509245093450944509545096450974509845099451004510145102451034510445105451064510745108451094511045111451124511345114451154511645117451184511945120451214512245123451244512545126451274512845129451304513145132451334513445135451364513745138451394514045141451424514345144451454514645147451484514945150451514515245153451544515545156451574515845159451604516145162451634516445165451664516745168451694517045171451724517345174451754517645177451784517945180451814518245183451844518545186451874518845189451904519145192451934519445195451964519745198451994520045201452024520345204452054520645207452084520945210452114521245213452144521545216452174521845219452204522145222452234522445225452264522745228452294523045231452324523345234452354523645237452384523945240452414524245243452444524545246452474524845249452504525145252452534525445255452564525745258452594526045261452624526345264452654526645267452684526945270452714527245273452744527545276452774527845279452804528145282452834528445285452864528745288452894529045291452924529345294452954529645297452984529945300453014530245303453044530545306453074530845309453104531145312453134531445315453164531745318453194532045321453224532345324453254532645327453284532945330453314533245333453344533545336453374533845339453404534145342453434534445345453464534745348453494535045351453524535345354453554535645357453584535945360453614536245363453644536545366453674536845369453704537145372453734537445375453764537745378453794538045381453824538345384453854538645387453884538945390453914539245393453944539545396453974539845399454004540145402454034540445405454064540745408454094541045411454124541345414454154541645417454184541945420454214542245423454244542545426454274542845429454304543145432454334543445435454364543745438454394544045441454424544345444454454544645447454484544945450454514545245453454544545545456454574545845459454604546145462454634546445465454664546745468454694547045471454724547345474454754547645477454784547945480454814548245483454844548545486454874548845489454904549145492454934549445495454964549745498454994550045501455024550345504455054550645507455084550945510455114551245513455144551545516455174551845519455204552145522455234552445525455264552745528455294553045531455324553345534455354553645537455384553945540455414554245543455444554545546455474554845549455504555145552455534555445555455564555745558455594556045561455624556345564455654556645567455684556945570455714557245573455744557545576455774557845579455804558145582455834558445585455864558745588455894559045591455924559345594455954559645597455984559945600456014560245603456044560545606456074560845609456104561145612456134561445615456164561745618456194562045621456224562345624456254562645627456284562945630456314563245633456344563545636456374563845639456404564145642456434564445645456464564745648456494565045651456524565345654456554565645657456584565945660456614566245663456644566545666456674566845669456704567145672456734567445675456764567745678456794568045681456824568345684456854568645687456884568945690456914569245693456944569545696456974569845699457004570145702457034570445705457064570745708457094571045711457124571345714457154571645717457184571945720457214572245723457244572545726457274572845729457304573145732457334573445735457364573745738457394574045741457424574345744457454574645747457484574945750457514575245753457544575545756457574575845759457604576145762457634576445765457664576745768457694577045771457724577345774457754577645777457784577945780457814578245783457844578545786457874578845789457904579145792457934579445795457964579745798457994580045801458024580345804458054580645807458084580945810458114581245813458144581545816458174581845819458204582145822458234582445825458264582745828458294583045831458324583345834458354583645837458384583945840458414584245843458444584545846458474584845849458504585145852458534585445855458564585745858458594586045861458624586345864458654586645867458684586945870458714587245873458744587545876458774587845879458804588145882458834588445885458864588745888458894589045891458924589345894458954589645897458984589945900459014590245903459044590545906459074590845909459104591145912459134591445915459164591745918459194592045921459224592345924459254592645927459284592945930459314593245933459344593545936459374593845939459404594145942459434594445945459464594745948459494595045951459524595345954459554595645957459584595945960459614596245963459644596545966459674596845969459704597145972459734597445975459764597745978459794598045981459824598345984459854598645987459884598945990459914599245993459944599545996459974599845999460004600146002460034600446005460064600746008460094601046011460124601346014460154601646017460184601946020460214602246023460244602546026460274602846029460304603146032460334603446035460364603746038460394604046041460424604346044460454604646047460484604946050460514605246053460544605546056460574605846059460604606146062460634606446065460664606746068460694607046071460724607346074460754607646077460784607946080460814608246083460844608546086460874608846089460904609146092460934609446095460964609746098460994610046101461024610346104461054610646107461084610946110461114611246113461144611546116461174611846119461204612146122461234612446125461264612746128461294613046131461324613346134461354613646137461384613946140461414614246143461444614546146461474614846149461504615146152461534615446155461564615746158461594616046161461624616346164461654616646167461684616946170461714617246173461744617546176461774617846179461804618146182461834618446185461864618746188461894619046191461924619346194461954619646197461984619946200462014620246203462044620546206462074620846209462104621146212462134621446215462164621746218462194622046221462224622346224462254622646227462284622946230462314623246233462344623546236462374623846239462404624146242462434624446245462464624746248462494625046251462524625346254462554625646257462584625946260462614626246263462644626546266462674626846269462704627146272462734627446275462764627746278462794628046281462824628346284462854628646287462884628946290462914629246293462944629546296462974629846299463004630146302463034630446305463064630746308463094631046311463124631346314463154631646317463184631946320463214632246323463244632546326463274632846329463304633146332463334633446335463364633746338463394634046341463424634346344463454634646347463484634946350463514635246353463544635546356463574635846359463604636146362463634636446365463664636746368463694637046371463724637346374463754637646377463784637946380463814638246383463844638546386463874638846389463904639146392463934639446395463964639746398463994640046401464024640346404464054640646407464084640946410464114641246413464144641546416464174641846419464204642146422464234642446425464264642746428464294643046431464324643346434464354643646437464384643946440464414644246443464444644546446464474644846449464504645146452464534645446455464564645746458464594646046461464624646346464464654646646467464684646946470464714647246473464744647546476464774647846479464804648146482464834648446485464864648746488464894649046491464924649346494464954649646497464984649946500465014650246503465044650546506465074650846509465104651146512465134651446515465164651746518465194652046521465224652346524465254652646527465284652946530465314653246533465344653546536465374653846539465404654146542465434654446545465464654746548465494655046551465524655346554465554655646557465584655946560465614656246563465644656546566465674656846569465704657146572465734657446575465764657746578465794658046581465824658346584465854658646587465884658946590465914659246593465944659546596465974659846599466004660146602466034660446605466064660746608466094661046611466124661346614466154661646617466184661946620466214662246623466244662546626466274662846629466304663146632466334663446635466364663746638466394664046641466424664346644466454664646647466484664946650466514665246653466544665546656466574665846659466604666146662466634666446665466664666746668466694667046671466724667346674466754667646677466784667946680466814668246683466844668546686466874668846689466904669146692466934669446695466964669746698466994670046701467024670346704467054670646707467084670946710467114671246713467144671546716467174671846719467204672146722467234672446725467264672746728467294673046731467324673346734467354673646737467384673946740467414674246743467444674546746467474674846749467504675146752467534675446755467564675746758467594676046761467624676346764467654676646767467684676946770467714677246773467744677546776467774677846779467804678146782467834678446785467864678746788467894679046791467924679346794467954679646797467984679946800468014680246803468044680546806468074680846809468104681146812468134681446815468164681746818468194682046821468224682346824468254682646827468284682946830468314683246833468344683546836468374683846839468404684146842468434684446845468464684746848468494685046851468524685346854468554685646857468584685946860468614686246863468644686546866468674686846869468704687146872468734687446875468764687746878468794688046881468824688346884468854688646887468884688946890468914689246893468944689546896468974689846899469004690146902469034690446905469064690746908469094691046911469124691346914469154691646917469184691946920469214692246923469244692546926469274692846929469304693146932469334693446935469364693746938469394694046941469424694346944469454694646947469484694946950469514695246953469544695546956469574695846959469604696146962469634696446965469664696746968469694697046971469724697346974469754697646977469784697946980469814698246983469844698546986469874698846989469904699146992469934699446995469964699746998469994700047001470024700347004470054700647007470084700947010470114701247013470144701547016470174701847019470204702147022470234702447025470264702747028470294703047031470324703347034470354703647037470384703947040470414704247043470444704547046470474704847049470504705147052470534705447055470564705747058470594706047061470624706347064470654706647067470684706947070470714707247073470744707547076470774707847079470804708147082470834708447085470864708747088470894709047091470924709347094470954709647097470984709947100471014710247103471044710547106471074710847109471104711147112471134711447115471164711747118471194712047121471224712347124471254712647127471284712947130471314713247133471344713547136471374713847139471404714147142471434714447145471464714747148471494715047151471524715347154471554715647157471584715947160471614716247163471644716547166471674716847169471704717147172471734717447175471764717747178471794718047181471824718347184471854718647187471884718947190471914719247193471944719547196471974719847199472004720147202472034720447205472064720747208472094721047211472124721347214472154721647217472184721947220472214722247223472244722547226472274722847229472304723147232472334723447235472364723747238472394724047241472424724347244472454724647247472484724947250472514725247253472544725547256472574725847259472604726147262472634726447265472664726747268472694727047271472724727347274472754727647277472784727947280472814728247283472844728547286472874728847289472904729147292472934729447295472964729747298472994730047301473024730347304473054730647307473084730947310473114731247313473144731547316473174731847319473204732147322473234732447325473264732747328473294733047331473324733347334473354733647337473384733947340473414734247343473444734547346473474734847349473504735147352473534735447355473564735747358473594736047361473624736347364473654736647367473684736947370473714737247373473744737547376473774737847379473804738147382473834738447385473864738747388473894739047391473924739347394473954739647397473984739947400474014740247403474044740547406474074740847409474104741147412474134741447415474164741747418474194742047421474224742347424474254742647427474284742947430474314743247433474344743547436474374743847439474404744147442474434744447445474464744747448474494745047451474524745347454474554745647457474584745947460474614746247463474644746547466474674746847469474704747147472474734747447475474764747747478474794748047481474824748347484474854748647487474884748947490474914749247493474944749547496474974749847499475004750147502475034750447505475064750747508475094751047511475124751347514475154751647517475184751947520475214752247523475244752547526475274752847529475304753147532475334753447535475364753747538475394754047541475424754347544475454754647547475484754947550475514755247553475544755547556475574755847559475604756147562475634756447565475664756747568475694757047571475724757347574475754757647577475784757947580475814758247583475844758547586475874758847589475904759147592475934759447595475964759747598475994760047601476024760347604476054760647607476084760947610476114761247613476144761547616476174761847619476204762147622476234762447625476264762747628476294763047631476324763347634476354763647637476384763947640476414764247643476444764547646476474764847649476504765147652476534765447655476564765747658476594766047661476624766347664476654766647667476684766947670476714767247673476744767547676476774767847679476804768147682476834768447685476864768747688476894769047691476924769347694476954769647697476984769947700477014770247703477044770547706477074770847709477104771147712477134771447715477164771747718477194772047721477224772347724477254772647727477284772947730477314773247733477344773547736477374773847739477404774147742477434774447745477464774747748477494775047751477524775347754477554775647757477584775947760477614776247763477644776547766477674776847769477704777147772477734777447775477764777747778477794778047781477824778347784477854778647787477884778947790477914779247793477944779547796477974779847799478004780147802478034780447805478064780747808478094781047811478124781347814478154781647817478184781947820478214782247823478244782547826478274782847829478304783147832478334783447835478364783747838478394784047841478424784347844478454784647847478484784947850478514785247853478544785547856478574785847859478604786147862478634786447865478664786747868478694787047871478724787347874478754787647877478784787947880478814788247883478844788547886478874788847889478904789147892478934789447895478964789747898478994790047901479024790347904479054790647907479084790947910479114791247913479144791547916479174791847919479204792147922479234792447925479264792747928479294793047931479324793347934479354793647937479384793947940479414794247943479444794547946479474794847949479504795147952479534795447955479564795747958479594796047961479624796347964479654796647967479684796947970479714797247973479744797547976479774797847979479804798147982479834798447985479864798747988479894799047991479924799347994479954799647997479984799948000480014800248003480044800548006480074800848009480104801148012480134801448015480164801748018480194802048021480224802348024480254802648027480284802948030480314803248033480344803548036480374803848039480404804148042480434804448045480464804748048480494805048051480524805348054480554805648057480584805948060480614806248063480644806548066480674806848069480704807148072480734807448075480764807748078480794808048081480824808348084480854808648087480884808948090480914809248093480944809548096480974809848099481004810148102481034810448105481064810748108481094811048111481124811348114481154811648117481184811948120481214812248123481244812548126481274812848129481304813148132481334813448135481364813748138481394814048141481424814348144481454814648147481484814948150481514815248153481544815548156481574815848159481604816148162481634816448165481664816748168481694817048171481724817348174481754817648177481784817948180481814818248183481844818548186481874818848189481904819148192481934819448195481964819748198481994820048201482024820348204482054820648207482084820948210482114821248213482144821548216482174821848219482204822148222482234822448225482264822748228482294823048231482324823348234482354823648237482384823948240482414824248243482444824548246482474824848249482504825148252482534825448255482564825748258482594826048261482624826348264482654826648267482684826948270482714827248273482744827548276482774827848279482804828148282482834828448285482864828748288482894829048291482924829348294482954829648297482984829948300483014830248303483044830548306483074830848309483104831148312483134831448315483164831748318483194832048321483224832348324483254832648327483284832948330483314833248333483344833548336483374833848339483404834148342483434834448345483464834748348483494835048351483524835348354483554835648357483584835948360483614836248363483644836548366483674836848369483704837148372483734837448375483764837748378483794838048381483824838348384483854838648387483884838948390483914839248393483944839548396483974839848399484004840148402484034840448405484064840748408484094841048411484124841348414484154841648417484184841948420484214842248423484244842548426484274842848429484304843148432484334843448435484364843748438484394844048441484424844348444484454844648447484484844948450484514845248453484544845548456484574845848459484604846148462484634846448465484664846748468484694847048471484724847348474484754847648477484784847948480484814848248483484844848548486484874848848489484904849148492484934849448495484964849748498484994850048501485024850348504485054850648507485084850948510485114851248513485144851548516485174851848519485204852148522485234852448525485264852748528485294853048531485324853348534485354853648537485384853948540485414854248543485444854548546485474854848549485504855148552485534855448555485564855748558485594856048561485624856348564485654856648567485684856948570485714857248573485744857548576485774857848579485804858148582485834858448585485864858748588485894859048591485924859348594485954859648597485984859948600486014860248603486044860548606486074860848609486104861148612486134861448615486164861748618486194862048621486224862348624486254862648627486284862948630486314863248633486344863548636486374863848639486404864148642486434864448645486464864748648486494865048651486524865348654486554865648657486584865948660486614866248663486644866548666486674866848669486704867148672486734867448675486764867748678486794868048681486824868348684486854868648687486884868948690486914869248693486944869548696486974869848699487004870148702487034870448705487064870748708487094871048711487124871348714487154871648717487184871948720487214872248723487244872548726487274872848729487304873148732487334873448735487364873748738487394874048741487424874348744487454874648747487484874948750487514875248753487544875548756487574875848759487604876148762487634876448765487664876748768487694877048771487724877348774487754877648777487784877948780487814878248783487844878548786487874878848789487904879148792487934879448795487964879748798487994880048801488024880348804488054880648807488084880948810488114881248813488144881548816488174881848819488204882148822488234882448825488264882748828488294883048831488324883348834488354883648837488384883948840488414884248843488444884548846488474884848849488504885148852488534885448855488564885748858488594886048861488624886348864488654886648867488684886948870488714887248873488744887548876488774887848879488804888148882488834888448885488864888748888488894889048891488924889348894488954889648897488984889948900489014890248903489044890548906489074890848909489104891148912489134891448915489164891748918489194892048921489224892348924489254892648927489284892948930489314893248933489344893548936489374893848939489404894148942489434894448945489464894748948489494895048951489524895348954489554895648957489584895948960489614896248963489644896548966489674896848969489704897148972489734897448975489764897748978489794898048981489824898348984489854898648987489884898948990489914899248993489944899548996489974899848999490004900149002490034900449005490064900749008490094901049011490124901349014490154901649017490184901949020490214902249023490244902549026490274902849029490304903149032490334903449035490364903749038490394904049041490424904349044490454904649047490484904949050490514905249053490544905549056490574905849059490604906149062490634906449065490664906749068490694907049071490724907349074490754907649077490784907949080490814908249083490844908549086490874908849089490904909149092490934909449095490964909749098490994910049101491024910349104491054910649107491084910949110491114911249113491144911549116491174911849119491204912149122491234912449125491264912749128491294913049131491324913349134491354913649137491384913949140491414914249143491444914549146491474914849149491504915149152491534915449155491564915749158491594916049161491624916349164491654916649167491684916949170491714917249173491744917549176491774917849179491804918149182491834918449185491864918749188491894919049191491924919349194491954919649197491984919949200492014920249203492044920549206492074920849209492104921149212492134921449215492164921749218492194922049221492224922349224492254922649227492284922949230492314923249233492344923549236492374923849239492404924149242492434924449245492464924749248492494925049251492524925349254492554925649257492584925949260492614926249263492644926549266492674926849269492704927149272492734927449275492764927749278492794928049281492824928349284492854928649287492884928949290492914929249293492944929549296492974929849299493004930149302493034930449305493064930749308493094931049311493124931349314493154931649317493184931949320493214932249323493244932549326493274932849329493304933149332493334933449335493364933749338493394934049341493424934349344493454934649347493484934949350493514935249353493544935549356493574935849359493604936149362493634936449365493664936749368493694937049371493724937349374493754937649377493784937949380493814938249383493844938549386493874938849389493904939149392493934939449395493964939749398493994940049401494024940349404494054940649407494084940949410494114941249413494144941549416494174941849419494204942149422494234942449425494264942749428494294943049431494324943349434494354943649437494384943949440494414944249443494444944549446494474944849449494504945149452494534945449455494564945749458494594946049461494624946349464494654946649467494684946949470494714947249473494744947549476494774947849479494804948149482494834948449485494864948749488494894949049491494924949349494494954949649497494984949949500495014950249503495044950549506495074950849509495104951149512495134951449515495164951749518495194952049521495224952349524495254952649527495284952949530495314953249533495344953549536495374953849539495404954149542495434954449545495464954749548495494955049551495524955349554495554955649557495584955949560495614956249563495644956549566495674956849569495704957149572495734957449575495764957749578495794958049581495824958349584495854958649587495884958949590495914959249593495944959549596495974959849599496004960149602496034960449605496064960749608496094961049611496124961349614496154961649617496184961949620496214962249623496244962549626496274962849629496304963149632496334963449635496364963749638496394964049641496424964349644496454964649647496484964949650496514965249653496544965549656496574965849659496604966149662496634966449665496664966749668496694967049671496724967349674496754967649677496784967949680496814968249683496844968549686496874968849689496904969149692496934969449695496964969749698496994970049701497024970349704497054970649707497084970949710497114971249713497144971549716497174971849719497204972149722497234972449725497264972749728497294973049731497324973349734497354973649737497384973949740497414974249743497444974549746497474974849749497504975149752497534975449755497564975749758497594976049761497624976349764497654976649767497684976949770497714977249773497744977549776497774977849779497804978149782497834978449785497864978749788497894979049791497924979349794497954979649797497984979949800498014980249803498044980549806498074980849809498104981149812498134981449815498164981749818498194982049821498224982349824498254982649827498284982949830498314983249833498344983549836498374983849839498404984149842498434984449845498464984749848498494985049851498524985349854498554985649857498584985949860498614986249863498644986549866498674986849869498704987149872498734987449875498764987749878498794988049881498824988349884498854988649887498884988949890498914989249893498944989549896498974989849899499004990149902499034990449905499064990749908499094991049911499124991349914499154991649917499184991949920499214992249923499244992549926499274992849929499304993149932499334993449935499364993749938499394994049941499424994349944499454994649947499484994949950499514995249953499544995549956499574995849959499604996149962499634996449965499664996749968499694997049971499724997349974499754997649977499784997949980499814998249983499844998549986499874998849989499904999149992499934999449995499964999749998499995000050001500025000350004500055000650007500085000950010500115001250013500145001550016500175001850019500205002150022500235002450025500265002750028500295003050031500325003350034500355003650037500385003950040500415004250043500445004550046500475004850049500505005150052500535005450055500565005750058500595006050061500625006350064500655006650067500685006950070500715007250073500745007550076500775007850079500805008150082500835008450085500865008750088500895009050091500925009350094500955009650097500985009950100501015010250103501045010550106501075010850109501105011150112501135011450115501165011750118501195012050121501225012350124501255012650127501285012950130501315013250133501345013550136501375013850139501405014150142501435014450145501465014750148501495015050151501525015350154501555015650157501585015950160501615016250163501645016550166501675016850169501705017150172501735017450175501765017750178501795018050181501825018350184501855018650187501885018950190501915019250193501945019550196501975019850199502005020150202502035020450205502065020750208502095021050211502125021350214502155021650217502185021950220502215022250223502245022550226502275022850229502305023150232502335023450235502365023750238502395024050241502425024350244502455024650247502485024950250502515025250253502545025550256502575025850259502605026150262502635026450265502665026750268502695027050271502725027350274502755027650277502785027950280502815028250283502845028550286502875028850289502905029150292502935029450295502965029750298502995030050301503025030350304503055030650307503085030950310503115031250313503145031550316503175031850319503205032150322503235032450325503265032750328503295033050331503325033350334503355033650337503385033950340503415034250343503445034550346503475034850349503505035150352503535035450355503565035750358503595036050361503625036350364503655036650367503685036950370503715037250373503745037550376503775037850379503805038150382503835038450385503865038750388503895039050391503925039350394503955039650397503985039950400504015040250403504045040550406504075040850409504105041150412504135041450415504165041750418504195042050421504225042350424504255042650427504285042950430504315043250433504345043550436504375043850439504405044150442504435044450445504465044750448504495045050451504525045350454504555045650457504585045950460504615046250463504645046550466504675046850469504705047150472504735047450475504765047750478504795048050481504825048350484504855048650487504885048950490504915049250493504945049550496504975049850499505005050150502505035050450505505065050750508505095051050511505125051350514505155051650517505185051950520505215052250523505245052550526505275052850529505305053150532505335053450535505365053750538505395054050541505425054350544505455054650547505485054950550505515055250553505545055550556505575055850559505605056150562505635056450565505665056750568505695057050571505725057350574505755057650577505785057950580505815058250583505845058550586505875058850589505905059150592505935059450595505965059750598505995060050601506025060350604506055060650607506085060950610506115061250613506145061550616506175061850619506205062150622506235062450625506265062750628506295063050631506325063350634506355063650637506385063950640506415064250643506445064550646506475064850649506505065150652506535065450655506565065750658506595066050661506625066350664506655066650667506685066950670506715067250673506745067550676506775067850679506805068150682506835068450685506865068750688506895069050691506925069350694506955069650697506985069950700507015070250703507045070550706507075070850709507105071150712507135071450715507165071750718507195072050721507225072350724507255072650727507285072950730507315073250733507345073550736507375073850739507405074150742507435074450745507465074750748507495075050751507525075350754507555075650757507585075950760507615076250763507645076550766507675076850769507705077150772507735077450775507765077750778507795078050781507825078350784507855078650787507885078950790507915079250793507945079550796507975079850799508005080150802508035080450805508065080750808508095081050811508125081350814508155081650817508185081950820508215082250823508245082550826508275082850829508305083150832508335083450835508365083750838508395084050841508425084350844508455084650847508485084950850508515085250853508545085550856508575085850859508605086150862508635086450865508665086750868508695087050871508725087350874508755087650877508785087950880508815088250883508845088550886508875088850889508905089150892508935089450895508965089750898508995090050901509025090350904509055090650907509085090950910509115091250913509145091550916509175091850919509205092150922509235092450925509265092750928509295093050931509325093350934509355093650937509385093950940509415094250943509445094550946509475094850949509505095150952509535095450955509565095750958509595096050961509625096350964509655096650967509685096950970509715097250973509745097550976509775097850979509805098150982509835098450985509865098750988509895099050991509925099350994509955099650997509985099951000510015100251003510045100551006510075100851009510105101151012510135101451015510165101751018510195102051021510225102351024510255102651027510285102951030510315103251033510345103551036510375103851039510405104151042510435104451045510465104751048510495105051051510525105351054510555105651057510585105951060510615106251063510645106551066510675106851069510705107151072510735107451075510765107751078510795108051081510825108351084510855108651087510885108951090510915109251093510945109551096510975109851099511005110151102511035110451105511065110751108511095111051111511125111351114511155111651117511185111951120511215112251123511245112551126511275112851129511305113151132511335113451135511365113751138511395114051141511425114351144511455114651147511485114951150511515115251153511545115551156511575115851159511605116151162511635116451165511665116751168511695117051171511725117351174511755117651177511785117951180511815118251183511845118551186511875118851189511905119151192511935119451195511965119751198511995120051201512025120351204512055120651207512085120951210512115121251213512145121551216512175121851219512205122151222512235122451225512265122751228512295123051231512325123351234512355123651237512385123951240512415124251243512445124551246512475124851249512505125151252512535125451255512565125751258512595126051261512625126351264512655126651267512685126951270512715127251273512745127551276512775127851279512805128151282512835128451285512865128751288512895129051291512925129351294512955129651297512985129951300513015130251303513045130551306513075130851309513105131151312513135131451315513165131751318513195132051321513225132351324513255132651327513285132951330513315133251333513345133551336513375133851339513405134151342513435134451345513465134751348513495135051351513525135351354513555135651357513585135951360513615136251363513645136551366513675136851369513705137151372513735137451375513765137751378513795138051381513825138351384513855138651387513885138951390513915139251393513945139551396513975139851399514005140151402514035140451405514065140751408514095141051411514125141351414514155141651417514185141951420514215142251423514245142551426514275142851429514305143151432514335143451435514365143751438514395144051441514425144351444514455144651447514485144951450514515145251453514545145551456514575145851459514605146151462514635146451465514665146751468514695147051471514725147351474514755147651477514785147951480514815148251483514845148551486514875148851489514905149151492514935149451495514965149751498514995150051501515025150351504515055150651507515085150951510515115151251513515145151551516515175151851519515205152151522515235152451525515265152751528515295153051531515325153351534515355153651537515385153951540515415154251543515445154551546515475154851549515505155151552515535155451555515565155751558515595156051561515625156351564515655156651567515685156951570515715157251573515745157551576515775157851579515805158151582515835158451585515865158751588515895159051591515925159351594515955159651597515985159951600516015160251603516045160551606516075160851609516105161151612516135161451615516165161751618516195162051621516225162351624516255162651627516285162951630516315163251633516345163551636516375163851639516405164151642516435164451645516465164751648516495165051651516525165351654516555165651657516585165951660516615166251663516645166551666516675166851669516705167151672516735167451675516765167751678516795168051681516825168351684516855168651687516885168951690516915169251693516945169551696516975169851699517005170151702517035170451705517065170751708517095171051711517125171351714517155171651717517185171951720517215172251723517245172551726517275172851729517305173151732517335173451735517365173751738517395174051741517425174351744517455174651747517485174951750517515175251753517545175551756517575175851759517605176151762517635176451765517665176751768517695177051771517725177351774517755177651777517785177951780517815178251783517845178551786517875178851789517905179151792517935179451795517965179751798517995180051801518025180351804518055180651807518085180951810518115181251813518145181551816518175181851819518205182151822518235182451825518265182751828518295183051831518325183351834518355183651837518385183951840518415184251843518445184551846518475184851849518505185151852518535185451855518565185751858518595186051861518625186351864518655186651867518685186951870518715187251873518745187551876518775187851879518805188151882518835188451885518865188751888518895189051891518925189351894518955189651897518985189951900519015190251903519045190551906519075190851909519105191151912519135191451915519165191751918519195192051921519225192351924519255192651927519285192951930519315193251933519345193551936519375193851939519405194151942519435194451945519465194751948519495195051951519525195351954519555195651957519585195951960519615196251963519645196551966519675196851969519705197151972519735197451975519765197751978519795198051981519825198351984519855198651987519885198951990519915199251993519945199551996519975199851999520005200152002520035200452005520065200752008520095201052011520125201352014520155201652017520185201952020520215202252023520245202552026520275202852029520305203152032520335203452035520365203752038520395204052041520425204352044520455204652047520485204952050520515205252053520545205552056520575205852059520605206152062520635206452065520665206752068520695207052071520725207352074520755207652077520785207952080520815208252083520845208552086520875208852089520905209152092520935209452095520965209752098520995210052101521025210352104521055210652107521085210952110521115211252113521145211552116521175211852119521205212152122521235212452125521265212752128521295213052131521325213352134521355213652137521385213952140521415214252143521445214552146521475214852149521505215152152521535215452155521565215752158521595216052161521625216352164521655216652167521685216952170521715217252173521745217552176521775217852179521805218152182521835218452185521865218752188521895219052191521925219352194521955219652197521985219952200522015220252203522045220552206522075220852209522105221152212522135221452215522165221752218522195222052221522225222352224522255222652227522285222952230522315223252233522345223552236522375223852239522405224152242522435224452245522465224752248522495225052251522525225352254522555225652257522585225952260522615226252263522645226552266522675226852269522705227152272522735227452275522765227752278522795228052281522825228352284522855228652287522885228952290522915229252293522945229552296522975229852299523005230152302523035230452305523065230752308523095231052311523125231352314523155231652317523185231952320523215232252323523245232552326523275232852329523305233152332523335233452335523365233752338523395234052341523425234352344523455234652347523485234952350523515235252353523545235552356523575235852359523605236152362523635236452365523665236752368523695237052371523725237352374523755237652377523785237952380523815238252383523845238552386523875238852389523905239152392523935239452395523965239752398523995240052401524025240352404524055240652407524085240952410524115241252413524145241552416524175241852419524205242152422524235242452425524265242752428524295243052431524325243352434524355243652437524385243952440524415244252443524445244552446524475244852449524505245152452524535245452455524565245752458524595246052461524625246352464524655246652467524685246952470524715247252473524745247552476524775247852479524805248152482524835248452485524865248752488524895249052491524925249352494524955249652497524985249952500525015250252503525045250552506525075250852509525105251152512525135251452515525165251752518525195252052521525225252352524525255252652527525285252952530525315253252533525345253552536525375253852539525405254152542525435254452545525465254752548525495255052551525525255352554525555255652557525585255952560525615256252563525645256552566525675256852569525705257152572525735257452575525765257752578525795258052581525825258352584525855258652587525885258952590525915259252593525945259552596525975259852599526005260152602526035260452605526065260752608526095261052611526125261352614526155261652617526185261952620526215262252623526245262552626526275262852629526305263152632526335263452635526365263752638526395264052641526425264352644526455264652647526485264952650526515265252653526545265552656526575265852659526605266152662526635266452665526665266752668526695267052671526725267352674526755267652677526785267952680526815268252683526845268552686526875268852689526905269152692526935269452695526965269752698526995270052701527025270352704527055270652707527085270952710527115271252713527145271552716527175271852719527205272152722527235272452725527265272752728527295273052731527325273352734527355273652737527385273952740527415274252743527445274552746527475274852749527505275152752527535275452755527565275752758527595276052761527625276352764527655276652767527685276952770527715277252773527745277552776527775277852779527805278152782527835278452785527865278752788527895279052791527925279352794527955279652797527985279952800528015280252803528045280552806528075280852809528105281152812528135281452815528165281752818528195282052821528225282352824528255282652827528285282952830528315283252833528345283552836528375283852839528405284152842528435284452845528465284752848528495285052851528525285352854528555285652857528585285952860528615286252863528645286552866528675286852869528705287152872528735287452875528765287752878528795288052881528825288352884528855288652887528885288952890528915289252893528945289552896528975289852899529005290152902529035290452905529065290752908529095291052911529125291352914529155291652917529185291952920529215292252923529245292552926529275292852929529305293152932529335293452935529365293752938529395294052941529425294352944529455294652947529485294952950529515295252953529545295552956529575295852959529605296152962529635296452965529665296752968529695297052971529725297352974529755297652977529785297952980529815298252983529845298552986529875298852989529905299152992529935299452995529965299752998529995300053001530025300353004530055300653007530085300953010530115301253013530145301553016530175301853019530205302153022530235302453025530265302753028530295303053031530325303353034530355303653037530385303953040530415304253043530445304553046530475304853049530505305153052530535305453055530565305753058530595306053061530625306353064530655306653067530685306953070530715307253073530745307553076530775307853079530805308153082530835308453085530865308753088530895309053091530925309353094530955309653097530985309953100531015310253103531045310553106531075310853109531105311153112531135311453115531165311753118531195312053121531225312353124531255312653127531285312953130531315313253133531345313553136531375313853139531405314153142531435314453145531465314753148531495315053151531525315353154531555315653157531585315953160531615316253163531645316553166531675316853169531705317153172531735317453175531765317753178531795318053181531825318353184531855318653187531885318953190531915319253193531945319553196531975319853199532005320153202532035320453205532065320753208532095321053211532125321353214532155321653217532185321953220532215322253223532245322553226532275322853229532305323153232532335323453235532365323753238532395324053241532425324353244532455324653247532485324953250532515325253253532545325553256532575325853259532605326153262532635326453265532665326753268532695327053271532725327353274532755327653277532785327953280532815328253283532845328553286532875328853289532905329153292532935329453295532965329753298532995330053301533025330353304533055330653307533085330953310533115331253313533145331553316533175331853319533205332153322533235332453325533265332753328533295333053331533325333353334533355333653337533385333953340533415334253343533445334553346533475334853349533505335153352533535335453355533565335753358533595336053361533625336353364533655336653367533685336953370533715337253373533745337553376533775337853379533805338153382533835338453385533865338753388533895339053391533925339353394533955339653397533985339953400534015340253403534045340553406534075340853409534105341153412534135341453415534165341753418534195342053421534225342353424534255342653427534285342953430534315343253433534345343553436534375343853439534405344153442534435344453445534465344753448534495345053451534525345353454534555345653457534585345953460534615346253463534645346553466534675346853469534705347153472534735347453475534765347753478534795348053481534825348353484534855348653487534885348953490534915349253493534945349553496534975349853499535005350153502535035350453505535065350753508535095351053511535125351353514535155351653517535185351953520535215352253523535245352553526535275352853529535305353153532535335353453535535365353753538535395354053541535425354353544535455354653547535485354953550535515355253553535545355553556535575355853559535605356153562535635356453565535665356753568535695357053571535725357353574535755357653577535785357953580535815358253583535845358553586535875358853589535905359153592535935359453595535965359753598535995360053601536025360353604536055360653607536085360953610536115361253613536145361553616536175361853619536205362153622536235362453625536265362753628536295363053631536325363353634536355363653637536385363953640536415364253643536445364553646536475364853649536505365153652536535365453655536565365753658536595366053661536625366353664536655366653667536685366953670536715367253673536745367553676536775367853679536805368153682536835368453685536865368753688536895369053691536925369353694536955369653697536985369953700537015370253703537045370553706537075370853709537105371153712537135371453715537165371753718537195372053721537225372353724537255372653727537285372953730537315373253733537345373553736537375373853739537405374153742537435374453745537465374753748537495375053751537525375353754537555375653757537585375953760537615376253763537645376553766537675376853769537705377153772537735377453775537765377753778537795378053781537825378353784537855378653787537885378953790537915379253793537945379553796537975379853799538005380153802538035380453805538065380753808538095381053811538125381353814538155381653817538185381953820538215382253823538245382553826538275382853829538305383153832538335383453835538365383753838538395384053841538425384353844538455384653847538485384953850538515385253853538545385553856538575385853859538605386153862538635386453865538665386753868538695387053871538725387353874538755387653877538785387953880538815388253883538845388553886538875388853889538905389153892538935389453895538965389753898538995390053901539025390353904539055390653907539085390953910539115391253913539145391553916539175391853919539205392153922539235392453925539265392753928539295393053931539325393353934539355393653937539385393953940539415394253943539445394553946539475394853949539505395153952539535395453955539565395753958539595396053961539625396353964539655396653967539685396953970539715397253973539745397553976539775397853979539805398153982539835398453985539865398753988539895399053991539925399353994539955399653997539985399954000540015400254003540045400554006540075400854009540105401154012540135401454015540165401754018540195402054021540225402354024540255402654027540285402954030540315403254033540345403554036540375403854039540405404154042540435404454045540465404754048540495405054051540525405354054540555405654057540585405954060540615406254063540645406554066540675406854069540705407154072540735407454075540765407754078540795408054081540825408354084540855408654087540885408954090540915409254093540945409554096540975409854099541005410154102541035410454105541065410754108541095411054111541125411354114541155411654117541185411954120541215412254123541245412554126541275412854129541305413154132541335413454135541365413754138541395414054141541425414354144541455414654147541485414954150541515415254153541545415554156541575415854159541605416154162541635416454165541665416754168541695417054171541725417354174541755417654177541785417954180541815418254183541845418554186541875418854189541905419154192541935419454195541965419754198541995420054201542025420354204542055420654207542085420954210542115421254213542145421554216542175421854219542205422154222542235422454225542265422754228542295423054231542325423354234542355423654237542385423954240542415424254243542445424554246542475424854249542505425154252542535425454255542565425754258542595426054261542625426354264542655426654267542685426954270542715427254273542745427554276542775427854279542805428154282542835428454285542865428754288542895429054291542925429354294542955429654297542985429954300543015430254303543045430554306543075430854309543105431154312543135431454315543165431754318543195432054321543225432354324543255432654327543285432954330543315433254333543345433554336543375433854339543405434154342543435434454345543465434754348543495435054351543525435354354543555435654357543585435954360543615436254363543645436554366543675436854369543705437154372543735437454375543765437754378543795438054381543825438354384543855438654387543885438954390543915439254393543945439554396543975439854399544005440154402544035440454405544065440754408544095441054411544125441354414544155441654417544185441954420544215442254423544245442554426544275442854429544305443154432544335443454435544365443754438544395444054441544425444354444544455444654447544485444954450544515445254453544545445554456544575445854459544605446154462544635446454465544665446754468544695447054471544725447354474544755447654477544785447954480544815448254483544845448554486544875448854489544905449154492544935449454495544965449754498544995450054501545025450354504545055450654507545085450954510545115451254513545145451554516545175451854519545205452154522545235452454525545265452754528545295453054531545325453354534545355453654537545385453954540545415454254543545445454554546545475454854549545505455154552545535455454555545565455754558545595456054561545625456354564545655456654567545685456954570545715457254573545745457554576545775457854579545805458154582545835458454585545865458754588545895459054591545925459354594545955459654597545985459954600546015460254603546045460554606546075460854609546105461154612546135461454615546165461754618546195462054621546225462354624546255462654627546285462954630546315463254633546345463554636546375463854639546405464154642546435464454645546465464754648546495465054651546525465354654546555465654657546585465954660546615466254663546645466554666546675466854669546705467154672546735467454675546765467754678546795468054681546825468354684546855468654687546885468954690546915469254693546945469554696546975469854699547005470154702547035470454705547065470754708547095471054711547125471354714547155471654717547185471954720547215472254723547245472554726547275472854729547305473154732547335473454735547365473754738547395474054741547425474354744547455474654747547485474954750547515475254753547545475554756547575475854759547605476154762547635476454765547665476754768547695477054771547725477354774547755477654777547785477954780547815478254783547845478554786547875478854789547905479154792547935479454795547965479754798547995480054801548025480354804548055480654807548085480954810548115481254813548145481554816548175481854819548205482154822548235482454825548265482754828548295483054831548325483354834548355483654837548385483954840548415484254843548445484554846548475484854849548505485154852548535485454855548565485754858548595486054861548625486354864548655486654867548685486954870548715487254873548745487554876548775487854879548805488154882548835488454885548865488754888548895489054891548925489354894548955489654897548985489954900549015490254903549045490554906549075490854909549105491154912549135491454915549165491754918549195492054921549225492354924549255492654927549285492954930549315493254933549345493554936549375493854939549405494154942549435494454945549465494754948549495495054951549525495354954549555495654957549585495954960549615496254963549645496554966549675496854969549705497154972549735497454975549765497754978549795498054981549825498354984549855498654987549885498954990549915499254993549945499554996549975499854999550005500155002550035500455005550065500755008550095501055011550125501355014550155501655017550185501955020550215502255023550245502555026550275502855029550305503155032550335503455035550365503755038550395504055041550425504355044550455504655047550485504955050550515505255053550545505555056550575505855059550605506155062550635506455065550665506755068550695507055071550725507355074550755507655077550785507955080550815508255083550845508555086550875508855089550905509155092550935509455095550965509755098550995510055101551025510355104551055510655107551085510955110551115511255113551145511555116551175511855119551205512155122551235512455125551265512755128551295513055131551325513355134551355513655137551385513955140551415514255143551445514555146551475514855149551505515155152551535515455155551565515755158551595516055161551625516355164551655516655167551685516955170551715517255173551745517555176551775517855179551805518155182551835518455185551865518755188551895519055191551925519355194551955519655197551985519955200552015520255203552045520555206552075520855209552105521155212552135521455215552165521755218552195522055221552225522355224552255522655227552285522955230552315523255233552345523555236552375523855239552405524155242552435524455245552465524755248552495525055251552525525355254552555525655257552585525955260552615526255263552645526555266552675526855269552705527155272552735527455275552765527755278552795528055281552825528355284552855528655287552885528955290552915529255293552945529555296552975529855299553005530155302553035530455305553065530755308553095531055311553125531355314553155531655317553185531955320553215532255323553245532555326553275532855329553305533155332553335533455335553365533755338553395534055341553425534355344553455534655347553485534955350553515535255353553545535555356553575535855359553605536155362553635536455365553665536755368553695537055371553725537355374553755537655377553785537955380553815538255383553845538555386553875538855389553905539155392553935539455395553965539755398553995540055401554025540355404554055540655407554085540955410554115541255413554145541555416554175541855419554205542155422554235542455425554265542755428554295543055431554325543355434554355543655437554385543955440554415544255443554445544555446554475544855449554505545155452554535545455455554565545755458554595546055461554625546355464554655546655467554685546955470554715547255473554745547555476554775547855479554805548155482554835548455485554865548755488554895549055491554925549355494554955549655497554985549955500555015550255503555045550555506555075550855509555105551155512555135551455515555165551755518555195552055521555225552355524555255552655527555285552955530555315553255533555345553555536555375553855539555405554155542555435554455545555465554755548555495555055551555525555355554555555555655557555585555955560555615556255563555645556555566555675556855569555705557155572555735557455575555765557755578555795558055581555825558355584555855558655587555885558955590555915559255593555945559555596555975559855599556005560155602556035560455605556065560755608556095561055611556125561355614556155561655617556185561955620556215562255623556245562555626556275562855629556305563155632556335563455635556365563755638556395564055641556425564355644556455564655647556485564955650556515565255653556545565555656556575565855659556605566155662556635566455665556665566755668556695567055671556725567355674556755567655677556785567955680556815568255683556845568555686556875568855689556905569155692556935569455695556965569755698556995570055701557025570355704557055570655707557085570955710557115571255713557145571555716557175571855719557205572155722557235572455725557265572755728557295573055731557325573355734557355573655737557385573955740557415574255743557445574555746557475574855749557505575155752557535575455755557565575755758557595576055761557625576355764557655576655767557685576955770557715577255773557745577555776557775577855779557805578155782557835578455785557865578755788557895579055791557925579355794557955579655797557985579955800558015580255803558045580555806558075580855809558105581155812558135581455815558165581755818558195582055821558225582355824558255582655827558285582955830558315583255833558345583555836558375583855839558405584155842558435584455845558465584755848558495585055851558525585355854558555585655857558585585955860558615586255863558645586555866558675586855869558705587155872558735587455875558765587755878558795588055881558825588355884558855588655887558885588955890558915589255893558945589555896558975589855899559005590155902559035590455905559065590755908559095591055911559125591355914559155591655917559185591955920559215592255923559245592555926559275592855929559305593155932559335593455935559365593755938559395594055941559425594355944559455594655947559485594955950559515595255953559545595555956559575595855959559605596155962559635596455965559665596755968559695597055971559725597355974559755597655977559785597955980559815598255983559845598555986559875598855989559905599155992559935599455995559965599755998559995600056001560025600356004560055600656007560085600956010560115601256013560145601556016560175601856019560205602156022560235602456025560265602756028560295603056031560325603356034560355603656037560385603956040560415604256043560445604556046560475604856049560505605156052560535605456055560565605756058560595606056061560625606356064560655606656067560685606956070560715607256073560745607556076560775607856079560805608156082560835608456085560865608756088560895609056091560925609356094560955609656097560985609956100561015610256103561045610556106561075610856109561105611156112561135611456115561165611756118561195612056121561225612356124561255612656127561285612956130561315613256133561345613556136561375613856139561405614156142561435614456145561465614756148561495615056151561525615356154561555615656157561585615956160561615616256163561645616556166561675616856169561705617156172561735617456175561765617756178561795618056181561825618356184561855618656187561885618956190561915619256193561945619556196561975619856199562005620156202562035620456205562065620756208562095621056211562125621356214562155621656217562185621956220562215622256223562245622556226562275622856229562305623156232562335623456235562365623756238562395624056241562425624356244562455624656247562485624956250562515625256253562545625556256562575625856259562605626156262562635626456265562665626756268562695627056271562725627356274562755627656277562785627956280562815628256283562845628556286562875628856289562905629156292562935629456295562965629756298562995630056301563025630356304563055630656307563085630956310563115631256313563145631556316563175631856319563205632156322563235632456325563265632756328563295633056331563325633356334563355633656337563385633956340563415634256343563445634556346563475634856349563505635156352563535635456355563565635756358563595636056361563625636356364563655636656367563685636956370563715637256373563745637556376563775637856379563805638156382563835638456385563865638756388563895639056391563925639356394563955639656397563985639956400564015640256403564045640556406564075640856409564105641156412564135641456415564165641756418564195642056421564225642356424564255642656427564285642956430564315643256433564345643556436564375643856439564405644156442564435644456445564465644756448564495645056451564525645356454564555645656457564585645956460564615646256463564645646556466564675646856469564705647156472564735647456475564765647756478564795648056481564825648356484564855648656487564885648956490564915649256493564945649556496564975649856499565005650156502565035650456505565065650756508565095651056511565125651356514565155651656517565185651956520565215652256523565245652556526565275652856529565305653156532565335653456535565365653756538565395654056541565425654356544565455654656547565485654956550565515655256553565545655556556565575655856559565605656156562565635656456565565665656756568565695657056571565725657356574565755657656577565785657956580565815658256583565845658556586565875658856589565905659156592565935659456595565965659756598565995660056601566025660356604566055660656607566085660956610566115661256613566145661556616566175661856619566205662156622566235662456625566265662756628566295663056631566325663356634566355663656637566385663956640566415664256643566445664556646566475664856649566505665156652566535665456655566565665756658566595666056661566625666356664566655666656667566685666956670566715667256673566745667556676566775667856679566805668156682566835668456685566865668756688566895669056691566925669356694566955669656697566985669956700567015670256703567045670556706567075670856709567105671156712567135671456715567165671756718567195672056721567225672356724567255672656727567285672956730567315673256733567345673556736567375673856739567405674156742567435674456745567465674756748567495675056751567525675356754567555675656757567585675956760567615676256763567645676556766567675676856769567705677156772567735677456775567765677756778567795678056781567825678356784567855678656787567885678956790567915679256793567945679556796567975679856799568005680156802568035680456805568065680756808568095681056811568125681356814568155681656817568185681956820568215682256823568245682556826568275682856829568305683156832568335683456835568365683756838568395684056841568425684356844568455684656847568485684956850568515685256853568545685556856568575685856859568605686156862568635686456865568665686756868568695687056871568725687356874568755687656877568785687956880568815688256883568845688556886568875688856889568905689156892568935689456895568965689756898568995690056901569025690356904569055690656907569085690956910569115691256913569145691556916569175691856919569205692156922569235692456925569265692756928569295693056931569325693356934569355693656937569385693956940569415694256943569445694556946569475694856949569505695156952569535695456955569565695756958569595696056961569625696356964569655696656967569685696956970569715697256973569745697556976569775697856979569805698156982569835698456985569865698756988569895699056991569925699356994569955699656997569985699957000570015700257003570045700557006570075700857009570105701157012570135701457015570165701757018570195702057021570225702357024570255702657027570285702957030570315703257033570345703557036570375703857039570405704157042570435704457045570465704757048570495705057051570525705357054570555705657057570585705957060570615706257063570645706557066570675706857069570705707157072570735707457075570765707757078570795708057081570825708357084570855708657087570885708957090570915709257093570945709557096570975709857099571005710157102571035710457105571065710757108571095711057111571125711357114571155711657117571185711957120571215712257123571245712557126571275712857129571305713157132571335713457135571365713757138571395714057141571425714357144571455714657147571485714957150571515715257153571545715557156571575715857159571605716157162571635716457165571665716757168571695717057171571725717357174571755717657177571785717957180571815718257183571845718557186571875718857189571905719157192571935719457195571965719757198571995720057201572025720357204572055720657207572085720957210572115721257213572145721557216572175721857219572205722157222572235722457225572265722757228572295723057231572325723357234572355723657237572385723957240572415724257243572445724557246572475724857249572505725157252572535725457255572565725757258572595726057261572625726357264572655726657267572685726957270572715727257273572745727557276572775727857279572805728157282572835728457285572865728757288572895729057291572925729357294572955729657297572985729957300573015730257303573045730557306573075730857309573105731157312573135731457315573165731757318573195732057321573225732357324573255732657327573285732957330573315733257333573345733557336573375733857339573405734157342573435734457345573465734757348573495735057351573525735357354573555735657357573585735957360573615736257363573645736557366573675736857369573705737157372573735737457375573765737757378573795738057381573825738357384573855738657387573885738957390573915739257393573945739557396573975739857399574005740157402574035740457405574065740757408574095741057411574125741357414574155741657417574185741957420574215742257423574245742557426574275742857429574305743157432574335743457435574365743757438574395744057441574425744357444574455744657447574485744957450574515745257453574545745557456574575745857459574605746157462574635746457465574665746757468574695747057471574725747357474574755747657477574785747957480574815748257483574845748557486574875748857489574905749157492574935749457495574965749757498574995750057501575025750357504575055750657507575085750957510575115751257513575145751557516575175751857519575205752157522575235752457525575265752757528575295753057531575325753357534575355753657537575385753957540575415754257543575445754557546575475754857549575505755157552575535755457555575565755757558575595756057561575625756357564575655756657567575685756957570575715757257573575745757557576575775757857579575805758157582575835758457585575865758757588575895759057591575925759357594575955759657597575985759957600576015760257603576045760557606576075760857609576105761157612576135761457615576165761757618576195762057621576225762357624576255762657627576285762957630576315763257633576345763557636576375763857639576405764157642576435764457645576465764757648576495765057651576525765357654576555765657657576585765957660576615766257663576645766557666576675766857669576705767157672576735767457675576765767757678576795768057681576825768357684576855768657687576885768957690576915769257693576945769557696576975769857699577005770157702577035770457705577065770757708577095771057711577125771357714577155771657717577185771957720577215772257723577245772557726577275772857729577305773157732577335773457735577365773757738577395774057741577425774357744577455774657747577485774957750577515775257753577545775557756577575775857759577605776157762577635776457765577665776757768577695777057771577725777357774577755777657777577785777957780577815778257783577845778557786577875778857789577905779157792577935779457795577965779757798577995780057801578025780357804578055780657807578085780957810578115781257813578145781557816578175781857819578205782157822578235782457825578265782757828578295783057831578325783357834578355783657837578385783957840578415784257843578445784557846578475784857849578505785157852578535785457855578565785757858578595786057861578625786357864578655786657867578685786957870578715787257873578745787557876578775787857879578805788157882578835788457885578865788757888578895789057891578925789357894578955789657897578985789957900579015790257903579045790557906579075790857909579105791157912579135791457915579165791757918579195792057921579225792357924579255792657927579285792957930579315793257933579345793557936579375793857939579405794157942579435794457945579465794757948579495795057951579525795357954579555795657957579585795957960579615796257963579645796557966579675796857969579705797157972579735797457975579765797757978579795798057981579825798357984579855798657987579885798957990579915799257993579945799557996579975799857999580005800158002580035800458005580065800758008580095801058011580125801358014580155801658017580185801958020580215802258023580245802558026580275802858029580305803158032580335803458035580365803758038580395804058041580425804358044580455804658047580485804958050580515805258053580545805558056580575805858059580605806158062580635806458065580665806758068580695807058071580725807358074580755807658077580785807958080580815808258083580845808558086580875808858089580905809158092580935809458095580965809758098580995810058101581025810358104581055810658107581085810958110581115811258113581145811558116581175811858119581205812158122581235812458125581265812758128581295813058131581325813358134581355813658137581385813958140581415814258143581445814558146581475814858149581505815158152581535815458155581565815758158581595816058161581625816358164581655816658167581685816958170581715817258173581745817558176581775817858179581805818158182581835818458185581865818758188581895819058191581925819358194581955819658197581985819958200582015820258203582045820558206582075820858209582105821158212582135821458215582165821758218582195822058221582225822358224582255822658227582285822958230582315823258233582345823558236582375823858239582405824158242582435824458245582465824758248582495825058251582525825358254582555825658257582585825958260582615826258263582645826558266582675826858269582705827158272582735827458275582765827758278582795828058281582825828358284582855828658287582885828958290582915829258293582945829558296582975829858299583005830158302583035830458305583065830758308583095831058311583125831358314583155831658317583185831958320583215832258323583245832558326583275832858329583305833158332583335833458335583365833758338583395834058341583425834358344583455834658347583485834958350583515835258353583545835558356583575835858359583605836158362583635836458365583665836758368583695837058371583725837358374583755837658377583785837958380583815838258383583845838558386583875838858389583905839158392583935839458395583965839758398583995840058401584025840358404584055840658407584085840958410584115841258413584145841558416584175841858419584205842158422584235842458425584265842758428584295843058431584325843358434584355843658437584385843958440584415844258443584445844558446584475844858449584505845158452584535845458455584565845758458584595846058461584625846358464584655846658467584685846958470584715847258473584745847558476584775847858479584805848158482584835848458485584865848758488584895849058491584925849358494584955849658497584985849958500585015850258503585045850558506585075850858509585105851158512585135851458515585165851758518585195852058521585225852358524585255852658527585285852958530585315853258533585345853558536585375853858539585405854158542585435854458545585465854758548585495855058551585525855358554585555855658557585585855958560585615856258563585645856558566585675856858569585705857158572585735857458575585765857758578585795858058581585825858358584585855858658587585885858958590585915859258593585945859558596585975859858599586005860158602586035860458605586065860758608586095861058611586125861358614586155861658617586185861958620586215862258623586245862558626586275862858629586305863158632586335863458635586365863758638586395864058641586425864358644586455864658647586485864958650586515865258653586545865558656586575865858659586605866158662586635866458665586665866758668586695867058671586725867358674586755867658677586785867958680586815868258683586845868558686586875868858689586905869158692586935869458695586965869758698586995870058701587025870358704587055870658707587085870958710587115871258713587145871558716587175871858719587205872158722587235872458725587265872758728587295873058731587325873358734587355873658737587385873958740587415874258743587445874558746587475874858749587505875158752587535875458755587565875758758587595876058761587625876358764587655876658767587685876958770587715877258773587745877558776587775877858779587805878158782587835878458785587865878758788587895879058791587925879358794587955879658797587985879958800588015880258803588045880558806588075880858809588105881158812588135881458815588165881758818588195882058821588225882358824588255882658827588285882958830588315883258833588345883558836588375883858839588405884158842588435884458845588465884758848588495885058851588525885358854588555885658857588585885958860588615886258863588645886558866588675886858869588705887158872588735887458875588765887758878588795888058881588825888358884588855888658887588885888958890588915889258893588945889558896588975889858899589005890158902589035890458905589065890758908589095891058911589125891358914589155891658917589185891958920589215892258923589245892558926589275892858929589305893158932589335893458935589365893758938589395894058941589425894358944589455894658947589485894958950589515895258953589545895558956589575895858959589605896158962589635896458965589665896758968589695897058971589725897358974589755897658977589785897958980589815898258983589845898558986589875898858989589905899158992589935899458995589965899758998589995900059001590025900359004590055900659007590085900959010590115901259013590145901559016590175901859019590205902159022590235902459025590265902759028590295903059031590325903359034590355903659037590385903959040590415904259043590445904559046590475904859049590505905159052590535905459055590565905759058590595906059061590625906359064590655906659067590685906959070590715907259073590745907559076590775907859079590805908159082590835908459085590865908759088590895909059091590925909359094590955909659097590985909959100591015910259103591045910559106591075910859109591105911159112591135911459115591165911759118591195912059121591225912359124591255912659127591285912959130591315913259133591345913559136591375913859139591405914159142591435914459145591465914759148591495915059151591525915359154591555915659157591585915959160591615916259163591645916559166591675916859169591705917159172591735917459175591765917759178591795918059181591825918359184591855918659187591885918959190591915919259193591945919559196591975919859199592005920159202592035920459205592065920759208592095921059211592125921359214592155921659217592185921959220592215922259223592245922559226592275922859229592305923159232592335923459235592365923759238592395924059241592425924359244592455924659247592485924959250592515925259253592545925559256592575925859259592605926159262592635926459265592665926759268592695927059271592725927359274592755927659277592785927959280592815928259283592845928559286592875928859289592905929159292592935929459295592965929759298592995930059301593025930359304593055930659307593085930959310593115931259313593145931559316593175931859319593205932159322593235932459325593265932759328593295933059331593325933359334593355933659337593385933959340593415934259343593445934559346593475934859349593505935159352593535935459355593565935759358593595936059361593625936359364593655936659367593685936959370593715937259373593745937559376593775937859379593805938159382593835938459385593865938759388593895939059391593925939359394593955939659397593985939959400594015940259403594045940559406594075940859409594105941159412594135941459415594165941759418594195942059421594225942359424594255942659427594285942959430594315943259433594345943559436594375943859439594405944159442594435944459445594465944759448594495945059451594525945359454594555945659457594585945959460594615946259463594645946559466594675946859469594705947159472594735947459475594765947759478594795948059481594825948359484594855948659487594885948959490594915949259493594945949559496594975949859499595005950159502595035950459505595065950759508595095951059511595125951359514595155951659517595185951959520595215952259523595245952559526595275952859529595305953159532595335953459535595365953759538595395954059541595425954359544595455954659547595485954959550595515955259553595545955559556595575955859559595605956159562595635956459565595665956759568595695957059571595725957359574595755957659577595785957959580595815958259583595845958559586595875958859589595905959159592595935959459595595965959759598595995960059601596025960359604596055960659607596085960959610596115961259613596145961559616596175961859619596205962159622596235962459625596265962759628596295963059631596325963359634596355963659637596385963959640596415964259643596445964559646596475964859649596505965159652596535965459655596565965759658596595966059661596625966359664596655966659667596685966959670596715967259673596745967559676596775967859679596805968159682596835968459685596865968759688596895969059691596925969359694596955969659697596985969959700597015970259703597045970559706597075970859709597105971159712597135971459715597165971759718597195972059721597225972359724597255972659727597285972959730597315973259733597345973559736597375973859739597405974159742597435974459745597465974759748597495975059751597525975359754597555975659757597585975959760597615976259763597645976559766597675976859769597705977159772597735977459775597765977759778597795978059781597825978359784597855978659787597885978959790597915979259793597945979559796597975979859799598005980159802598035980459805598065980759808598095981059811598125981359814598155981659817598185981959820598215982259823598245982559826598275982859829598305983159832598335983459835598365983759838598395984059841598425984359844598455984659847598485984959850598515985259853598545985559856598575985859859598605986159862598635986459865598665986759868598695987059871598725987359874598755987659877598785987959880598815988259883598845988559886598875988859889598905989159892598935989459895598965989759898598995990059901599025990359904599055990659907599085990959910599115991259913599145991559916599175991859919599205992159922599235992459925599265992759928599295993059931599325993359934599355993659937599385993959940599415994259943599445994559946599475994859949599505995159952599535995459955599565995759958599595996059961599625996359964599655996659967599685996959970599715997259973599745997559976599775997859979599805998159982599835998459985599865998759988599895999059991599925999359994599955999659997599985999960000600016000260003600046000560006600076000860009600106001160012600136001460015600166001760018600196002060021600226002360024600256002660027600286002960030600316003260033600346003560036600376003860039600406004160042600436004460045600466004760048600496005060051600526005360054600556005660057600586005960060600616006260063600646006560066600676006860069600706007160072600736007460075600766007760078600796008060081600826008360084600856008660087600886008960090600916009260093600946009560096600976009860099601006010160102601036010460105601066010760108601096011060111601126011360114601156011660117601186011960120601216012260123601246012560126601276012860129601306013160132601336013460135601366013760138601396014060141601426014360144601456014660147601486014960150601516015260153601546015560156601576015860159601606016160162601636016460165601666016760168601696017060171601726017360174601756017660177601786017960180601816018260183601846018560186601876018860189601906019160192601936019460195601966019760198601996020060201602026020360204602056020660207602086020960210602116021260213602146021560216602176021860219602206022160222602236022460225602266022760228602296023060231602326023360234602356023660237602386023960240602416024260243602446024560246602476024860249602506025160252602536025460255602566025760258602596026060261602626026360264602656026660267602686026960270602716027260273602746027560276602776027860279602806028160282602836028460285602866028760288602896029060291602926029360294602956029660297602986029960300603016030260303603046030560306603076030860309603106031160312603136031460315603166031760318603196032060321603226032360324603256032660327603286032960330603316033260333603346033560336603376033860339603406034160342603436034460345603466034760348603496035060351603526035360354603556035660357603586035960360603616036260363603646036560366603676036860369603706037160372603736037460375603766037760378603796038060381603826038360384603856038660387603886038960390603916039260393603946039560396603976039860399604006040160402604036040460405604066040760408604096041060411604126041360414604156041660417604186041960420604216042260423604246042560426604276042860429604306043160432604336043460435604366043760438604396044060441604426044360444604456044660447604486044960450604516045260453604546045560456604576045860459604606046160462604636046460465604666046760468604696047060471604726047360474604756047660477604786047960480604816048260483604846048560486604876048860489604906049160492604936049460495604966049760498604996050060501605026050360504605056050660507605086050960510605116051260513605146051560516605176051860519605206052160522605236052460525605266052760528605296053060531605326053360534605356053660537605386053960540605416054260543605446054560546605476054860549605506055160552605536055460555605566055760558605596056060561605626056360564605656056660567605686056960570605716057260573605746057560576605776057860579605806058160582605836058460585605866058760588605896059060591605926059360594605956059660597605986059960600606016060260603606046060560606606076060860609606106061160612606136061460615606166061760618606196062060621606226062360624606256062660627606286062960630606316063260633606346063560636606376063860639606406064160642606436064460645606466064760648606496065060651606526065360654606556065660657606586065960660606616066260663606646066560666606676066860669606706067160672606736067460675606766067760678606796068060681606826068360684606856068660687606886068960690606916069260693606946069560696606976069860699607006070160702607036070460705607066070760708607096071060711607126071360714607156071660717607186071960720607216072260723607246072560726607276072860729607306073160732607336073460735607366073760738607396074060741607426074360744607456074660747607486074960750607516075260753607546075560756607576075860759607606076160762607636076460765607666076760768607696077060771607726077360774607756077660777607786077960780607816078260783607846078560786607876078860789607906079160792607936079460795607966079760798607996080060801608026080360804608056080660807608086080960810608116081260813608146081560816608176081860819608206082160822608236082460825608266082760828608296083060831608326083360834608356083660837608386083960840608416084260843608446084560846608476084860849608506085160852608536085460855608566085760858608596086060861608626086360864608656086660867608686086960870608716087260873608746087560876608776087860879608806088160882608836088460885608866088760888608896089060891608926089360894608956089660897608986089960900609016090260903609046090560906609076090860909609106091160912609136091460915609166091760918609196092060921609226092360924609256092660927609286092960930609316093260933609346093560936609376093860939609406094160942609436094460945609466094760948609496095060951609526095360954609556095660957609586095960960609616096260963609646096560966609676096860969609706097160972609736097460975609766097760978609796098060981609826098360984609856098660987609886098960990609916099260993609946099560996609976099860999610006100161002610036100461005610066100761008610096101061011610126101361014610156101661017610186101961020610216102261023610246102561026610276102861029610306103161032610336103461035610366103761038610396104061041610426104361044610456104661047610486104961050610516105261053610546105561056610576105861059610606106161062610636106461065610666106761068610696107061071610726107361074610756107661077610786107961080610816108261083610846108561086610876108861089610906109161092610936109461095610966109761098610996110061101611026110361104611056110661107611086110961110611116111261113611146111561116611176111861119611206112161122611236112461125611266112761128611296113061131611326113361134611356113661137611386113961140611416114261143611446114561146611476114861149611506115161152611536115461155611566115761158611596116061161611626116361164611656116661167611686116961170611716117261173611746117561176611776117861179611806118161182611836118461185611866118761188611896119061191611926119361194611956119661197611986119961200612016120261203612046120561206612076120861209612106121161212612136121461215612166121761218612196122061221612226122361224612256122661227612286122961230612316123261233612346123561236612376123861239612406124161242612436124461245612466124761248612496125061251612526125361254612556125661257612586125961260612616126261263612646126561266612676126861269612706127161272612736127461275612766127761278612796128061281612826128361284612856128661287612886128961290612916129261293612946129561296612976129861299613006130161302613036130461305613066130761308613096131061311613126131361314613156131661317613186131961320613216132261323613246132561326613276132861329613306133161332613336133461335613366133761338613396134061341613426134361344613456134661347613486134961350613516135261353613546135561356613576135861359613606136161362613636136461365613666136761368613696137061371613726137361374613756137661377613786137961380613816138261383613846138561386613876138861389613906139161392613936139461395613966139761398613996140061401614026140361404614056140661407614086140961410614116141261413614146141561416614176141861419614206142161422614236142461425614266142761428614296143061431614326143361434614356143661437614386143961440614416144261443614446144561446614476144861449614506145161452614536145461455614566145761458614596146061461614626146361464614656146661467614686146961470614716147261473614746147561476614776147861479614806148161482614836148461485614866148761488614896149061491614926149361494614956149661497614986149961500615016150261503615046150561506615076150861509615106151161512615136151461515615166151761518615196152061521615226152361524615256152661527615286152961530615316153261533615346153561536615376153861539615406154161542615436154461545615466154761548615496155061551615526155361554615556155661557615586155961560615616156261563615646156561566615676156861569615706157161572615736157461575615766157761578615796158061581615826158361584615856158661587615886158961590615916159261593615946159561596615976159861599616006160161602616036160461605616066160761608616096161061611616126161361614616156161661617616186161961620616216162261623616246162561626616276162861629616306163161632616336163461635616366163761638616396164061641616426164361644616456164661647616486164961650616516165261653616546165561656616576165861659616606166161662616636166461665616666166761668616696167061671616726167361674616756167661677616786167961680616816168261683616846168561686616876168861689616906169161692616936169461695616966169761698616996170061701617026170361704617056170661707617086170961710617116171261713617146171561716617176171861719617206172161722617236172461725617266172761728617296173061731617326173361734617356173661737617386173961740617416174261743617446174561746617476174861749617506175161752617536175461755617566175761758617596176061761617626176361764617656176661767617686176961770617716177261773617746177561776617776177861779617806178161782617836178461785617866178761788617896179061791617926179361794617956179661797617986179961800618016180261803618046180561806618076180861809618106181161812618136181461815618166181761818618196182061821618226182361824618256182661827618286182961830618316183261833618346183561836618376183861839618406184161842618436184461845618466184761848618496185061851618526185361854618556185661857618586185961860618616186261863618646186561866618676186861869618706187161872618736187461875618766187761878618796188061881618826188361884618856188661887618886188961890618916189261893618946189561896618976189861899619006190161902619036190461905619066190761908619096191061911619126191361914619156191661917619186191961920619216192261923619246192561926619276192861929619306193161932619336193461935619366193761938619396194061941619426194361944619456194661947619486194961950619516195261953619546195561956619576195861959619606196161962619636196461965619666196761968619696197061971619726197361974619756197661977619786197961980619816198261983619846198561986619876198861989619906199161992619936199461995619966199761998619996200062001620026200362004620056200662007620086200962010620116201262013620146201562016620176201862019620206202162022620236202462025620266202762028620296203062031620326203362034620356203662037620386203962040620416204262043620446204562046620476204862049620506205162052620536205462055620566205762058620596206062061620626206362064620656206662067620686206962070620716207262073620746207562076620776207862079620806208162082620836208462085620866208762088620896209062091620926209362094620956209662097620986209962100621016210262103621046210562106621076210862109621106211162112621136211462115621166211762118621196212062121621226212362124621256212662127621286212962130621316213262133621346213562136621376213862139621406214162142621436214462145621466214762148621496215062151621526215362154621556215662157621586215962160621616216262163621646216562166621676216862169621706217162172621736217462175621766217762178621796218062181621826218362184621856218662187621886218962190621916219262193621946219562196621976219862199622006220162202622036220462205622066220762208622096221062211622126221362214622156221662217622186221962220622216222262223622246222562226622276222862229622306223162232622336223462235622366223762238622396224062241622426224362244622456224662247622486224962250622516225262253622546225562256622576225862259622606226162262622636226462265622666226762268622696227062271622726227362274622756227662277622786227962280622816228262283622846228562286622876228862289622906229162292622936229462295622966229762298622996230062301623026230362304623056230662307623086230962310623116231262313623146231562316623176231862319623206232162322623236232462325623266232762328623296233062331623326233362334623356233662337623386233962340623416234262343623446234562346623476234862349623506235162352623536235462355623566235762358623596236062361623626236362364623656236662367623686236962370623716237262373623746237562376623776237862379623806238162382623836238462385623866238762388623896239062391623926239362394623956239662397623986239962400624016240262403624046240562406624076240862409624106241162412624136241462415624166241762418624196242062421624226242362424624256242662427624286242962430624316243262433624346243562436624376243862439624406244162442624436244462445624466244762448624496245062451624526245362454624556245662457624586245962460624616246262463624646246562466624676246862469624706247162472624736247462475624766247762478624796248062481624826248362484624856248662487624886248962490624916249262493624946249562496624976249862499625006250162502625036250462505625066250762508625096251062511625126251362514625156251662517625186251962520625216252262523625246252562526625276252862529625306253162532625336253462535625366253762538625396254062541625426254362544625456254662547625486254962550625516255262553625546255562556625576255862559625606256162562625636256462565625666256762568625696257062571625726257362574625756257662577625786257962580625816258262583625846258562586625876258862589625906259162592625936259462595625966259762598625996260062601626026260362604626056260662607626086260962610626116261262613626146261562616626176261862619626206262162622626236262462625626266262762628626296263062631626326263362634626356263662637626386263962640626416264262643626446264562646626476264862649626506265162652626536265462655626566265762658626596266062661626626266362664626656266662667626686266962670626716267262673626746267562676626776267862679626806268162682626836268462685626866268762688626896269062691626926269362694626956269662697626986269962700627016270262703627046270562706627076270862709627106271162712627136271462715627166271762718627196272062721627226272362724627256272662727627286272962730627316273262733627346273562736627376273862739627406274162742627436274462745627466274762748627496275062751627526275362754627556275662757627586275962760627616276262763627646276562766627676276862769627706277162772627736277462775627766277762778627796278062781627826278362784627856278662787627886278962790627916279262793627946279562796627976279862799628006280162802628036280462805628066280762808628096281062811628126281362814628156281662817628186281962820628216282262823628246282562826628276282862829628306283162832628336283462835628366283762838628396284062841628426284362844628456284662847628486284962850628516285262853628546285562856628576285862859628606286162862628636286462865628666286762868628696287062871628726287362874628756287662877628786287962880628816288262883628846288562886628876288862889628906289162892628936289462895628966289762898628996290062901629026290362904629056290662907629086290962910629116291262913629146291562916629176291862919629206292162922629236292462925629266292762928629296293062931629326293362934629356293662937629386293962940629416294262943629446294562946629476294862949629506295162952629536295462955629566295762958629596296062961629626296362964629656296662967629686296962970629716297262973629746297562976629776297862979629806298162982629836298462985629866298762988629896299062991629926299362994629956299662997629986299963000630016300263003630046300563006630076300863009630106301163012630136301463015630166301763018630196302063021630226302363024630256302663027630286302963030630316303263033630346303563036630376303863039630406304163042630436304463045630466304763048630496305063051630526305363054630556305663057630586305963060630616306263063630646306563066630676306863069630706307163072630736307463075630766307763078630796308063081630826308363084630856308663087630886308963090630916309263093630946309563096630976309863099631006310163102631036310463105631066310763108631096311063111631126311363114631156311663117631186311963120631216312263123631246312563126631276312863129631306313163132631336313463135631366313763138631396314063141631426314363144631456314663147631486314963150631516315263153631546315563156631576315863159631606316163162631636316463165631666316763168631696317063171631726317363174631756317663177631786317963180631816318263183631846318563186631876318863189631906319163192631936319463195631966319763198631996320063201632026320363204632056320663207632086320963210632116321263213632146321563216632176321863219632206322163222632236322463225632266322763228632296323063231632326323363234632356323663237632386323963240632416324263243632446324563246632476324863249632506325163252632536325463255632566325763258632596326063261632626326363264632656326663267632686326963270632716327263273632746327563276632776327863279632806328163282632836328463285632866328763288632896329063291632926329363294632956329663297632986329963300633016330263303633046330563306633076330863309633106331163312633136331463315633166331763318633196332063321633226332363324633256332663327633286332963330633316333263333633346333563336633376333863339633406334163342633436334463345633466334763348633496335063351633526335363354633556335663357633586335963360633616336263363633646336563366633676336863369633706337163372633736337463375633766337763378633796338063381633826338363384633856338663387633886338963390633916339263393633946339563396633976339863399634006340163402634036340463405634066340763408634096341063411634126341363414634156341663417634186341963420634216342263423634246342563426634276342863429634306343163432634336343463435634366343763438634396344063441634426344363444634456344663447634486344963450634516345263453634546345563456634576345863459634606346163462634636346463465634666346763468634696347063471634726347363474634756347663477634786347963480634816348263483634846348563486634876348863489634906349163492634936349463495634966349763498634996350063501635026350363504635056350663507635086350963510635116351263513635146351563516635176351863519635206352163522635236352463525635266352763528635296353063531635326353363534635356353663537635386353963540635416354263543635446354563546635476354863549635506355163552635536355463555635566355763558635596356063561635626356363564635656356663567635686356963570635716357263573635746357563576635776357863579635806358163582635836358463585635866358763588635896359063591635926359363594635956359663597635986359963600636016360263603636046360563606636076360863609636106361163612636136361463615636166361763618636196362063621636226362363624636256362663627636286362963630636316363263633636346363563636636376363863639636406364163642636436364463645636466364763648636496365063651636526365363654636556365663657636586365963660636616366263663636646366563666636676366863669636706367163672636736367463675636766367763678636796368063681636826368363684636856368663687636886368963690636916369263693636946369563696636976369863699637006370163702637036370463705637066370763708637096371063711637126371363714637156371663717637186371963720637216372263723637246372563726637276372863729637306373163732637336373463735637366373763738637396374063741637426374363744637456374663747637486374963750637516375263753637546375563756637576375863759637606376163762637636376463765637666376763768637696377063771637726377363774637756377663777637786377963780637816378263783637846378563786637876378863789637906379163792637936379463795637966379763798637996380063801638026380363804638056380663807638086380963810638116381263813638146381563816638176381863819638206382163822638236382463825638266382763828638296383063831638326383363834638356383663837638386383963840638416384263843638446384563846638476384863849638506385163852638536385463855638566385763858638596386063861638626386363864638656386663867638686386963870638716387263873638746387563876638776387863879638806388163882638836388463885638866388763888638896389063891638926389363894638956389663897638986389963900639016390263903639046390563906639076390863909639106391163912639136391463915639166391763918639196392063921639226392363924639256392663927639286392963930639316393263933639346393563936639376393863939639406394163942639436394463945639466394763948639496395063951639526395363954639556395663957639586395963960639616396263963639646396563966639676396863969639706397163972639736397463975639766397763978639796398063981639826398363984639856398663987639886398963990639916399263993639946399563996639976399863999640006400164002640036400464005640066400764008640096401064011640126401364014640156401664017640186401964020640216402264023640246402564026640276402864029640306403164032640336403464035640366403764038640396404064041640426404364044640456404664047640486404964050640516405264053640546405564056640576405864059640606406164062640636406464065640666406764068640696407064071640726407364074640756407664077640786407964080640816408264083640846408564086640876408864089640906409164092640936409464095640966409764098640996410064101641026410364104641056410664107641086410964110641116411264113641146411564116641176411864119641206412164122641236412464125641266412764128641296413064131641326413364134641356413664137641386413964140641416414264143641446414564146641476414864149641506415164152641536415464155641566415764158641596416064161641626416364164641656416664167641686416964170641716417264173641746417564176641776417864179641806418164182641836418464185641866418764188641896419064191641926419364194641956419664197641986419964200642016420264203642046420564206642076420864209642106421164212642136421464215642166421764218642196422064221642226422364224642256422664227642286422964230642316423264233642346423564236642376423864239642406424164242642436424464245642466424764248642496425064251642526425364254642556425664257642586425964260642616426264263642646426564266642676426864269642706427164272642736427464275642766427764278642796428064281642826428364284642856428664287642886428964290642916429264293642946429564296642976429864299643006430164302643036430464305643066430764308643096431064311643126431364314643156431664317643186431964320643216432264323643246432564326643276432864329643306433164332643336433464335643366433764338643396434064341643426434364344643456434664347643486434964350643516435264353643546435564356643576435864359643606436164362643636436464365643666436764368643696437064371643726437364374643756437664377643786437964380643816438264383643846438564386643876438864389643906439164392643936439464395643966439764398643996440064401644026440364404644056440664407644086440964410644116441264413644146441564416644176441864419644206442164422644236442464425644266442764428644296443064431644326443364434644356443664437644386443964440644416444264443644446444564446644476444864449644506445164452644536445464455644566445764458644596446064461644626446364464644656446664467644686446964470644716447264473644746447564476644776447864479644806448164482644836448464485644866448764488644896449064491644926449364494644956449664497644986449964500645016450264503645046450564506645076450864509645106451164512645136451464515645166451764518645196452064521645226452364524645256452664527645286452964530645316453264533645346453564536645376453864539645406454164542645436454464545645466454764548645496455064551645526455364554645556455664557645586455964560645616456264563645646456564566645676456864569645706457164572645736457464575645766457764578645796458064581645826458364584645856458664587645886458964590645916459264593645946459564596645976459864599646006460164602646036460464605646066460764608646096461064611646126461364614646156461664617646186461964620646216462264623646246462564626646276462864629646306463164632646336463464635646366463764638646396464064641646426464364644646456464664647646486464964650646516465264653646546465564656646576465864659646606466164662646636466464665646666466764668646696467064671646726467364674646756467664677646786467964680646816468264683646846468564686646876468864689646906469164692646936469464695646966469764698646996470064701647026470364704647056470664707647086470964710647116471264713647146471564716647176471864719647206472164722647236472464725647266472764728647296473064731647326473364734647356473664737647386473964740647416474264743647446474564746647476474864749647506475164752647536475464755647566475764758647596476064761647626476364764647656476664767647686476964770647716477264773647746477564776647776477864779647806478164782647836478464785647866478764788647896479064791647926479364794647956479664797647986479964800648016480264803648046480564806648076480864809648106481164812648136481464815648166481764818648196482064821648226482364824648256482664827648286482964830648316483264833648346483564836648376483864839648406484164842648436484464845648466484764848648496485064851648526485364854648556485664857648586485964860648616486264863648646486564866648676486864869648706487164872648736487464875648766487764878648796488064881648826488364884648856488664887648886488964890648916489264893648946489564896648976489864899649006490164902649036490464905649066490764908649096491064911649126491364914649156491664917649186491964920649216492264923649246492564926649276492864929649306493164932649336493464935649366493764938649396494064941649426494364944649456494664947649486494964950649516495264953649546495564956649576495864959649606496164962649636496464965649666496764968649696497064971649726497364974649756497664977649786497964980649816498264983649846498564986649876498864989649906499164992649936499464995649966499764998649996500065001650026500365004650056500665007650086500965010650116501265013650146501565016650176501865019650206502165022650236502465025650266502765028650296503065031650326503365034650356503665037650386503965040650416504265043650446504565046650476504865049650506505165052650536505465055650566505765058650596506065061650626506365064650656506665067650686506965070650716507265073650746507565076650776507865079650806508165082650836508465085650866508765088650896509065091650926509365094650956509665097650986509965100651016510265103651046510565106651076510865109651106511165112651136511465115651166511765118651196512065121651226512365124651256512665127651286512965130651316513265133651346513565136651376513865139651406514165142651436514465145651466514765148651496515065151651526515365154651556515665157651586515965160651616516265163651646516565166651676516865169651706517165172651736517465175651766517765178651796518065181651826518365184651856518665187651886518965190651916519265193651946519565196651976519865199652006520165202652036520465205652066520765208652096521065211652126521365214652156521665217652186521965220652216522265223652246522565226652276522865229652306523165232652336523465235652366523765238652396524065241652426524365244652456524665247652486524965250652516525265253652546525565256652576525865259652606526165262652636526465265652666526765268652696527065271652726527365274652756527665277652786527965280652816528265283652846528565286652876528865289652906529165292652936529465295652966529765298652996530065301653026530365304653056530665307653086530965310653116531265313653146531565316653176531865319653206532165322653236532465325653266532765328653296533065331653326533365334653356533665337653386533965340653416534265343653446534565346653476534865349653506535165352653536535465355653566535765358653596536065361653626536365364653656536665367653686536965370653716537265373653746537565376653776537865379653806538165382653836538465385653866538765388653896539065391653926539365394653956539665397653986539965400654016540265403654046540565406654076540865409654106541165412654136541465415654166541765418654196542065421654226542365424654256542665427654286542965430654316543265433654346543565436654376543865439654406544165442654436544465445654466544765448654496545065451654526545365454654556545665457654586545965460654616546265463654646546565466654676546865469654706547165472654736547465475654766547765478654796548065481654826548365484654856548665487654886548965490654916549265493654946549565496654976549865499655006550165502655036550465505655066550765508655096551065511655126551365514655156551665517655186551965520655216552265523655246552565526655276552865529655306553165532655336553465535655366553765538655396554065541655426554365544655456554665547655486554965550655516555265553655546555565556655576555865559655606556165562655636556465565655666556765568655696557065571655726557365574655756557665577655786557965580655816558265583655846558565586655876558865589655906559165592655936559465595655966559765598655996560065601656026560365604656056560665607656086560965610656116561265613656146561565616656176561865619656206562165622656236562465625656266562765628656296563065631656326563365634656356563665637656386563965640656416564265643656446564565646656476564865649656506565165652656536565465655656566565765658656596566065661656626566365664656656566665667656686566965670656716567265673656746567565676656776567865679656806568165682656836568465685656866568765688656896569065691656926569365694656956569665697656986569965700657016570265703657046570565706657076570865709657106571165712657136571465715657166571765718657196572065721657226572365724657256572665727657286572965730657316573265733657346573565736657376573865739657406574165742657436574465745657466574765748657496575065751657526575365754657556575665757657586575965760657616576265763657646576565766657676576865769657706577165772657736577465775657766577765778657796578065781657826578365784657856578665787657886578965790657916579265793657946579565796657976579865799658006580165802658036580465805658066580765808658096581065811658126581365814658156581665817658186581965820658216582265823658246582565826658276582865829658306583165832658336583465835658366583765838658396584065841658426584365844658456584665847658486584965850658516585265853658546585565856658576585865859658606586165862658636586465865658666586765868658696587065871658726587365874658756587665877658786587965880658816588265883658846588565886658876588865889658906589165892658936589465895658966589765898658996590065901659026590365904659056590665907659086590965910659116591265913659146591565916659176591865919659206592165922659236592465925659266592765928659296593065931659326593365934659356593665937659386593965940659416594265943659446594565946659476594865949659506595165952659536595465955659566595765958659596596065961659626596365964659656596665967659686596965970659716597265973659746597565976659776597865979659806598165982659836598465985659866598765988659896599065991659926599365994659956599665997659986599966000660016600266003660046600566006660076600866009660106601166012660136601466015660166601766018660196602066021660226602366024660256602666027660286602966030660316603266033660346603566036660376603866039660406604166042660436604466045660466604766048660496605066051660526605366054660556605666057660586605966060660616606266063660646606566066660676606866069660706607166072660736607466075660766607766078660796608066081660826608366084660856608666087660886608966090660916609266093660946609566096660976609866099661006610166102661036610466105661066610766108661096611066111661126611366114661156611666117661186611966120661216612266123661246612566126661276612866129661306613166132661336613466135661366613766138661396614066141661426614366144661456614666147661486614966150661516615266153661546615566156661576615866159661606616166162661636616466165661666616766168661696617066171661726617366174661756617666177661786617966180661816618266183661846618566186661876618866189661906619166192661936619466195661966619766198661996620066201662026620366204662056620666207662086620966210662116621266213662146621566216662176621866219662206622166222662236622466225662266622766228662296623066231662326623366234662356623666237662386623966240662416624266243662446624566246662476624866249662506625166252662536625466255662566625766258662596626066261662626626366264662656626666267662686626966270662716627266273662746627566276662776627866279662806628166282662836628466285662866628766288662896629066291662926629366294662956629666297662986629966300663016630266303663046630566306663076630866309663106631166312663136631466315663166631766318663196632066321663226632366324663256632666327663286632966330663316633266333663346633566336663376633866339663406634166342663436634466345663466634766348663496635066351663526635366354663556635666357663586635966360663616636266363663646636566366663676636866369663706637166372663736637466375663766637766378663796638066381663826638366384663856638666387663886638966390663916639266393663946639566396663976639866399664006640166402664036640466405664066640766408664096641066411664126641366414664156641666417664186641966420664216642266423664246642566426664276642866429664306643166432664336643466435664366643766438664396644066441664426644366444664456644666447664486644966450664516645266453664546645566456664576645866459664606646166462664636646466465664666646766468664696647066471664726647366474664756647666477664786647966480664816648266483664846648566486664876648866489664906649166492664936649466495664966649766498664996650066501665026650366504665056650666507665086650966510665116651266513665146651566516665176651866519665206652166522665236652466525665266652766528665296653066531665326653366534665356653666537665386653966540665416654266543665446654566546665476654866549665506655166552665536655466555665566655766558665596656066561665626656366564665656656666567665686656966570665716657266573665746657566576665776657866579665806658166582665836658466585665866658766588665896659066591665926659366594665956659666597665986659966600666016660266603666046660566606666076660866609666106661166612666136661466615666166661766618666196662066621666226662366624666256662666627666286662966630666316663266633666346663566636666376663866639666406664166642666436664466645666466664766648666496665066651666526665366654666556665666657666586665966660666616666266663666646666566666666676666866669666706667166672666736667466675666766667766678666796668066681666826668366684666856668666687666886668966690666916669266693666946669566696666976669866699667006670166702667036670466705667066670766708667096671066711667126671366714667156671666717667186671966720667216672266723667246672566726667276672866729667306673166732667336673466735667366673766738667396674066741667426674366744667456674666747667486674966750667516675266753667546675566756667576675866759667606676166762667636676466765667666676766768667696677066771667726677366774667756677666777667786677966780667816678266783667846678566786667876678866789667906679166792667936679466795667966679766798667996680066801668026680366804668056680666807668086680966810668116681266813668146681566816668176681866819668206682166822668236682466825668266682766828668296683066831668326683366834668356683666837668386683966840668416684266843668446684566846668476684866849668506685166852668536685466855668566685766858668596686066861668626686366864668656686666867668686686966870668716687266873668746687566876668776687866879668806688166882668836688466885668866688766888668896689066891668926689366894668956689666897668986689966900669016690266903669046690566906669076690866909669106691166912669136691466915669166691766918669196692066921669226692366924669256692666927669286692966930669316693266933669346693566936669376693866939669406694166942669436694466945669466694766948669496695066951669526695366954669556695666957669586695966960669616696266963669646696566966669676696866969669706697166972669736697466975669766697766978669796698066981669826698366984669856698666987669886698966990669916699266993669946699566996669976699866999670006700167002670036700467005670066700767008670096701067011670126701367014670156701667017670186701967020670216702267023670246702567026670276702867029670306703167032670336703467035670366703767038670396704067041670426704367044670456704667047670486704967050670516705267053670546705567056670576705867059670606706167062670636706467065670666706767068670696707067071670726707367074670756707667077670786707967080670816708267083670846708567086670876708867089670906709167092670936709467095670966709767098670996710067101671026710367104671056710667107671086710967110671116711267113671146711567116671176711867119671206712167122671236712467125671266712767128671296713067131671326713367134671356713667137671386713967140671416714267143671446714567146671476714867149671506715167152671536715467155671566715767158671596716067161671626716367164671656716667167671686716967170671716717267173671746717567176671776717867179671806718167182671836718467185671866718767188671896719067191671926719367194671956719667197671986719967200672016720267203672046720567206672076720867209672106721167212672136721467215672166721767218672196722067221672226722367224672256722667227672286722967230672316723267233672346723567236672376723867239672406724167242672436724467245672466724767248672496725067251672526725367254672556725667257672586725967260672616726267263672646726567266672676726867269672706727167272672736727467275672766727767278672796728067281672826728367284672856728667287672886728967290672916729267293672946729567296672976729867299673006730167302673036730467305673066730767308673096731067311673126731367314673156731667317673186731967320673216732267323673246732567326673276732867329673306733167332673336733467335673366733767338673396734067341673426734367344673456734667347673486734967350673516735267353673546735567356673576735867359673606736167362673636736467365673666736767368673696737067371673726737367374673756737667377673786737967380673816738267383673846738567386673876738867389673906739167392673936739467395673966739767398673996740067401674026740367404674056740667407674086740967410674116741267413674146741567416674176741867419674206742167422674236742467425674266742767428674296743067431674326743367434674356743667437674386743967440674416744267443674446744567446674476744867449674506745167452674536745467455674566745767458674596746067461674626746367464674656746667467674686746967470674716747267473674746747567476674776747867479674806748167482674836748467485674866748767488674896749067491674926749367494674956749667497674986749967500675016750267503675046750567506675076750867509675106751167512675136751467515675166751767518675196752067521675226752367524675256752667527675286752967530675316753267533675346753567536675376753867539675406754167542675436754467545675466754767548675496755067551675526755367554675556755667557675586755967560675616756267563675646756567566675676756867569675706757167572675736757467575675766757767578675796758067581675826758367584675856758667587675886758967590675916759267593675946759567596675976759867599676006760167602676036760467605676066760767608676096761067611676126761367614676156761667617676186761967620676216762267623676246762567626676276762867629676306763167632676336763467635676366763767638676396764067641676426764367644676456764667647676486764967650676516765267653676546765567656676576765867659676606766167662676636766467665676666766767668676696767067671676726767367674676756767667677676786767967680676816768267683676846768567686676876768867689676906769167692676936769467695676966769767698676996770067701677026770367704677056770667707677086770967710677116771267713677146771567716677176771867719677206772167722677236772467725677266772767728677296773067731677326773367734677356773667737677386773967740677416774267743677446774567746677476774867749677506775167752677536775467755677566775767758677596776067761677626776367764677656776667767677686776967770677716777267773677746777567776677776777867779677806778167782677836778467785677866778767788677896779067791677926779367794677956779667797677986779967800678016780267803678046780567806678076780867809678106781167812678136781467815678166781767818678196782067821678226782367824678256782667827678286782967830678316783267833678346783567836678376783867839678406784167842678436784467845678466784767848678496785067851678526785367854678556785667857678586785967860678616786267863678646786567866678676786867869678706787167872678736787467875678766787767878678796788067881678826788367884678856788667887678886788967890678916789267893678946789567896678976789867899679006790167902679036790467905679066790767908679096791067911679126791367914679156791667917679186791967920679216792267923679246792567926679276792867929679306793167932679336793467935679366793767938679396794067941679426794367944679456794667947679486794967950679516795267953679546795567956679576795867959679606796167962679636796467965679666796767968679696797067971679726797367974679756797667977679786797967980679816798267983679846798567986679876798867989679906799167992679936799467995679966799767998679996800068001680026800368004680056800668007680086800968010680116801268013680146801568016680176801868019680206802168022680236802468025680266802768028680296803068031680326803368034680356803668037680386803968040680416804268043680446804568046680476804868049680506805168052680536805468055680566805768058680596806068061680626806368064680656806668067680686806968070680716807268073680746807568076680776807868079680806808168082680836808468085680866808768088680896809068091680926809368094680956809668097680986809968100681016810268103681046810568106681076810868109681106811168112681136811468115681166811768118681196812068121681226812368124681256812668127681286812968130681316813268133681346813568136681376813868139681406814168142681436814468145681466814768148681496815068151681526815368154681556815668157681586815968160681616816268163681646816568166681676816868169681706817168172681736817468175681766817768178681796818068181681826818368184681856818668187681886818968190681916819268193681946819568196681976819868199682006820168202682036820468205682066820768208682096821068211682126821368214682156821668217682186821968220682216822268223682246822568226682276822868229682306823168232682336823468235682366823768238682396824068241682426824368244682456824668247682486824968250682516825268253682546825568256682576825868259682606826168262682636826468265682666826768268682696827068271682726827368274682756827668277682786827968280682816828268283682846828568286682876828868289682906829168292682936829468295682966829768298682996830068301683026830368304683056830668307683086830968310683116831268313683146831568316683176831868319683206832168322683236832468325683266832768328683296833068331683326833368334683356833668337683386833968340683416834268343683446834568346683476834868349683506835168352683536835468355683566835768358683596836068361683626836368364683656836668367683686836968370683716837268373683746837568376683776837868379683806838168382683836838468385683866838768388683896839068391683926839368394683956839668397683986839968400684016840268403684046840568406684076840868409684106841168412684136841468415684166841768418684196842068421684226842368424684256842668427684286842968430684316843268433684346843568436684376843868439684406844168442684436844468445684466844768448684496845068451684526845368454684556845668457684586845968460684616846268463684646846568466684676846868469684706847168472684736847468475684766847768478684796848068481684826848368484684856848668487684886848968490684916849268493684946849568496684976849868499685006850168502685036850468505685066850768508685096851068511685126851368514685156851668517685186851968520685216852268523685246852568526685276852868529685306853168532685336853468535685366853768538685396854068541685426854368544685456854668547685486854968550685516855268553685546855568556685576855868559685606856168562685636856468565685666856768568685696857068571685726857368574685756857668577685786857968580685816858268583685846858568586685876858868589685906859168592685936859468595685966859768598685996860068601686026860368604686056860668607686086860968610686116861268613686146861568616686176861868619686206862168622686236862468625686266862768628686296863068631686326863368634686356863668637686386863968640686416864268643686446864568646686476864868649686506865168652686536865468655686566865768658686596866068661686626866368664686656866668667686686866968670686716867268673686746867568676686776867868679686806868168682686836868468685686866868768688686896869068691686926869368694686956869668697686986869968700687016870268703687046870568706687076870868709687106871168712687136871468715687166871768718687196872068721687226872368724687256872668727687286872968730687316873268733687346873568736687376873868739687406874168742687436874468745687466874768748687496875068751687526875368754687556875668757687586875968760687616876268763687646876568766687676876868769687706877168772687736877468775687766877768778687796878068781687826878368784687856878668787687886878968790687916879268793687946879568796687976879868799688006880168802688036880468805688066880768808688096881068811688126881368814688156881668817688186881968820688216882268823688246882568826688276882868829688306883168832688336883468835688366883768838688396884068841688426884368844688456884668847688486884968850688516885268853688546885568856688576885868859688606886168862688636886468865688666886768868688696887068871688726887368874688756887668877688786887968880688816888268883688846888568886688876888868889688906889168892688936889468895688966889768898688996890068901689026890368904689056890668907689086890968910689116891268913689146891568916689176891868919689206892168922689236892468925689266892768928689296893068931689326893368934689356893668937689386893968940689416894268943689446894568946689476894868949689506895168952689536895468955689566895768958689596896068961689626896368964689656896668967689686896968970689716897268973689746897568976689776897868979689806898168982689836898468985689866898768988689896899068991689926899368994689956899668997689986899969000690016900269003690046900569006690076900869009690106901169012690136901469015690166901769018690196902069021690226902369024690256902669027690286902969030690316903269033690346903569036690376903869039690406904169042690436904469045690466904769048690496905069051690526905369054690556905669057690586905969060690616906269063690646906569066690676906869069690706907169072690736907469075690766907769078690796908069081690826908369084690856908669087690886908969090690916909269093690946909569096690976909869099691006910169102691036910469105691066910769108691096911069111691126911369114691156911669117691186911969120691216912269123691246912569126691276912869129691306913169132691336913469135691366913769138691396914069141691426914369144691456914669147691486914969150691516915269153691546915569156691576915869159691606916169162691636916469165691666916769168691696917069171691726917369174691756917669177691786917969180691816918269183691846918569186691876918869189691906919169192691936919469195691966919769198691996920069201692026920369204692056920669207692086920969210692116921269213692146921569216692176921869219692206922169222692236922469225692266922769228692296923069231692326923369234692356923669237692386923969240692416924269243692446924569246692476924869249692506925169252692536925469255692566925769258692596926069261692626926369264692656926669267692686926969270692716927269273692746927569276692776927869279692806928169282692836928469285692866928769288692896929069291692926929369294692956929669297692986929969300693016930269303693046930569306693076930869309693106931169312693136931469315693166931769318693196932069321693226932369324693256932669327693286932969330693316933269333693346933569336693376933869339693406934169342693436934469345693466934769348693496935069351693526935369354693556935669357693586935969360693616936269363693646936569366693676936869369693706937169372693736937469375693766937769378693796938069381693826938369384693856938669387693886938969390693916939269393693946939569396693976939869399694006940169402694036940469405694066940769408694096941069411694126941369414694156941669417694186941969420694216942269423694246942569426694276942869429694306943169432694336943469435694366943769438694396944069441694426944369444694456944669447694486944969450694516945269453694546945569456694576945869459694606946169462694636946469465694666946769468694696947069471694726947369474694756947669477694786947969480694816948269483694846948569486694876948869489694906949169492694936949469495694966949769498694996950069501695026950369504695056950669507695086950969510695116951269513695146951569516695176951869519695206952169522695236952469525695266952769528695296953069531695326953369534695356953669537695386953969540695416954269543695446954569546695476954869549695506955169552695536955469555695566955769558695596956069561695626956369564695656956669567695686956969570695716957269573695746957569576695776957869579695806958169582695836958469585695866958769588695896959069591695926959369594695956959669597695986959969600696016960269603696046960569606696076960869609696106961169612696136961469615696166961769618696196962069621696226962369624696256962669627696286962969630696316963269633696346963569636696376963869639696406964169642696436964469645696466964769648696496965069651696526965369654696556965669657696586965969660696616966269663696646966569666696676966869669696706967169672696736967469675696766967769678696796968069681696826968369684696856968669687696886968969690696916969269693696946969569696696976969869699697006970169702697036970469705697066970769708697096971069711697126971369714697156971669717697186971969720697216972269723697246972569726697276972869729697306973169732697336973469735697366973769738697396974069741697426974369744697456974669747697486974969750697516975269753697546975569756697576975869759697606976169762697636976469765697666976769768697696977069771697726977369774697756977669777697786977969780697816978269783697846978569786697876978869789697906979169792697936979469795697966979769798697996980069801698026980369804698056980669807698086980969810698116981269813698146981569816698176981869819698206982169822698236982469825698266982769828698296983069831698326983369834698356983669837698386983969840698416984269843698446984569846698476984869849698506985169852698536985469855698566985769858698596986069861698626986369864698656986669867698686986969870698716987269873698746987569876698776987869879698806988169882698836988469885698866988769888698896989069891698926989369894698956989669897698986989969900699016990269903699046990569906699076990869909699106991169912699136991469915699166991769918699196992069921699226992369924699256992669927699286992969930699316993269933699346993569936699376993869939699406994169942699436994469945699466994769948699496995069951699526995369954699556995669957699586995969960699616996269963699646996569966699676996869969699706997169972699736997469975699766997769978699796998069981699826998369984699856998669987699886998969990699916999269993699946999569996699976999869999700007000170002700037000470005700067000770008700097001070011700127001370014700157001670017700187001970020700217002270023700247002570026700277002870029700307003170032700337003470035700367003770038700397004070041700427004370044700457004670047700487004970050700517005270053700547005570056700577005870059700607006170062700637006470065700667006770068700697007070071700727007370074700757007670077700787007970080700817008270083700847008570086700877008870089700907009170092700937009470095700967009770098700997010070101701027010370104701057010670107701087010970110701117011270113701147011570116701177011870119701207012170122701237012470125701267012770128701297013070131701327013370134701357013670137701387013970140701417014270143701447014570146701477014870149701507015170152701537015470155701567015770158701597016070161701627016370164701657016670167701687016970170701717017270173701747017570176701777017870179701807018170182701837018470185701867018770188701897019070191701927019370194701957019670197701987019970200702017020270203702047020570206702077020870209702107021170212702137021470215702167021770218702197022070221702227022370224702257022670227702287022970230702317023270233702347023570236702377023870239702407024170242702437024470245702467024770248702497025070251702527025370254702557025670257702587025970260702617026270263702647026570266702677026870269702707027170272702737027470275702767027770278702797028070281702827028370284702857028670287702887028970290702917029270293702947029570296702977029870299703007030170302703037030470305703067030770308703097031070311703127031370314703157031670317703187031970320703217032270323703247032570326703277032870329703307033170332703337033470335703367033770338703397034070341703427034370344703457034670347703487034970350703517035270353703547035570356703577035870359703607036170362703637036470365703667036770368703697037070371703727037370374703757037670377703787037970380703817038270383703847038570386703877038870389703907039170392703937039470395703967039770398703997040070401704027040370404704057040670407704087040970410704117041270413704147041570416704177041870419704207042170422704237042470425704267042770428704297043070431704327043370434704357043670437704387043970440704417044270443704447044570446704477044870449704507045170452704537045470455704567045770458704597046070461704627046370464704657046670467704687046970470704717047270473704747047570476704777047870479704807048170482704837048470485704867048770488704897049070491704927049370494704957049670497704987049970500705017050270503705047050570506705077050870509705107051170512705137051470515705167051770518705197052070521705227052370524705257052670527705287052970530705317053270533705347053570536705377053870539705407054170542705437054470545705467054770548705497055070551705527055370554705557055670557705587055970560705617056270563705647056570566705677056870569705707057170572705737057470575705767057770578705797058070581705827058370584705857058670587705887058970590705917059270593705947059570596705977059870599706007060170602706037060470605706067060770608706097061070611706127061370614706157061670617706187061970620706217062270623706247062570626706277062870629706307063170632706337063470635706367063770638706397064070641706427064370644706457064670647706487064970650706517065270653706547065570656706577065870659706607066170662706637066470665706667066770668706697067070671706727067370674706757067670677706787067970680706817068270683706847068570686706877068870689706907069170692706937069470695706967069770698706997070070701707027070370704707057070670707707087070970710707117071270713707147071570716707177071870719707207072170722707237072470725707267072770728707297073070731707327073370734707357073670737707387073970740707417074270743707447074570746707477074870749707507075170752707537075470755707567075770758707597076070761707627076370764707657076670767707687076970770707717077270773707747077570776707777077870779707807078170782707837078470785707867078770788707897079070791707927079370794707957079670797707987079970800708017080270803708047080570806708077080870809708107081170812708137081470815708167081770818708197082070821708227082370824708257082670827708287082970830708317083270833708347083570836708377083870839708407084170842708437084470845708467084770848708497085070851708527085370854708557085670857708587085970860708617086270863708647086570866708677086870869708707087170872708737087470875708767087770878708797088070881708827088370884708857088670887708887088970890708917089270893708947089570896708977089870899709007090170902709037090470905709067090770908709097091070911709127091370914709157091670917709187091970920709217092270923709247092570926709277092870929709307093170932709337093470935709367093770938709397094070941709427094370944709457094670947709487094970950709517095270953709547095570956709577095870959709607096170962709637096470965709667096770968709697097070971709727097370974709757097670977709787097970980709817098270983709847098570986709877098870989709907099170992709937099470995709967099770998709997100071001710027100371004710057100671007710087100971010710117101271013710147101571016710177101871019710207102171022710237102471025710267102771028710297103071031710327103371034710357103671037710387103971040710417104271043710447104571046710477104871049710507105171052710537105471055710567105771058710597106071061710627106371064710657106671067710687106971070710717107271073710747107571076710777107871079710807108171082710837108471085710867108771088710897109071091710927109371094710957109671097710987109971100711017110271103711047110571106711077110871109711107111171112711137111471115711167111771118711197112071121711227112371124711257112671127711287112971130711317113271133711347113571136711377113871139711407114171142711437114471145711467114771148711497115071151711527115371154711557115671157711587115971160711617116271163711647116571166711677116871169711707117171172711737117471175711767117771178711797118071181711827118371184711857118671187711887118971190711917119271193711947119571196711977119871199712007120171202712037120471205712067120771208712097121071211712127121371214712157121671217712187121971220712217122271223712247122571226712277122871229712307123171232712337123471235712367123771238712397124071241712427124371244712457124671247712487124971250712517125271253712547125571256712577125871259712607126171262712637126471265712667126771268712697127071271712727127371274712757127671277712787127971280712817128271283712847128571286712877128871289712907129171292712937129471295712967129771298712997130071301713027130371304713057130671307713087130971310713117131271313713147131571316713177131871319713207132171322713237132471325713267132771328713297133071331713327133371334713357133671337713387133971340713417134271343713447134571346713477134871349713507135171352713537135471355713567135771358713597136071361713627136371364713657136671367713687136971370713717137271373713747137571376713777137871379713807138171382713837138471385713867138771388713897139071391713927139371394713957139671397713987139971400714017140271403714047140571406714077140871409714107141171412714137141471415714167141771418714197142071421714227142371424714257142671427714287142971430714317143271433714347143571436714377143871439714407144171442714437144471445714467144771448714497145071451714527145371454714557145671457714587145971460714617146271463714647146571466714677146871469714707147171472714737147471475714767147771478714797148071481714827148371484714857148671487714887148971490714917149271493714947149571496714977149871499715007150171502715037150471505715067150771508715097151071511715127151371514715157151671517715187151971520715217152271523715247152571526715277152871529715307153171532715337153471535715367153771538715397154071541715427154371544715457154671547715487154971550715517155271553715547155571556715577155871559715607156171562715637156471565715667156771568715697157071571715727157371574715757157671577715787157971580715817158271583715847158571586715877158871589715907159171592715937159471595715967159771598715997160071601716027160371604716057160671607716087160971610716117161271613716147161571616716177161871619716207162171622716237162471625716267162771628716297163071631716327163371634716357163671637716387163971640716417164271643716447164571646716477164871649716507165171652716537165471655716567165771658716597166071661716627166371664716657166671667716687166971670716717167271673716747167571676716777167871679716807168171682716837168471685716867168771688716897169071691716927169371694716957169671697716987169971700717017170271703717047170571706717077170871709717107171171712717137171471715717167171771718717197172071721717227172371724717257172671727717287172971730717317173271733717347173571736717377173871739717407174171742717437174471745717467174771748717497175071751717527175371754717557175671757717587175971760717617176271763717647176571766717677176871769717707177171772717737177471775717767177771778717797178071781717827178371784717857178671787717887178971790717917179271793717947179571796717977179871799718007180171802718037180471805718067180771808718097181071811718127181371814718157181671817718187181971820718217182271823718247182571826718277182871829718307183171832718337183471835718367183771838718397184071841718427184371844718457184671847718487184971850718517185271853718547185571856718577185871859718607186171862718637186471865718667186771868718697187071871718727187371874718757187671877718787187971880718817188271883718847188571886718877188871889718907189171892718937189471895718967189771898718997190071901719027190371904719057190671907719087190971910719117191271913719147191571916719177191871919719207192171922719237192471925719267192771928719297193071931719327193371934719357193671937719387193971940719417194271943719447194571946719477194871949719507195171952719537195471955719567195771958719597196071961719627196371964719657196671967719687196971970719717197271973719747197571976719777197871979719807198171982719837198471985719867198771988719897199071991719927199371994719957199671997719987199972000720017200272003720047200572006720077200872009720107201172012720137201472015720167201772018720197202072021720227202372024720257202672027720287202972030720317203272033720347203572036720377203872039720407204172042720437204472045720467204772048720497205072051720527205372054720557205672057720587205972060720617206272063720647206572066720677206872069720707207172072720737207472075720767207772078720797208072081720827208372084720857208672087720887208972090720917209272093720947209572096720977209872099721007210172102721037210472105721067210772108721097211072111721127211372114721157211672117721187211972120721217212272123721247212572126721277212872129721307213172132721337213472135721367213772138721397214072141721427214372144721457214672147721487214972150721517215272153721547215572156721577215872159721607216172162721637216472165721667216772168721697217072171721727217372174721757217672177721787217972180721817218272183721847218572186721877218872189721907219172192721937219472195721967219772198721997220072201722027220372204722057220672207722087220972210722117221272213722147221572216722177221872219722207222172222722237222472225722267222772228722297223072231722327223372234722357223672237722387223972240722417224272243722447224572246722477224872249722507225172252722537225472255722567225772258722597226072261722627226372264722657226672267722687226972270722717227272273722747227572276722777227872279722807228172282722837228472285722867228772288722897229072291722927229372294722957229672297722987229972300723017230272303723047230572306723077230872309723107231172312723137231472315723167231772318723197232072321723227232372324723257232672327723287232972330723317233272333723347233572336723377233872339723407234172342723437234472345723467234772348723497235072351723527235372354723557235672357723587235972360723617236272363723647236572366723677236872369723707237172372723737237472375723767237772378723797238072381723827238372384723857238672387723887238972390723917239272393723947239572396723977239872399724007240172402724037240472405724067240772408724097241072411724127241372414724157241672417724187241972420724217242272423724247242572426724277242872429724307243172432724337243472435724367243772438724397244072441724427244372444724457244672447724487244972450724517245272453724547245572456724577245872459724607246172462724637246472465724667246772468724697247072471724727247372474724757247672477724787247972480724817248272483724847248572486724877248872489724907249172492724937249472495724967249772498724997250072501725027250372504725057250672507725087250972510725117251272513725147251572516725177251872519725207252172522725237252472525725267252772528725297253072531725327253372534725357253672537725387253972540725417254272543725447254572546725477254872549725507255172552725537255472555725567255772558725597256072561725627256372564725657256672567725687256972570725717257272573725747257572576725777257872579725807258172582725837258472585725867258772588725897259072591725927259372594725957259672597725987259972600726017260272603726047260572606726077260872609726107261172612726137261472615726167261772618726197262072621726227262372624726257262672627726287262972630726317263272633726347263572636726377263872639726407264172642726437264472645726467264772648726497265072651726527265372654726557265672657726587265972660726617266272663726647266572666726677266872669726707267172672726737267472675726767267772678726797268072681726827268372684726857268672687726887268972690726917269272693726947269572696726977269872699727007270172702727037270472705727067270772708727097271072711727127271372714727157271672717727187271972720727217272272723727247272572726727277272872729727307273172732727337273472735727367273772738727397274072741727427274372744727457274672747727487274972750727517275272753727547275572756727577275872759727607276172762727637276472765727667276772768727697277072771727727277372774727757277672777727787277972780727817278272783727847278572786727877278872789727907279172792727937279472795727967279772798727997280072801728027280372804728057280672807728087280972810728117281272813728147281572816728177281872819728207282172822728237282472825728267282772828728297283072831728327283372834728357283672837728387283972840728417284272843728447284572846728477284872849728507285172852728537285472855728567285772858728597286072861728627286372864728657286672867728687286972870728717287272873728747287572876728777287872879728807288172882728837288472885728867288772888728897289072891728927289372894728957289672897728987289972900729017290272903729047290572906729077290872909729107291172912729137291472915729167291772918729197292072921729227292372924729257292672927729287292972930729317293272933729347293572936729377293872939729407294172942729437294472945729467294772948729497295072951729527295372954729557295672957729587295972960729617296272963729647296572966729677296872969729707297172972729737297472975729767297772978729797298072981729827298372984729857298672987729887298972990729917299272993729947299572996729977299872999730007300173002730037300473005730067300773008730097301073011730127301373014730157301673017730187301973020730217302273023730247302573026730277302873029730307303173032730337303473035730367303773038730397304073041730427304373044730457304673047730487304973050730517305273053730547305573056730577305873059730607306173062730637306473065730667306773068730697307073071730727307373074730757307673077730787307973080730817308273083730847308573086730877308873089730907309173092730937309473095730967309773098730997310073101731027310373104731057310673107731087310973110731117311273113731147311573116731177311873119731207312173122731237312473125731267312773128731297313073131731327313373134731357313673137731387313973140731417314273143731447314573146731477314873149731507315173152731537315473155731567315773158731597316073161731627316373164731657316673167731687316973170731717317273173731747317573176731777317873179731807318173182731837318473185731867318773188731897319073191731927319373194731957319673197731987319973200732017320273203732047320573206732077320873209732107321173212732137321473215732167321773218732197322073221732227322373224732257322673227732287322973230732317323273233732347323573236732377323873239732407324173242732437324473245732467324773248732497325073251732527325373254732557325673257732587325973260732617326273263732647326573266732677326873269732707327173272732737327473275732767327773278732797328073281732827328373284732857328673287732887328973290732917329273293732947329573296732977329873299733007330173302733037330473305733067330773308733097331073311733127331373314733157331673317733187331973320733217332273323733247332573326733277332873329733307333173332733337333473335733367333773338733397334073341733427334373344733457334673347733487334973350733517335273353733547335573356733577335873359733607336173362733637336473365733667336773368733697337073371733727337373374733757337673377733787337973380733817338273383733847338573386733877338873389733907339173392733937339473395733967339773398733997340073401734027340373404734057340673407734087340973410734117341273413734147341573416734177341873419734207342173422734237342473425734267342773428734297343073431734327343373434734357343673437734387343973440734417344273443734447344573446734477344873449734507345173452734537345473455734567345773458734597346073461734627346373464734657346673467734687346973470734717347273473734747347573476734777347873479734807348173482734837348473485734867348773488734897349073491734927349373494734957349673497734987349973500735017350273503735047350573506735077350873509735107351173512735137351473515735167351773518735197352073521735227352373524735257352673527735287352973530735317353273533735347353573536735377353873539735407354173542735437354473545735467354773548735497355073551735527355373554735557355673557735587355973560735617356273563735647356573566735677356873569735707357173572735737357473575735767357773578735797358073581735827358373584735857358673587735887358973590735917359273593735947359573596735977359873599736007360173602736037360473605736067360773608736097361073611736127361373614736157361673617736187361973620736217362273623736247362573626736277362873629736307363173632736337363473635736367363773638736397364073641736427364373644736457364673647736487364973650736517365273653736547365573656736577365873659736607366173662736637366473665736667366773668736697367073671736727367373674736757367673677736787367973680736817368273683736847368573686736877368873689736907369173692736937369473695736967369773698736997370073701737027370373704737057370673707737087370973710737117371273713737147371573716737177371873719737207372173722737237372473725737267372773728737297373073731737327373373734737357373673737737387373973740737417374273743737447374573746737477374873749737507375173752737537375473755737567375773758737597376073761737627376373764737657376673767737687376973770737717377273773737747377573776737777377873779737807378173782737837378473785737867378773788737897379073791737927379373794737957379673797737987379973800738017380273803738047380573806738077380873809738107381173812738137381473815738167381773818738197382073821738227382373824738257382673827738287382973830738317383273833738347383573836738377383873839738407384173842738437384473845738467384773848738497385073851738527385373854738557385673857738587385973860738617386273863738647386573866738677386873869738707387173872738737387473875738767387773878738797388073881738827388373884738857388673887738887388973890738917389273893738947389573896738977389873899739007390173902739037390473905739067390773908739097391073911739127391373914739157391673917739187391973920739217392273923739247392573926739277392873929739307393173932739337393473935739367393773938739397394073941739427394373944739457394673947739487394973950739517395273953739547395573956739577395873959739607396173962739637396473965739667396773968739697397073971739727397373974739757397673977739787397973980739817398273983739847398573986739877398873989739907399173992739937399473995739967399773998739997400074001740027400374004740057400674007740087400974010740117401274013740147401574016740177401874019740207402174022740237402474025740267402774028740297403074031740327403374034740357403674037740387403974040740417404274043740447404574046740477404874049740507405174052740537405474055740567405774058740597406074061740627406374064740657406674067740687406974070740717407274073740747407574076740777407874079740807408174082740837408474085740867408774088740897409074091740927409374094740957409674097740987409974100741017410274103741047410574106741077410874109741107411174112741137411474115741167411774118741197412074121741227412374124741257412674127741287412974130741317413274133741347413574136741377413874139741407414174142741437414474145741467414774148741497415074151741527415374154741557415674157741587415974160741617416274163741647416574166741677416874169741707417174172741737417474175741767417774178741797418074181741827418374184741857418674187741887418974190741917419274193741947419574196741977419874199742007420174202742037420474205742067420774208742097421074211742127421374214742157421674217742187421974220742217422274223742247422574226742277422874229742307423174232742337423474235742367423774238742397424074241742427424374244742457424674247742487424974250742517425274253742547425574256742577425874259742607426174262742637426474265742667426774268742697427074271742727427374274742757427674277742787427974280742817428274283742847428574286742877428874289742907429174292742937429474295742967429774298742997430074301743027430374304743057430674307743087430974310743117431274313743147431574316743177431874319743207432174322743237432474325743267432774328743297433074331743327433374334743357433674337743387433974340743417434274343743447434574346743477434874349743507435174352743537435474355743567435774358743597436074361743627436374364743657436674367743687436974370743717437274373743747437574376743777437874379743807438174382743837438474385743867438774388743897439074391743927439374394743957439674397743987439974400744017440274403744047440574406744077440874409744107441174412744137441474415744167441774418744197442074421744227442374424744257442674427744287442974430744317443274433744347443574436744377443874439744407444174442744437444474445744467444774448744497445074451744527445374454744557445674457744587445974460744617446274463744647446574466744677446874469744707447174472744737447474475744767447774478744797448074481744827448374484744857448674487744887448974490744917449274493744947449574496744977449874499745007450174502745037450474505745067450774508745097451074511745127451374514745157451674517745187451974520745217452274523745247452574526745277452874529745307453174532745337453474535745367453774538745397454074541745427454374544745457454674547745487454974550745517455274553745547455574556745577455874559745607456174562745637456474565745667456774568745697457074571745727457374574745757457674577745787457974580745817458274583745847458574586745877458874589745907459174592745937459474595745967459774598745997460074601746027460374604746057460674607746087460974610746117461274613746147461574616746177461874619746207462174622746237462474625746267462774628746297463074631746327463374634746357463674637746387463974640746417464274643746447464574646746477464874649746507465174652746537465474655746567465774658746597466074661746627466374664746657466674667746687466974670746717467274673746747467574676746777467874679746807468174682746837468474685746867468774688746897469074691746927469374694746957469674697746987469974700747017470274703747047470574706747077470874709747107471174712747137471474715747167471774718747197472074721747227472374724747257472674727747287472974730747317473274733747347473574736747377473874739747407474174742747437474474745747467474774748747497475074751747527475374754747557475674757747587475974760747617476274763747647476574766747677476874769747707477174772747737477474775747767477774778747797478074781747827478374784747857478674787747887478974790747917479274793747947479574796747977479874799748007480174802748037480474805748067480774808748097481074811748127481374814748157481674817748187481974820748217482274823748247482574826748277482874829748307483174832748337483474835748367483774838748397484074841748427484374844748457484674847748487484974850748517485274853748547485574856748577485874859748607486174862748637486474865748667486774868748697487074871748727487374874748757487674877748787487974880748817488274883748847488574886748877488874889748907489174892748937489474895748967489774898748997490074901749027490374904749057490674907749087490974910749117491274913749147491574916749177491874919749207492174922749237492474925749267492774928749297493074931749327493374934749357493674937749387493974940749417494274943749447494574946749477494874949749507495174952749537495474955749567495774958749597496074961749627496374964749657496674967749687496974970749717497274973749747497574976749777497874979749807498174982749837498474985749867498774988749897499074991749927499374994749957499674997749987499975000750017500275003750047500575006750077500875009750107501175012750137501475015750167501775018750197502075021750227502375024750257502675027750287502975030750317503275033750347503575036750377503875039750407504175042750437504475045750467504775048750497505075051750527505375054750557505675057750587505975060750617506275063750647506575066750677506875069750707507175072750737507475075750767507775078750797508075081750827508375084750857508675087750887508975090750917509275093750947509575096750977509875099751007510175102751037510475105751067510775108751097511075111751127511375114751157511675117751187511975120751217512275123751247512575126751277512875129751307513175132751337513475135751367513775138751397514075141751427514375144751457514675147751487514975150751517515275153751547515575156751577515875159751607516175162751637516475165751667516775168751697517075171751727517375174751757517675177751787517975180751817518275183751847518575186751877518875189751907519175192751937519475195751967519775198751997520075201752027520375204752057520675207752087520975210752117521275213752147521575216752177521875219752207522175222752237522475225752267522775228752297523075231752327523375234752357523675237752387523975240752417524275243752447524575246752477524875249752507525175252752537525475255752567525775258752597526075261752627526375264752657526675267752687526975270752717527275273752747527575276752777527875279752807528175282752837528475285752867528775288752897529075291752927529375294752957529675297752987529975300753017530275303753047530575306753077530875309753107531175312753137531475315753167531775318753197532075321753227532375324753257532675327753287532975330753317533275333753347533575336753377533875339753407534175342753437534475345753467534775348753497535075351753527535375354753557535675357753587535975360753617536275363753647536575366753677536875369753707537175372753737537475375753767537775378753797538075381753827538375384753857538675387753887538975390753917539275393753947539575396753977539875399754007540175402754037540475405754067540775408754097541075411754127541375414754157541675417754187541975420754217542275423754247542575426754277542875429754307543175432754337543475435754367543775438754397544075441754427544375444754457544675447754487544975450754517545275453754547545575456754577545875459754607546175462754637546475465754667546775468754697547075471754727547375474754757547675477754787547975480754817548275483754847548575486754877548875489754907549175492754937549475495754967549775498754997550075501755027550375504755057550675507755087550975510755117551275513755147551575516755177551875519755207552175522755237552475525755267552775528755297553075531755327553375534755357553675537755387553975540755417554275543755447554575546755477554875549755507555175552755537555475555755567555775558755597556075561755627556375564755657556675567755687556975570755717557275573755747557575576755777557875579755807558175582755837558475585755867558775588755897559075591755927559375594755957559675597755987559975600756017560275603756047560575606756077560875609756107561175612756137561475615756167561775618756197562075621756227562375624756257562675627756287562975630756317563275633756347563575636756377563875639756407564175642756437564475645756467564775648756497565075651756527565375654756557565675657756587565975660756617566275663756647566575666756677566875669756707567175672756737567475675756767567775678756797568075681756827568375684756857568675687756887568975690756917569275693756947569575696756977569875699757007570175702757037570475705757067570775708757097571075711757127571375714757157571675717757187571975720757217572275723757247572575726757277572875729757307573175732757337573475735757367573775738757397574075741757427574375744757457574675747757487574975750757517575275753757547575575756757577575875759757607576175762757637576475765757667576775768757697577075771757727577375774757757577675777757787577975780757817578275783757847578575786757877578875789757907579175792757937579475795757967579775798757997580075801758027580375804758057580675807758087580975810758117581275813758147581575816758177581875819758207582175822758237582475825758267582775828758297583075831758327583375834758357583675837758387583975840758417584275843758447584575846758477584875849758507585175852758537585475855758567585775858758597586075861758627586375864758657586675867758687586975870758717587275873758747587575876758777587875879758807588175882758837588475885758867588775888758897589075891758927589375894758957589675897758987589975900759017590275903759047590575906759077590875909759107591175912759137591475915759167591775918759197592075921759227592375924759257592675927759287592975930759317593275933759347593575936759377593875939759407594175942759437594475945759467594775948759497595075951759527595375954759557595675957759587595975960759617596275963759647596575966759677596875969759707597175972759737597475975759767597775978759797598075981759827598375984759857598675987759887598975990759917599275993759947599575996759977599875999760007600176002760037600476005760067600776008760097601076011760127601376014760157601676017760187601976020760217602276023760247602576026760277602876029760307603176032760337603476035760367603776038760397604076041760427604376044760457604676047760487604976050760517605276053760547605576056760577605876059760607606176062760637606476065760667606776068760697607076071760727607376074760757607676077760787607976080760817608276083760847608576086760877608876089760907609176092760937609476095760967609776098760997610076101761027610376104761057610676107761087610976110761117611276113761147611576116761177611876119761207612176122761237612476125761267612776128761297613076131761327613376134761357613676137761387613976140761417614276143761447614576146761477614876149761507615176152761537615476155761567615776158761597616076161761627616376164761657616676167761687616976170761717617276173761747617576176761777617876179761807618176182761837618476185761867618776188761897619076191761927619376194761957619676197761987619976200762017620276203762047620576206762077620876209762107621176212762137621476215762167621776218762197622076221762227622376224762257622676227762287622976230762317623276233762347623576236762377623876239762407624176242762437624476245762467624776248762497625076251762527625376254762557625676257762587625976260762617626276263762647626576266762677626876269762707627176272762737627476275762767627776278762797628076281762827628376284762857628676287762887628976290762917629276293762947629576296762977629876299763007630176302763037630476305763067630776308763097631076311763127631376314763157631676317763187631976320763217632276323763247632576326763277632876329763307633176332763337633476335763367633776338763397634076341763427634376344763457634676347763487634976350763517635276353763547635576356763577635876359763607636176362763637636476365763667636776368763697637076371763727637376374763757637676377763787637976380763817638276383763847638576386763877638876389763907639176392763937639476395763967639776398763997640076401764027640376404764057640676407764087640976410764117641276413764147641576416764177641876419764207642176422764237642476425764267642776428764297643076431764327643376434764357643676437764387643976440764417644276443764447644576446764477644876449764507645176452764537645476455764567645776458764597646076461764627646376464764657646676467764687646976470764717647276473764747647576476764777647876479764807648176482764837648476485764867648776488764897649076491764927649376494764957649676497764987649976500765017650276503765047650576506765077650876509765107651176512765137651476515765167651776518765197652076521765227652376524765257652676527765287652976530765317653276533765347653576536765377653876539765407654176542765437654476545765467654776548765497655076551765527655376554765557655676557765587655976560765617656276563765647656576566765677656876569765707657176572765737657476575765767657776578765797658076581765827658376584765857658676587765887658976590765917659276593765947659576596765977659876599766007660176602766037660476605766067660776608766097661076611766127661376614766157661676617766187661976620766217662276623766247662576626766277662876629766307663176632766337663476635766367663776638766397664076641766427664376644766457664676647766487664976650766517665276653766547665576656766577665876659766607666176662766637666476665766667666776668766697667076671766727667376674766757667676677766787667976680766817668276683766847668576686766877668876689766907669176692766937669476695766967669776698766997670076701767027670376704767057670676707767087670976710767117671276713767147671576716767177671876719767207672176722767237672476725767267672776728767297673076731767327673376734767357673676737767387673976740767417674276743767447674576746767477674876749767507675176752767537675476755767567675776758767597676076761767627676376764767657676676767767687676976770767717677276773767747677576776767777677876779767807678176782767837678476785767867678776788767897679076791767927679376794767957679676797767987679976800768017680276803768047680576806768077680876809768107681176812768137681476815768167681776818768197682076821768227682376824768257682676827768287682976830768317683276833768347683576836768377683876839768407684176842768437684476845768467684776848768497685076851768527685376854768557685676857768587685976860768617686276863768647686576866768677686876869768707687176872768737687476875768767687776878768797688076881768827688376884768857688676887768887688976890768917689276893768947689576896768977689876899769007690176902769037690476905769067690776908769097691076911769127691376914769157691676917769187691976920769217692276923769247692576926769277692876929769307693176932769337693476935769367693776938769397694076941769427694376944769457694676947769487694976950769517695276953769547695576956769577695876959769607696176962769637696476965769667696776968769697697076971769727697376974769757697676977769787697976980769817698276983769847698576986769877698876989769907699176992769937699476995769967699776998769997700077001770027700377004770057700677007770087700977010770117701277013770147701577016770177701877019770207702177022770237702477025770267702777028770297703077031770327703377034770357703677037770387703977040770417704277043770447704577046770477704877049770507705177052770537705477055770567705777058770597706077061770627706377064770657706677067770687706977070770717707277073770747707577076770777707877079770807708177082770837708477085770867708777088770897709077091770927709377094770957709677097770987709977100771017710277103771047710577106771077710877109771107711177112771137711477115771167711777118771197712077121771227712377124771257712677127771287712977130771317713277133771347713577136771377713877139771407714177142771437714477145771467714777148771497715077151771527715377154771557715677157771587715977160771617716277163771647716577166771677716877169771707717177172771737717477175771767717777178771797718077181771827718377184771857718677187771887718977190771917719277193771947719577196771977719877199772007720177202772037720477205772067720777208772097721077211772127721377214772157721677217772187721977220772217722277223772247722577226772277722877229772307723177232772337723477235772367723777238772397724077241772427724377244772457724677247772487724977250772517725277253772547725577256772577725877259772607726177262772637726477265772667726777268772697727077271772727727377274772757727677277772787727977280772817728277283772847728577286772877728877289772907729177292772937729477295772967729777298772997730077301773027730377304773057730677307773087730977310773117731277313773147731577316773177731877319773207732177322773237732477325773267732777328773297733077331773327733377334773357733677337773387733977340773417734277343773447734577346773477734877349773507735177352773537735477355773567735777358773597736077361773627736377364773657736677367773687736977370773717737277373773747737577376773777737877379773807738177382773837738477385773867738777388773897739077391773927739377394773957739677397773987739977400774017740277403774047740577406774077740877409774107741177412774137741477415774167741777418774197742077421774227742377424774257742677427774287742977430774317743277433774347743577436774377743877439774407744177442774437744477445774467744777448774497745077451774527745377454774557745677457774587745977460774617746277463774647746577466774677746877469774707747177472774737747477475774767747777478774797748077481774827748377484774857748677487774887748977490774917749277493774947749577496774977749877499775007750177502775037750477505775067750777508775097751077511775127751377514775157751677517775187751977520775217752277523775247752577526775277752877529775307753177532775337753477535775367753777538775397754077541775427754377544775457754677547775487754977550775517755277553775547755577556775577755877559775607756177562775637756477565775667756777568775697757077571775727757377574775757757677577775787757977580775817758277583775847758577586775877758877589775907759177592775937759477595775967759777598775997760077601776027760377604776057760677607776087760977610776117761277613776147761577616776177761877619776207762177622776237762477625776267762777628776297763077631776327763377634776357763677637776387763977640776417764277643776447764577646776477764877649776507765177652776537765477655776567765777658776597766077661776627766377664776657766677667776687766977670776717767277673776747767577676776777767877679776807768177682776837768477685776867768777688776897769077691776927769377694776957769677697776987769977700777017770277703777047770577706777077770877709777107771177712777137771477715777167771777718777197772077721777227772377724777257772677727777287772977730777317773277733777347773577736777377773877739777407774177742777437774477745777467774777748777497775077751777527775377754777557775677757777587775977760777617776277763777647776577766777677776877769777707777177772777737777477775777767777777778777797778077781777827778377784777857778677787777887778977790777917779277793777947779577796777977779877799778007780177802778037780477805778067780777808778097781077811778127781377814778157781677817778187781977820778217782277823778247782577826778277782877829778307783177832778337783477835778367783777838778397784077841778427784377844778457784677847778487784977850778517785277853778547785577856778577785877859778607786177862778637786477865778667786777868778697787077871778727787377874778757787677877778787787977880778817788277883778847788577886778877788877889778907789177892778937789477895778967789777898778997790077901779027790377904779057790677907779087790977910779117791277913779147791577916779177791877919779207792177922779237792477925779267792777928779297793077931779327793377934779357793677937779387793977940779417794277943779447794577946779477794877949779507795177952779537795477955779567795777958779597796077961779627796377964779657796677967779687796977970779717797277973779747797577976779777797877979779807798177982779837798477985779867798777988779897799077991779927799377994779957799677997779987799978000780017800278003780047800578006780077800878009780107801178012780137801478015780167801778018780197802078021780227802378024780257802678027780287802978030780317803278033780347803578036780377803878039780407804178042780437804478045780467804778048780497805078051780527805378054780557805678057780587805978060780617806278063780647806578066780677806878069780707807178072780737807478075780767807778078780797808078081780827808378084780857808678087780887808978090780917809278093780947809578096780977809878099781007810178102781037810478105781067810778108781097811078111781127811378114781157811678117781187811978120781217812278123781247812578126781277812878129781307813178132781337813478135781367813778138781397814078141781427814378144781457814678147781487814978150781517815278153781547815578156781577815878159781607816178162781637816478165781667816778168781697817078171781727817378174781757817678177781787817978180781817818278183781847818578186781877818878189781907819178192781937819478195781967819778198781997820078201782027820378204782057820678207782087820978210782117821278213782147821578216782177821878219782207822178222782237822478225782267822778228782297823078231782327823378234782357823678237782387823978240782417824278243782447824578246782477824878249782507825178252782537825478255782567825778258782597826078261782627826378264782657826678267782687826978270782717827278273782747827578276782777827878279782807828178282782837828478285782867828778288782897829078291782927829378294782957829678297782987829978300783017830278303783047830578306783077830878309783107831178312783137831478315783167831778318783197832078321783227832378324783257832678327783287832978330783317833278333783347833578336783377833878339783407834178342783437834478345783467834778348783497835078351783527835378354783557835678357783587835978360783617836278363783647836578366783677836878369783707837178372783737837478375783767837778378783797838078381783827838378384783857838678387783887838978390783917839278393783947839578396783977839878399784007840178402784037840478405784067840778408784097841078411784127841378414784157841678417784187841978420784217842278423784247842578426784277842878429784307843178432784337843478435784367843778438784397844078441784427844378444784457844678447784487844978450784517845278453784547845578456784577845878459784607846178462784637846478465784667846778468784697847078471784727847378474784757847678477784787847978480784817848278483784847848578486784877848878489784907849178492784937849478495784967849778498784997850078501785027850378504785057850678507785087850978510785117851278513785147851578516785177851878519785207852178522785237852478525785267852778528785297853078531785327853378534785357853678537785387853978540785417854278543785447854578546785477854878549785507855178552785537855478555785567855778558785597856078561785627856378564785657856678567785687856978570785717857278573785747857578576785777857878579785807858178582785837858478585785867858778588785897859078591785927859378594785957859678597785987859978600786017860278603786047860578606786077860878609786107861178612786137861478615786167861778618786197862078621786227862378624786257862678627786287862978630786317863278633786347863578636786377863878639786407864178642786437864478645786467864778648786497865078651786527865378654786557865678657786587865978660786617866278663786647866578666786677866878669786707867178672786737867478675786767867778678786797868078681786827868378684786857868678687786887868978690786917869278693786947869578696786977869878699787007870178702787037870478705787067870778708787097871078711787127871378714787157871678717787187871978720787217872278723787247872578726787277872878729787307873178732787337873478735787367873778738787397874078741787427874378744787457874678747787487874978750787517875278753787547875578756787577875878759787607876178762787637876478765787667876778768787697877078771787727877378774787757877678777787787877978780787817878278783787847878578786787877878878789787907879178792787937879478795787967879778798787997880078801788027880378804788057880678807788087880978810788117881278813788147881578816788177881878819788207882178822788237882478825788267882778828788297883078831788327883378834788357883678837788387883978840788417884278843788447884578846788477884878849788507885178852788537885478855788567885778858788597886078861788627886378864788657886678867788687886978870788717887278873788747887578876788777887878879788807888178882788837888478885788867888778888788897889078891788927889378894788957889678897788987889978900789017890278903789047890578906789077890878909789107891178912789137891478915789167891778918789197892078921789227892378924789257892678927789287892978930789317893278933789347893578936789377893878939789407894178942789437894478945789467894778948789497895078951789527895378954789557895678957789587895978960789617896278963789647896578966789677896878969789707897178972789737897478975789767897778978789797898078981789827898378984789857898678987789887898978990789917899278993789947899578996789977899878999790007900179002790037900479005790067900779008790097901079011790127901379014790157901679017790187901979020790217902279023790247902579026790277902879029790307903179032790337903479035790367903779038790397904079041790427904379044790457904679047790487904979050790517905279053790547905579056790577905879059790607906179062790637906479065790667906779068790697907079071790727907379074790757907679077790787907979080790817908279083790847908579086790877908879089790907909179092790937909479095790967909779098790997910079101791027910379104791057910679107791087910979110791117911279113791147911579116791177911879119791207912179122791237912479125791267912779128791297913079131791327913379134791357913679137791387913979140791417914279143791447914579146791477914879149791507915179152791537915479155791567915779158791597916079161791627916379164791657916679167791687916979170791717917279173791747917579176791777917879179791807918179182791837918479185791867918779188791897919079191791927919379194791957919679197791987919979200792017920279203792047920579206792077920879209792107921179212792137921479215792167921779218792197922079221792227922379224792257922679227792287922979230792317923279233792347923579236792377923879239792407924179242792437924479245792467924779248792497925079251792527925379254792557925679257792587925979260792617926279263792647926579266792677926879269792707927179272792737927479275792767927779278792797928079281792827928379284792857928679287792887928979290792917929279293792947929579296792977929879299793007930179302793037930479305793067930779308793097931079311793127931379314793157931679317793187931979320793217932279323793247932579326793277932879329793307933179332793337933479335793367933779338793397934079341793427934379344793457934679347793487934979350793517935279353793547935579356793577935879359793607936179362793637936479365793667936779368793697937079371793727937379374793757937679377793787937979380793817938279383793847938579386793877938879389793907939179392793937939479395793967939779398793997940079401794027940379404794057940679407794087940979410794117941279413794147941579416794177941879419794207942179422794237942479425794267942779428794297943079431794327943379434794357943679437794387943979440794417944279443794447944579446794477944879449794507945179452794537945479455794567945779458794597946079461794627946379464794657946679467794687946979470794717947279473794747947579476794777947879479794807948179482794837948479485794867948779488794897949079491794927949379494794957949679497794987949979500795017950279503795047950579506795077950879509795107951179512795137951479515795167951779518795197952079521795227952379524795257952679527795287952979530795317953279533795347953579536795377953879539795407954179542795437954479545795467954779548795497955079551795527955379554795557955679557795587955979560795617956279563795647956579566795677956879569795707957179572795737957479575795767957779578795797958079581795827958379584795857958679587795887958979590795917959279593795947959579596795977959879599796007960179602796037960479605796067960779608796097961079611796127961379614796157961679617796187961979620796217962279623796247962579626796277962879629796307963179632796337963479635796367963779638796397964079641796427964379644796457964679647796487964979650796517965279653796547965579656796577965879659796607966179662796637966479665796667966779668796697967079671796727967379674796757967679677796787967979680796817968279683796847968579686796877968879689796907969179692796937969479695796967969779698796997970079701797027970379704797057970679707797087970979710797117971279713797147971579716797177971879719797207972179722797237972479725797267972779728797297973079731797327973379734797357973679737797387973979740797417974279743797447974579746797477974879749797507975179752797537975479755797567975779758797597976079761797627976379764797657976679767797687976979770797717977279773797747977579776797777977879779797807978179782797837978479785797867978779788797897979079791797927979379794797957979679797797987979979800798017980279803798047980579806798077980879809798107981179812798137981479815798167981779818798197982079821798227982379824798257982679827798287982979830798317983279833798347983579836798377983879839798407984179842798437984479845798467984779848798497985079851798527985379854798557985679857798587985979860798617986279863798647986579866798677986879869798707987179872798737987479875798767987779878798797988079881798827988379884798857988679887798887988979890798917989279893798947989579896798977989879899799007990179902799037990479905799067990779908799097991079911799127991379914799157991679917799187991979920799217992279923799247992579926799277992879929799307993179932799337993479935799367993779938799397994079941799427994379944799457994679947799487994979950799517995279953799547995579956799577995879959799607996179962799637996479965799667996779968799697997079971799727997379974799757997679977799787997979980799817998279983799847998579986799877998879989799907999179992799937999479995799967999779998799998000080001800028000380004800058000680007800088000980010800118001280013800148001580016800178001880019800208002180022800238002480025800268002780028800298003080031800328003380034800358003680037800388003980040800418004280043800448004580046800478004880049800508005180052800538005480055800568005780058800598006080061800628006380064800658006680067800688006980070800718007280073800748007580076800778007880079800808008180082800838008480085800868008780088800898009080091800928009380094800958009680097800988009980100801018010280103801048010580106801078010880109801108011180112801138011480115801168011780118801198012080121801228012380124801258012680127801288012980130801318013280133801348013580136801378013880139801408014180142801438014480145801468014780148801498015080151801528015380154801558015680157801588015980160801618016280163801648016580166801678016880169801708017180172801738017480175801768017780178801798018080181801828018380184801858018680187801888018980190801918019280193801948019580196801978019880199802008020180202802038020480205802068020780208802098021080211802128021380214802158021680217802188021980220802218022280223802248022580226802278022880229802308023180232802338023480235802368023780238802398024080241802428024380244802458024680247802488024980250802518025280253802548025580256802578025880259802608026180262802638026480265802668026780268802698027080271802728027380274802758027680277802788027980280802818028280283802848028580286802878028880289802908029180292802938029480295802968029780298802998030080301803028030380304803058030680307803088030980310803118031280313803148031580316803178031880319803208032180322803238032480325803268032780328803298033080331803328033380334803358033680337803388033980340803418034280343803448034580346803478034880349803508035180352803538035480355803568035780358803598036080361803628036380364803658036680367803688036980370803718037280373803748037580376803778037880379803808038180382803838038480385803868038780388803898039080391803928039380394803958039680397803988039980400804018040280403804048040580406804078040880409804108041180412804138041480415804168041780418804198042080421804228042380424804258042680427804288042980430804318043280433804348043580436804378043880439804408044180442804438044480445804468044780448804498045080451804528045380454804558045680457804588045980460804618046280463804648046580466804678046880469804708047180472804738047480475804768047780478804798048080481804828048380484804858048680487804888048980490804918049280493804948049580496804978049880499805008050180502805038050480505805068050780508805098051080511805128051380514805158051680517805188051980520805218052280523805248052580526805278052880529805308053180532805338053480535805368053780538805398054080541805428054380544805458054680547805488054980550805518055280553805548055580556805578055880559805608056180562805638056480565805668056780568805698057080571805728057380574805758057680577805788057980580805818058280583805848058580586805878058880589805908059180592805938059480595805968059780598805998060080601806028060380604806058060680607806088060980610806118061280613806148061580616806178061880619806208062180622806238062480625806268062780628806298063080631806328063380634806358063680637806388063980640806418064280643806448064580646806478064880649806508065180652806538065480655806568065780658806598066080661806628066380664806658066680667806688066980670806718067280673806748067580676806778067880679806808068180682806838068480685806868068780688806898069080691806928069380694806958069680697806988069980700807018070280703807048070580706807078070880709807108071180712807138071480715807168071780718807198072080721807228072380724807258072680727807288072980730807318073280733807348073580736807378073880739807408074180742807438074480745807468074780748807498075080751807528075380754807558075680757807588075980760807618076280763807648076580766807678076880769807708077180772807738077480775807768077780778807798078080781807828078380784807858078680787807888078980790807918079280793807948079580796807978079880799808008080180802808038080480805808068080780808808098081080811808128081380814808158081680817808188081980820808218082280823808248082580826808278082880829808308083180832808338083480835808368083780838808398084080841808428084380844808458084680847808488084980850808518085280853808548085580856808578085880859808608086180862808638086480865808668086780868808698087080871808728087380874808758087680877808788087980880808818088280883808848088580886808878088880889808908089180892808938089480895808968089780898808998090080901809028090380904809058090680907809088090980910809118091280913809148091580916809178091880919809208092180922809238092480925809268092780928809298093080931809328093380934809358093680937809388093980940809418094280943809448094580946809478094880949809508095180952809538095480955809568095780958809598096080961809628096380964809658096680967809688096980970809718097280973809748097580976809778097880979809808098180982809838098480985809868098780988809898099080991809928099380994809958099680997809988099981000810018100281003810048100581006810078100881009810108101181012810138101481015810168101781018810198102081021810228102381024810258102681027810288102981030810318103281033810348103581036810378103881039810408104181042810438104481045810468104781048810498105081051810528105381054810558105681057810588105981060810618106281063810648106581066810678106881069810708107181072810738107481075810768107781078810798108081081810828108381084810858108681087810888108981090810918109281093810948109581096810978109881099811008110181102811038110481105811068110781108811098111081111811128111381114811158111681117811188111981120811218112281123811248112581126811278112881129811308113181132811338113481135811368113781138811398114081141811428114381144811458114681147811488114981150811518115281153811548115581156811578115881159811608116181162811638116481165811668116781168811698117081171811728117381174811758117681177811788117981180811818118281183811848118581186811878118881189811908119181192811938119481195811968119781198811998120081201812028120381204812058120681207812088120981210812118121281213812148121581216812178121881219812208122181222812238122481225812268122781228812298123081231812328123381234812358123681237812388123981240812418124281243812448124581246812478124881249812508125181252812538125481255812568125781258812598126081261812628126381264812658126681267812688126981270812718127281273812748127581276812778127881279812808128181282812838128481285812868128781288812898129081291812928129381294812958129681297812988129981300813018130281303813048130581306813078130881309813108131181312813138131481315813168131781318813198132081321813228132381324813258132681327813288132981330813318133281333813348133581336813378133881339813408134181342813438134481345813468134781348813498135081351813528135381354813558135681357813588135981360813618136281363813648136581366813678136881369813708137181372813738137481375813768137781378813798138081381813828138381384813858138681387813888138981390813918139281393813948139581396813978139881399814008140181402814038140481405814068140781408814098141081411814128141381414814158141681417814188141981420814218142281423814248142581426814278142881429814308143181432814338143481435814368143781438814398144081441814428144381444814458144681447814488144981450814518145281453814548145581456814578145881459814608146181462814638146481465814668146781468814698147081471814728147381474814758147681477814788147981480814818148281483814848148581486814878148881489814908149181492814938149481495814968149781498814998150081501815028150381504815058150681507815088150981510815118151281513815148151581516815178151881519815208152181522815238152481525815268152781528815298153081531815328153381534815358153681537815388153981540815418154281543815448154581546815478154881549815508155181552815538155481555815568155781558815598156081561815628156381564815658156681567815688156981570815718157281573815748157581576815778157881579815808158181582815838158481585815868158781588815898159081591815928159381594815958159681597815988159981600816018160281603816048160581606816078160881609816108161181612816138161481615816168161781618816198162081621816228162381624816258162681627816288162981630816318163281633816348163581636816378163881639816408164181642816438164481645816468164781648816498165081651816528165381654816558165681657816588165981660816618166281663816648166581666816678166881669816708167181672816738167481675816768167781678816798168081681816828168381684816858168681687816888168981690816918169281693816948169581696816978169881699817008170181702817038170481705817068170781708817098171081711817128171381714817158171681717817188171981720817218172281723817248172581726817278172881729817308173181732817338173481735817368173781738817398174081741817428174381744817458174681747817488174981750817518175281753817548175581756817578175881759817608176181762817638176481765817668176781768817698177081771817728177381774817758177681777817788177981780817818178281783817848178581786817878178881789817908179181792817938179481795817968179781798817998180081801818028180381804818058180681807818088180981810818118181281813818148181581816818178181881819818208182181822818238182481825818268182781828818298183081831818328183381834818358183681837818388183981840818418184281843818448184581846818478184881849818508185181852818538185481855818568185781858818598186081861818628186381864818658186681867818688186981870818718187281873818748187581876818778187881879818808188181882818838188481885818868188781888818898189081891818928189381894818958189681897818988189981900819018190281903819048190581906819078190881909819108191181912819138191481915819168191781918819198192081921819228192381924819258192681927819288192981930819318193281933819348193581936819378193881939819408194181942819438194481945819468194781948819498195081951819528195381954819558195681957819588195981960819618196281963819648196581966819678196881969819708197181972819738197481975819768197781978819798198081981819828198381984819858198681987819888198981990819918199281993819948199581996819978199881999820008200182002820038200482005820068200782008820098201082011820128201382014820158201682017820188201982020820218202282023820248202582026820278202882029820308203182032820338203482035820368203782038820398204082041820428204382044820458204682047820488204982050820518205282053820548205582056820578205882059820608206182062820638206482065820668206782068820698207082071820728207382074820758207682077820788207982080820818208282083820848208582086820878208882089820908209182092820938209482095820968209782098820998210082101821028210382104821058210682107821088210982110821118211282113821148211582116821178211882119821208212182122821238212482125821268212782128821298213082131821328213382134821358213682137821388213982140821418214282143821448214582146821478214882149821508215182152821538215482155821568215782158821598216082161821628216382164821658216682167821688216982170821718217282173821748217582176821778217882179821808218182182821838218482185821868218782188821898219082191821928219382194821958219682197821988219982200822018220282203822048220582206822078220882209822108221182212822138221482215822168221782218822198222082221822228222382224822258222682227822288222982230822318223282233822348223582236822378223882239822408224182242822438224482245822468224782248822498225082251822528225382254822558225682257822588225982260822618226282263822648226582266822678226882269822708227182272822738227482275822768227782278822798228082281822828228382284822858228682287822888228982290822918229282293822948229582296822978229882299823008230182302823038230482305823068230782308823098231082311823128231382314823158231682317823188231982320823218232282323823248232582326823278232882329823308233182332823338233482335823368233782338823398234082341823428234382344823458234682347823488234982350823518235282353823548235582356823578235882359823608236182362823638236482365823668236782368823698237082371823728237382374823758237682377823788237982380823818238282383823848238582386823878238882389823908239182392823938239482395823968239782398823998240082401824028240382404824058240682407824088240982410824118241282413824148241582416824178241882419824208242182422824238242482425824268242782428824298243082431824328243382434824358243682437824388243982440824418244282443824448244582446824478244882449824508245182452824538245482455824568245782458824598246082461824628246382464824658246682467824688246982470824718247282473824748247582476824778247882479824808248182482824838248482485824868248782488824898249082491824928249382494824958249682497824988249982500825018250282503825048250582506825078250882509825108251182512825138251482515825168251782518825198252082521825228252382524825258252682527825288252982530825318253282533825348253582536825378253882539825408254182542825438254482545825468254782548825498255082551825528255382554825558255682557825588255982560825618256282563825648256582566825678256882569825708257182572825738257482575825768257782578825798258082581825828258382584825858258682587825888258982590825918259282593825948259582596825978259882599826008260182602826038260482605826068260782608826098261082611826128261382614826158261682617826188261982620826218262282623826248262582626826278262882629826308263182632826338263482635826368263782638826398264082641826428264382644826458264682647826488264982650826518265282653826548265582656826578265882659826608266182662826638266482665826668266782668826698267082671826728267382674826758267682677826788267982680826818268282683826848268582686826878268882689826908269182692826938269482695826968269782698826998270082701827028270382704827058270682707827088270982710827118271282713827148271582716827178271882719827208272182722827238272482725827268272782728827298273082731827328273382734827358273682737827388273982740827418274282743827448274582746827478274882749827508275182752827538275482755827568275782758827598276082761827628276382764827658276682767827688276982770827718277282773827748277582776827778277882779827808278182782827838278482785827868278782788827898279082791827928279382794827958279682797827988279982800828018280282803828048280582806828078280882809828108281182812828138281482815828168281782818828198282082821828228282382824828258282682827828288282982830828318283282833828348283582836828378283882839828408284182842828438284482845828468284782848828498285082851828528285382854828558285682857828588285982860828618286282863828648286582866828678286882869828708287182872828738287482875828768287782878828798288082881828828288382884828858288682887828888288982890828918289282893828948289582896828978289882899829008290182902829038290482905829068290782908829098291082911829128291382914829158291682917829188291982920829218292282923829248292582926829278292882929829308293182932829338293482935829368293782938829398294082941829428294382944829458294682947829488294982950829518295282953829548295582956829578295882959829608296182962829638296482965829668296782968829698297082971829728297382974829758297682977829788297982980829818298282983829848298582986829878298882989829908299182992829938299482995829968299782998829998300083001830028300383004830058300683007830088300983010830118301283013830148301583016830178301883019830208302183022830238302483025830268302783028830298303083031830328303383034830358303683037830388303983040830418304283043830448304583046830478304883049830508305183052830538305483055830568305783058830598306083061830628306383064830658306683067830688306983070830718307283073830748307583076830778307883079830808308183082830838308483085830868308783088830898309083091830928309383094830958309683097830988309983100831018310283103831048310583106831078310883109831108311183112831138311483115831168311783118831198312083121831228312383124831258312683127831288312983130831318313283133831348313583136831378313883139831408314183142831438314483145831468314783148831498315083151831528315383154831558315683157831588315983160831618316283163831648316583166831678316883169831708317183172831738317483175831768317783178831798318083181831828318383184831858318683187831888318983190831918319283193831948319583196831978319883199832008320183202832038320483205832068320783208832098321083211832128321383214832158321683217832188321983220832218322283223832248322583226832278322883229832308323183232832338323483235832368323783238832398324083241832428324383244832458324683247832488324983250832518325283253832548325583256832578325883259832608326183262832638326483265832668326783268832698327083271832728327383274832758327683277832788327983280832818328283283832848328583286832878328883289832908329183292832938329483295832968329783298832998330083301833028330383304833058330683307833088330983310833118331283313833148331583316833178331883319833208332183322833238332483325833268332783328833298333083331833328333383334833358333683337833388333983340833418334283343833448334583346833478334883349833508335183352833538335483355833568335783358833598336083361833628336383364833658336683367833688336983370833718337283373833748337583376833778337883379833808338183382833838338483385833868338783388833898339083391833928339383394833958339683397833988339983400834018340283403834048340583406834078340883409834108341183412834138341483415834168341783418834198342083421834228342383424834258342683427834288342983430834318343283433834348343583436834378343883439834408344183442834438344483445834468344783448834498345083451834528345383454834558345683457834588345983460834618346283463834648346583466834678346883469834708347183472834738347483475834768347783478834798348083481834828348383484834858348683487834888348983490834918349283493834948349583496834978349883499835008350183502835038350483505835068350783508835098351083511835128351383514835158351683517835188351983520835218352283523835248352583526835278352883529835308353183532835338353483535835368353783538835398354083541835428354383544835458354683547835488354983550835518355283553835548355583556835578355883559835608356183562835638356483565835668356783568835698357083571835728357383574835758357683577835788357983580835818358283583835848358583586835878358883589835908359183592835938359483595835968359783598835998360083601836028360383604836058360683607836088360983610836118361283613836148361583616836178361883619836208362183622836238362483625836268362783628836298363083631836328363383634836358363683637836388363983640836418364283643836448364583646836478364883649836508365183652836538365483655836568365783658836598366083661836628366383664836658366683667836688366983670836718367283673836748367583676836778367883679836808368183682836838368483685836868368783688836898369083691836928369383694836958369683697836988369983700837018370283703837048370583706837078370883709837108371183712837138371483715837168371783718837198372083721837228372383724837258372683727837288372983730837318373283733837348373583736837378373883739837408374183742837438374483745837468374783748837498375083751837528375383754837558375683757837588375983760837618376283763837648376583766837678376883769837708377183772837738377483775837768377783778837798378083781837828378383784837858378683787837888378983790837918379283793837948379583796837978379883799838008380183802838038380483805838068380783808838098381083811838128381383814838158381683817838188381983820838218382283823838248382583826838278382883829838308383183832838338383483835838368383783838838398384083841838428384383844838458384683847838488384983850838518385283853838548385583856838578385883859838608386183862838638386483865838668386783868838698387083871838728387383874838758387683877838788387983880838818388283883838848388583886838878388883889838908389183892838938389483895838968389783898838998390083901839028390383904839058390683907839088390983910839118391283913839148391583916839178391883919839208392183922839238392483925839268392783928839298393083931839328393383934839358393683937839388393983940839418394283943839448394583946839478394883949839508395183952839538395483955839568395783958839598396083961839628396383964839658396683967839688396983970839718397283973839748397583976839778397883979839808398183982839838398483985839868398783988839898399083991839928399383994839958399683997839988399984000840018400284003840048400584006840078400884009840108401184012840138401484015840168401784018840198402084021840228402384024840258402684027840288402984030840318403284033840348403584036840378403884039840408404184042840438404484045840468404784048840498405084051840528405384054840558405684057840588405984060840618406284063840648406584066840678406884069840708407184072840738407484075840768407784078840798408084081840828408384084840858408684087840888408984090840918409284093840948409584096840978409884099841008410184102841038410484105841068410784108841098411084111841128411384114841158411684117841188411984120841218412284123841248412584126841278412884129841308413184132841338413484135841368413784138841398414084141841428414384144841458414684147841488414984150841518415284153841548415584156841578415884159841608416184162841638416484165841668416784168841698417084171841728417384174841758417684177841788417984180841818418284183841848418584186841878418884189841908419184192841938419484195841968419784198841998420084201842028420384204842058420684207842088420984210842118421284213842148421584216842178421884219842208422184222842238422484225842268422784228842298423084231842328423384234842358423684237842388423984240842418424284243842448424584246842478424884249842508425184252842538425484255842568425784258842598426084261842628426384264842658426684267842688426984270842718427284273842748427584276842778427884279842808428184282842838428484285842868428784288842898429084291842928429384294842958429684297842988429984300843018430284303843048430584306843078430884309843108431184312843138431484315843168431784318843198432084321843228432384324843258432684327843288432984330843318433284333843348433584336843378433884339843408434184342843438434484345843468434784348843498435084351843528435384354843558435684357843588435984360843618436284363843648436584366843678436884369843708437184372843738437484375843768437784378843798438084381843828438384384843858438684387843888438984390843918439284393843948439584396843978439884399844008440184402844038440484405844068440784408844098441084411844128441384414844158441684417844188441984420844218442284423844248442584426844278442884429844308443184432844338443484435844368443784438844398444084441844428444384444844458444684447844488444984450844518445284453844548445584456844578445884459844608446184462844638446484465844668446784468844698447084471844728447384474844758447684477844788447984480844818448284483844848448584486844878448884489844908449184492844938449484495844968449784498844998450084501845028450384504845058450684507845088450984510845118451284513845148451584516845178451884519845208452184522845238452484525845268452784528845298453084531845328453384534845358453684537845388453984540845418454284543845448454584546845478454884549845508455184552845538455484555845568455784558845598456084561845628456384564845658456684567845688456984570845718457284573845748457584576845778457884579845808458184582845838458484585845868458784588845898459084591845928459384594845958459684597845988459984600846018460284603846048460584606846078460884609846108461184612846138461484615846168461784618846198462084621846228462384624846258462684627846288462984630846318463284633846348463584636846378463884639846408464184642846438464484645846468464784648846498465084651846528465384654846558465684657846588465984660846618466284663846648466584666846678466884669846708467184672846738467484675846768467784678846798468084681846828468384684846858468684687846888468984690846918469284693846948469584696846978469884699847008470184702847038470484705847068470784708847098471084711847128471384714847158471684717847188471984720847218472284723847248472584726847278472884729847308473184732847338473484735847368473784738847398474084741847428474384744847458474684747847488474984750847518475284753847548475584756847578475884759847608476184762847638476484765847668476784768847698477084771847728477384774847758477684777847788477984780847818478284783847848478584786847878478884789847908479184792847938479484795847968479784798847998480084801848028480384804848058480684807848088480984810848118481284813848148481584816848178481884819848208482184822848238482484825848268482784828848298483084831848328483384834848358483684837848388483984840848418484284843848448484584846848478484884849848508485184852848538485484855848568485784858848598486084861848628486384864848658486684867848688486984870848718487284873848748487584876848778487884879848808488184882848838488484885848868488784888848898489084891848928489384894848958489684897848988489984900849018490284903849048490584906849078490884909849108491184912849138491484915849168491784918849198492084921849228492384924849258492684927849288492984930849318493284933849348493584936849378493884939849408494184942849438494484945849468494784948849498495084951849528495384954849558495684957849588495984960849618496284963849648496584966849678496884969849708497184972849738497484975849768497784978849798498084981849828498384984849858498684987849888498984990849918499284993849948499584996849978499884999850008500185002850038500485005850068500785008850098501085011850128501385014850158501685017850188501985020850218502285023850248502585026850278502885029850308503185032850338503485035850368503785038850398504085041850428504385044850458504685047850488504985050850518505285053850548505585056850578505885059850608506185062850638506485065850668506785068850698507085071850728507385074850758507685077850788507985080850818508285083850848508585086850878508885089850908509185092850938509485095850968509785098850998510085101851028510385104851058510685107851088510985110851118511285113851148511585116851178511885119851208512185122851238512485125851268512785128851298513085131851328513385134851358513685137851388513985140851418514285143851448514585146851478514885149851508515185152851538515485155851568515785158851598516085161851628516385164851658516685167851688516985170851718517285173851748517585176851778517885179851808518185182851838518485185851868518785188851898519085191851928519385194851958519685197851988519985200852018520285203852048520585206852078520885209852108521185212852138521485215852168521785218852198522085221852228522385224852258522685227852288522985230852318523285233852348523585236852378523885239852408524185242852438524485245852468524785248852498525085251852528525385254852558525685257852588525985260852618526285263852648526585266852678526885269852708527185272852738527485275852768527785278852798528085281852828528385284852858528685287852888528985290852918529285293852948529585296852978529885299853008530185302853038530485305853068530785308853098531085311853128531385314853158531685317853188531985320853218532285323853248532585326853278532885329853308533185332853338533485335853368533785338853398534085341853428534385344853458534685347853488534985350853518535285353853548535585356853578535885359853608536185362853638536485365853668536785368853698537085371853728537385374853758537685377853788537985380853818538285383853848538585386853878538885389853908539185392853938539485395853968539785398853998540085401854028540385404854058540685407854088540985410854118541285413854148541585416854178541885419854208542185422854238542485425854268542785428854298543085431854328543385434854358543685437854388543985440854418544285443854448544585446854478544885449854508545185452854538545485455854568545785458854598546085461854628546385464854658546685467854688546985470854718547285473854748547585476854778547885479854808548185482854838548485485854868548785488854898549085491854928549385494854958549685497854988549985500855018550285503855048550585506855078550885509855108551185512855138551485515855168551785518855198552085521855228552385524855258552685527855288552985530855318553285533855348553585536855378553885539855408554185542855438554485545855468554785548855498555085551855528555385554855558555685557855588555985560855618556285563855648556585566855678556885569855708557185572855738557485575855768557785578855798558085581855828558385584855858558685587855888558985590855918559285593855948559585596855978559885599856008560185602856038560485605856068560785608856098561085611856128561385614856158561685617856188561985620856218562285623856248562585626856278562885629856308563185632856338563485635856368563785638856398564085641856428564385644856458564685647856488564985650856518565285653856548565585656856578565885659856608566185662856638566485665856668566785668856698567085671856728567385674856758567685677856788567985680856818568285683856848568585686856878568885689856908569185692856938569485695856968569785698856998570085701857028570385704857058570685707857088570985710857118571285713857148571585716857178571885719857208572185722857238572485725857268572785728857298573085731857328573385734857358573685737857388573985740857418574285743857448574585746857478574885749857508575185752857538575485755857568575785758857598576085761857628576385764857658576685767857688576985770857718577285773857748577585776857778577885779857808578185782857838578485785857868578785788857898579085791857928579385794857958579685797857988579985800858018580285803858048580585806858078580885809858108581185812858138581485815858168581785818858198582085821858228582385824858258582685827858288582985830858318583285833858348583585836858378583885839858408584185842858438584485845858468584785848858498585085851858528585385854858558585685857858588585985860858618586285863858648586585866858678586885869858708587185872858738587485875858768587785878858798588085881858828588385884858858588685887858888588985890858918589285893858948589585896858978589885899859008590185902859038590485905859068590785908859098591085911859128591385914859158591685917859188591985920859218592285923859248592585926859278592885929859308593185932859338593485935859368593785938859398594085941859428594385944859458594685947859488594985950859518595285953859548595585956859578595885959859608596185962859638596485965859668596785968859698597085971859728597385974859758597685977859788597985980859818598285983859848598585986859878598885989859908599185992859938599485995859968599785998859998600086001860028600386004860058600686007860088600986010860118601286013860148601586016860178601886019860208602186022860238602486025860268602786028860298603086031860328603386034860358603686037860388603986040860418604286043860448604586046860478604886049860508605186052860538605486055860568605786058860598606086061860628606386064860658606686067860688606986070860718607286073860748607586076860778607886079860808608186082860838608486085860868608786088860898609086091860928609386094860958609686097860988609986100861018610286103861048610586106861078610886109861108611186112861138611486115861168611786118861198612086121861228612386124861258612686127861288612986130861318613286133861348613586136861378613886139861408614186142861438614486145861468614786148861498615086151861528615386154861558615686157861588615986160861618616286163861648616586166861678616886169861708617186172861738617486175861768617786178861798618086181861828618386184861858618686187861888618986190861918619286193861948619586196861978619886199862008620186202862038620486205862068620786208862098621086211862128621386214862158621686217862188621986220862218622286223862248622586226862278622886229862308623186232862338623486235862368623786238862398624086241862428624386244862458624686247862488624986250862518625286253862548625586256862578625886259862608626186262862638626486265862668626786268862698627086271862728627386274862758627686277862788627986280862818628286283862848628586286862878628886289862908629186292862938629486295862968629786298862998630086301863028630386304863058630686307863088630986310863118631286313863148631586316863178631886319863208632186322863238632486325863268632786328863298633086331863328633386334863358633686337863388633986340863418634286343863448634586346863478634886349863508635186352863538635486355863568635786358863598636086361863628636386364863658636686367863688636986370863718637286373863748637586376863778637886379863808638186382863838638486385863868638786388863898639086391863928639386394863958639686397863988639986400864018640286403864048640586406864078640886409864108641186412864138641486415864168641786418864198642086421864228642386424864258642686427864288642986430864318643286433864348643586436864378643886439864408644186442864438644486445864468644786448864498645086451864528645386454864558645686457864588645986460864618646286463864648646586466864678646886469864708647186472864738647486475864768647786478864798648086481864828648386484864858648686487864888648986490864918649286493864948649586496864978649886499865008650186502865038650486505865068650786508865098651086511865128651386514865158651686517865188651986520865218652286523865248652586526865278652886529865308653186532865338653486535865368653786538865398654086541865428654386544865458654686547865488654986550865518655286553865548655586556865578655886559865608656186562865638656486565865668656786568865698657086571865728657386574865758657686577865788657986580865818658286583865848658586586865878658886589865908659186592865938659486595865968659786598865998660086601866028660386604866058660686607866088660986610866118661286613866148661586616866178661886619866208662186622866238662486625866268662786628866298663086631866328663386634866358663686637866388663986640866418664286643866448664586646866478664886649866508665186652866538665486655866568665786658866598666086661866628666386664866658666686667866688666986670866718667286673866748667586676866778667886679866808668186682866838668486685866868668786688866898669086691866928669386694866958669686697866988669986700867018670286703867048670586706867078670886709867108671186712867138671486715867168671786718867198672086721867228672386724867258672686727867288672986730867318673286733867348673586736867378673886739867408674186742867438674486745867468674786748867498675086751867528675386754867558675686757867588675986760867618676286763867648676586766867678676886769867708677186772867738677486775867768677786778867798678086781867828678386784867858678686787867888678986790867918679286793867948679586796867978679886799868008680186802868038680486805868068680786808868098681086811868128681386814868158681686817868188681986820868218682286823868248682586826868278682886829868308683186832868338683486835868368683786838868398684086841868428684386844868458684686847868488684986850868518685286853868548685586856868578685886859868608686186862868638686486865868668686786868868698687086871868728687386874868758687686877868788687986880868818688286883868848688586886868878688886889868908689186892868938689486895868968689786898868998690086901869028690386904869058690686907869088690986910869118691286913869148691586916869178691886919869208692186922869238692486925869268692786928869298693086931869328693386934869358693686937869388693986940869418694286943869448694586946869478694886949869508695186952869538695486955869568695786958869598696086961869628696386964869658696686967869688696986970869718697286973869748697586976869778697886979869808698186982869838698486985869868698786988869898699086991869928699386994869958699686997869988699987000870018700287003870048700587006870078700887009870108701187012870138701487015870168701787018870198702087021870228702387024870258702687027870288702987030870318703287033870348703587036870378703887039870408704187042870438704487045870468704787048870498705087051870528705387054870558705687057870588705987060870618706287063870648706587066870678706887069870708707187072870738707487075870768707787078870798708087081870828708387084870858708687087870888708987090870918709287093870948709587096870978709887099871008710187102871038710487105871068710787108871098711087111871128711387114871158711687117871188711987120871218712287123871248712587126871278712887129871308713187132871338713487135871368713787138871398714087141871428714387144871458714687147871488714987150871518715287153871548715587156871578715887159871608716187162871638716487165871668716787168871698717087171871728717387174871758717687177871788717987180871818718287183871848718587186871878718887189871908719187192871938719487195871968719787198871998720087201872028720387204872058720687207872088720987210872118721287213872148721587216872178721887219872208722187222872238722487225872268722787228872298723087231872328723387234872358723687237872388723987240872418724287243872448724587246872478724887249872508725187252872538725487255872568725787258872598726087261872628726387264872658726687267872688726987270872718727287273872748727587276872778727887279872808728187282872838728487285872868728787288872898729087291872928729387294872958729687297872988729987300873018730287303873048730587306873078730887309873108731187312873138731487315873168731787318873198732087321873228732387324873258732687327873288732987330873318733287333873348733587336873378733887339873408734187342873438734487345873468734787348873498735087351873528735387354873558735687357873588735987360873618736287363873648736587366873678736887369873708737187372873738737487375873768737787378873798738087381873828738387384873858738687387873888738987390873918739287393873948739587396873978739887399874008740187402874038740487405874068740787408874098741087411874128741387414874158741687417874188741987420874218742287423874248742587426874278742887429874308743187432874338743487435874368743787438874398744087441874428744387444874458744687447874488744987450874518745287453874548745587456874578745887459874608746187462874638746487465874668746787468874698747087471874728747387474874758747687477874788747987480874818748287483874848748587486874878748887489874908749187492874938749487495874968749787498874998750087501875028750387504875058750687507875088750987510875118751287513875148751587516875178751887519875208752187522875238752487525875268752787528875298753087531875328753387534875358753687537875388753987540875418754287543875448754587546875478754887549875508755187552875538755487555875568755787558875598756087561875628756387564875658756687567875688756987570875718757287573875748757587576875778757887579875808758187582875838758487585875868758787588875898759087591875928759387594875958759687597875988759987600876018760287603876048760587606876078760887609876108761187612876138761487615876168761787618876198762087621876228762387624876258762687627876288762987630876318763287633876348763587636876378763887639876408764187642876438764487645876468764787648876498765087651876528765387654876558765687657876588765987660876618766287663876648766587666876678766887669876708767187672876738767487675876768767787678876798768087681876828768387684876858768687687876888768987690876918769287693876948769587696876978769887699877008770187702877038770487705877068770787708877098771087711877128771387714877158771687717877188771987720877218772287723877248772587726877278772887729877308773187732877338773487735877368773787738877398774087741877428774387744877458774687747877488774987750877518775287753877548775587756877578775887759877608776187762877638776487765877668776787768877698777087771877728777387774877758777687777877788777987780877818778287783877848778587786877878778887789877908779187792877938779487795877968779787798877998780087801878028780387804878058780687807878088780987810878118781287813878148781587816878178781887819878208782187822878238782487825878268782787828878298783087831878328783387834878358783687837878388783987840878418784287843878448784587846878478784887849878508785187852878538785487855878568785787858878598786087861878628786387864878658786687867878688786987870878718787287873878748787587876878778787887879878808788187882878838788487885878868788787888878898789087891878928789387894878958789687897878988789987900879018790287903879048790587906879078790887909879108791187912879138791487915879168791787918879198792087921879228792387924879258792687927879288792987930879318793287933879348793587936879378793887939879408794187942879438794487945879468794787948879498795087951879528795387954879558795687957879588795987960879618796287963879648796587966879678796887969879708797187972879738797487975879768797787978879798798087981879828798387984879858798687987879888798987990879918799287993879948799587996879978799887999880008800188002880038800488005880068800788008880098801088011880128801388014880158801688017880188801988020880218802288023880248802588026880278802888029880308803188032880338803488035880368803788038880398804088041880428804388044880458804688047880488804988050880518805288053880548805588056880578805888059880608806188062880638806488065880668806788068880698807088071880728807388074880758807688077880788807988080880818808288083880848808588086880878808888089880908809188092880938809488095880968809788098880998810088101881028810388104881058810688107881088810988110881118811288113881148811588116881178811888119881208812188122881238812488125881268812788128881298813088131881328813388134881358813688137881388813988140881418814288143881448814588146881478814888149881508815188152881538815488155881568815788158881598816088161881628816388164881658816688167881688816988170881718817288173881748817588176881778817888179881808818188182881838818488185881868818788188881898819088191881928819388194881958819688197881988819988200882018820288203882048820588206882078820888209882108821188212882138821488215882168821788218882198822088221882228822388224882258822688227882288822988230882318823288233882348823588236882378823888239882408824188242882438824488245882468824788248882498825088251882528825388254882558825688257882588825988260882618826288263882648826588266882678826888269882708827188272882738827488275882768827788278882798828088281882828828388284882858828688287882888828988290882918829288293882948829588296882978829888299883008830188302883038830488305883068830788308883098831088311883128831388314883158831688317883188831988320883218832288323883248832588326883278832888329883308833188332883338833488335883368833788338883398834088341883428834388344883458834688347883488834988350883518835288353883548835588356883578835888359883608836188362883638836488365883668836788368883698837088371883728837388374883758837688377883788837988380883818838288383883848838588386883878838888389883908839188392883938839488395883968839788398883998840088401884028840388404884058840688407884088840988410884118841288413884148841588416884178841888419884208842188422884238842488425884268842788428884298843088431884328843388434884358843688437884388843988440884418844288443884448844588446884478844888449884508845188452884538845488455884568845788458884598846088461884628846388464884658846688467884688846988470884718847288473884748847588476884778847888479884808848188482884838848488485884868848788488884898849088491884928849388494884958849688497884988849988500885018850288503885048850588506885078850888509885108851188512885138851488515885168851788518885198852088521885228852388524885258852688527885288852988530885318853288533885348853588536885378853888539885408854188542885438854488545885468854788548885498855088551885528855388554885558855688557885588855988560885618856288563885648856588566885678856888569885708857188572885738857488575885768857788578885798858088581885828858388584885858858688587885888858988590885918859288593885948859588596885978859888599886008860188602886038860488605886068860788608886098861088611886128861388614886158861688617886188861988620886218862288623886248862588626886278862888629886308863188632886338863488635886368863788638886398864088641886428864388644886458864688647886488864988650886518865288653886548865588656886578865888659886608866188662886638866488665886668866788668886698867088671886728867388674886758867688677886788867988680886818868288683886848868588686886878868888689886908869188692886938869488695886968869788698886998870088701887028870388704887058870688707887088870988710887118871288713887148871588716887178871888719887208872188722887238872488725887268872788728887298873088731887328873388734887358873688737887388873988740887418874288743887448874588746887478874888749887508875188752887538875488755887568875788758887598876088761887628876388764887658876688767887688876988770887718877288773887748877588776887778877888779887808878188782887838878488785887868878788788887898879088791887928879388794887958879688797887988879988800888018880288803888048880588806888078880888809888108881188812888138881488815888168881788818888198882088821888228882388824888258882688827888288882988830888318883288833888348883588836888378883888839888408884188842888438884488845888468884788848888498885088851888528885388854888558885688857888588885988860888618886288863888648886588866888678886888869888708887188872888738887488875888768887788878888798888088881888828888388884888858888688887888888888988890888918889288893888948889588896888978889888899889008890188902889038890488905889068890788908889098891088911889128891388914889158891688917889188891988920889218892288923889248892588926889278892888929889308893188932889338893488935889368893788938889398894088941889428894388944889458894688947889488894988950889518895288953889548895588956889578895888959889608896188962889638896488965889668896788968889698897088971889728897388974889758897688977889788897988980889818898288983889848898588986889878898888989889908899188992889938899488995889968899788998889998900089001890028900389004890058900689007890088900989010890118901289013890148901589016890178901889019890208902189022890238902489025890268902789028890298903089031890328903389034890358903689037890388903989040890418904289043890448904589046890478904889049890508905189052890538905489055890568905789058890598906089061890628906389064890658906689067890688906989070890718907289073890748907589076890778907889079890808908189082890838908489085890868908789088890898909089091890928909389094890958909689097890988909989100891018910289103891048910589106891078910889109891108911189112891138911489115891168911789118891198912089121891228912389124891258912689127891288912989130891318913289133891348913589136891378913889139891408914189142891438914489145891468914789148891498915089151891528915389154891558915689157891588915989160891618916289163891648916589166891678916889169891708917189172891738917489175891768917789178891798918089181891828918389184891858918689187891888918989190891918919289193891948919589196891978919889199892008920189202892038920489205892068920789208892098921089211892128921389214892158921689217892188921989220892218922289223892248922589226892278922889229892308923189232892338923489235892368923789238892398924089241892428924389244892458924689247892488924989250892518925289253892548925589256892578925889259892608926189262892638926489265892668926789268892698927089271892728927389274892758927689277892788927989280892818928289283892848928589286892878928889289892908929189292892938929489295892968929789298892998930089301893028930389304893058930689307893088930989310893118931289313893148931589316893178931889319893208932189322893238932489325893268932789328893298933089331893328933389334893358933689337893388933989340893418934289343893448934589346893478934889349893508935189352893538935489355893568935789358893598936089361893628936389364893658936689367893688936989370893718937289373893748937589376893778937889379893808938189382893838938489385893868938789388893898939089391893928939389394893958939689397893988939989400894018940289403894048940589406894078940889409894108941189412894138941489415894168941789418894198942089421894228942389424894258942689427894288942989430894318943289433894348943589436894378943889439894408944189442894438944489445894468944789448894498945089451894528945389454894558945689457894588945989460894618946289463894648946589466894678946889469894708947189472894738947489475894768947789478894798948089481894828948389484894858948689487894888948989490894918949289493894948949589496894978949889499895008950189502895038950489505895068950789508895098951089511895128951389514895158951689517895188951989520895218952289523895248952589526895278952889529895308953189532895338953489535895368953789538895398954089541895428954389544895458954689547895488954989550895518955289553895548955589556895578955889559895608956189562895638956489565895668956789568895698957089571895728957389574895758957689577895788957989580895818958289583895848958589586895878958889589895908959189592895938959489595895968959789598895998960089601896028960389604896058960689607896088960989610896118961289613896148961589616896178961889619896208962189622896238962489625896268962789628896298963089631896328963389634896358963689637896388963989640896418964289643896448964589646896478964889649896508965189652896538965489655896568965789658896598966089661896628966389664896658966689667896688966989670896718967289673896748967589676896778967889679896808968189682896838968489685896868968789688896898969089691896928969389694896958969689697896988969989700897018970289703897048970589706897078970889709897108971189712897138971489715897168971789718897198972089721897228972389724897258972689727897288972989730897318973289733897348973589736897378973889739897408974189742897438974489745897468974789748897498975089751897528975389754897558975689757897588975989760897618976289763897648976589766897678976889769897708977189772897738977489775897768977789778897798978089781897828978389784897858978689787897888978989790897918979289793897948979589796897978979889799898008980189802898038980489805898068980789808898098981089811898128981389814898158981689817898188981989820898218982289823898248982589826898278982889829898308983189832898338983489835898368983789838898398984089841898428984389844898458984689847898488984989850898518985289853898548985589856898578985889859898608986189862898638986489865898668986789868898698987089871898728987389874898758987689877898788987989880898818988289883898848988589886898878988889889898908989189892898938989489895898968989789898898998990089901899028990389904899058990689907899088990989910899118991289913899148991589916899178991889919899208992189922899238992489925899268992789928899298993089931899328993389934899358993689937899388993989940899418994289943899448994589946899478994889949899508995189952899538995489955899568995789958899598996089961899628996389964899658996689967899688996989970899718997289973899748997589976899778997889979899808998189982899838998489985899868998789988899898999089991899928999389994899958999689997899988999990000900019000290003900049000590006900079000890009900109001190012900139001490015900169001790018900199002090021900229002390024900259002690027900289002990030900319003290033900349003590036900379003890039900409004190042900439004490045900469004790048900499005090051900529005390054900559005690057900589005990060900619006290063900649006590066900679006890069900709007190072900739007490075900769007790078900799008090081900829008390084900859008690087900889008990090900919009290093900949009590096900979009890099901009010190102901039010490105901069010790108901099011090111901129011390114901159011690117901189011990120901219012290123901249012590126901279012890129901309013190132901339013490135901369013790138901399014090141901429014390144901459014690147901489014990150901519015290153901549015590156901579015890159901609016190162901639016490165901669016790168901699017090171901729017390174901759017690177901789017990180901819018290183901849018590186901879018890189901909019190192901939019490195901969019790198901999020090201902029020390204902059020690207902089020990210902119021290213902149021590216902179021890219902209022190222902239022490225902269022790228902299023090231902329023390234902359023690237902389023990240902419024290243902449024590246902479024890249902509025190252902539025490255902569025790258902599026090261902629026390264902659026690267902689026990270902719027290273902749027590276902779027890279902809028190282902839028490285902869028790288902899029090291902929029390294902959029690297902989029990300903019030290303903049030590306903079030890309903109031190312903139031490315903169031790318903199032090321903229032390324903259032690327903289032990330903319033290333903349033590336903379033890339903409034190342903439034490345903469034790348903499035090351903529035390354903559035690357903589035990360903619036290363903649036590366903679036890369903709037190372903739037490375903769037790378903799038090381903829038390384903859038690387903889038990390903919039290393903949039590396903979039890399904009040190402904039040490405904069040790408904099041090411904129041390414904159041690417904189041990420904219042290423904249042590426904279042890429904309043190432904339043490435904369043790438904399044090441904429044390444904459044690447904489044990450904519045290453904549045590456904579045890459904609046190462904639046490465904669046790468904699047090471904729047390474904759047690477904789047990480904819048290483904849048590486904879048890489904909049190492904939049490495904969049790498904999050090501905029050390504905059050690507905089050990510905119051290513905149051590516905179051890519905209052190522905239052490525905269052790528905299053090531905329053390534905359053690537905389053990540905419054290543905449054590546905479054890549905509055190552905539055490555905569055790558905599056090561905629056390564905659056690567905689056990570905719057290573905749057590576905779057890579905809058190582905839058490585905869058790588905899059090591905929059390594905959059690597905989059990600906019060290603906049060590606906079060890609906109061190612906139061490615906169061790618906199062090621906229062390624906259062690627906289062990630906319063290633906349063590636906379063890639906409064190642906439064490645906469064790648906499065090651906529065390654906559065690657906589065990660906619066290663906649066590666906679066890669906709067190672906739067490675906769067790678906799068090681906829068390684906859068690687906889068990690906919069290693906949069590696906979069890699907009070190702907039070490705907069070790708907099071090711907129071390714907159071690717907189071990720907219072290723907249072590726907279072890729907309073190732907339073490735907369073790738907399074090741907429074390744907459074690747907489074990750907519075290753907549075590756907579075890759907609076190762907639076490765907669076790768907699077090771907729077390774907759077690777907789077990780907819078290783907849078590786907879078890789907909079190792907939079490795907969079790798907999080090801908029080390804908059080690807908089080990810908119081290813908149081590816908179081890819908209082190822908239082490825908269082790828908299083090831908329083390834908359083690837908389083990840908419084290843908449084590846908479084890849908509085190852908539085490855908569085790858908599086090861908629086390864908659086690867908689086990870908719087290873908749087590876908779087890879908809088190882908839088490885908869088790888908899089090891908929089390894908959089690897908989089990900909019090290903909049090590906909079090890909909109091190912909139091490915909169091790918909199092090921909229092390924909259092690927909289092990930909319093290933909349093590936909379093890939909409094190942909439094490945909469094790948909499095090951909529095390954909559095690957909589095990960909619096290963909649096590966909679096890969909709097190972909739097490975909769097790978909799098090981909829098390984909859098690987909889098990990909919099290993909949099590996909979099890999910009100191002910039100491005910069100791008910099101091011910129101391014910159101691017910189101991020910219102291023910249102591026910279102891029910309103191032910339103491035910369103791038910399104091041910429104391044910459104691047910489104991050910519105291053910549105591056910579105891059910609106191062910639106491065910669106791068910699107091071910729107391074910759107691077910789107991080910819108291083910849108591086910879108891089910909109191092910939109491095910969109791098910999110091101911029110391104911059110691107911089110991110911119111291113911149111591116911179111891119911209112191122911239112491125911269112791128911299113091131911329113391134911359113691137911389113991140911419114291143911449114591146911479114891149911509115191152911539115491155911569115791158911599116091161911629116391164911659116691167911689116991170911719117291173911749117591176911779117891179911809118191182911839118491185911869118791188911899119091191911929119391194911959119691197911989119991200912019120291203912049120591206912079120891209912109121191212912139121491215912169121791218912199122091221912229122391224912259122691227912289122991230912319123291233912349123591236912379123891239912409124191242912439124491245912469124791248912499125091251912529125391254912559125691257912589125991260912619126291263912649126591266912679126891269912709127191272912739127491275912769127791278912799128091281912829128391284912859128691287912889128991290912919129291293912949129591296912979129891299913009130191302913039130491305913069130791308913099131091311913129131391314913159131691317913189131991320913219132291323913249132591326913279132891329913309133191332913339133491335913369133791338913399134091341913429134391344913459134691347913489134991350913519135291353913549135591356913579135891359913609136191362913639136491365913669136791368913699137091371913729137391374913759137691377913789137991380913819138291383913849138591386913879138891389913909139191392913939139491395913969139791398913999140091401914029140391404914059140691407914089140991410914119141291413914149141591416914179141891419914209142191422914239142491425914269142791428914299143091431914329143391434914359143691437914389143991440914419144291443914449144591446914479144891449914509145191452914539145491455914569145791458914599146091461914629146391464914659146691467914689146991470914719147291473914749147591476914779147891479914809148191482914839148491485914869148791488914899149091491914929149391494914959149691497914989149991500915019150291503915049150591506915079150891509915109151191512915139151491515915169151791518915199152091521915229152391524915259152691527915289152991530915319153291533915349153591536915379153891539915409154191542915439154491545915469154791548915499155091551915529155391554915559155691557915589155991560915619156291563915649156591566915679156891569915709157191572915739157491575915769157791578915799158091581915829158391584915859158691587915889158991590915919159291593915949159591596915979159891599916009160191602916039160491605916069160791608916099161091611916129161391614916159161691617916189161991620916219162291623916249162591626916279162891629916309163191632916339163491635916369163791638916399164091641916429164391644916459164691647916489164991650916519165291653916549165591656916579165891659916609166191662916639166491665916669166791668916699167091671916729167391674916759167691677916789167991680916819168291683916849168591686916879168891689916909169191692916939169491695916969169791698916999170091701917029170391704917059170691707917089170991710917119171291713917149171591716917179171891719917209172191722917239172491725917269172791728917299173091731917329173391734917359173691737917389173991740917419174291743917449174591746917479174891749917509175191752917539175491755917569175791758917599176091761917629176391764917659176691767917689176991770917719177291773917749177591776917779177891779917809178191782917839178491785917869178791788917899179091791917929179391794917959179691797917989179991800918019180291803918049180591806918079180891809918109181191812918139181491815918169181791818918199182091821918229182391824918259182691827918289182991830918319183291833918349183591836918379183891839918409184191842918439184491845918469184791848918499185091851918529185391854918559185691857918589185991860918619186291863918649186591866918679186891869918709187191872918739187491875918769187791878918799188091881918829188391884918859188691887918889188991890918919189291893918949189591896918979189891899919009190191902919039190491905919069190791908919099191091911919129191391914919159191691917919189191991920919219192291923919249192591926919279192891929919309193191932919339193491935919369193791938919399194091941919429194391944919459194691947919489194991950919519195291953919549195591956919579195891959919609196191962919639196491965919669196791968919699197091971919729197391974919759197691977919789197991980919819198291983919849198591986919879198891989919909199191992919939199491995919969199791998919999200092001920029200392004920059200692007920089200992010920119201292013920149201592016920179201892019920209202192022920239202492025920269202792028920299203092031920329203392034920359203692037920389203992040920419204292043920449204592046920479204892049920509205192052920539205492055920569205792058920599206092061920629206392064920659206692067920689206992070920719207292073920749207592076920779207892079920809208192082920839208492085920869208792088920899209092091920929209392094920959209692097920989209992100921019210292103921049210592106921079210892109921109211192112921139211492115921169211792118921199212092121921229212392124921259212692127921289212992130921319213292133921349213592136921379213892139921409214192142921439214492145921469214792148921499215092151921529215392154921559215692157921589215992160921619216292163921649216592166921679216892169921709217192172921739217492175921769217792178921799218092181921829218392184921859218692187921889218992190921919219292193921949219592196921979219892199922009220192202922039220492205922069220792208922099221092211922129221392214922159221692217922189221992220922219222292223922249222592226922279222892229922309223192232922339223492235922369223792238922399224092241922429224392244922459224692247922489224992250922519225292253922549225592256922579225892259922609226192262922639226492265922669226792268922699227092271922729227392274922759227692277922789227992280922819228292283922849228592286922879228892289922909229192292922939229492295922969229792298922999230092301923029230392304923059230692307923089230992310923119231292313923149231592316923179231892319923209232192322923239232492325923269232792328923299233092331923329233392334923359233692337923389233992340923419234292343923449234592346923479234892349923509235192352923539235492355923569235792358923599236092361923629236392364923659236692367923689236992370923719237292373923749237592376923779237892379923809238192382923839238492385923869238792388923899239092391923929239392394923959239692397923989239992400924019240292403924049240592406924079240892409924109241192412924139241492415924169241792418924199242092421924229242392424924259242692427924289242992430924319243292433924349243592436924379243892439924409244192442924439244492445924469244792448924499245092451924529245392454924559245692457924589245992460924619246292463924649246592466924679246892469924709247192472924739247492475924769247792478924799248092481924829248392484924859248692487924889248992490924919249292493924949249592496924979249892499925009250192502925039250492505925069250792508925099251092511925129251392514925159251692517925189251992520925219252292523925249252592526925279252892529925309253192532925339253492535925369253792538925399254092541925429254392544925459254692547925489254992550925519255292553925549255592556925579255892559925609256192562925639256492565925669256792568925699257092571925729257392574925759257692577925789257992580925819258292583925849258592586925879258892589925909259192592925939259492595925969259792598925999260092601926029260392604926059260692607926089260992610926119261292613926149261592616926179261892619926209262192622926239262492625926269262792628926299263092631926329263392634926359263692637926389263992640926419264292643926449264592646926479264892649926509265192652926539265492655926569265792658926599266092661926629266392664926659266692667926689266992670926719267292673926749267592676926779267892679926809268192682926839268492685926869268792688926899269092691926929269392694926959269692697926989269992700927019270292703927049270592706927079270892709927109271192712927139271492715927169271792718927199272092721927229272392724927259272692727927289272992730927319273292733927349273592736927379273892739927409274192742927439274492745927469274792748927499275092751927529275392754927559275692757927589275992760927619276292763927649276592766927679276892769927709277192772927739277492775927769277792778927799278092781927829278392784927859278692787927889278992790927919279292793927949279592796927979279892799928009280192802928039280492805928069280792808928099281092811928129281392814928159281692817928189281992820928219282292823928249282592826928279282892829928309283192832928339283492835928369283792838928399284092841928429284392844928459284692847928489284992850928519285292853928549285592856928579285892859928609286192862928639286492865928669286792868928699287092871928729287392874928759287692877928789287992880928819288292883928849288592886928879288892889928909289192892928939289492895928969289792898928999290092901929029290392904929059290692907929089290992910929119291292913929149291592916929179291892919929209292192922929239292492925929269292792928929299293092931929329293392934929359293692937929389293992940929419294292943929449294592946929479294892949929509295192952929539295492955929569295792958929599296092961929629296392964929659296692967929689296992970929719297292973929749297592976929779297892979929809298192982929839298492985929869298792988929899299092991929929299392994929959299692997929989299993000930019300293003930049300593006930079300893009930109301193012930139301493015930169301793018930199302093021930229302393024930259302693027930289302993030930319303293033930349303593036930379303893039930409304193042930439304493045930469304793048930499305093051930529305393054930559305693057930589305993060930619306293063930649306593066930679306893069930709307193072930739307493075930769307793078930799308093081930829308393084930859308693087930889308993090930919309293093930949309593096930979309893099931009310193102931039310493105931069310793108931099311093111931129311393114931159311693117931189311993120931219312293123931249312593126931279312893129931309313193132931339313493135931369313793138931399314093141931429314393144931459314693147931489314993150931519315293153931549315593156931579315893159931609316193162931639316493165931669316793168931699317093171931729317393174931759317693177931789317993180931819318293183931849318593186931879318893189931909319193192931939319493195931969319793198931999320093201932029320393204932059320693207932089320993210932119321293213932149321593216932179321893219932209322193222932239322493225932269322793228932299323093231932329323393234932359323693237932389323993240932419324293243932449324593246932479324893249932509325193252932539325493255932569325793258932599326093261932629326393264932659326693267932689326993270932719327293273932749327593276932779327893279932809328193282932839328493285932869328793288932899329093291932929329393294932959329693297932989329993300933019330293303933049330593306933079330893309933109331193312933139331493315933169331793318933199332093321933229332393324933259332693327933289332993330933319333293333933349333593336933379333893339933409334193342933439334493345933469334793348933499335093351933529335393354933559335693357933589335993360933619336293363933649336593366933679336893369933709337193372933739337493375933769337793378933799338093381933829338393384933859338693387933889338993390933919339293393933949339593396933979339893399934009340193402934039340493405934069340793408934099341093411934129341393414934159341693417934189341993420934219342293423934249342593426934279342893429934309343193432934339343493435934369343793438934399344093441934429344393444934459344693447934489344993450934519345293453934549345593456934579345893459934609346193462934639346493465934669346793468934699347093471934729347393474934759347693477934789347993480934819348293483934849348593486934879348893489934909349193492934939349493495934969349793498934999350093501935029350393504935059350693507935089350993510935119351293513935149351593516935179351893519935209352193522935239352493525935269352793528935299353093531935329353393534935359353693537935389353993540935419354293543935449354593546935479354893549935509355193552935539355493555935569355793558935599356093561935629356393564935659356693567935689356993570935719357293573935749357593576935779357893579935809358193582935839358493585935869358793588935899359093591935929359393594935959359693597935989359993600936019360293603936049360593606936079360893609936109361193612936139361493615936169361793618936199362093621936229362393624936259362693627936289362993630936319363293633936349363593636936379363893639936409364193642936439364493645936469364793648936499365093651936529365393654936559365693657936589365993660936619366293663936649366593666936679366893669936709367193672936739367493675936769367793678936799368093681936829368393684936859368693687936889368993690936919369293693936949369593696936979369893699937009370193702937039370493705937069370793708937099371093711937129371393714937159371693717937189371993720937219372293723937249372593726937279372893729937309373193732937339373493735937369373793738937399374093741937429374393744937459374693747937489374993750937519375293753937549375593756937579375893759937609376193762937639376493765937669376793768937699377093771937729377393774937759377693777937789377993780937819378293783937849378593786937879378893789937909379193792937939379493795937969379793798937999380093801938029380393804938059380693807938089380993810938119381293813938149381593816938179381893819938209382193822938239382493825938269382793828938299383093831938329383393834938359383693837938389383993840938419384293843938449384593846938479384893849938509385193852938539385493855938569385793858938599386093861938629386393864938659386693867938689386993870938719387293873938749387593876938779387893879938809388193882938839388493885938869388793888938899389093891938929389393894938959389693897938989389993900939019390293903939049390593906939079390893909939109391193912939139391493915939169391793918939199392093921939229392393924939259392693927939289392993930939319393293933939349393593936939379393893939939409394193942939439394493945939469394793948939499395093951939529395393954939559395693957939589395993960939619396293963939649396593966939679396893969939709397193972939739397493975939769397793978939799398093981939829398393984939859398693987939889398993990939919399293993939949399593996939979399893999940009400194002940039400494005940069400794008940099401094011940129401394014940159401694017940189401994020940219402294023940249402594026940279402894029940309403194032940339403494035940369403794038940399404094041940429404394044940459404694047940489404994050940519405294053940549405594056940579405894059940609406194062940639406494065940669406794068940699407094071940729407394074940759407694077940789407994080940819408294083940849408594086940879408894089940909409194092940939409494095940969409794098940999410094101941029410394104941059410694107941089410994110941119411294113941149411594116941179411894119941209412194122941239412494125941269412794128941299413094131941329413394134941359413694137941389413994140941419414294143941449414594146941479414894149941509415194152941539415494155941569415794158941599416094161941629416394164941659416694167941689416994170941719417294173941749417594176941779417894179941809418194182941839418494185941869418794188941899419094191941929419394194941959419694197941989419994200942019420294203942049420594206942079420894209942109421194212942139421494215942169421794218942199422094221942229422394224942259422694227942289422994230942319423294233942349423594236942379423894239942409424194242942439424494245942469424794248942499425094251942529425394254942559425694257942589425994260942619426294263942649426594266942679426894269942709427194272942739427494275942769427794278942799428094281942829428394284942859428694287942889428994290942919429294293942949429594296942979429894299943009430194302943039430494305943069430794308943099431094311943129431394314943159431694317943189431994320943219432294323943249432594326943279432894329943309433194332943339433494335943369433794338943399434094341943429434394344943459434694347943489434994350943519435294353943549435594356943579435894359943609436194362943639436494365943669436794368943699437094371943729437394374943759437694377943789437994380943819438294383943849438594386943879438894389943909439194392943939439494395943969439794398943999440094401944029440394404944059440694407944089440994410944119441294413944149441594416944179441894419944209442194422944239442494425944269442794428944299443094431944329443394434944359443694437944389443994440944419444294443944449444594446944479444894449944509445194452944539445494455944569445794458944599446094461944629446394464944659446694467944689446994470944719447294473944749447594476944779447894479944809448194482944839448494485944869448794488944899449094491944929449394494944959449694497944989449994500945019450294503945049450594506945079450894509945109451194512945139451494515945169451794518945199452094521945229452394524945259452694527945289452994530945319453294533945349453594536945379453894539945409454194542945439454494545945469454794548945499455094551945529455394554945559455694557945589455994560945619456294563945649456594566945679456894569945709457194572945739457494575945769457794578945799458094581945829458394584945859458694587945889458994590945919459294593945949459594596945979459894599946009460194602946039460494605946069460794608946099461094611946129461394614946159461694617946189461994620946219462294623946249462594626946279462894629946309463194632946339463494635946369463794638946399464094641946429464394644946459464694647946489464994650946519465294653946549465594656946579465894659946609466194662946639466494665946669466794668946699467094671946729467394674946759467694677946789467994680946819468294683946849468594686946879468894689946909469194692946939469494695946969469794698946999470094701947029470394704947059470694707947089470994710947119471294713947149471594716947179471894719947209472194722947239472494725947269472794728947299473094731947329473394734947359473694737947389473994740947419474294743947449474594746947479474894749947509475194752947539475494755947569475794758947599476094761947629476394764947659476694767947689476994770947719477294773947749477594776947779477894779947809478194782947839478494785947869478794788947899479094791947929479394794947959479694797947989479994800948019480294803948049480594806948079480894809948109481194812948139481494815948169481794818948199482094821948229482394824948259482694827948289482994830948319483294833948349483594836948379483894839948409484194842948439484494845948469484794848948499485094851948529485394854948559485694857948589485994860948619486294863948649486594866948679486894869948709487194872948739487494875948769487794878948799488094881948829488394884948859488694887948889488994890948919489294893948949489594896948979489894899949009490194902949039490494905949069490794908949099491094911949129491394914949159491694917949189491994920949219492294923949249492594926949279492894929949309493194932949339493494935949369493794938949399494094941949429494394944949459494694947949489494994950949519495294953949549495594956949579495894959949609496194962949639496494965949669496794968949699497094971949729497394974949759497694977949789497994980949819498294983949849498594986949879498894989949909499194992949939499494995949969499794998949999500095001950029500395004950059500695007950089500995010950119501295013950149501595016950179501895019950209502195022950239502495025950269502795028950299503095031950329503395034950359503695037950389503995040950419504295043950449504595046950479504895049950509505195052950539505495055950569505795058950599506095061950629506395064950659506695067950689506995070950719507295073950749507595076950779507895079950809508195082950839508495085950869508795088950899509095091950929509395094950959509695097950989509995100951019510295103951049510595106951079510895109951109511195112951139511495115951169511795118951199512095121951229512395124951259512695127951289512995130951319513295133951349513595136951379513895139951409514195142951439514495145951469514795148951499515095151951529515395154951559515695157951589515995160951619516295163951649516595166951679516895169951709517195172951739517495175951769517795178951799518095181951829518395184951859518695187951889518995190951919519295193951949519595196951979519895199952009520195202952039520495205952069520795208952099521095211952129521395214952159521695217952189521995220952219522295223952249522595226952279522895229952309523195232952339523495235952369523795238952399524095241952429524395244952459524695247952489524995250952519525295253952549525595256952579525895259952609526195262952639526495265952669526795268952699527095271952729527395274952759527695277952789527995280952819528295283952849528595286952879528895289952909529195292952939529495295952969529795298952999530095301953029530395304953059530695307953089530995310953119531295313953149531595316953179531895319953209532195322953239532495325953269532795328953299533095331953329533395334953359533695337953389533995340953419534295343953449534595346953479534895349953509535195352953539535495355953569535795358953599536095361953629536395364953659536695367953689536995370953719537295373953749537595376953779537895379953809538195382953839538495385953869538795388953899539095391953929539395394953959539695397953989539995400954019540295403954049540595406954079540895409954109541195412954139541495415954169541795418954199542095421954229542395424954259542695427954289542995430954319543295433954349543595436954379543895439954409544195442954439544495445954469544795448954499545095451954529545395454954559545695457954589545995460954619546295463954649546595466954679546895469954709547195472954739547495475954769547795478954799548095481954829548395484954859548695487954889548995490954919549295493954949549595496954979549895499955009550195502955039550495505955069550795508955099551095511955129551395514955159551695517955189551995520955219552295523955249552595526955279552895529955309553195532955339553495535955369553795538955399554095541955429554395544955459554695547955489554995550955519555295553955549555595556955579555895559955609556195562955639556495565955669556795568955699557095571955729557395574955759557695577955789557995580955819558295583955849558595586955879558895589955909559195592955939559495595955969559795598955999560095601956029560395604956059560695607956089560995610956119561295613956149561595616956179561895619956209562195622956239562495625956269562795628956299563095631956329563395634956359563695637956389563995640956419564295643956449564595646956479564895649956509565195652956539565495655956569565795658956599566095661956629566395664956659566695667956689566995670956719567295673956749567595676956779567895679956809568195682956839568495685956869568795688956899569095691956929569395694956959569695697956989569995700957019570295703957049570595706957079570895709957109571195712957139571495715957169571795718957199572095721957229572395724957259572695727957289572995730957319573295733957349573595736957379573895739957409574195742957439574495745957469574795748957499575095751957529575395754957559575695757957589575995760957619576295763957649576595766957679576895769957709577195772957739577495775957769577795778957799578095781957829578395784957859578695787957889578995790957919579295793957949579595796957979579895799958009580195802958039580495805958069580795808958099581095811958129581395814958159581695817958189581995820958219582295823958249582595826958279582895829958309583195832958339583495835958369583795838958399584095841958429584395844958459584695847958489584995850958519585295853958549585595856958579585895859958609586195862958639586495865958669586795868958699587095871958729587395874958759587695877958789587995880958819588295883958849588595886958879588895889958909589195892958939589495895958969589795898958999590095901959029590395904959059590695907959089590995910959119591295913959149591595916959179591895919959209592195922959239592495925959269592795928959299593095931959329593395934959359593695937959389593995940959419594295943959449594595946959479594895949959509595195952959539595495955959569595795958959599596095961959629596395964959659596695967959689596995970959719597295973959749597595976959779597895979959809598195982959839598495985959869598795988959899599095991959929599395994959959599695997959989599996000960019600296003960049600596006960079600896009960109601196012960139601496015960169601796018960199602096021960229602396024960259602696027960289602996030960319603296033960349603596036960379603896039960409604196042960439604496045960469604796048960499605096051960529605396054960559605696057960589605996060960619606296063960649606596066960679606896069960709607196072960739607496075960769607796078960799608096081960829608396084960859608696087960889608996090960919609296093960949609596096960979609896099961009610196102961039610496105961069610796108961099611096111961129611396114961159611696117961189611996120961219612296123961249612596126961279612896129961309613196132961339613496135961369613796138961399614096141961429614396144961459614696147961489614996150961519615296153961549615596156961579615896159961609616196162961639616496165961669616796168961699617096171961729617396174961759617696177961789617996180961819618296183961849618596186961879618896189961909619196192961939619496195961969619796198961999620096201962029620396204962059620696207962089620996210962119621296213962149621596216962179621896219962209622196222962239622496225962269622796228962299623096231962329623396234962359623696237962389623996240962419624296243962449624596246962479624896249962509625196252962539625496255962569625796258962599626096261962629626396264962659626696267962689626996270962719627296273962749627596276962779627896279962809628196282962839628496285962869628796288962899629096291962929629396294962959629696297962989629996300963019630296303963049630596306963079630896309963109631196312963139631496315963169631796318963199632096321963229632396324963259632696327963289632996330963319633296333963349633596336963379633896339963409634196342963439634496345963469634796348963499635096351963529635396354963559635696357963589635996360963619636296363963649636596366963679636896369963709637196372963739637496375963769637796378963799638096381963829638396384963859638696387963889638996390963919639296393963949639596396963979639896399964009640196402964039640496405964069640796408964099641096411964129641396414964159641696417964189641996420964219642296423964249642596426964279642896429964309643196432964339643496435964369643796438964399644096441964429644396444964459644696447964489644996450964519645296453964549645596456964579645896459964609646196462964639646496465964669646796468964699647096471964729647396474964759647696477964789647996480964819648296483964849648596486964879648896489964909649196492964939649496495964969649796498964999650096501965029650396504965059650696507965089650996510965119651296513965149651596516965179651896519965209652196522965239652496525965269652796528965299653096531965329653396534965359653696537965389653996540965419654296543965449654596546965479654896549965509655196552965539655496555965569655796558965599656096561965629656396564965659656696567965689656996570965719657296573965749657596576965779657896579965809658196582965839658496585965869658796588965899659096591965929659396594965959659696597965989659996600966019660296603966049660596606966079660896609966109661196612966139661496615966169661796618966199662096621966229662396624966259662696627966289662996630966319663296633966349663596636966379663896639966409664196642966439664496645966469664796648966499665096651966529665396654966559665696657966589665996660966619666296663966649666596666966679666896669966709667196672966739667496675966769667796678966799668096681966829668396684966859668696687966889668996690966919669296693966949669596696966979669896699967009670196702967039670496705967069670796708967099671096711967129671396714967159671696717967189671996720967219672296723967249672596726967279672896729967309673196732967339673496735967369673796738967399674096741967429674396744967459674696747967489674996750967519675296753967549675596756967579675896759967609676196762967639676496765967669676796768967699677096771967729677396774967759677696777967789677996780967819678296783967849678596786967879678896789967909679196792967939679496795967969679796798967999680096801968029680396804968059680696807968089680996810968119681296813968149681596816968179681896819968209682196822968239682496825968269682796828968299683096831968329683396834968359683696837968389683996840968419684296843968449684596846968479684896849968509685196852968539685496855968569685796858968599686096861968629686396864968659686696867968689686996870968719687296873968749687596876968779687896879968809688196882968839688496885968869688796888968899689096891968929689396894968959689696897968989689996900969019690296903969049690596906969079690896909969109691196912969139691496915969169691796918969199692096921969229692396924969259692696927969289692996930969319693296933969349693596936969379693896939969409694196942969439694496945969469694796948969499695096951969529695396954969559695696957969589695996960969619696296963969649696596966969679696896969969709697196972969739697496975969769697796978969799698096981969829698396984969859698696987969889698996990969919699296993969949699596996969979699896999970009700197002970039700497005970069700797008970099701097011970129701397014970159701697017970189701997020970219702297023970249702597026970279702897029970309703197032970339703497035970369703797038970399704097041970429704397044970459704697047970489704997050970519705297053970549705597056970579705897059970609706197062970639706497065970669706797068970699707097071970729707397074970759707697077970789707997080970819708297083970849708597086970879708897089970909709197092970939709497095970969709797098970999710097101971029710397104971059710697107971089710997110971119711297113971149711597116971179711897119971209712197122971239712497125971269712797128971299713097131971329713397134971359713697137971389713997140971419714297143971449714597146971479714897149971509715197152971539715497155971569715797158971599716097161971629716397164971659716697167971689716997170971719717297173971749717597176971779717897179971809718197182971839718497185971869718797188971899719097191971929719397194971959719697197971989719997200972019720297203972049720597206972079720897209972109721197212972139721497215972169721797218972199722097221972229722397224972259722697227972289722997230972319723297233972349723597236972379723897239972409724197242972439724497245972469724797248972499725097251972529725397254972559725697257972589725997260972619726297263972649726597266972679726897269972709727197272972739727497275972769727797278972799728097281972829728397284972859728697287972889728997290972919729297293972949729597296972979729897299973009730197302973039730497305973069730797308973099731097311973129731397314973159731697317973189731997320973219732297323973249732597326973279732897329973309733197332973339733497335973369733797338973399734097341973429734397344973459734697347973489734997350973519735297353973549735597356973579735897359973609736197362973639736497365973669736797368973699737097371973729737397374973759737697377973789737997380973819738297383973849738597386973879738897389973909739197392973939739497395973969739797398973999740097401974029740397404974059740697407974089740997410974119741297413974149741597416974179741897419974209742197422974239742497425974269742797428974299743097431974329743397434974359743697437974389743997440974419744297443974449744597446974479744897449974509745197452974539745497455974569745797458974599746097461974629746397464974659746697467974689746997470974719747297473974749747597476974779747897479974809748197482974839748497485974869748797488974899749097491974929749397494974959749697497974989749997500975019750297503975049750597506975079750897509975109751197512975139751497515975169751797518975199752097521975229752397524975259752697527975289752997530975319753297533975349753597536975379753897539975409754197542975439754497545975469754797548975499755097551975529755397554975559755697557975589755997560975619756297563975649756597566975679756897569975709757197572975739757497575975769757797578975799758097581975829758397584975859758697587975889758997590975919759297593975949759597596975979759897599976009760197602976039760497605976069760797608976099761097611976129761397614976159761697617976189761997620976219762297623976249762597626976279762897629976309763197632976339763497635976369763797638976399764097641976429764397644976459764697647976489764997650976519765297653976549765597656976579765897659976609766197662976639766497665976669766797668976699767097671976729767397674976759767697677976789767997680976819768297683976849768597686976879768897689976909769197692976939769497695976969769797698976999770097701977029770397704977059770697707977089770997710977119771297713977149771597716977179771897719977209772197722977239772497725977269772797728977299773097731977329773397734977359773697737977389773997740977419774297743977449774597746977479774897749977509775197752977539775497755977569775797758977599776097761977629776397764977659776697767977689776997770977719777297773977749777597776977779777897779977809778197782977839778497785977869778797788977899779097791977929779397794977959779697797977989779997800978019780297803978049780597806978079780897809978109781197812978139781497815978169781797818978199782097821978229782397824978259782697827978289782997830978319783297833978349783597836978379783897839978409784197842978439784497845978469784797848978499785097851978529785397854978559785697857978589785997860978619786297863978649786597866978679786897869978709787197872978739787497875978769787797878978799788097881978829788397884978859788697887978889788997890978919789297893978949789597896978979789897899979009790197902979039790497905979069790797908979099791097911979129791397914979159791697917979189791997920979219792297923979249792597926979279792897929979309793197932979339793497935979369793797938979399794097941979429794397944979459794697947979489794997950979519795297953979549795597956979579795897959979609796197962979639796497965979669796797968979699797097971979729797397974979759797697977979789797997980979819798297983979849798597986979879798897989979909799197992979939799497995979969799797998979999800098001980029800398004980059800698007980089800998010980119801298013980149801598016980179801898019980209802198022980239802498025980269802798028980299803098031980329803398034980359803698037980389803998040980419804298043980449804598046980479804898049980509805198052980539805498055980569805798058980599806098061980629806398064980659806698067980689806998070980719807298073980749807598076980779807898079980809808198082980839808498085980869808798088980899809098091980929809398094980959809698097980989809998100981019810298103981049810598106981079810898109981109811198112981139811498115981169811798118981199812098121981229812398124981259812698127981289812998130981319813298133981349813598136981379813898139981409814198142981439814498145981469814798148981499815098151981529815398154981559815698157981589815998160981619816298163981649816598166981679816898169981709817198172981739817498175981769817798178981799818098181981829818398184981859818698187981889818998190981919819298193981949819598196981979819898199982009820198202982039820498205982069820798208982099821098211982129821398214982159821698217982189821998220982219822298223982249822598226982279822898229982309823198232982339823498235982369823798238982399824098241982429824398244982459824698247982489824998250982519825298253982549825598256982579825898259982609826198262982639826498265982669826798268982699827098271982729827398274982759827698277982789827998280982819828298283982849828598286982879828898289982909829198292982939829498295982969829798298982999830098301983029830398304983059830698307983089830998310983119831298313983149831598316983179831898319983209832198322983239832498325983269832798328983299833098331983329833398334983359833698337983389833998340983419834298343983449834598346983479834898349983509835198352983539835498355983569835798358983599836098361983629836398364983659836698367983689836998370983719837298373983749837598376983779837898379983809838198382983839838498385983869838798388983899839098391983929839398394983959839698397983989839998400984019840298403984049840598406984079840898409984109841198412984139841498415984169841798418984199842098421984229842398424984259842698427984289842998430984319843298433984349843598436984379843898439984409844198442984439844498445984469844798448984499845098451984529845398454984559845698457984589845998460984619846298463984649846598466984679846898469984709847198472984739847498475984769847798478984799848098481984829848398484984859848698487984889848998490984919849298493984949849598496984979849898499985009850198502985039850498505985069850798508985099851098511985129851398514985159851698517985189851998520985219852298523985249852598526985279852898529985309853198532985339853498535985369853798538985399854098541985429854398544985459854698547985489854998550985519855298553985549855598556985579855898559985609856198562985639856498565985669856798568985699857098571985729857398574985759857698577985789857998580985819858298583985849858598586985879858898589985909859198592985939859498595985969859798598985999860098601986029860398604986059860698607986089860998610986119861298613986149861598616986179861898619986209862198622986239862498625986269862798628986299863098631986329863398634986359863698637986389863998640986419864298643986449864598646986479864898649986509865198652986539865498655986569865798658986599866098661986629866398664986659866698667986689866998670986719867298673986749867598676986779867898679986809868198682986839868498685986869868798688986899869098691986929869398694986959869698697986989869998700987019870298703987049870598706987079870898709987109871198712987139871498715987169871798718987199872098721987229872398724987259872698727987289872998730987319873298733987349873598736987379873898739987409874198742987439874498745987469874798748987499875098751987529875398754987559875698757987589875998760987619876298763987649876598766987679876898769987709877198772987739877498775987769877798778987799878098781987829878398784987859878698787987889878998790987919879298793987949879598796987979879898799988009880198802988039880498805988069880798808988099881098811988129881398814988159881698817988189881998820988219882298823988249882598826988279882898829988309883198832988339883498835988369883798838988399884098841988429884398844988459884698847988489884998850988519885298853988549885598856988579885898859988609886198862988639886498865988669886798868988699887098871988729887398874988759887698877988789887998880988819888298883988849888598886988879888898889988909889198892988939889498895988969889798898988999890098901989029890398904989059890698907989089890998910989119891298913989149891598916989179891898919989209892198922989239892498925989269892798928989299893098931989329893398934989359893698937989389893998940989419894298943989449894598946989479894898949989509895198952989539895498955989569895798958989599896098961989629896398964989659896698967989689896998970989719897298973989749897598976989779897898979989809898198982989839898498985989869898798988989899899098991989929899398994989959899698997989989899999000990019900299003990049900599006990079900899009990109901199012990139901499015990169901799018990199902099021990229902399024990259902699027990289902999030990319903299033990349903599036990379903899039990409904199042990439904499045990469904799048990499905099051990529905399054990559905699057990589905999060990619906299063990649906599066990679906899069990709907199072990739907499075990769907799078990799908099081990829908399084990859908699087990889908999090990919909299093990949909599096990979909899099991009910199102991039910499105991069910799108991099911099111991129911399114991159911699117991189911999120991219912299123991249912599126991279912899129991309913199132991339913499135991369913799138991399914099141991429914399144991459914699147991489914999150991519915299153991549915599156991579915899159991609916199162991639916499165991669916799168991699917099171991729917399174991759917699177991789917999180991819918299183991849918599186991879918899189991909919199192991939919499195991969919799198991999920099201992029920399204992059920699207992089920999210992119921299213992149921599216992179921899219992209922199222992239922499225992269922799228992299923099231992329923399234992359923699237992389923999240992419924299243992449924599246992479924899249992509925199252992539925499255992569925799258992599926099261992629926399264992659926699267992689926999270992719927299273992749927599276992779927899279992809928199282992839928499285992869928799288992899929099291992929929399294992959929699297992989929999300993019930299303993049930599306993079930899309993109931199312993139931499315993169931799318993199932099321993229932399324993259932699327993289932999330993319933299333993349933599336993379933899339993409934199342993439934499345993469934799348993499935099351993529935399354993559935699357993589935999360993619936299363993649936599366993679936899369993709937199372993739937499375993769937799378993799938099381993829938399384993859938699387993889938999390993919939299393993949939599396993979939899399994009940199402994039940499405994069940799408994099941099411994129941399414994159941699417994189941999420994219942299423994249942599426994279942899429994309943199432994339943499435994369943799438994399944099441994429944399444994459944699447994489944999450994519945299453994549945599456994579945899459994609946199462994639946499465994669946799468994699947099471994729947399474994759947699477994789947999480994819948299483994849948599486994879948899489994909949199492994939949499495994969949799498994999950099501995029950399504995059950699507995089950999510995119951299513995149951599516995179951899519995209952199522995239952499525995269952799528995299953099531995329953399534995359953699537995389953999540995419954299543995449954599546995479954899549995509955199552995539955499555995569955799558995599956099561995629956399564995659956699567995689956999570995719957299573995749957599576995779957899579995809958199582995839958499585995869958799588995899959099591995929959399594995959959699597995989959999600996019960299603996049960599606996079960899609996109961199612996139961499615996169961799618996199962099621996229962399624996259962699627996289962999630996319963299633996349963599636996379963899639996409964199642996439964499645996469964799648996499965099651996529965399654996559965699657996589965999660996619966299663996649966599666996679966899669996709967199672996739967499675996769967799678996799968099681996829968399684996859968699687996889968999690996919969299693996949969599696996979969899699997009970199702997039970499705997069970799708997099971099711997129971399714997159971699717997189971999720997219972299723997249972599726997279972899729997309973199732997339973499735997369973799738997399974099741997429974399744997459974699747997489974999750997519975299753997549975599756997579975899759997609976199762997639976499765997669976799768997699977099771997729977399774997759977699777997789977999780997819978299783997849978599786997879978899789997909979199792997939979499795997969979799798997999980099801998029980399804998059980699807998089980999810998119981299813998149981599816998179981899819998209982199822998239982499825998269982799828998299983099831998329983399834998359983699837998389983999840998419984299843998449984599846998479984899849998509985199852998539985499855998569985799858998599986099861998629986399864998659986699867998689986999870998719987299873998749987599876998779987899879998809988199882998839988499885998869988799888998899989099891998929989399894998959989699897998989989999900999019990299903999049990599906999079990899909999109991199912999139991499915999169991799918999199992099921999229992399924999259992699927999289992999930999319993299933999349993599936999379993899939999409994199942999439994499945999469994799948999499995099951999529995399954999559995699957999589995999960999619996299963999649996599966999679996899969999709997199972999739997499975999769997799978999799998099981999829998399984999859998699987999889998999990999919999299993999949999599996999979999899999100000100001100002100003100004100005100006100007100008100009100010100011100012100013100014100015100016100017100018100019100020100021100022100023100024100025100026100027100028100029100030100031100032100033100034100035100036100037100038100039100040100041100042100043100044100045100046100047100048100049100050100051100052100053100054100055100056100057100058100059100060100061100062100063100064100065100066100067100068100069100070100071100072100073100074100075100076100077100078100079100080100081100082100083100084100085100086100087100088100089100090100091100092100093100094100095100096100097100098100099100100100101100102100103100104100105100106100107100108100109100110100111100112100113100114100115100116100117100118100119100120100121100122100123100124100125100126100127100128100129100130100131100132100133100134100135100136100137100138100139100140100141100142100143100144100145100146100147100148100149100150100151100152100153100154100155100156100157100158100159100160100161100162100163100164100165100166100167100168100169100170100171100172100173100174100175100176100177100178100179100180100181100182100183100184100185100186100187100188100189100190100191100192100193100194100195100196100197100198100199100200100201100202100203100204100205100206100207100208100209100210100211100212100213100214100215100216100217100218100219100220100221100222100223100224100225100226100227100228100229100230100231100232100233100234100235100236100237100238100239100240100241100242100243100244100245100246100247100248100249100250100251100252100253100254100255100256100257100258100259100260100261100262100263100264100265100266100267100268100269100270100271100272100273100274100275100276100277100278100279100280100281100282100283100284100285100286100287100288100289100290100291100292100293100294100295100296100297100298100299100300100301100302100303100304100305100306100307100308100309100310100311100312100313100314100315100316100317100318100319100320100321100322100323100324100325100326100327100328100329100330100331100332100333100334100335100336100337100338100339100340100341100342100343100344100345100346100347100348100349100350100351100352100353100354100355100356100357100358100359100360100361100362100363100364100365100366100367100368100369100370100371100372100373100374100375100376100377100378100379100380100381100382100383100384100385100386100387100388100389100390100391100392100393100394100395100396100397100398100399100400100401100402100403100404100405100406100407100408100409100410100411100412100413100414100415100416100417100418100419100420100421100422100423100424100425100426100427100428100429100430100431100432100433100434100435100436100437100438100439100440100441100442100443100444100445100446100447100448100449100450100451100452100453100454100455100456100457100458100459100460100461100462100463100464100465100466100467100468100469100470100471100472100473100474100475100476100477100478100479100480100481100482100483100484100485100486100487100488100489100490100491100492100493100494100495100496100497100498100499100500100501100502100503100504100505100506100507100508100509100510100511100512100513100514100515100516100517100518100519100520100521100522100523100524100525100526100527100528100529100530100531100532100533100534100535100536100537100538100539100540100541100542100543100544100545100546100547100548100549100550100551100552100553100554100555100556100557100558100559100560100561100562100563100564100565100566100567100568100569100570100571100572100573100574100575100576100577100578100579100580100581100582100583100584100585100586100587100588100589100590100591100592100593100594100595100596100597100598100599100600100601100602100603100604100605100606100607100608100609100610100611100612100613100614100615100616100617100618100619100620100621100622100623100624100625100626100627100628100629100630100631100632100633100634100635100636100637100638100639100640100641100642100643100644100645100646100647100648100649100650100651100652100653100654100655100656100657100658100659100660100661100662100663100664100665100666100667100668100669100670100671100672100673100674100675100676100677100678100679100680100681100682100683100684100685100686100687100688100689100690100691100692100693100694100695100696100697100698100699100700100701100702100703100704100705100706100707100708100709100710100711100712100713100714100715100716100717100718100719100720100721100722100723100724100725100726100727100728100729100730100731100732100733100734100735100736100737100738100739100740100741100742100743100744100745100746100747100748100749100750100751100752100753100754100755100756100757100758100759100760100761100762100763100764100765100766100767100768100769100770100771100772100773100774100775100776100777100778100779100780100781100782100783100784100785100786100787100788100789100790100791100792100793100794100795100796100797100798100799100800100801100802100803100804100805100806100807100808100809100810100811100812100813100814100815100816100817100818100819100820100821100822100823100824100825100826100827100828100829100830100831100832100833100834100835100836100837100838100839100840100841100842100843100844100845100846100847100848100849100850100851100852100853100854100855100856100857100858100859100860100861100862100863100864100865100866100867100868100869100870100871100872100873100874100875100876100877100878100879100880100881100882100883100884100885100886100887100888100889100890100891100892100893100894100895100896100897100898100899100900100901100902100903100904100905100906100907100908100909100910100911100912100913100914100915100916100917100918100919100920100921100922100923100924100925100926100927100928100929100930100931100932100933100934100935100936100937100938100939100940100941100942100943100944100945100946100947100948100949100950100951100952100953100954100955100956100957100958100959100960100961100962100963100964100965100966100967100968100969100970100971100972100973100974100975100976100977100978100979100980100981100982100983100984100985100986100987100988100989100990100991100992100993100994100995100996100997100998100999101000101001101002101003101004101005101006101007101008101009101010101011101012101013101014101015101016101017101018101019101020101021101022101023101024101025101026101027101028101029101030101031101032101033101034101035101036101037101038101039101040101041101042101043101044101045101046101047101048101049101050101051101052101053101054101055101056101057101058101059101060101061101062101063101064101065101066101067101068101069101070101071101072101073101074101075101076101077101078101079101080101081101082101083101084101085101086101087101088101089101090101091101092101093101094101095101096101097101098101099101100101101101102101103101104101105101106101107101108101109101110101111101112101113101114101115101116101117101118101119101120101121101122101123101124101125101126101127101128101129101130101131101132101133101134101135101136101137101138101139101140101141101142101143101144101145101146101147101148101149101150101151101152101153101154101155101156101157101158101159101160101161101162101163101164101165101166101167101168101169101170101171101172101173101174101175101176101177101178101179101180101181101182101183101184101185101186101187101188101189101190101191101192101193101194101195101196101197101198101199101200101201101202101203101204101205101206101207101208101209101210101211101212101213101214101215101216101217101218101219101220101221101222101223101224101225101226101227101228101229101230101231101232101233101234101235101236101237101238101239101240101241101242101243101244101245101246101247101248101249101250101251101252101253101254101255101256101257101258101259101260101261101262101263101264101265101266101267101268101269101270101271101272101273101274101275101276101277101278101279101280101281101282101283101284101285101286101287101288101289101290101291101292101293101294101295101296101297101298101299101300101301101302101303101304101305101306101307101308101309101310101311101312101313101314101315101316101317101318101319101320101321101322101323101324101325101326101327101328101329101330101331101332101333101334101335101336101337101338101339101340101341101342101343101344101345101346101347101348101349101350101351101352101353101354101355101356101357101358101359101360101361101362101363101364101365101366101367101368101369101370101371101372101373101374101375101376101377101378101379101380101381101382101383101384101385101386101387101388101389101390101391101392101393101394101395101396101397101398101399101400101401101402101403101404101405101406101407101408101409101410101411101412101413101414101415101416101417101418101419101420101421101422101423101424101425101426101427101428101429101430101431101432101433101434101435101436101437101438101439101440101441101442101443101444101445101446101447101448101449101450101451101452101453101454101455101456101457101458101459101460101461101462101463101464101465101466101467101468101469101470101471101472101473101474101475101476101477101478101479101480101481101482101483101484101485101486101487101488101489101490101491101492101493101494101495101496101497101498101499101500101501101502101503101504101505101506101507101508101509101510101511101512101513101514101515101516101517101518101519101520101521101522101523101524101525101526101527101528101529101530101531101532101533101534101535101536101537101538101539101540101541101542101543101544101545101546101547101548101549101550101551101552101553101554101555101556101557101558101559101560101561101562101563101564101565101566101567101568101569101570101571101572101573101574101575101576101577101578101579101580101581101582101583101584101585101586101587101588101589101590101591101592101593101594101595101596101597101598101599101600101601101602101603101604101605101606101607101608101609101610101611101612101613101614101615101616101617101618101619101620101621101622101623101624101625101626101627101628101629101630101631101632101633101634101635101636101637101638101639101640101641101642101643101644101645101646101647101648101649101650101651101652101653101654101655101656101657101658101659101660101661101662101663101664101665101666101667101668101669101670101671101672101673101674101675101676101677101678101679101680101681101682101683101684101685101686101687101688101689101690101691101692101693101694101695101696101697101698101699101700101701101702101703101704101705101706101707101708101709101710101711101712101713101714101715101716101717101718101719101720101721101722101723101724101725101726101727101728101729101730101731101732101733101734101735101736101737101738101739101740101741101742101743101744101745101746101747101748101749101750101751101752101753101754101755101756101757101758101759101760101761101762101763101764101765101766101767101768101769101770101771101772101773101774101775101776101777101778101779101780101781101782101783101784101785101786101787101788101789101790101791101792101793101794101795101796101797101798101799101800101801101802101803101804101805101806101807101808101809101810101811101812101813101814101815101816101817101818101819101820101821101822101823101824101825101826101827101828101829101830101831101832101833101834101835101836101837101838101839101840101841101842101843101844101845101846101847101848101849101850101851101852101853101854101855101856101857101858101859101860101861101862101863101864101865101866101867101868101869101870101871101872101873101874101875101876101877101878101879101880101881101882101883101884101885101886101887101888101889101890101891101892101893101894101895101896101897101898101899101900101901101902101903101904101905101906101907101908101909101910101911101912101913101914101915101916101917101918101919101920101921101922101923101924101925101926101927101928101929101930101931101932101933101934101935101936101937101938101939101940101941101942101943101944101945101946101947101948101949101950101951101952101953101954101955101956101957101958101959101960101961101962101963101964101965101966101967101968101969101970101971101972101973101974101975101976101977101978101979101980101981101982101983101984101985101986101987101988101989101990101991101992101993101994101995101996101997101998101999102000102001102002102003102004102005102006102007102008102009102010102011102012102013102014102015102016102017102018102019102020102021102022102023102024102025102026102027102028102029102030102031102032102033102034102035102036102037102038102039102040102041102042102043102044102045102046102047102048102049102050102051102052102053102054102055102056102057102058102059102060102061102062102063102064102065102066102067102068102069102070102071102072102073102074102075102076102077102078102079102080102081102082102083102084102085102086102087102088102089102090102091102092102093102094102095102096102097102098102099102100102101102102102103102104102105102106102107102108102109102110102111102112102113102114102115102116102117102118102119102120102121102122102123102124102125102126102127102128102129102130102131102132102133102134102135102136102137102138102139102140102141102142102143102144102145102146102147102148102149102150102151102152102153102154102155102156102157102158102159102160102161102162102163102164102165102166102167102168102169102170102171102172102173102174102175102176102177102178102179102180102181102182102183102184102185102186102187102188102189102190102191102192102193102194102195102196102197102198102199102200102201102202102203102204102205102206102207102208102209102210102211102212102213102214102215102216102217102218102219102220102221102222102223102224102225102226102227102228102229102230102231102232102233102234102235102236102237102238102239102240102241102242102243102244102245102246102247102248102249102250102251102252102253102254102255102256102257102258102259102260102261102262102263102264102265102266102267102268102269102270102271102272102273102274102275102276102277102278102279102280102281102282102283102284102285102286102287102288102289102290102291102292102293102294102295102296102297102298102299102300102301102302102303102304102305102306102307102308102309102310102311102312102313102314102315102316102317102318102319102320102321102322102323102324102325102326102327102328102329102330102331102332102333102334102335102336102337102338102339102340102341102342102343102344102345102346102347102348102349102350102351102352102353102354102355102356102357102358102359102360102361102362102363102364102365102366102367102368102369102370102371102372102373102374102375102376102377102378102379102380102381102382102383102384102385102386102387102388102389102390102391102392102393102394102395102396102397102398102399102400102401102402102403102404102405102406102407102408102409102410102411102412102413102414102415102416102417102418102419102420102421102422102423102424102425102426102427102428102429102430102431102432102433102434102435102436102437102438102439102440102441102442102443102444102445102446102447102448102449102450102451102452102453102454102455102456102457102458102459102460102461102462102463102464102465102466102467102468102469102470102471102472102473102474102475102476102477102478102479102480102481102482102483102484102485102486102487102488102489102490102491102492102493102494102495102496102497102498102499102500102501102502102503102504102505102506102507102508102509102510102511102512102513102514102515102516102517102518102519102520102521102522102523102524102525102526102527102528102529102530102531102532102533102534102535102536102537102538102539102540102541102542102543102544102545102546102547102548102549102550102551102552102553102554102555102556102557102558102559102560102561102562102563102564102565102566102567102568102569102570102571102572102573102574102575102576102577102578102579102580102581102582102583102584102585102586102587102588102589102590102591102592102593102594102595102596102597102598102599102600102601102602102603102604102605102606102607102608102609102610102611102612102613102614102615102616102617102618102619102620102621102622102623102624102625102626102627102628102629102630102631102632102633102634102635102636102637102638102639102640102641102642102643102644102645102646102647102648102649102650102651102652102653102654102655102656102657102658102659102660102661102662102663102664102665102666102667102668102669102670102671102672102673102674102675102676102677102678102679102680102681102682102683102684102685102686102687102688102689102690102691102692102693102694102695102696102697102698102699102700102701102702102703102704102705102706102707102708102709102710102711102712102713102714102715102716102717102718102719102720102721102722102723102724102725102726102727102728102729102730102731102732102733102734102735102736102737102738102739102740102741102742102743102744102745102746102747102748102749102750102751102752102753102754102755102756102757102758102759102760102761102762102763102764102765102766102767102768102769102770102771102772102773102774102775102776102777102778102779102780102781102782102783102784102785102786102787102788102789102790102791102792102793102794102795102796102797102798102799102800102801102802102803102804102805102806102807102808102809102810102811102812102813102814102815102816102817102818102819102820102821102822102823102824102825102826102827102828102829102830102831102832102833102834102835102836102837102838102839102840102841102842102843102844102845102846102847102848102849102850102851102852102853102854102855102856102857102858102859102860102861102862102863102864102865102866102867102868102869102870102871102872102873102874102875102876102877102878102879102880102881102882102883102884102885102886102887102888102889102890102891102892102893102894102895102896102897102898102899102900102901102902102903102904102905102906102907102908102909102910102911102912102913102914102915102916102917102918102919102920102921102922102923102924102925102926102927102928102929102930102931102932102933102934102935102936102937102938102939102940102941102942102943102944102945102946102947102948102949102950102951102952102953102954102955102956102957102958102959102960102961102962102963102964102965102966102967102968102969102970102971102972102973102974102975102976102977102978102979102980102981102982102983102984102985102986102987102988102989102990102991102992102993102994102995102996102997102998102999103000103001103002103003103004103005103006103007103008103009103010103011103012103013103014103015103016103017103018103019103020103021103022103023103024103025103026103027103028103029103030103031103032103033103034103035103036103037103038103039103040103041103042103043103044103045103046103047103048103049103050103051103052103053103054103055103056103057103058103059103060103061103062103063103064103065103066103067103068103069103070103071103072103073103074103075103076103077103078103079103080103081103082103083103084103085103086103087103088103089103090103091103092103093103094103095103096103097103098103099103100103101103102103103103104103105103106103107103108103109103110103111103112103113103114103115103116103117103118103119103120103121103122103123103124103125103126103127103128103129103130103131103132103133103134103135103136103137103138103139103140103141103142103143103144103145103146103147103148103149103150103151103152103153103154103155103156103157103158103159103160103161103162103163103164103165103166103167103168103169103170103171103172103173103174103175103176103177103178103179103180103181103182103183103184103185103186103187103188103189103190103191103192103193103194103195103196103197103198103199103200103201103202103203103204103205103206103207103208103209103210103211103212103213103214103215103216103217103218103219103220103221103222103223103224103225103226103227103228103229103230103231103232103233103234103235103236103237103238103239103240103241103242103243103244103245103246103247103248103249103250103251103252103253103254103255103256103257103258103259103260103261103262103263103264103265103266103267103268103269103270103271103272103273103274103275103276103277103278103279103280103281103282103283103284103285103286103287103288103289103290103291103292103293103294103295103296103297103298103299103300103301103302103303103304103305103306103307103308103309103310103311103312103313103314103315103316103317103318103319103320103321103322103323103324103325103326103327103328103329103330103331103332103333103334103335103336103337103338103339103340103341103342103343103344103345103346103347103348103349103350103351103352103353103354103355103356103357103358103359103360103361103362103363103364103365103366103367103368103369103370103371103372103373103374103375103376103377103378103379103380103381103382103383103384103385103386103387103388103389103390103391103392103393103394103395103396103397103398103399103400103401103402103403103404103405103406103407103408103409103410103411103412103413103414103415103416103417103418103419103420103421103422103423103424103425103426103427103428103429103430103431103432103433103434103435103436103437103438103439103440103441103442103443103444103445103446103447103448103449103450103451103452103453103454103455103456103457103458103459103460103461103462103463103464103465103466103467103468103469103470103471103472103473103474103475103476103477103478103479103480103481103482103483103484103485103486103487103488103489103490103491103492103493103494103495103496103497103498103499103500103501103502103503103504103505103506103507103508103509103510103511103512103513103514103515103516103517103518103519103520103521103522103523103524103525103526103527103528103529103530103531103532103533103534103535103536103537103538103539103540103541103542103543103544103545103546103547103548103549103550103551103552103553103554103555103556103557103558103559103560103561103562103563103564103565103566103567103568103569103570103571103572103573103574103575103576103577103578103579103580103581103582103583103584103585103586103587103588103589103590103591103592103593103594103595103596103597103598103599103600103601103602103603103604103605103606103607103608103609103610103611103612103613103614103615103616103617103618103619103620103621103622103623103624103625103626103627103628103629103630103631103632103633103634103635103636103637103638103639103640103641103642103643103644103645103646103647103648103649103650103651103652103653103654103655103656103657103658103659103660103661103662103663103664103665103666103667103668103669103670103671103672103673103674103675103676103677103678103679103680103681103682103683103684103685103686103687103688103689103690103691103692103693103694103695103696103697103698103699103700103701103702103703103704103705103706103707103708103709103710103711103712103713103714103715103716103717103718103719103720103721103722103723103724103725103726103727103728103729103730103731103732103733103734103735103736103737103738103739103740103741103742103743103744103745103746103747103748103749103750103751103752103753103754103755103756103757103758103759103760103761103762103763103764103765103766103767103768103769103770103771103772103773103774103775103776103777103778103779103780103781103782103783103784103785103786103787103788103789103790103791103792103793103794103795103796103797103798103799103800103801103802103803103804103805103806103807103808103809103810103811103812103813103814103815103816103817103818103819103820103821103822103823103824103825103826103827103828103829103830103831103832103833103834103835103836103837103838103839103840103841103842103843103844103845103846103847103848103849103850103851103852103853103854103855103856103857103858103859103860103861103862103863103864103865103866103867103868103869103870103871103872103873103874103875103876103877103878103879103880103881103882103883103884103885103886103887103888103889103890103891103892103893103894103895103896103897103898103899103900103901103902103903103904103905103906103907103908103909103910103911103912103913103914103915103916103917103918103919103920103921103922103923103924103925103926103927103928103929103930103931103932103933103934103935103936103937103938103939103940103941103942103943103944103945103946103947103948103949103950103951103952103953103954103955103956103957103958103959103960103961103962103963103964103965103966103967103968103969103970103971103972103973103974103975103976103977103978103979103980103981103982103983103984103985103986103987103988103989103990103991103992103993103994103995103996103997103998103999104000104001104002104003104004104005104006104007104008104009104010104011104012104013104014104015104016104017104018104019104020104021104022104023104024104025104026104027104028104029104030104031104032104033104034104035104036104037104038104039104040104041104042104043104044104045104046104047104048104049104050104051104052104053104054104055104056104057104058104059104060104061104062104063104064104065104066104067104068104069104070104071104072104073104074104075104076104077104078104079104080104081104082104083104084104085104086104087104088104089104090104091104092104093104094104095104096104097104098104099104100104101104102104103104104104105104106104107104108104109104110104111104112104113104114104115104116104117104118104119104120104121104122104123104124104125104126104127104128104129104130104131104132104133104134104135104136104137104138104139104140104141104142104143104144104145104146104147104148104149104150104151104152104153104154104155104156104157104158104159104160104161104162104163104164104165104166104167104168104169104170104171104172104173104174104175104176104177104178104179104180104181104182104183104184104185104186104187104188104189104190104191104192104193104194104195104196104197104198104199104200104201104202104203104204104205104206104207104208104209104210104211104212104213104214104215104216104217104218104219104220104221104222104223104224104225104226104227104228104229104230104231104232104233104234104235104236104237104238104239104240104241104242104243104244104245104246104247104248104249104250104251104252104253104254104255104256104257104258104259104260104261104262104263104264104265104266104267104268104269104270104271104272104273104274104275104276104277104278104279104280104281104282104283104284104285104286104287104288104289104290104291104292104293104294104295104296104297104298104299104300104301104302104303104304104305104306104307104308104309104310104311104312104313104314104315104316104317104318104319104320104321104322104323104324104325104326104327104328104329104330104331104332104333104334104335104336104337104338104339104340104341104342104343104344104345104346104347104348104349104350104351104352104353104354104355104356104357104358104359104360104361104362104363104364104365104366104367104368104369104370104371104372104373104374104375104376104377104378104379104380104381104382104383104384104385104386104387104388104389104390104391104392104393104394104395104396104397104398104399104400104401104402104403104404104405104406104407104408104409104410104411104412104413104414104415104416104417104418104419104420104421104422104423104424104425104426104427104428104429104430104431104432104433104434104435104436104437104438104439104440104441104442104443104444104445104446104447104448104449104450104451104452104453104454104455104456104457104458104459104460104461104462104463104464104465104466104467104468104469104470104471104472104473104474104475104476104477104478104479104480104481104482104483104484104485104486104487104488104489104490104491104492104493104494104495104496104497104498104499104500104501104502104503104504104505104506104507104508104509104510104511104512104513104514104515104516104517104518104519104520104521104522104523104524104525104526104527104528104529104530104531104532104533104534104535104536104537104538104539104540104541104542104543104544104545104546104547104548104549104550104551104552104553104554104555104556104557104558104559104560104561104562104563104564104565104566104567104568104569104570104571104572104573104574104575104576104577104578104579104580104581104582104583104584104585104586104587104588104589104590104591104592104593104594104595104596104597104598104599104600104601104602104603104604104605104606104607104608104609104610104611104612104613104614104615104616104617104618104619104620104621104622104623104624104625104626104627104628104629104630104631104632104633104634104635104636104637104638104639104640104641104642104643104644104645104646104647104648104649104650104651104652104653104654104655104656104657104658104659104660104661104662104663104664104665104666104667104668104669104670104671104672104673104674104675104676104677104678104679104680104681104682104683104684104685104686104687104688104689104690104691104692104693104694104695104696104697104698104699104700104701104702104703104704104705104706104707104708104709104710104711104712104713104714104715104716104717104718104719104720104721104722104723104724104725104726104727104728104729104730104731104732104733104734104735104736104737104738104739104740104741104742104743104744104745104746104747104748104749104750104751104752104753104754104755104756104757104758104759104760104761104762104763104764104765104766104767104768104769104770104771104772104773104774104775104776104777104778104779104780104781104782104783104784104785104786104787104788104789104790104791104792104793104794104795104796104797104798104799104800104801104802104803104804104805104806104807104808104809104810104811104812104813104814104815104816104817104818104819104820104821104822104823104824104825104826104827104828104829104830104831104832104833104834104835104836104837104838104839104840104841104842104843104844104845104846104847104848104849104850104851104852104853104854104855104856104857104858104859104860104861104862104863104864104865104866104867104868104869104870104871104872104873104874104875104876104877104878104879104880104881104882104883104884104885104886104887104888104889104890104891104892104893104894104895104896104897104898104899104900104901104902104903104904104905104906104907104908104909104910104911104912104913104914104915104916104917104918104919104920104921104922104923104924104925104926104927104928104929104930104931104932104933104934104935104936104937104938104939104940104941104942104943104944104945104946104947104948104949104950104951104952104953104954104955104956104957104958104959104960104961104962104963104964104965104966104967104968104969104970104971104972104973104974104975104976104977104978104979104980104981104982104983104984104985104986104987104988104989104990104991104992104993104994104995104996104997104998104999105000105001105002105003105004105005105006105007105008105009105010105011105012105013105014105015105016105017105018105019105020105021105022105023105024105025105026105027105028105029105030105031105032105033105034105035105036105037105038105039105040105041105042105043105044105045105046105047105048105049105050105051105052105053105054105055105056105057105058105059105060105061105062105063105064105065105066105067105068105069105070105071105072105073105074105075105076105077105078105079105080105081105082105083105084105085105086105087105088105089105090105091105092105093105094105095105096105097105098105099105100105101105102105103105104105105105106105107105108105109105110105111105112105113105114105115105116105117105118105119105120105121105122105123105124105125105126105127105128105129105130105131105132105133105134105135105136105137105138105139105140105141105142105143105144105145105146105147105148105149105150105151105152105153105154105155105156105157105158105159105160105161105162105163105164105165105166105167105168105169105170105171105172105173105174105175105176105177105178105179105180105181105182105183105184105185105186105187105188105189105190105191105192105193105194105195105196105197105198105199105200105201105202105203105204105205105206105207105208105209105210105211105212105213105214105215105216105217105218105219105220105221105222105223105224105225105226105227105228105229105230105231105232105233105234105235105236105237105238105239105240105241105242105243105244105245105246105247105248105249105250105251105252105253105254105255105256105257105258105259105260105261105262105263105264105265105266105267105268105269105270105271105272105273105274105275105276105277105278105279105280105281105282105283105284105285105286105287105288105289105290105291105292105293105294105295105296105297105298105299105300105301105302105303105304105305105306105307105308105309105310105311105312105313105314105315105316105317105318105319105320105321105322105323105324105325105326105327105328105329105330105331105332105333105334105335105336105337105338105339105340105341105342105343105344105345105346105347105348105349105350105351105352105353105354105355105356105357105358105359105360105361105362105363105364105365105366105367105368105369105370105371105372105373105374105375105376105377105378105379105380105381105382105383105384105385105386105387105388105389105390105391105392105393105394105395105396105397105398105399105400105401105402105403105404105405105406105407105408105409105410105411105412105413105414105415105416105417105418105419105420105421105422105423105424105425105426105427105428105429105430105431105432105433105434105435105436105437105438105439105440105441105442105443105444105445105446105447105448105449105450105451105452105453105454105455105456105457105458105459105460105461105462105463105464105465105466105467105468105469105470105471105472105473105474105475105476105477105478105479105480105481105482105483105484105485105486105487105488105489105490105491105492105493105494105495105496105497105498105499105500105501105502105503105504105505105506105507105508105509105510105511105512105513105514105515105516105517105518105519105520105521105522105523105524105525105526105527105528105529105530105531105532105533105534105535105536105537105538105539105540105541105542105543105544105545105546105547105548105549105550105551105552105553105554105555105556105557105558105559105560105561105562105563105564105565105566105567105568105569105570105571105572105573105574105575105576105577105578105579105580105581105582105583105584105585105586105587105588105589105590105591105592105593105594105595105596105597105598105599105600105601105602105603105604105605105606105607105608105609105610105611105612105613105614105615105616105617105618105619105620105621105622105623105624105625105626105627105628105629105630105631105632105633105634105635105636105637105638105639105640105641105642105643105644105645105646105647105648105649105650105651105652105653105654105655105656105657105658105659105660105661105662105663105664105665105666105667105668105669105670105671105672105673105674105675105676105677105678105679105680105681105682105683105684105685105686105687105688105689105690105691105692105693105694105695105696105697105698105699105700105701105702105703105704105705105706105707105708105709105710105711105712105713105714105715105716105717105718105719105720105721105722105723105724105725105726105727105728105729105730105731105732105733105734105735105736105737105738105739105740105741105742105743105744105745105746105747105748105749105750105751105752105753105754105755105756105757105758105759105760105761105762105763105764105765105766105767105768105769105770105771105772105773105774105775105776105777105778105779105780105781105782105783105784105785105786105787105788105789105790105791105792105793105794105795105796105797105798105799105800105801105802105803105804105805105806105807105808105809105810105811105812105813105814105815105816105817105818105819105820105821105822105823105824105825105826105827105828105829105830105831105832105833105834105835105836105837105838105839105840105841105842105843105844105845105846105847105848105849105850105851105852105853105854105855105856105857105858105859105860105861105862105863105864105865105866105867105868105869105870105871105872105873105874105875105876105877105878105879105880105881105882105883105884105885105886105887105888105889105890105891105892105893105894105895105896105897105898105899105900105901105902105903105904105905105906105907105908105909105910105911105912105913105914105915105916105917105918105919105920105921105922105923105924105925105926105927105928105929105930105931105932105933105934105935105936105937105938105939105940105941105942105943105944105945105946105947105948105949105950105951105952105953105954105955105956105957105958105959105960105961105962105963105964105965105966105967105968105969105970105971105972105973105974105975105976105977105978105979105980105981105982105983105984105985105986105987105988105989105990105991105992105993105994105995105996105997105998105999106000106001106002106003106004106005106006106007106008106009106010106011106012106013106014106015106016106017106018106019106020106021106022106023106024106025106026106027106028106029106030106031106032106033106034106035106036106037106038106039106040106041106042106043106044106045106046106047106048106049106050106051106052106053106054106055106056106057106058106059106060106061106062106063106064106065106066106067106068106069106070106071106072106073106074106075106076106077106078106079106080106081106082106083106084106085106086106087106088106089106090106091106092106093106094106095106096106097106098106099106100106101106102106103106104106105106106106107106108106109106110106111106112106113106114106115106116106117106118106119106120106121106122106123106124106125106126106127106128106129106130106131106132106133106134106135106136106137106138106139106140106141106142106143106144106145106146106147106148106149106150106151106152106153106154106155106156106157106158106159106160106161106162106163106164106165106166106167106168106169106170106171106172106173106174106175106176106177106178106179106180106181106182106183106184106185106186106187106188106189106190106191106192106193106194106195106196106197106198106199106200106201106202106203106204106205106206106207106208106209106210106211106212106213106214106215106216106217106218106219106220106221106222106223106224106225106226106227106228106229106230106231106232106233106234106235106236106237106238106239106240106241106242106243106244106245106246106247106248106249106250106251106252106253106254106255106256106257106258106259106260106261106262106263106264106265106266106267106268106269106270106271106272106273106274106275106276106277106278106279106280106281106282106283106284106285106286106287106288106289106290106291106292106293106294106295106296106297106298106299106300106301106302106303106304106305106306106307106308106309106310106311106312106313106314106315106316106317106318106319106320106321106322106323106324106325106326106327106328106329106330106331106332106333106334106335106336106337106338106339106340106341106342106343106344106345106346106347106348106349106350106351106352106353106354106355106356106357106358106359106360106361106362106363106364106365106366106367106368106369106370106371106372106373106374106375106376106377106378106379106380106381106382106383106384106385106386106387106388106389106390106391106392106393106394106395106396106397106398106399106400106401106402106403106404106405106406106407106408106409106410106411106412106413106414106415106416106417106418106419106420106421106422106423106424106425106426106427106428106429106430106431106432106433106434106435106436106437106438106439106440106441106442106443106444106445106446106447106448106449106450106451106452106453106454106455106456106457106458106459106460106461106462106463106464106465106466106467106468106469106470106471106472106473106474106475106476106477106478106479106480106481106482106483106484106485106486106487106488106489106490106491106492106493106494106495106496106497106498106499106500106501106502106503106504106505106506106507106508106509106510106511106512106513106514106515106516106517106518106519106520106521106522106523106524106525106526106527106528106529106530106531106532106533106534106535106536106537106538106539106540106541106542106543106544106545106546106547106548106549106550106551106552106553106554106555106556106557106558106559106560106561106562106563106564106565106566106567106568106569106570106571106572106573106574106575106576106577106578106579106580106581106582106583106584106585106586106587106588106589106590106591106592106593106594106595106596106597106598106599106600106601106602106603106604106605106606106607106608106609106610106611106612106613106614106615106616106617106618106619106620106621106622106623106624106625106626106627106628106629106630106631106632106633106634106635106636106637106638106639106640106641106642106643106644106645106646106647106648106649106650106651106652106653106654106655106656106657106658106659106660106661106662106663106664106665106666106667106668106669106670106671106672106673106674106675106676106677106678106679106680106681106682106683106684106685106686106687106688106689106690106691106692106693106694106695106696106697106698106699106700106701106702106703106704106705106706106707106708106709106710106711106712106713106714106715106716106717106718106719106720106721106722106723106724106725106726106727106728106729106730106731106732106733106734106735106736106737106738106739106740106741106742106743106744106745106746106747106748106749106750106751106752106753106754106755106756106757106758106759106760106761106762106763106764106765106766106767106768106769106770106771106772106773106774106775106776106777106778106779106780106781106782106783106784106785106786106787106788106789106790106791106792106793106794106795106796106797106798106799106800106801106802106803106804106805106806106807106808106809106810106811106812106813106814106815106816106817106818106819106820106821106822106823106824106825106826106827106828106829106830106831106832106833106834106835106836106837106838106839106840106841106842106843106844106845106846106847106848106849106850106851106852106853106854106855106856106857106858106859106860106861106862106863106864106865106866106867106868106869106870106871106872106873106874106875106876106877106878106879106880106881106882106883106884106885106886106887106888106889106890106891106892106893106894106895106896106897106898106899106900106901106902106903106904106905106906106907106908106909106910106911106912106913106914106915106916106917106918106919106920106921106922106923106924106925106926106927106928106929106930106931106932106933106934106935106936106937106938106939106940106941106942106943106944106945106946106947106948106949106950106951106952106953106954106955106956106957106958106959106960106961106962106963106964106965106966106967106968106969106970106971106972106973106974106975106976106977106978106979106980106981106982106983106984106985106986106987106988106989106990106991106992106993106994106995106996106997106998106999107000107001107002107003107004107005107006107007107008107009107010107011107012107013107014107015107016107017107018107019107020107021107022107023107024107025107026107027107028107029107030107031107032107033107034107035107036107037107038107039107040107041107042107043107044107045107046107047107048107049107050107051107052107053107054107055107056107057107058107059107060107061107062107063107064107065107066107067107068107069107070107071107072107073107074107075107076107077107078107079107080107081107082107083107084107085107086107087107088107089107090107091107092107093107094107095107096107097107098107099107100107101107102107103107104107105107106107107107108107109107110107111107112107113107114107115107116107117107118107119107120107121107122107123107124107125107126107127107128107129107130107131107132107133107134107135107136107137107138107139107140107141107142107143107144107145107146107147107148107149107150107151107152107153107154107155107156107157107158107159107160107161107162107163107164107165107166107167107168107169107170107171107172107173107174107175107176107177107178107179107180107181107182107183107184107185107186107187107188107189107190107191107192107193107194107195107196107197107198107199107200107201107202107203107204107205107206107207107208107209107210107211107212107213107214107215107216107217107218107219107220107221107222107223107224107225107226107227107228107229107230107231107232107233107234107235107236107237107238107239107240107241107242107243107244107245107246107247107248107249107250107251107252107253107254107255107256107257107258107259107260107261107262107263107264107265107266107267107268107269107270107271107272107273107274107275107276107277107278107279107280107281107282107283107284107285107286107287107288107289107290107291107292107293107294107295107296107297107298107299107300107301107302107303107304107305107306107307107308107309107310107311107312107313107314107315107316107317107318107319107320107321107322107323107324107325107326107327107328107329107330107331107332107333107334107335107336107337107338107339107340107341107342107343107344107345107346107347107348107349107350107351107352107353107354107355107356107357107358107359107360107361107362107363107364107365107366107367107368107369107370107371107372107373107374107375107376107377107378107379107380107381107382107383107384107385107386107387107388107389107390107391107392107393107394107395107396107397107398107399107400107401107402107403107404107405107406107407107408107409107410107411107412107413107414107415107416107417107418107419107420107421107422107423107424107425107426107427107428107429107430107431107432107433107434107435107436107437107438107439107440107441107442107443107444107445107446107447107448107449107450107451107452107453107454107455107456107457107458107459107460107461107462107463107464107465107466107467107468107469107470107471107472107473107474107475107476107477107478107479107480107481107482107483107484107485107486107487107488107489107490107491107492107493107494107495107496107497107498107499107500107501107502107503107504107505107506107507107508107509107510107511107512107513107514107515107516107517107518107519107520107521107522107523107524107525107526107527107528107529107530107531107532107533107534107535107536107537107538107539107540107541107542107543107544107545107546107547107548107549107550107551107552107553107554107555107556107557107558107559107560107561107562107563107564107565107566107567107568107569107570107571107572107573107574107575107576107577107578107579107580107581107582107583107584107585107586107587107588107589107590107591107592107593107594107595107596107597107598107599107600107601107602107603107604107605107606107607107608107609107610107611107612107613107614107615107616107617107618107619107620107621107622107623107624107625107626107627107628107629107630107631107632107633107634107635107636107637107638107639107640107641107642107643107644107645107646107647107648107649107650107651107652107653107654107655107656107657107658107659107660107661107662107663107664107665107666107667107668107669107670107671107672107673107674107675107676107677107678107679107680107681107682107683107684107685107686107687107688107689107690107691107692107693107694107695107696107697107698107699107700107701107702107703107704107705107706107707107708107709107710107711107712107713107714107715107716107717107718107719107720107721107722107723107724107725107726107727107728107729107730107731107732107733107734107735107736107737107738107739107740107741107742107743107744107745107746107747107748107749107750107751107752107753107754107755107756107757107758107759107760107761107762107763107764107765107766107767107768107769107770107771107772107773107774107775107776107777107778107779107780107781107782107783107784107785107786107787107788107789107790107791107792107793107794107795107796107797107798107799107800107801107802107803107804107805107806107807107808107809107810107811107812107813107814107815107816107817107818107819107820107821107822107823107824107825107826107827107828107829107830107831107832107833107834107835107836107837107838107839107840107841107842107843107844107845107846107847107848107849107850107851107852107853107854107855107856107857107858107859107860107861107862107863107864107865107866107867107868107869107870107871107872107873107874107875107876107877107878107879107880107881107882107883107884107885107886107887107888107889107890107891107892107893107894107895107896107897107898107899107900107901107902107903107904107905107906107907107908107909107910107911107912107913107914107915107916107917107918107919107920107921107922107923107924107925107926107927107928107929107930107931107932107933107934107935107936107937107938107939107940107941107942107943107944107945107946107947107948107949107950107951107952107953107954107955107956107957107958107959107960107961107962107963107964107965107966107967107968107969107970107971107972107973107974107975107976107977107978107979107980107981107982107983107984107985107986107987107988107989107990107991107992107993107994107995107996107997107998107999108000108001108002108003108004108005108006108007108008108009108010108011108012108013108014108015108016108017108018108019108020108021108022108023108024108025108026108027108028108029108030108031108032108033108034108035108036108037108038108039108040108041108042108043108044108045108046108047108048108049108050108051108052108053108054108055108056108057108058108059108060108061108062108063108064108065108066108067108068108069108070108071108072108073108074108075108076108077108078108079108080108081108082108083108084108085108086108087108088108089108090108091108092108093108094108095108096108097108098108099108100108101108102108103108104108105108106108107108108108109108110108111108112108113108114108115108116108117108118108119108120108121108122108123108124108125108126108127108128108129108130108131108132108133108134108135108136108137108138108139108140108141108142108143108144108145108146108147108148108149108150108151108152108153108154108155108156108157108158108159108160108161108162108163108164108165108166108167108168108169108170108171108172108173108174108175108176108177108178108179108180108181108182108183108184108185108186108187108188108189108190108191108192108193108194108195108196108197108198108199108200108201108202108203108204108205108206108207108208108209108210108211108212108213108214108215108216108217108218108219108220108221108222108223108224108225108226108227108228108229108230108231108232108233108234108235108236108237108238108239108240108241108242108243108244108245108246108247108248108249108250108251108252108253108254108255108256108257108258108259108260108261108262108263108264108265108266108267108268108269108270108271108272108273108274108275108276108277108278108279108280108281108282108283108284108285108286108287108288108289108290108291108292108293108294108295108296108297108298108299108300108301108302108303108304108305108306108307108308108309108310108311108312108313108314108315108316108317108318108319108320108321108322108323108324108325108326108327108328108329108330108331108332108333108334108335108336108337108338108339108340108341108342108343108344108345108346108347108348108349108350108351108352108353108354108355108356108357108358108359108360108361108362108363108364108365108366108367108368108369108370108371108372108373108374108375108376108377108378108379108380108381108382108383108384108385108386108387108388108389108390108391108392108393108394108395108396108397108398108399108400108401108402108403108404108405108406108407108408108409108410108411108412108413108414108415108416108417108418108419108420108421108422108423108424108425108426108427108428108429108430108431108432108433108434108435108436108437108438108439108440108441108442108443108444108445108446108447108448108449108450108451108452108453108454108455108456108457108458108459108460108461108462108463108464108465108466108467108468108469108470108471108472108473108474108475108476108477108478108479108480108481108482108483108484108485108486108487108488108489108490108491108492108493108494108495108496108497108498108499108500108501108502108503108504108505108506108507108508108509108510108511108512108513108514108515108516108517108518108519108520108521108522108523108524108525108526108527108528108529108530108531108532108533108534108535108536108537108538108539108540108541108542108543108544108545108546108547108548108549108550108551108552108553108554108555108556108557108558108559108560108561108562108563108564108565108566108567108568108569108570108571108572108573108574108575108576108577108578108579108580108581108582108583108584108585108586108587108588108589108590108591108592108593108594108595108596108597108598108599108600108601108602108603108604108605108606108607108608108609108610108611108612108613108614108615108616108617108618108619108620108621108622108623108624108625108626108627108628108629108630108631108632108633108634108635108636108637108638108639108640108641108642108643108644108645108646108647108648108649108650108651108652108653108654108655108656108657108658108659108660108661108662108663108664108665108666108667108668108669108670108671108672108673108674108675108676108677108678108679108680108681108682108683108684108685108686108687108688108689108690108691108692108693108694108695108696108697108698108699108700108701108702108703108704108705108706108707108708108709108710108711108712108713108714108715108716108717108718108719108720108721108722108723108724108725108726108727108728108729108730108731108732108733108734108735108736108737108738108739108740108741108742108743108744108745108746108747108748108749108750108751108752108753108754108755108756108757108758108759108760108761108762108763108764108765108766108767108768108769108770108771108772108773108774108775108776108777108778108779108780108781108782108783108784108785108786108787108788108789108790108791108792108793108794108795108796108797108798108799108800108801108802108803108804108805108806108807108808108809108810108811108812108813108814108815108816108817108818108819108820108821108822108823108824108825108826108827108828108829108830108831108832108833108834108835108836108837108838108839108840108841108842108843108844108845108846108847108848108849108850108851108852108853108854108855108856108857108858108859108860108861108862108863108864108865108866108867108868108869108870108871108872108873108874108875108876108877108878108879108880108881108882108883108884108885108886108887108888108889108890108891108892108893108894108895108896108897108898108899108900108901108902108903108904108905108906108907108908108909108910108911108912108913108914108915108916108917108918108919108920108921108922108923108924108925108926108927108928108929108930108931108932108933108934108935108936108937108938108939108940108941108942108943108944108945108946108947108948108949108950108951108952108953108954108955108956108957108958108959108960108961108962108963108964108965108966108967108968108969108970108971108972108973108974108975108976108977108978108979108980108981108982108983108984108985108986108987108988108989108990108991108992108993108994108995108996108997108998108999109000109001109002109003109004109005109006109007109008109009109010109011109012109013109014109015109016109017109018109019109020109021109022109023109024109025109026109027109028109029109030109031109032109033109034109035109036109037109038109039109040109041109042109043109044109045109046109047109048109049109050109051109052109053109054109055109056109057109058109059109060109061109062109063109064109065109066109067109068109069109070109071109072109073109074109075109076109077109078109079109080109081109082109083109084109085109086109087109088109089109090109091109092109093109094109095109096109097109098109099109100109101109102109103109104109105109106109107109108109109109110109111109112109113109114109115109116109117109118109119109120109121109122109123109124109125109126109127109128109129109130109131109132109133109134109135109136109137109138109139109140109141109142109143109144109145109146109147109148109149109150109151109152109153109154109155109156109157109158109159109160109161109162109163109164109165109166109167109168109169109170109171109172109173109174109175109176109177109178109179109180109181109182109183109184109185109186109187109188109189109190109191109192109193109194109195109196109197109198109199109200109201109202109203109204109205109206109207109208109209109210109211109212109213109214109215109216109217109218109219109220109221109222109223109224109225109226109227109228109229109230109231109232109233109234109235109236109237109238109239109240109241109242109243109244109245109246109247109248109249109250109251109252109253109254109255109256109257109258109259109260109261109262109263109264109265109266109267109268109269109270109271109272109273109274109275109276109277109278109279109280109281109282109283109284109285109286109287109288109289109290109291109292109293109294109295109296109297109298109299109300109301109302109303109304109305109306109307109308109309109310109311109312109313109314109315109316109317109318109319109320109321109322109323109324109325109326109327109328109329109330109331109332109333109334109335109336109337109338109339109340109341109342109343109344109345109346109347109348109349109350109351109352109353109354109355109356109357109358109359109360109361109362109363109364109365109366109367109368109369109370109371109372109373109374109375109376109377109378109379109380109381109382109383109384109385109386109387109388109389109390109391109392109393109394109395109396109397109398109399109400109401109402109403109404109405109406109407109408109409109410109411109412109413109414109415109416109417109418109419109420109421109422109423109424109425109426109427109428109429109430109431109432109433109434109435109436109437109438109439109440109441109442109443109444109445109446109447109448109449109450109451109452109453109454109455109456109457109458109459109460109461109462109463109464109465109466109467109468109469109470109471109472109473109474109475109476109477109478109479109480109481109482109483109484109485109486109487109488109489109490109491109492109493109494109495109496109497109498109499109500109501109502109503109504109505109506109507109508109509109510109511109512109513109514109515109516109517109518109519109520109521109522109523109524109525109526109527109528109529109530109531109532109533109534109535109536109537109538109539109540109541109542109543109544109545109546109547109548109549109550109551109552109553109554109555109556109557109558109559109560109561109562109563109564109565109566109567109568109569109570109571109572109573109574109575109576109577109578109579109580109581109582109583109584109585109586109587109588109589109590109591109592109593109594109595109596109597109598109599109600109601109602109603109604109605109606109607109608109609109610109611109612109613109614109615109616109617109618109619109620109621109622109623109624109625109626109627109628109629109630109631109632109633109634109635109636109637109638109639109640109641109642109643109644109645109646109647109648109649109650109651109652109653109654109655109656109657109658109659109660109661109662109663109664109665109666109667109668109669109670109671109672109673109674109675109676109677109678109679109680109681109682109683109684109685109686109687109688109689109690109691109692109693109694109695109696109697109698109699109700109701109702109703109704109705109706109707109708109709109710109711109712109713109714109715109716109717109718109719109720109721109722109723109724109725109726109727109728109729109730109731109732109733109734109735109736109737109738109739109740109741109742109743109744109745109746109747109748109749109750109751109752109753109754109755109756109757109758109759109760109761109762109763109764109765109766109767109768109769109770109771109772109773109774109775109776109777109778109779109780109781109782109783109784109785109786109787109788109789109790109791109792109793109794109795109796109797109798109799109800109801109802109803109804109805109806109807109808109809109810109811109812109813109814109815109816109817109818109819109820109821109822109823109824109825109826109827109828109829109830109831109832109833109834109835109836109837109838109839109840109841109842109843109844109845109846109847109848109849109850109851109852109853109854109855109856109857109858109859109860109861109862109863109864109865109866109867109868109869109870109871109872109873109874109875109876109877109878109879109880109881109882109883109884109885109886109887109888109889109890109891109892109893109894109895109896109897109898109899109900109901109902109903109904109905109906109907109908109909109910109911109912109913109914109915109916109917109918109919109920109921109922109923109924109925109926109927109928109929109930109931109932109933109934109935109936109937109938109939109940109941109942109943109944109945109946109947109948109949109950109951109952109953109954109955109956109957109958109959109960109961109962109963109964109965109966109967109968109969109970109971109972109973109974109975109976109977109978109979109980109981109982109983109984109985109986109987109988109989109990109991109992109993109994109995109996109997109998109999110000110001110002110003110004110005110006110007110008110009110010110011110012110013110014110015110016110017110018110019110020110021110022110023110024110025110026110027110028110029110030110031110032110033110034110035110036110037110038110039110040110041110042110043110044110045110046110047110048110049110050110051110052110053110054110055110056110057110058110059110060110061110062110063110064110065110066110067110068110069110070110071110072110073110074110075110076110077110078110079110080110081110082110083110084110085110086110087110088110089110090110091110092110093110094110095110096110097110098110099110100110101110102110103110104110105110106110107110108110109110110110111110112110113110114110115110116110117110118110119110120110121110122110123110124110125110126110127110128110129110130110131110132110133110134110135110136110137110138110139110140110141110142110143110144110145110146110147110148110149110150110151110152110153110154110155110156110157110158110159110160110161110162110163110164110165110166110167110168110169110170110171110172110173110174110175110176110177110178110179110180110181110182110183110184110185110186110187110188110189110190110191110192110193110194110195110196110197110198110199110200110201110202110203110204110205110206110207110208110209110210110211110212110213110214110215110216110217110218110219110220110221110222110223110224110225110226110227110228110229110230110231110232110233110234110235110236110237110238110239110240110241110242110243110244110245110246110247110248110249110250110251110252110253110254110255110256110257110258110259110260110261110262110263110264110265110266110267110268110269110270110271110272110273110274110275110276110277110278110279110280110281110282110283110284110285110286110287110288110289110290110291110292110293110294110295110296110297110298110299110300110301110302110303110304110305110306110307110308110309110310110311110312110313110314110315110316110317110318110319110320110321110322110323110324110325110326110327110328110329110330110331110332110333110334110335110336110337110338110339110340110341110342110343110344110345110346110347110348110349110350110351110352110353110354110355110356110357110358110359110360110361110362110363110364110365110366110367110368110369110370110371110372110373110374110375110376110377110378110379110380110381110382110383110384110385110386110387110388110389110390110391110392110393110394110395110396110397110398110399110400110401110402110403110404110405110406110407110408110409110410110411110412110413110414110415110416110417110418110419110420110421110422110423110424110425110426110427110428110429110430110431110432110433110434110435110436110437110438110439110440110441110442110443110444110445110446110447110448110449110450110451110452110453110454110455110456110457110458110459110460110461110462110463110464110465110466110467110468110469110470110471110472110473110474110475110476110477110478110479110480110481110482110483110484110485110486110487110488110489110490110491110492110493110494110495110496110497110498110499110500110501110502110503110504110505110506110507110508110509110510110511110512110513110514110515110516110517110518110519110520110521110522110523110524110525110526110527110528110529110530110531110532110533110534110535110536110537110538110539110540110541110542110543110544110545110546110547110548110549110550110551110552110553110554110555110556110557110558110559110560110561110562110563110564110565110566110567110568110569110570110571110572110573110574110575110576110577110578110579110580110581110582110583110584110585110586110587110588110589110590110591110592110593110594110595110596110597110598110599110600110601110602110603110604110605110606110607110608110609110610110611110612110613110614110615110616110617110618110619110620110621110622110623110624110625110626110627110628110629110630110631110632110633110634110635110636110637110638110639110640110641110642110643110644110645110646110647110648110649110650110651110652110653110654110655110656110657110658110659110660110661110662110663110664110665110666110667110668110669110670110671110672110673110674110675110676110677110678110679110680110681110682110683110684110685110686110687110688110689110690110691110692110693110694110695110696110697110698110699110700110701110702110703110704110705110706110707110708110709110710110711110712110713110714110715110716110717110718110719110720110721110722110723110724110725110726110727110728110729110730110731110732110733110734110735110736110737110738110739110740110741110742110743110744110745110746110747110748110749110750110751110752110753110754110755110756110757110758110759110760110761110762110763110764110765110766110767110768110769110770110771110772110773110774110775110776110777110778110779110780110781110782110783110784110785110786110787110788110789110790110791110792110793110794110795110796110797110798110799110800110801110802110803110804110805110806110807110808110809110810110811110812110813110814110815110816110817110818110819110820110821110822110823110824110825110826110827110828110829110830110831110832110833110834110835110836110837110838110839110840110841110842110843110844110845110846110847110848110849110850110851110852110853110854110855110856110857110858110859110860110861110862110863110864110865110866110867110868110869110870110871110872110873110874110875110876110877110878110879110880110881110882110883110884110885110886110887110888110889110890110891110892110893110894110895110896110897110898110899110900110901110902110903110904110905110906110907110908110909110910110911110912110913110914110915110916110917110918110919110920110921110922110923110924110925110926110927110928110929110930110931110932110933110934110935110936110937110938110939110940110941110942110943110944110945110946110947110948110949110950110951110952110953110954110955110956110957110958110959110960110961110962110963110964110965110966110967110968110969110970110971110972110973110974110975110976110977110978110979110980110981110982110983110984110985110986110987110988110989110990110991110992110993110994110995110996110997110998110999111000111001111002111003111004111005111006111007111008111009111010111011111012111013111014111015111016111017111018111019111020111021111022111023111024111025111026111027111028111029111030111031111032111033111034111035111036111037111038111039111040111041111042111043111044111045111046111047111048111049111050111051111052111053111054111055111056111057111058111059111060111061111062111063111064111065111066111067111068111069111070111071111072111073111074111075111076111077111078111079111080111081111082111083111084111085111086111087111088111089111090111091111092111093111094111095111096111097111098111099111100111101111102111103111104111105111106111107111108111109111110111111111112111113111114111115111116111117111118111119111120111121111122111123111124111125111126111127111128111129111130111131111132111133111134111135111136111137111138111139111140111141111142111143111144111145111146111147111148111149111150111151111152111153111154111155111156111157111158111159111160111161111162111163111164111165111166111167111168111169111170111171111172111173111174111175111176111177111178111179111180111181111182111183111184111185111186111187111188111189111190111191111192111193111194111195111196111197111198111199111200111201111202111203111204111205111206111207111208111209111210111211111212111213111214111215111216111217111218111219111220111221111222111223111224111225111226111227111228111229111230111231111232111233111234111235111236111237111238111239111240111241111242111243111244111245111246111247111248111249111250111251111252111253111254111255111256111257111258111259111260111261111262111263111264111265111266111267111268111269111270111271111272111273111274111275111276111277111278111279111280111281111282111283111284111285111286111287111288111289111290111291111292111293111294111295111296111297111298111299111300111301111302111303111304111305111306111307111308111309111310111311111312111313111314111315111316111317111318111319111320111321111322111323111324111325111326111327111328111329111330111331111332111333111334111335111336111337111338111339111340111341111342111343111344111345111346111347111348111349111350111351111352111353111354111355111356111357111358111359111360111361111362111363111364111365111366111367111368111369111370111371111372111373111374111375111376111377111378111379111380111381111382111383111384111385111386111387111388111389111390111391111392111393111394111395111396111397111398111399111400111401111402111403111404111405111406111407111408111409111410111411111412111413111414111415111416111417111418111419111420111421111422111423111424111425111426111427111428111429111430111431111432111433111434111435111436111437111438111439111440111441111442111443111444111445111446111447111448111449111450111451111452111453111454111455111456111457111458111459111460111461111462111463111464111465111466111467111468111469111470111471111472111473111474111475111476111477111478111479111480111481111482111483111484111485111486111487111488111489111490111491111492111493111494111495111496111497111498111499111500111501111502111503111504111505111506111507111508111509111510111511111512111513111514111515111516111517111518111519111520111521111522111523111524111525111526111527111528111529111530111531111532111533111534111535111536111537111538111539111540111541111542111543111544111545111546111547111548111549111550111551111552111553111554111555111556111557111558111559111560111561111562111563111564111565111566111567111568111569111570111571111572111573111574111575111576111577111578111579111580111581111582111583111584111585111586111587111588111589111590111591111592111593111594111595111596111597111598111599111600111601111602111603111604111605111606111607111608111609111610111611111612111613111614111615111616111617111618111619111620111621111622111623111624111625111626111627111628111629111630111631111632111633111634111635111636111637111638111639111640111641111642111643111644111645111646111647111648111649111650111651111652111653111654111655111656111657111658111659111660111661111662111663111664111665111666111667111668111669111670111671111672111673111674111675111676111677111678111679111680111681111682111683111684111685111686111687111688111689111690111691111692111693111694111695111696111697111698111699111700111701111702111703111704111705111706111707111708111709111710111711111712111713111714111715111716111717111718111719111720111721111722111723111724111725111726111727111728111729111730111731111732111733111734111735111736111737111738111739111740111741111742111743111744111745111746111747111748111749111750111751111752111753111754111755111756111757111758111759111760111761111762111763111764111765111766111767111768111769111770111771111772111773111774111775111776111777111778111779111780111781111782111783111784111785111786111787111788111789111790111791111792111793111794111795111796111797111798111799111800111801111802111803111804111805111806111807111808111809111810111811111812111813111814111815111816111817111818111819111820111821111822111823111824111825111826111827111828111829111830111831111832111833111834111835111836111837111838111839111840111841111842111843111844111845111846111847111848111849111850111851111852111853111854111855111856111857111858111859111860111861111862111863111864111865111866111867111868111869111870111871111872111873111874111875111876111877111878111879111880111881111882111883111884111885111886111887111888111889111890111891111892111893111894111895111896111897111898111899111900111901111902111903111904111905111906111907111908111909111910111911111912111913111914111915111916111917111918111919111920111921111922111923111924111925111926111927111928111929111930111931111932111933111934111935111936111937111938111939111940111941111942111943111944111945111946111947111948111949111950111951111952111953111954111955111956111957111958111959111960111961111962111963111964111965111966111967111968111969111970111971111972111973111974111975111976111977111978111979111980111981111982111983111984111985111986111987111988111989111990111991111992111993111994111995111996111997111998111999112000112001112002112003112004112005112006112007112008112009112010112011112012112013112014112015112016112017112018112019112020112021112022112023112024112025112026112027112028112029112030112031112032112033112034112035112036112037112038112039112040112041112042112043112044112045112046112047112048112049112050112051112052112053112054112055112056112057112058112059112060112061112062112063112064112065112066112067112068112069112070112071112072112073112074112075112076112077112078112079112080112081112082112083112084112085112086112087112088112089112090112091112092112093112094112095112096112097112098112099112100112101112102112103112104112105112106112107112108112109112110112111112112112113112114112115112116112117112118112119112120112121112122112123112124112125112126112127112128112129112130112131112132112133112134112135112136112137112138112139112140112141112142112143112144112145112146112147112148112149112150112151112152112153112154112155112156112157112158112159112160112161112162112163112164112165112166112167112168112169112170112171112172112173112174112175112176112177112178112179112180112181112182112183112184112185112186112187112188112189112190112191112192112193112194112195112196112197112198112199112200112201112202112203112204112205112206112207112208112209112210112211112212112213112214112215112216112217112218112219112220112221112222112223112224112225112226112227112228112229112230112231112232112233112234112235112236112237112238112239112240112241112242112243112244112245112246112247112248112249112250112251112252112253112254112255112256112257112258112259112260112261112262112263112264112265112266112267112268112269112270112271112272112273112274112275112276112277112278112279112280112281112282112283112284112285112286112287112288112289112290112291112292112293112294112295112296112297112298112299112300112301112302112303112304112305112306112307112308112309112310112311112312112313112314112315112316112317112318112319112320112321112322112323112324112325112326112327112328112329112330112331112332112333112334112335112336112337112338112339112340112341112342112343112344112345112346112347112348112349112350112351112352112353112354112355112356112357112358112359112360112361112362112363112364112365112366112367112368112369112370112371112372112373112374112375112376112377112378112379112380112381112382112383112384112385112386112387112388112389112390112391112392112393112394112395112396112397112398112399112400112401112402112403112404112405112406112407112408112409112410112411112412112413112414112415112416112417112418112419112420112421112422112423112424112425112426112427112428112429112430112431112432112433112434112435112436112437112438112439112440112441112442112443112444112445112446112447112448112449112450112451112452112453112454112455112456112457112458112459112460112461112462112463112464112465112466112467112468112469112470112471112472112473112474112475112476112477112478112479112480112481112482112483112484112485112486112487112488112489112490112491112492112493112494112495112496112497112498112499112500112501112502112503112504112505112506112507112508112509112510112511112512112513112514112515112516112517112518112519112520112521112522112523112524112525112526112527112528112529112530112531112532112533112534112535112536112537112538112539112540112541112542112543112544112545112546112547112548112549112550112551112552112553112554112555112556112557112558112559112560112561112562112563112564112565112566112567112568112569112570112571112572112573112574112575112576112577112578112579112580112581112582112583112584112585112586112587112588112589112590112591112592112593112594112595112596112597112598112599112600112601112602112603112604112605112606112607112608112609112610112611112612112613112614112615112616112617112618112619112620112621112622112623112624112625112626112627112628112629112630112631112632112633112634112635112636112637112638112639112640112641112642112643112644112645112646112647112648112649112650112651112652112653112654112655112656112657112658112659112660112661112662112663112664112665112666112667112668112669112670112671112672112673112674112675112676112677112678112679112680112681112682112683112684112685112686112687112688112689112690112691112692112693112694112695112696112697112698112699112700112701112702112703112704112705112706112707112708112709112710112711112712112713112714112715112716112717112718112719112720112721112722112723112724112725112726112727112728112729112730112731112732112733112734112735112736112737112738112739112740112741112742112743112744112745112746112747112748112749112750112751112752112753112754112755112756112757112758112759112760112761112762112763112764112765112766112767112768112769112770112771112772112773112774112775112776112777112778112779112780112781112782112783112784112785112786112787112788112789112790112791112792112793112794112795112796112797112798112799112800112801112802112803112804112805112806112807112808112809112810112811112812112813112814112815112816112817112818112819112820112821112822112823112824112825112826112827112828112829112830112831112832112833112834112835112836112837112838112839112840112841112842112843112844112845112846112847112848112849112850112851112852112853112854112855112856112857112858112859112860112861112862112863112864112865112866112867112868112869112870112871112872112873112874112875112876112877112878112879112880112881112882112883112884112885112886112887112888112889112890112891112892112893112894112895112896112897112898112899112900112901112902112903112904112905112906112907112908112909112910112911112912112913112914112915112916112917112918112919112920112921112922112923112924112925112926112927112928112929112930112931112932112933112934112935112936112937112938112939112940112941112942112943112944112945112946112947112948112949112950112951112952112953112954112955112956112957112958112959112960112961112962112963112964112965112966112967112968112969112970112971112972112973112974112975112976112977112978112979112980112981112982112983112984112985112986112987112988112989112990112991112992112993112994112995112996112997112998112999113000113001113002113003113004113005113006113007113008113009113010113011113012113013113014113015113016113017113018113019113020113021113022113023113024113025113026113027113028113029113030113031113032113033113034113035113036113037113038113039113040113041113042113043113044113045113046113047113048113049113050113051113052113053113054113055113056113057113058113059113060113061113062113063113064113065113066113067113068113069113070113071113072113073113074113075113076113077113078113079113080113081113082113083113084113085113086113087113088113089113090113091113092113093113094113095113096113097113098113099113100113101113102113103113104113105113106113107113108113109113110113111113112113113113114113115113116113117113118113119113120113121113122113123113124113125113126113127113128113129113130113131113132113133113134113135113136113137113138113139113140113141113142113143113144113145113146113147113148113149113150113151113152113153113154113155113156113157113158113159113160113161113162113163113164113165113166113167113168113169113170113171113172113173113174113175113176113177113178113179113180113181113182113183113184113185113186113187113188113189113190113191113192113193113194113195113196113197113198113199113200113201113202113203113204113205113206113207113208113209113210113211113212113213113214113215113216113217113218113219113220113221113222113223113224113225113226113227113228113229113230113231113232113233113234113235113236113237113238113239113240113241113242113243113244113245113246113247113248113249113250113251113252113253113254113255113256113257113258113259113260113261113262113263113264113265113266113267113268113269113270113271113272113273113274113275113276113277113278113279113280113281113282113283113284113285113286113287113288113289113290113291113292113293113294113295113296113297113298113299113300113301113302113303113304113305113306113307113308113309113310113311113312113313113314113315113316113317113318113319113320113321113322113323113324113325113326113327113328113329113330113331113332113333113334113335113336113337113338113339113340113341113342113343113344113345113346113347113348113349113350113351113352113353113354113355113356113357113358113359113360113361113362113363113364113365113366113367113368113369113370113371113372113373113374113375113376113377113378113379113380113381113382113383113384113385113386113387113388113389113390113391113392113393113394113395113396113397113398113399113400113401113402113403113404113405113406113407113408113409113410113411113412113413113414113415113416113417113418113419113420113421113422113423113424113425113426113427113428113429113430113431113432113433113434113435113436113437113438113439113440113441113442113443113444113445113446113447113448113449113450113451113452113453113454113455113456113457113458113459113460113461113462113463113464113465113466113467113468113469113470113471113472113473113474113475113476113477113478113479113480113481113482113483113484113485113486113487113488113489113490113491113492113493113494113495113496113497113498113499113500113501113502113503113504113505113506113507113508113509113510113511113512113513113514113515113516113517113518113519113520113521113522113523113524113525113526113527113528113529113530113531113532113533113534113535113536113537113538113539113540113541113542113543113544113545113546113547113548113549113550113551113552113553113554113555113556113557113558113559113560113561113562113563113564113565113566113567113568113569113570113571113572113573113574113575113576113577113578113579113580113581113582113583113584113585113586113587113588113589113590113591113592113593113594113595113596113597113598113599113600113601113602113603113604113605113606113607113608113609113610113611113612113613113614113615113616113617113618113619113620113621113622113623113624113625113626113627113628113629113630113631113632113633113634113635113636113637113638113639113640113641113642113643113644113645113646113647113648113649113650113651113652113653113654113655113656113657113658113659113660113661113662113663113664113665113666113667113668113669113670113671113672113673113674113675113676113677113678113679113680113681113682113683113684113685113686113687113688113689113690113691113692113693113694113695113696113697113698113699113700113701113702113703113704113705113706113707113708113709113710113711113712113713113714113715113716113717113718113719113720113721113722113723113724113725113726113727113728113729113730113731113732113733113734113735113736113737113738113739113740113741113742113743113744113745113746113747113748113749113750113751113752113753113754113755113756113757113758113759113760113761113762113763113764113765113766113767113768113769113770113771113772113773113774113775113776113777113778113779113780113781113782113783113784113785113786113787113788113789113790113791113792113793113794113795113796113797113798113799113800113801113802113803113804113805113806113807113808113809113810113811113812113813113814113815113816113817113818113819113820113821113822113823113824113825113826113827113828113829113830113831113832113833113834113835113836113837113838113839113840113841113842113843113844113845113846113847113848113849113850113851113852113853113854113855113856113857113858113859113860113861113862113863113864113865113866113867113868113869113870113871113872113873113874113875113876113877113878113879113880113881113882113883113884113885113886113887113888113889113890113891113892113893113894113895113896113897113898113899113900113901113902113903113904113905113906113907113908113909113910113911113912113913113914113915113916113917113918113919113920113921113922113923113924113925113926113927113928113929113930113931113932113933113934113935113936113937113938113939113940113941113942113943113944113945113946113947113948113949113950113951113952113953113954113955113956113957113958113959113960113961113962113963113964113965113966113967113968113969113970113971113972113973113974113975113976113977113978113979113980113981113982113983113984113985113986113987113988113989113990113991113992113993113994113995113996113997113998113999114000114001114002114003114004114005114006114007114008114009114010114011114012114013114014114015114016114017114018114019114020114021114022114023114024114025114026114027114028114029114030114031114032114033114034114035114036114037114038114039114040114041114042114043114044114045114046114047114048114049114050114051114052114053114054114055114056114057114058114059114060114061114062114063114064114065114066114067114068114069114070114071114072114073114074114075114076114077114078114079114080114081114082114083114084114085114086114087114088114089114090114091114092114093114094114095114096114097114098114099114100114101114102114103114104114105114106114107114108114109114110114111114112114113114114114115114116114117114118114119114120114121114122114123114124114125114126114127114128114129114130114131114132114133114134114135114136114137114138114139114140114141114142114143114144114145114146114147114148114149114150114151114152114153114154114155114156114157114158114159114160114161114162114163114164114165114166114167114168114169114170114171114172114173114174114175114176114177114178114179114180114181114182114183114184114185114186114187114188114189114190114191114192114193114194114195114196114197114198114199114200114201114202114203114204114205114206114207114208114209114210114211114212114213114214114215114216114217114218114219114220114221114222114223114224114225114226114227114228114229114230114231114232114233114234114235114236114237114238114239114240114241114242114243114244114245114246114247114248114249114250114251114252114253114254114255114256114257114258114259114260114261114262114263114264114265114266114267114268114269114270114271114272114273114274114275114276114277114278114279114280114281114282114283114284114285114286114287114288114289114290114291114292114293114294114295114296114297114298114299114300114301114302114303114304114305114306114307114308114309114310114311114312114313114314114315114316114317114318114319114320114321114322114323114324114325114326114327114328114329114330114331114332114333114334114335114336114337114338114339114340114341114342114343114344114345114346114347114348114349114350114351114352114353114354114355114356114357114358114359114360114361114362114363114364114365114366114367114368114369114370114371114372114373114374114375114376114377114378114379114380114381114382114383114384114385114386114387114388114389114390114391114392114393114394114395114396114397114398114399114400114401114402114403114404114405114406114407114408114409114410114411114412114413114414114415114416114417114418114419114420114421114422114423114424114425114426114427114428114429114430114431114432114433114434114435114436114437114438114439114440114441114442114443114444114445114446114447114448114449114450114451114452114453114454114455114456114457114458114459114460114461114462114463114464114465114466114467114468114469114470114471114472114473114474114475114476114477114478114479114480114481114482114483114484114485114486114487114488114489114490114491114492114493114494114495114496114497114498114499114500114501114502114503114504114505114506114507114508114509114510114511114512114513114514114515114516114517114518114519114520114521114522114523114524114525114526114527114528114529114530114531114532114533114534114535114536114537114538114539114540114541114542114543114544114545114546114547114548114549114550114551114552114553114554114555114556114557114558114559114560114561114562114563114564114565114566114567114568114569114570114571114572114573114574114575114576114577114578114579114580114581114582114583114584114585114586114587114588114589114590114591114592114593114594114595114596114597114598114599114600114601114602114603114604114605114606114607114608114609114610114611114612114613114614114615114616114617114618114619114620114621114622114623114624114625114626114627114628114629114630114631114632114633114634114635114636114637114638114639114640114641114642114643114644114645114646114647114648114649114650114651114652114653114654114655114656114657114658114659114660114661114662114663114664114665114666114667114668114669114670114671114672114673114674114675114676114677114678114679114680114681114682114683114684114685114686114687114688114689114690114691114692114693114694114695114696114697114698114699114700114701114702114703114704114705114706114707114708114709114710114711114712114713114714114715114716114717114718114719114720114721114722114723114724114725114726114727114728114729114730114731114732114733114734114735114736114737114738114739114740114741114742114743114744114745114746114747114748114749114750114751114752114753114754114755114756114757114758114759114760114761114762114763114764114765114766114767114768114769114770114771114772114773114774114775114776114777114778114779114780114781114782114783114784114785114786114787114788114789114790114791114792114793114794114795114796114797114798114799114800114801114802114803114804114805114806114807114808114809114810114811114812114813114814114815114816114817114818114819114820114821114822114823114824114825114826114827114828114829114830114831114832114833114834114835114836114837114838114839114840114841114842114843114844114845114846114847114848114849114850114851114852114853114854114855114856114857114858114859114860114861114862114863114864114865114866114867114868114869114870114871114872114873114874114875114876114877114878114879114880114881114882114883114884114885114886114887114888114889114890114891114892114893114894114895114896114897114898114899114900114901114902114903114904114905114906114907114908114909114910114911114912114913114914114915114916114917114918114919114920114921114922114923114924114925114926114927114928114929114930114931114932114933114934114935114936114937114938114939114940114941114942114943114944114945114946114947114948114949114950114951114952114953114954114955114956114957114958114959114960114961114962114963114964114965114966114967114968114969114970114971114972114973114974114975114976114977114978114979114980114981114982114983114984114985114986114987114988114989114990114991114992114993114994114995114996114997114998114999115000115001115002115003115004115005115006115007115008115009115010115011115012115013115014115015115016115017115018115019115020115021115022115023115024115025115026115027115028115029115030115031115032115033115034115035115036115037115038115039115040115041115042115043115044115045115046115047115048115049115050115051115052115053115054115055115056115057115058115059115060115061115062115063115064115065115066115067115068115069115070115071115072115073115074115075115076115077115078115079115080115081115082115083115084115085115086115087115088115089115090115091115092115093115094115095115096115097115098115099115100115101115102115103115104115105115106115107115108115109115110115111115112115113115114115115115116115117115118115119115120115121115122115123115124115125115126115127115128115129115130115131115132115133115134115135115136115137115138115139115140115141115142115143115144115145115146115147115148115149115150115151115152115153115154115155115156115157115158115159115160115161115162115163115164115165115166115167115168115169115170115171115172115173115174115175115176115177115178115179115180115181115182115183115184115185115186115187115188115189115190115191115192115193115194115195115196115197115198115199115200115201115202115203115204115205115206115207115208115209115210115211115212115213115214115215115216115217115218115219115220115221115222115223115224115225115226115227115228115229115230115231115232115233115234115235115236115237115238115239115240115241115242115243115244115245115246115247115248115249115250115251115252115253115254115255115256115257115258115259115260115261115262115263115264115265115266115267115268115269115270115271115272115273115274115275115276115277115278115279115280115281115282115283115284115285115286115287115288115289115290115291115292115293115294115295115296115297115298115299115300115301115302115303115304115305115306115307115308115309115310115311115312115313115314115315115316115317115318115319115320115321115322115323115324115325115326115327115328115329115330115331115332115333115334115335115336115337115338115339115340115341115342115343115344115345115346115347115348115349115350115351115352115353115354115355115356115357115358115359115360115361115362115363115364115365115366115367115368115369115370115371115372115373115374115375115376115377115378115379115380115381115382115383115384115385115386115387115388115389115390115391115392115393115394115395115396115397115398115399115400115401115402115403115404115405115406115407115408115409115410115411115412115413115414115415115416115417115418115419115420115421115422115423115424115425115426115427115428115429115430115431115432115433115434115435115436115437115438115439115440115441115442115443115444115445115446115447115448115449115450115451115452115453115454115455115456115457115458115459115460115461115462115463115464115465115466115467115468115469115470115471115472115473115474115475115476115477115478115479115480115481115482115483115484115485115486115487115488115489115490115491115492115493115494115495115496115497115498115499115500115501115502115503115504115505115506115507115508115509115510115511115512115513115514115515115516115517115518115519115520115521115522115523115524115525115526115527115528115529115530115531115532115533115534115535115536115537115538115539115540115541115542115543115544115545115546115547115548115549115550115551115552115553115554115555115556115557115558115559115560115561115562115563115564115565115566115567115568115569115570115571115572115573115574115575115576115577115578115579115580115581115582115583115584115585115586115587115588115589115590115591115592115593115594115595115596115597115598115599115600115601115602115603115604115605115606115607115608115609115610115611115612115613115614115615115616115617115618115619115620115621115622115623115624115625115626115627115628115629115630115631115632115633115634115635115636115637115638115639115640115641115642115643115644115645115646115647115648115649115650115651115652115653115654115655115656115657115658115659115660115661115662115663115664115665115666115667115668115669115670115671115672115673115674115675115676115677115678115679115680115681115682115683115684115685115686115687115688115689115690115691115692115693115694115695115696115697115698115699115700115701115702115703115704115705115706115707115708115709115710115711115712115713115714115715115716115717115718115719115720115721115722115723115724115725115726115727115728115729115730115731115732115733115734115735115736115737115738115739115740115741115742115743115744115745115746115747115748115749115750115751115752115753115754115755115756115757115758115759115760115761115762115763115764115765115766115767115768115769115770115771115772115773115774115775115776115777115778115779115780115781115782115783115784115785115786115787115788115789115790115791115792115793115794115795115796115797115798115799115800115801115802115803115804115805115806115807115808115809115810115811115812115813115814115815115816115817115818115819115820115821115822115823115824115825115826115827115828115829115830115831115832115833115834115835115836115837115838115839115840115841115842115843115844115845115846115847115848115849115850115851115852115853115854115855115856115857115858115859115860115861115862115863115864115865115866115867115868115869115870115871115872115873115874115875115876115877115878115879115880115881115882115883115884115885115886115887115888115889115890115891115892115893115894115895115896115897115898115899115900115901115902115903115904115905115906115907115908115909115910115911115912115913115914115915115916115917115918115919115920115921115922115923115924115925115926115927115928115929115930115931115932115933115934115935115936115937115938115939115940115941115942115943115944115945115946115947115948115949115950115951115952115953115954115955115956115957115958115959115960115961115962115963115964115965115966115967115968115969115970115971115972115973115974115975115976115977115978115979115980115981115982115983115984115985115986115987115988115989115990115991115992115993115994115995115996115997115998115999116000116001116002116003116004116005116006116007116008116009116010116011116012116013116014116015116016116017116018116019116020116021116022116023116024116025116026116027116028116029116030116031116032116033116034116035116036116037116038116039116040116041116042116043116044116045116046116047116048116049116050116051116052116053116054116055116056116057116058116059116060116061116062116063116064116065116066116067116068116069116070116071116072116073116074116075116076116077116078116079116080116081116082116083116084116085116086116087116088116089116090116091116092116093116094116095116096116097116098116099116100116101116102116103116104116105116106116107116108116109116110116111116112116113116114116115116116116117116118116119116120116121116122116123116124116125116126116127116128116129116130116131116132116133116134116135116136116137116138116139116140116141116142116143116144116145116146116147116148116149116150116151116152116153116154116155116156116157116158116159116160116161116162116163116164116165116166116167116168116169116170116171116172116173116174116175116176116177116178116179116180116181116182116183116184116185116186116187116188116189116190116191116192116193116194116195116196116197116198116199116200116201116202116203116204116205116206116207116208116209116210116211116212116213116214116215116216116217116218116219116220116221116222116223116224116225116226116227116228116229116230116231116232116233116234116235116236116237116238116239116240116241116242116243116244116245116246116247116248116249116250116251116252116253116254116255116256116257116258116259116260116261116262116263116264116265116266116267116268116269116270116271116272116273116274116275116276116277116278116279116280116281116282116283116284116285116286116287116288116289116290116291116292116293116294116295116296116297116298116299116300116301116302116303116304116305116306116307116308116309116310116311116312116313116314116315116316116317116318116319116320116321116322116323116324116325116326116327116328116329116330116331116332116333116334116335116336116337116338116339116340116341116342116343116344116345116346116347116348116349116350116351116352116353116354116355116356116357116358116359116360116361116362116363116364116365116366116367116368116369116370116371116372116373116374116375116376116377116378116379116380116381116382116383116384116385116386116387116388116389116390116391116392116393116394116395116396116397116398116399116400116401116402116403116404116405116406116407116408116409116410116411116412116413116414116415116416116417116418116419116420116421116422116423116424116425116426116427116428116429116430116431116432116433116434116435116436116437116438116439116440116441116442116443116444116445116446116447116448116449116450116451116452116453116454116455116456116457116458116459116460116461116462116463116464116465116466116467116468116469116470116471116472116473116474116475116476116477116478116479116480116481116482116483116484116485116486116487116488116489116490116491116492116493116494116495116496116497116498116499116500116501116502116503116504116505116506116507116508116509116510116511116512116513116514116515116516116517116518116519116520116521116522116523116524116525116526116527116528116529116530116531116532116533116534116535116536116537116538116539116540116541116542116543116544116545116546116547116548116549116550116551116552116553116554116555116556116557116558116559116560116561116562116563116564116565116566116567116568116569116570116571116572116573116574116575116576116577116578116579116580116581116582116583116584116585116586116587116588116589116590116591116592116593116594116595116596116597116598116599116600116601116602116603116604116605116606116607116608116609116610116611116612116613116614116615116616116617116618116619116620116621116622116623116624116625116626116627116628116629116630116631116632116633116634116635116636116637116638116639116640116641116642116643116644116645116646116647116648116649116650116651116652116653116654116655116656116657116658116659116660116661116662116663116664116665116666116667116668116669116670116671116672116673116674116675116676116677116678116679116680116681116682116683116684116685116686116687116688116689116690116691116692116693116694116695116696116697116698116699116700116701116702116703116704116705116706116707116708116709116710116711116712116713116714116715116716116717116718116719116720116721116722116723116724116725116726116727116728116729116730116731116732116733116734116735116736116737116738116739116740116741116742116743116744116745116746116747116748116749116750116751116752116753116754116755116756116757116758116759116760116761116762116763116764116765116766116767116768116769116770116771116772116773116774116775116776116777116778116779116780116781116782116783116784116785116786116787116788116789116790116791116792116793116794116795116796116797116798116799116800116801116802116803116804116805116806116807116808116809116810116811116812116813116814116815116816116817116818116819116820116821116822116823116824116825116826116827116828116829116830116831116832116833116834116835116836116837116838116839116840116841116842116843116844116845116846116847116848116849116850116851116852116853116854116855116856116857116858116859116860116861116862116863116864116865116866116867116868116869116870116871116872116873116874116875116876116877116878116879116880116881116882116883116884116885116886116887116888116889116890116891116892116893116894116895116896116897116898116899116900116901116902116903116904116905116906116907116908116909116910116911116912116913116914116915116916116917116918116919116920116921116922116923116924116925116926116927116928116929116930116931116932116933116934116935116936116937116938116939116940116941116942116943116944116945116946116947116948116949116950116951116952116953116954116955116956116957116958116959116960116961116962116963116964116965116966116967116968116969116970116971116972116973116974116975116976116977116978116979116980116981116982116983116984116985116986116987116988116989116990116991116992116993116994116995116996116997116998116999117000117001117002117003117004117005117006117007117008117009117010117011117012117013117014117015117016117017117018117019117020117021117022117023117024117025117026117027117028117029117030117031117032117033117034117035117036117037117038117039117040117041117042117043117044117045117046117047117048117049117050117051117052117053117054117055117056117057117058117059117060117061117062117063117064117065117066117067117068117069117070117071117072117073117074117075117076117077117078117079117080117081117082117083117084117085117086117087117088117089117090117091117092117093117094117095117096117097117098117099117100117101117102117103117104117105117106117107117108117109117110117111117112117113117114117115117116117117117118117119117120117121117122117123117124117125117126117127117128117129117130117131117132117133117134117135117136117137117138117139117140117141117142117143117144117145117146117147117148117149117150117151117152117153117154117155117156117157117158117159117160117161117162117163117164117165117166117167117168117169117170117171117172117173117174117175117176117177117178117179117180117181117182117183117184117185117186117187117188117189117190117191117192117193117194117195117196117197117198117199117200117201117202117203117204117205117206117207117208117209117210117211117212117213117214117215117216117217117218117219117220117221117222117223117224117225117226117227117228117229117230117231117232117233117234117235117236117237117238117239117240117241117242117243117244117245117246117247117248117249117250117251117252117253117254117255117256117257117258117259117260117261117262117263117264117265117266117267117268117269117270117271117272117273117274117275117276117277117278117279117280117281117282117283117284117285117286117287117288117289117290117291117292117293117294117295117296117297117298117299117300117301117302117303117304117305117306117307117308117309117310117311117312117313117314117315117316117317117318117319117320117321117322117323117324117325117326117327117328117329117330117331117332117333117334117335117336117337117338117339117340117341117342117343117344117345117346117347117348117349117350117351117352117353117354117355117356117357117358117359117360117361117362117363117364117365117366117367117368117369117370117371117372117373117374117375117376117377117378117379117380117381117382117383117384117385117386117387117388117389117390117391117392117393117394117395117396117397117398117399117400117401117402117403117404117405117406117407117408117409117410117411117412117413117414117415117416117417117418117419117420117421117422117423117424117425117426117427117428117429117430117431117432117433117434117435117436117437117438117439117440117441117442117443117444117445117446117447117448117449117450117451117452117453117454117455117456117457117458117459117460117461117462117463117464117465117466117467117468117469117470117471117472117473117474117475117476117477117478117479117480117481117482117483117484117485117486117487117488117489117490117491117492117493117494117495117496117497117498117499117500117501117502117503117504117505117506117507117508117509117510117511117512117513117514117515117516117517117518117519117520117521117522117523117524117525117526117527117528117529117530117531117532117533117534117535117536117537117538117539117540117541117542117543117544117545117546117547117548117549117550117551117552117553117554117555117556117557117558117559117560117561117562117563117564117565117566117567117568117569117570117571117572117573117574117575117576117577117578117579117580117581117582117583117584117585117586117587117588117589117590117591117592117593117594117595117596117597117598117599117600117601117602117603117604117605117606117607117608117609117610117611117612117613117614117615117616117617117618117619117620117621117622117623117624117625117626117627117628117629117630117631117632117633117634117635117636117637117638117639117640117641117642117643117644117645117646117647117648117649117650117651117652117653117654117655117656117657117658117659117660117661117662117663117664117665117666117667117668117669117670117671117672117673117674117675117676117677117678117679117680117681117682117683117684117685117686117687117688117689117690117691117692117693117694117695117696117697117698117699117700117701117702117703117704117705117706117707117708117709117710117711117712117713117714117715117716117717117718117719117720117721117722117723117724117725117726117727117728117729117730117731117732117733117734117735117736117737117738117739117740117741117742117743117744117745117746117747117748117749117750117751117752117753117754117755117756117757117758117759117760117761117762117763117764117765117766117767117768117769117770117771117772117773117774117775117776117777117778117779117780117781117782117783117784117785117786117787117788117789117790117791117792117793117794117795117796117797117798117799117800117801117802117803117804117805117806117807117808117809117810117811117812117813117814117815117816117817117818117819117820117821117822117823117824117825117826117827117828117829117830117831117832117833117834117835117836117837117838117839117840117841117842117843117844117845117846117847117848117849117850117851117852117853117854117855117856117857117858117859117860117861117862117863117864117865117866117867117868117869117870117871117872117873117874117875117876117877117878117879117880117881117882117883117884117885117886117887117888117889117890117891117892117893117894117895117896117897117898117899117900117901117902117903117904117905117906117907117908117909117910117911117912117913117914117915117916117917117918117919117920117921117922117923117924117925117926117927117928117929117930117931117932117933117934117935117936117937117938117939117940117941117942117943117944117945117946117947117948117949117950117951117952117953117954117955117956117957117958117959117960117961117962117963117964117965117966117967117968117969117970117971117972117973117974117975117976117977117978117979117980117981117982117983117984117985117986117987117988117989117990117991117992117993117994117995117996117997117998117999118000118001118002118003118004118005118006118007118008118009118010118011118012118013118014118015118016118017118018118019118020118021118022118023118024118025118026118027118028118029118030118031118032118033118034118035118036118037118038118039118040118041118042118043118044118045118046118047118048118049118050118051118052118053118054118055118056118057118058118059118060118061118062118063118064118065118066118067118068118069118070118071118072118073118074118075118076118077118078118079118080118081118082118083118084118085118086118087118088118089118090118091118092118093118094118095118096118097118098118099118100118101118102118103118104118105118106118107118108118109118110118111118112118113118114118115118116118117118118118119118120118121118122118123118124118125118126118127118128118129118130118131118132118133118134118135118136118137118138118139118140118141118142118143118144118145118146118147118148118149118150118151118152118153118154118155118156118157118158118159118160118161118162118163118164118165118166118167118168118169118170118171118172118173118174118175118176118177118178118179118180118181118182118183118184118185118186118187118188118189118190118191118192118193118194118195118196118197118198118199118200118201118202118203118204118205118206118207118208118209118210118211118212118213118214118215118216118217118218118219118220118221118222118223118224118225118226118227118228118229118230118231118232118233118234118235118236118237118238118239118240118241118242118243118244118245118246118247118248118249118250118251118252118253118254118255118256118257118258118259118260118261118262118263118264118265118266118267118268118269118270118271118272118273118274118275118276118277118278118279118280118281118282118283118284118285118286118287118288118289118290118291118292118293118294118295118296118297118298118299118300118301118302118303118304118305118306118307118308118309118310118311118312118313118314118315118316118317118318118319118320118321118322118323118324118325118326118327118328118329118330118331118332118333118334118335118336118337118338118339118340118341118342118343118344118345118346118347118348118349118350118351118352118353118354118355118356118357118358118359118360118361118362118363118364118365118366118367118368118369118370118371118372118373118374118375118376118377118378118379118380118381118382118383118384118385118386118387118388118389118390118391118392118393118394118395118396118397118398118399118400118401118402118403118404118405118406118407118408118409118410118411118412118413118414118415118416118417118418118419118420118421118422118423118424118425118426118427118428118429118430118431118432118433118434118435118436118437118438118439118440118441118442118443118444118445118446118447118448118449118450118451118452118453118454118455118456118457118458118459118460118461118462118463118464118465118466118467118468118469118470118471118472118473118474118475118476118477118478118479118480118481118482118483118484118485118486118487118488118489118490118491118492118493118494118495118496118497118498118499118500118501118502118503118504118505118506118507118508118509118510118511118512118513118514118515118516118517118518118519118520118521118522118523118524118525118526118527118528118529118530118531118532118533118534118535118536118537118538118539118540118541118542118543118544118545118546118547118548118549118550118551118552118553118554118555118556118557118558118559118560118561118562118563118564118565118566118567118568118569118570118571118572118573118574118575118576118577118578118579118580118581118582118583118584118585118586118587118588118589118590118591118592118593118594118595118596118597118598118599118600118601118602118603118604118605118606118607118608118609118610118611118612118613118614118615118616118617118618118619118620118621118622118623118624118625118626118627118628118629118630118631118632118633118634118635118636118637118638118639118640118641118642118643118644118645118646118647118648118649118650118651118652118653118654118655118656118657118658118659118660118661118662118663118664118665118666118667118668118669118670118671118672118673118674118675118676118677118678118679118680118681118682118683118684118685118686118687118688118689118690118691118692118693118694118695118696118697118698118699118700118701118702118703118704118705118706118707118708118709118710118711118712118713118714118715118716118717118718118719118720118721118722118723118724118725118726118727118728118729118730118731118732118733118734118735118736118737118738118739118740118741118742118743118744118745118746118747118748118749118750118751118752118753118754118755118756118757118758118759118760118761118762118763118764118765118766118767118768118769118770118771118772118773118774118775118776118777118778118779118780118781118782118783118784118785118786118787118788118789118790118791118792118793118794118795118796118797118798118799118800118801118802118803118804118805118806118807118808118809118810118811118812118813118814118815118816118817118818118819118820118821118822118823118824118825118826118827118828118829118830118831118832118833118834118835118836118837118838118839118840118841118842118843118844118845118846118847118848118849118850118851118852118853118854118855118856118857118858118859118860118861118862118863118864118865118866118867118868118869118870118871118872118873118874118875118876118877118878118879118880118881118882118883118884118885118886118887118888118889118890118891118892118893118894118895118896118897118898118899118900118901118902118903118904118905118906118907118908118909118910118911118912118913118914118915118916118917118918118919118920118921118922118923118924118925118926118927118928118929118930118931118932118933118934118935118936118937118938118939118940118941118942118943118944118945118946118947118948118949118950118951118952118953118954118955118956118957118958118959118960118961118962118963118964118965118966118967118968118969118970118971118972118973118974118975118976118977118978118979118980118981118982118983118984118985118986118987118988118989118990118991118992118993118994118995118996118997118998118999119000119001119002119003119004119005119006119007119008119009119010119011119012119013119014119015119016119017119018119019119020119021119022119023119024119025119026119027119028119029119030119031119032119033119034119035119036119037119038119039119040119041119042119043119044119045119046119047119048119049119050119051119052119053119054119055119056119057119058119059119060119061119062119063119064119065119066119067119068119069119070119071119072119073119074119075119076119077119078119079119080119081119082119083119084119085119086119087119088119089119090119091119092119093119094119095119096119097119098119099119100119101119102119103119104119105119106119107119108119109119110119111119112119113119114119115119116119117119118119119119120119121119122119123119124119125119126119127119128119129119130119131119132119133119134119135119136119137119138119139119140119141119142119143119144119145119146119147119148119149119150119151119152119153119154119155119156119157119158119159119160119161119162119163119164119165119166119167119168119169119170119171119172119173119174119175119176119177119178119179119180119181119182119183119184119185119186119187119188119189119190119191119192119193119194119195119196119197119198119199119200119201119202119203119204119205119206119207119208119209119210119211119212119213119214119215119216119217119218119219119220119221119222119223119224119225119226119227119228119229119230119231119232119233119234119235119236119237119238119239119240119241119242119243119244119245119246119247119248119249119250119251119252119253119254119255119256119257119258119259119260119261119262119263119264119265119266119267119268119269119270119271119272119273119274119275119276119277119278119279119280119281119282119283119284119285119286119287119288119289119290119291119292119293119294119295119296119297119298119299119300119301119302119303119304119305119306119307119308119309119310119311119312119313119314119315119316119317119318119319119320119321119322119323119324119325119326119327119328119329119330119331119332119333119334119335119336119337119338119339119340119341119342119343119344119345119346119347119348119349119350119351119352119353119354119355119356119357119358119359119360119361119362119363119364119365119366119367119368119369119370119371119372119373119374119375119376119377119378119379119380119381119382119383119384119385119386119387119388119389119390119391119392119393119394119395119396119397119398119399119400119401119402119403119404119405119406119407119408119409119410119411119412119413119414119415119416119417119418119419119420119421119422119423119424119425119426119427119428119429119430119431119432119433119434119435119436119437119438119439119440119441119442119443119444119445119446119447119448119449119450119451119452119453119454119455119456119457119458119459119460119461119462119463119464119465119466119467119468119469119470119471119472119473119474119475119476119477119478119479119480119481119482119483119484119485119486119487119488119489119490119491119492119493119494119495119496119497119498119499119500119501119502119503119504119505119506119507119508119509119510119511119512119513119514119515119516119517119518119519119520119521119522119523119524119525119526119527119528119529119530119531119532119533119534119535119536119537119538119539119540119541119542119543119544119545119546119547119548119549119550119551119552119553119554119555119556119557119558119559119560119561119562119563119564119565119566119567119568119569119570119571119572119573119574119575119576119577119578119579119580119581119582119583119584119585119586119587119588119589119590119591119592119593119594119595119596119597119598119599119600119601119602119603119604119605119606119607119608119609119610119611119612119613119614119615119616119617119618119619119620119621119622119623119624119625119626119627119628119629119630119631119632119633119634119635119636119637119638119639119640119641119642119643119644119645119646119647119648119649119650119651119652119653119654119655119656119657119658119659119660119661119662119663119664119665119666119667119668119669119670119671119672119673119674119675119676119677119678119679119680119681119682119683119684119685119686119687119688119689119690119691119692119693119694119695119696119697119698119699119700119701119702119703119704119705119706119707119708119709119710119711119712119713119714119715119716119717119718119719119720119721119722119723119724119725119726119727119728119729119730119731119732119733119734119735119736119737119738119739119740119741119742119743119744119745119746119747119748119749119750119751119752119753119754119755119756119757119758119759119760119761119762119763119764119765119766119767119768119769119770119771119772119773119774119775119776119777119778119779119780119781119782119783119784119785119786119787119788119789119790119791119792119793119794119795119796119797119798119799119800119801119802119803119804119805119806119807119808119809119810119811119812119813119814119815119816119817119818119819119820119821119822119823119824119825119826119827119828119829119830119831119832119833119834119835119836119837119838119839119840119841119842119843119844119845119846119847119848119849119850119851119852119853119854119855119856119857119858119859119860119861119862119863119864119865119866119867119868119869119870119871119872119873119874119875119876119877119878119879119880119881119882119883119884119885119886119887119888119889119890119891119892119893119894119895119896119897119898119899119900119901119902119903119904119905119906119907119908119909119910119911119912119913119914119915119916119917119918119919119920119921119922119923119924119925119926119927119928119929119930119931119932119933119934119935119936119937119938119939119940119941119942119943119944119945119946119947119948119949119950119951119952119953119954119955119956119957119958119959119960119961119962119963119964119965119966119967119968119969119970119971119972119973119974119975119976119977119978119979119980119981119982119983119984119985119986119987119988119989119990119991119992119993119994119995119996119997119998119999120000120001120002120003120004120005120006120007120008120009120010120011120012120013120014120015120016120017120018120019120020120021120022120023120024120025120026120027120028120029120030120031120032120033120034120035120036120037120038120039120040120041120042120043120044120045120046120047120048120049120050120051120052120053120054120055120056120057120058120059120060120061120062120063120064120065120066120067120068120069120070120071120072120073120074120075120076120077120078120079120080120081120082120083120084120085120086120087120088120089120090120091120092120093120094120095120096120097120098120099120100120101120102120103120104120105120106120107120108120109120110120111120112120113120114120115120116120117120118120119120120120121120122120123120124120125120126120127120128120129120130120131120132120133120134120135120136120137120138120139120140120141120142120143120144120145120146120147120148120149120150120151120152120153120154120155120156120157120158120159120160120161120162120163120164120165120166120167120168120169120170120171120172120173120174120175120176120177120178120179120180120181120182120183120184120185120186120187120188120189120190120191120192120193120194120195120196120197120198120199120200120201120202120203120204120205120206120207120208120209120210120211120212120213120214120215120216120217120218120219120220120221120222120223120224120225120226120227120228120229120230120231120232120233120234120235120236120237120238120239120240120241120242120243120244120245120246120247120248120249120250120251120252120253120254120255120256120257120258120259120260120261120262120263120264120265120266120267120268120269120270120271120272120273120274120275120276120277120278120279120280120281120282120283120284120285120286120287120288120289120290120291120292120293120294120295120296120297120298120299120300120301120302120303120304120305120306120307120308120309120310120311120312120313120314120315120316120317120318120319120320120321120322120323120324120325120326120327120328120329120330120331120332120333120334120335120336120337120338120339120340120341120342120343120344120345120346120347120348120349120350120351120352120353120354120355120356120357120358120359120360120361120362120363120364120365120366120367120368120369120370120371120372120373120374120375120376120377120378120379120380120381120382120383120384120385120386120387120388120389120390120391120392120393120394120395120396120397120398120399120400120401120402120403120404120405120406120407120408120409120410120411120412120413120414120415120416120417120418120419120420120421120422120423120424120425120426120427120428120429120430120431120432120433120434120435120436120437120438120439120440120441120442120443120444120445120446120447120448120449120450120451120452120453120454120455120456120457120458120459120460120461120462120463120464120465120466120467120468120469120470120471120472120473120474120475120476120477120478120479120480120481120482120483120484120485120486120487120488120489120490120491120492120493120494120495120496120497120498120499120500120501120502120503120504120505120506120507120508120509120510120511120512120513120514120515120516120517120518120519120520120521120522120523120524120525120526120527120528120529120530120531120532120533120534120535120536120537120538120539120540120541120542120543120544120545120546120547120548120549120550120551120552120553120554120555120556120557120558120559120560120561120562120563120564120565120566120567120568120569120570120571120572120573120574120575120576120577120578120579120580120581120582120583120584120585120586120587120588120589120590120591120592120593120594120595120596120597120598120599120600120601120602120603120604120605120606120607120608120609120610120611120612120613120614120615120616120617120618120619120620120621120622120623120624120625120626120627120628120629120630120631120632120633120634120635120636120637120638120639120640120641120642120643120644120645120646120647120648120649120650120651120652120653120654120655120656120657120658120659120660120661120662120663120664120665120666120667120668120669120670120671120672120673120674120675120676120677120678120679120680120681120682120683120684120685120686120687120688120689120690120691120692120693120694120695120696120697120698120699120700120701120702120703120704120705120706120707120708120709120710120711120712120713120714120715120716120717120718120719120720120721120722120723120724120725120726120727120728120729120730120731120732120733120734120735120736120737120738120739120740120741120742120743120744120745120746120747120748120749120750120751120752120753120754120755120756120757120758120759120760120761120762120763120764120765120766120767120768120769120770120771120772120773120774120775120776120777120778120779120780120781120782120783120784120785120786120787120788120789120790120791120792120793120794120795120796120797120798120799120800120801120802120803120804120805120806120807120808120809120810120811120812120813120814120815120816120817120818120819120820120821120822120823120824120825120826120827120828120829120830120831120832120833120834120835120836120837120838120839120840120841120842120843120844120845120846120847120848120849120850120851120852120853120854120855120856120857120858120859120860120861120862120863120864120865120866120867120868120869120870120871120872120873120874120875120876120877120878120879120880120881120882120883120884120885120886120887120888120889120890120891120892120893120894120895120896120897120898120899120900120901120902120903120904120905120906120907120908120909120910120911120912120913120914120915120916120917120918120919120920120921120922120923120924120925120926120927120928120929120930120931120932120933120934120935120936120937120938120939120940120941120942120943120944120945120946120947120948120949120950120951120952120953120954120955120956120957120958120959120960120961120962120963120964120965120966120967120968120969120970120971120972120973120974120975120976120977120978120979120980120981120982120983120984120985120986120987120988120989120990120991120992120993120994120995120996120997120998120999121000121001121002121003121004121005121006121007121008121009121010121011121012121013121014121015121016121017121018121019121020121021121022121023121024121025121026121027121028121029121030121031121032121033121034121035121036121037121038121039121040121041121042121043121044121045121046121047121048121049121050121051121052121053121054121055121056121057121058121059121060121061121062121063121064121065121066121067121068121069121070121071121072121073121074121075121076121077121078121079121080121081121082121083121084121085121086121087121088121089121090121091121092121093121094121095121096121097121098121099121100121101121102121103121104121105121106121107121108121109121110121111121112121113121114121115121116121117121118121119121120121121121122121123121124121125121126121127121128121129121130121131121132121133121134121135121136121137121138121139121140121141121142121143121144121145121146121147121148121149121150121151121152121153121154121155121156121157121158121159121160121161121162121163121164121165121166121167121168121169121170121171121172121173121174121175121176121177121178121179121180121181121182121183121184121185121186121187121188121189121190121191121192121193121194121195121196121197121198121199121200121201121202121203121204121205121206121207121208121209121210121211121212121213121214121215121216121217121218121219121220121221121222121223121224121225121226121227121228121229121230121231121232121233121234121235121236121237121238121239121240121241121242121243121244121245121246121247121248121249121250121251121252121253121254121255121256121257121258121259121260121261121262121263121264121265121266121267121268121269121270121271121272121273121274121275121276121277121278121279121280121281121282121283121284121285121286121287121288121289121290121291121292121293121294121295121296121297121298121299121300121301121302121303121304121305121306121307121308121309121310121311121312121313121314121315121316121317121318121319121320121321121322121323121324121325121326121327121328121329121330121331121332121333121334121335121336121337121338121339121340121341121342121343121344121345121346121347121348121349121350121351121352121353121354121355121356121357121358121359121360121361121362121363121364121365121366121367121368121369121370121371121372121373121374121375121376121377121378121379121380121381121382121383121384121385121386121387121388121389121390121391121392121393121394121395121396121397121398121399121400121401121402121403121404121405121406121407121408121409121410121411121412121413121414121415121416121417121418121419121420121421121422121423121424121425121426121427121428121429121430121431121432121433121434121435121436121437121438121439121440121441121442121443121444121445121446121447121448121449121450121451121452121453121454121455121456121457121458121459121460121461121462121463121464121465121466121467121468121469121470121471121472121473121474121475121476121477121478121479121480121481121482121483121484121485121486121487121488121489121490121491121492121493121494121495121496121497121498121499121500121501121502121503121504121505121506121507121508121509121510121511121512121513121514121515121516121517121518121519121520121521121522121523121524121525121526121527121528121529121530121531121532121533121534121535121536121537121538121539121540121541121542121543121544121545121546121547121548121549121550121551121552121553121554121555121556121557121558121559121560121561121562121563121564121565121566121567121568121569121570121571121572121573121574121575121576121577121578121579121580121581121582121583121584121585121586121587121588121589121590121591121592121593121594121595121596121597121598121599121600121601121602121603121604121605121606121607121608121609121610121611121612121613121614121615121616121617121618121619121620121621121622121623121624121625121626121627121628121629121630121631121632121633121634121635121636121637121638121639121640121641121642121643121644121645121646121647121648121649121650121651121652121653121654121655121656121657121658121659121660121661121662121663121664121665121666121667121668121669121670121671121672121673121674121675121676121677121678121679121680121681121682121683121684121685121686121687121688121689121690121691121692121693121694121695121696121697121698121699121700121701121702121703121704121705121706121707121708121709121710121711121712121713121714121715121716121717121718121719121720121721121722121723121724121725121726121727121728121729121730121731121732121733121734121735121736121737121738121739121740121741121742121743121744121745121746121747121748121749121750121751121752121753121754121755121756121757121758121759121760121761121762121763121764121765121766121767121768121769121770121771121772121773121774121775121776121777121778121779121780121781121782121783121784121785121786121787121788121789121790121791121792121793121794121795121796121797121798121799121800121801121802121803121804121805121806121807121808121809121810121811121812121813121814121815121816121817121818121819121820121821121822121823121824121825121826121827121828121829121830121831121832121833121834121835121836121837121838121839121840121841121842121843121844121845121846121847121848121849121850121851121852121853121854121855121856121857121858121859121860121861121862121863121864121865121866121867121868121869121870121871121872121873121874121875121876121877121878121879121880121881121882121883121884121885121886121887121888121889121890121891121892121893121894121895121896121897121898121899121900121901121902121903121904121905121906121907121908121909121910121911121912121913121914121915121916121917121918121919121920121921121922121923121924121925121926121927121928121929121930121931121932121933121934121935121936121937121938121939121940121941121942121943121944121945121946121947121948121949121950121951121952121953121954121955121956121957121958121959121960121961121962121963121964121965121966121967121968121969121970121971121972121973121974121975121976121977121978121979121980121981121982121983121984121985121986121987121988121989121990121991121992121993121994121995121996121997121998121999122000122001122002122003122004122005122006122007122008122009122010122011122012122013122014122015122016122017122018122019122020122021122022122023122024122025122026122027122028122029122030122031122032122033122034122035122036122037122038122039122040122041122042122043122044122045122046122047122048122049122050122051122052122053122054122055122056122057122058122059122060122061122062122063122064122065122066122067122068122069122070122071122072122073122074122075122076122077122078122079122080122081122082122083122084122085122086122087122088122089122090122091122092122093122094122095122096122097122098122099122100122101122102122103122104122105122106122107122108122109122110122111122112122113122114122115122116122117122118122119122120122121122122122123122124122125122126122127122128122129122130122131122132122133122134122135122136122137122138122139122140122141122142122143122144122145122146122147122148122149122150122151122152122153122154122155122156122157122158122159122160122161122162122163122164122165122166122167122168122169122170122171122172122173122174122175122176122177122178122179122180122181122182122183122184122185122186122187122188122189122190122191122192122193122194122195122196122197122198122199122200122201122202122203122204122205122206122207122208122209122210122211122212122213122214122215122216122217122218122219122220122221122222122223122224122225122226122227122228122229122230122231122232122233122234122235122236122237122238122239122240122241122242122243122244122245122246122247122248122249122250122251122252122253122254122255122256122257122258122259122260122261122262122263122264122265122266122267122268122269122270122271122272122273122274122275122276122277122278122279122280122281122282122283122284122285122286122287122288122289122290122291122292122293122294122295122296122297122298122299122300122301122302122303122304122305122306122307122308122309122310122311122312122313122314122315122316122317122318122319122320122321122322122323122324122325122326122327122328122329122330122331122332122333122334122335122336122337122338122339122340122341122342122343122344122345122346122347122348122349122350122351122352122353122354122355122356122357122358122359122360122361122362122363122364122365122366122367122368122369122370122371122372122373122374122375122376122377122378122379122380122381122382122383122384122385122386122387122388122389122390122391122392122393122394122395122396122397122398122399122400122401122402122403122404122405122406122407122408122409122410122411122412122413122414122415122416122417122418122419122420122421122422122423122424122425122426122427122428122429122430122431122432122433122434122435122436122437122438122439122440122441122442122443122444122445122446122447122448122449122450122451122452122453122454122455122456122457122458122459122460122461122462122463122464122465122466122467122468122469122470122471122472122473122474122475122476122477122478122479122480122481122482122483122484122485122486122487122488122489122490122491122492122493122494122495122496122497122498122499122500122501122502122503122504122505122506122507122508122509122510122511122512122513122514122515122516122517122518122519122520122521122522122523122524122525122526122527122528122529122530122531122532122533122534122535122536122537122538122539122540122541122542122543122544122545122546122547122548122549122550122551122552122553122554122555122556122557122558122559122560122561122562122563122564122565122566122567122568122569122570122571122572122573122574122575122576122577122578122579122580122581122582122583122584122585122586122587122588122589122590122591122592122593122594122595122596122597122598122599122600122601122602122603122604122605122606122607122608122609122610122611122612122613122614122615122616122617122618122619122620122621122622122623122624122625122626122627122628122629122630122631122632122633122634122635122636122637122638122639122640122641122642122643122644122645122646122647122648122649122650122651122652122653122654122655122656122657122658122659122660122661122662122663122664122665122666122667122668122669122670122671122672122673122674122675122676122677122678122679122680122681122682122683122684122685122686122687122688122689122690122691122692122693122694122695122696122697122698122699122700122701122702122703122704122705122706122707122708122709122710122711122712122713122714122715122716122717122718122719122720122721122722122723122724122725122726122727122728122729122730122731122732122733122734122735122736122737122738122739122740122741122742122743122744122745122746122747122748122749122750122751122752122753122754122755122756122757122758122759122760122761122762122763122764122765122766122767122768122769122770122771122772122773122774122775122776122777122778122779122780122781122782122783122784122785122786122787122788122789122790122791122792122793122794122795122796122797122798122799122800122801122802122803122804122805122806122807122808122809122810122811122812122813122814122815122816122817122818122819122820122821122822122823122824122825122826122827122828122829122830122831122832122833122834122835122836122837122838122839122840122841122842122843122844122845122846122847122848122849122850122851122852122853122854122855122856122857122858122859122860122861122862122863122864122865122866122867122868122869122870122871122872122873122874122875122876122877122878122879122880122881122882122883122884122885122886122887122888122889122890122891122892122893122894122895122896122897122898122899122900122901122902122903122904122905122906122907122908122909122910122911122912122913122914122915122916122917122918122919122920122921122922122923122924122925122926122927122928122929122930122931122932122933122934122935122936122937122938122939122940122941122942122943122944122945122946122947122948122949122950122951122952122953122954122955122956122957122958122959122960122961122962122963122964122965122966122967122968122969122970122971122972122973122974122975122976122977122978122979122980122981122982122983122984122985122986122987122988122989122990122991122992122993122994122995122996122997122998122999123000123001123002123003123004123005123006123007123008123009123010123011123012123013123014123015123016123017123018123019123020123021123022123023123024123025123026123027123028123029123030123031123032123033123034123035123036123037123038123039123040123041123042123043123044123045123046123047123048123049123050123051123052123053123054123055123056123057123058123059123060123061123062123063123064123065123066123067123068123069123070123071123072123073123074123075123076123077123078123079123080123081123082123083123084123085123086123087123088123089123090123091123092123093123094123095123096123097123098123099123100123101123102123103123104123105123106123107123108123109123110123111123112123113123114123115123116123117123118123119123120123121123122123123123124123125123126123127123128123129123130123131123132123133123134123135123136123137123138123139123140123141123142123143123144123145123146123147123148123149123150123151123152123153123154123155123156123157123158123159123160123161123162123163123164123165123166123167123168123169123170123171123172123173123174123175123176123177123178123179123180123181123182123183123184123185123186123187123188123189123190123191123192123193123194123195123196123197123198123199123200123201123202123203123204123205123206123207123208123209123210123211123212123213123214123215123216123217123218123219123220123221123222123223123224123225123226123227123228123229123230123231123232123233123234123235123236123237123238123239123240123241123242123243123244123245123246123247123248123249123250123251123252123253123254123255123256123257123258123259123260123261123262123263123264123265123266123267123268123269123270123271123272123273123274123275123276123277123278123279123280123281123282123283123284123285123286123287123288123289123290123291123292123293123294123295123296123297123298123299123300123301123302123303123304123305123306123307123308123309123310123311123312123313123314123315123316123317123318123319123320123321123322123323123324123325123326123327123328123329123330123331123332123333123334123335123336123337123338123339123340123341123342123343123344123345123346123347123348123349123350123351123352123353123354123355123356123357123358123359123360123361123362123363123364123365123366123367123368123369123370123371123372123373123374123375123376123377123378123379123380123381123382123383123384123385123386123387123388123389123390123391123392123393123394123395123396123397123398123399123400123401123402123403123404123405123406123407123408123409123410123411123412123413123414123415123416123417123418123419123420123421123422123423123424123425123426123427123428123429123430123431123432123433123434123435123436123437123438123439123440123441123442123443123444123445123446123447123448123449123450123451123452123453123454123455123456123457123458123459123460123461123462123463123464123465123466123467123468123469123470123471123472123473123474123475123476123477123478123479123480123481123482123483123484123485123486123487123488123489123490123491123492123493123494123495123496123497123498123499123500123501123502123503123504123505123506123507123508123509123510123511123512123513123514123515123516123517123518123519123520123521123522123523123524123525123526123527123528123529123530123531123532123533123534123535123536123537123538123539123540123541123542123543123544123545123546123547123548123549123550123551123552123553123554123555123556123557123558123559123560123561123562123563123564123565123566123567123568123569123570123571123572123573123574123575123576123577123578123579123580123581123582123583123584123585123586123587123588123589123590123591123592123593123594123595123596123597123598123599123600123601123602123603123604123605123606123607123608123609123610123611123612123613123614123615123616123617123618123619123620123621123622123623123624123625123626123627123628123629123630123631123632123633123634123635123636123637123638123639123640123641123642123643123644123645123646123647123648123649123650123651123652123653123654123655123656123657123658123659123660123661123662123663123664123665123666123667123668123669123670123671123672123673123674123675123676123677123678123679123680123681123682123683123684123685123686123687123688123689123690123691123692123693123694123695123696123697123698123699123700123701123702123703123704123705123706123707123708123709123710123711123712123713123714123715123716123717123718123719123720123721123722123723123724123725123726123727123728123729123730123731123732123733123734123735123736123737123738123739123740123741123742123743123744123745123746123747123748123749123750123751123752123753123754123755123756123757123758123759123760123761123762123763123764123765123766123767123768123769123770123771123772123773123774123775123776123777123778123779123780123781123782123783123784123785123786123787123788123789123790123791123792123793123794123795123796123797123798123799123800123801123802123803123804123805123806123807123808123809123810123811123812123813123814123815123816123817123818123819123820123821123822123823123824123825123826123827123828123829123830123831123832123833123834123835123836123837123838123839123840123841123842123843123844123845123846123847123848123849123850123851123852123853123854123855123856123857123858123859123860123861123862123863123864123865123866123867123868123869123870123871123872123873123874123875123876123877123878123879123880123881123882123883123884123885123886123887123888123889123890123891123892123893123894123895123896123897123898123899123900123901123902123903123904123905123906123907123908123909123910123911123912123913123914123915123916123917123918123919123920123921123922123923123924123925123926123927123928123929123930123931123932123933123934123935123936123937123938123939123940123941123942123943123944123945123946123947123948123949123950123951123952123953123954123955123956123957123958123959123960123961123962123963123964123965123966123967123968123969123970123971123972123973123974123975123976123977123978123979123980123981123982123983123984123985123986123987123988123989123990123991123992123993123994123995123996123997123998123999124000124001124002124003124004124005124006124007124008124009124010124011124012124013124014124015124016124017124018124019124020124021124022124023124024124025124026124027124028124029124030124031124032124033124034124035124036124037124038124039124040124041124042124043124044124045124046124047124048124049124050124051124052124053124054124055124056124057124058124059124060124061124062124063124064124065124066124067124068124069124070124071124072124073124074124075124076124077124078124079124080124081124082124083124084124085124086124087124088124089124090124091124092124093124094124095124096124097124098124099124100124101124102124103124104124105124106124107124108124109124110124111124112124113124114124115124116124117124118124119124120124121124122124123124124124125124126124127124128124129124130124131124132124133124134124135124136124137124138124139124140124141124142124143124144124145124146124147124148124149124150124151124152124153124154124155124156124157124158124159124160124161124162124163124164124165124166124167124168124169124170124171124172124173124174124175124176124177124178124179124180124181124182124183124184124185124186124187124188124189124190124191124192124193124194124195124196124197124198124199124200124201124202124203124204124205124206124207124208124209124210124211124212124213124214124215124216124217124218124219124220124221124222124223124224124225124226124227124228124229124230124231124232124233124234124235124236124237124238124239124240124241124242124243124244124245124246124247124248124249124250124251124252124253124254124255124256124257124258124259124260124261124262124263124264124265124266124267124268124269124270124271124272124273124274124275124276124277124278124279124280124281124282124283124284124285124286124287124288124289124290124291124292124293124294124295124296124297124298124299124300124301124302124303124304124305124306124307124308124309124310124311124312124313124314124315124316124317124318124319124320124321124322124323124324124325124326124327124328124329124330124331124332124333124334124335124336124337124338124339124340124341124342124343124344124345124346124347124348124349124350124351124352124353124354124355124356124357124358124359124360124361124362124363124364124365124366124367124368124369124370124371124372124373124374124375124376124377124378124379124380124381124382124383124384124385124386124387124388124389124390124391124392124393124394124395124396124397124398124399124400124401124402124403124404124405124406124407124408124409124410124411124412124413124414124415124416124417124418124419124420124421124422124423124424124425124426124427124428124429124430124431124432124433124434124435124436124437124438124439124440124441124442124443124444124445124446124447124448124449124450124451124452124453124454124455124456124457124458124459124460124461124462124463124464124465124466124467124468124469124470124471124472124473124474124475124476124477124478124479124480124481124482124483124484124485124486124487124488124489124490124491124492124493124494124495124496124497124498124499124500124501124502124503124504124505124506124507124508124509124510124511124512124513124514124515124516124517124518124519124520124521124522124523124524124525124526124527124528124529124530124531124532124533124534124535124536124537124538124539124540124541124542124543124544124545124546124547124548124549124550124551124552124553124554124555124556124557124558124559124560124561124562124563124564124565124566124567124568124569124570124571124572124573124574124575124576124577124578124579124580124581124582124583124584124585124586124587124588124589124590124591124592124593124594124595124596124597124598124599124600124601124602124603124604124605124606124607124608124609124610124611124612124613124614124615124616124617124618124619124620124621124622124623124624124625124626124627124628124629124630124631124632124633124634124635124636124637124638124639124640124641124642124643124644124645124646124647124648124649124650124651124652124653124654124655124656124657124658124659124660124661124662124663124664124665124666124667124668124669124670124671124672124673124674124675124676124677124678124679124680124681124682124683124684124685124686124687124688124689124690124691124692124693124694124695124696124697124698124699124700124701124702124703124704124705124706124707124708124709124710124711124712124713124714124715124716124717124718124719124720124721124722124723124724124725124726124727124728124729124730124731124732124733124734124735124736124737124738124739124740124741124742124743124744124745124746124747124748124749124750124751124752124753124754124755124756124757124758124759124760124761124762124763124764124765124766124767124768124769124770124771124772124773124774124775124776124777124778124779124780124781124782124783124784124785124786124787124788124789124790124791124792124793124794124795124796124797124798124799124800124801124802124803124804124805124806124807124808124809124810124811124812124813124814124815124816124817124818124819124820124821124822124823124824124825124826124827124828124829124830124831124832124833124834124835124836124837124838124839124840124841124842124843124844124845124846124847124848124849124850124851124852124853124854124855124856124857124858124859124860124861124862124863124864124865124866124867124868124869124870124871124872124873124874124875124876124877124878124879124880124881124882124883124884124885124886124887124888124889124890124891124892124893124894124895124896124897124898124899124900124901124902124903124904124905124906124907124908124909124910124911124912124913124914124915124916124917124918124919124920124921124922124923124924124925124926124927124928124929124930124931124932124933124934124935124936124937124938124939124940124941124942124943124944124945124946124947124948124949124950124951124952124953124954124955124956124957124958124959124960124961124962124963124964124965124966124967124968124969124970124971124972124973124974124975124976124977124978124979124980124981124982124983124984124985124986124987124988124989124990124991124992124993124994124995124996124997124998124999125000125001125002125003125004125005125006125007125008125009125010125011125012125013125014125015125016125017125018125019125020125021125022125023125024125025125026125027125028125029125030125031125032125033125034125035125036125037125038125039125040125041125042125043125044125045125046125047125048125049125050125051125052125053125054125055125056125057125058125059125060125061125062125063125064125065125066125067125068125069125070125071125072125073125074125075125076125077125078125079125080125081125082125083125084125085125086125087125088125089125090125091125092125093125094125095125096125097125098125099125100125101125102125103125104125105125106125107125108125109125110125111125112125113125114125115125116125117125118125119125120125121125122125123125124125125125126125127125128125129125130125131125132125133125134125135125136125137125138125139125140125141125142125143125144125145125146125147125148125149125150125151125152125153125154125155125156125157125158125159125160125161125162125163125164125165125166125167125168125169125170125171125172125173125174125175125176125177125178125179125180125181125182125183125184125185125186125187125188125189125190125191125192125193125194125195125196125197125198125199125200125201125202125203125204125205125206125207125208125209125210125211125212125213125214125215125216125217125218125219125220125221125222125223125224125225125226125227125228125229125230125231125232125233125234125235125236125237125238125239125240125241125242125243125244125245125246125247125248125249125250125251125252125253125254125255125256125257125258125259125260125261125262125263125264125265125266125267125268125269125270125271125272125273125274125275125276125277125278125279125280125281125282125283125284125285125286125287125288125289125290125291125292125293125294125295125296125297125298125299125300125301125302125303125304125305125306125307125308125309125310125311125312125313125314125315125316125317125318125319125320125321125322125323125324125325125326125327125328125329125330125331125332125333125334125335125336125337125338125339125340125341125342125343125344125345125346125347125348125349125350125351125352125353125354125355125356125357125358125359125360125361125362125363125364125365125366125367125368125369125370125371125372125373125374125375125376125377125378125379125380125381125382125383125384125385125386125387125388125389125390125391125392125393125394125395125396125397125398125399125400125401125402125403125404125405125406125407125408125409125410125411125412125413125414125415125416125417125418125419125420125421125422125423125424125425125426125427125428125429125430125431125432125433125434125435125436125437125438125439125440125441125442125443125444125445125446125447125448125449125450125451125452125453125454125455125456125457125458125459125460125461125462125463125464125465125466125467125468125469125470125471125472125473125474125475125476125477125478125479125480125481125482125483125484125485125486125487125488125489125490125491125492125493125494125495125496125497125498125499125500125501125502125503125504125505125506125507125508125509125510125511125512125513125514125515125516125517125518125519125520125521125522125523125524125525125526125527125528125529125530125531125532125533125534125535125536125537125538125539125540125541125542125543125544125545125546125547125548125549125550125551125552125553125554125555125556125557125558125559125560125561125562125563125564125565125566125567125568125569125570125571125572125573125574125575125576125577125578125579125580125581125582125583125584125585125586125587125588125589125590125591125592125593125594125595125596125597125598125599125600125601125602125603125604125605125606125607125608125609125610125611125612125613125614125615125616125617125618125619125620125621125622125623125624125625125626125627125628125629125630125631125632125633125634125635125636125637125638125639125640125641125642125643125644125645125646125647125648125649125650125651125652125653125654125655125656125657125658125659125660125661125662125663125664125665125666125667125668125669125670125671125672125673125674125675125676125677125678125679125680125681125682125683125684125685125686125687125688125689125690125691125692125693125694125695125696125697125698125699125700125701125702125703125704125705125706125707125708125709125710125711125712125713125714125715125716125717125718125719125720125721125722125723125724125725125726125727125728125729125730125731125732125733125734125735125736125737125738125739125740125741125742125743125744125745125746125747125748125749125750125751125752125753125754125755125756125757125758125759125760125761125762125763125764125765125766125767125768125769125770125771125772125773125774125775125776125777125778125779125780125781125782125783125784125785125786125787125788125789125790125791125792125793125794125795125796125797125798125799125800125801125802125803125804125805125806125807125808125809125810125811125812125813125814125815125816125817125818125819125820125821125822125823125824125825125826125827125828125829125830125831125832125833125834125835125836125837125838125839125840125841125842125843125844125845125846125847125848125849125850125851125852125853125854125855125856125857125858125859125860125861125862125863125864125865125866125867125868125869125870125871125872125873125874125875125876125877125878125879125880125881125882125883125884125885125886125887125888125889125890125891125892125893125894125895125896125897125898125899125900125901125902125903125904125905125906125907125908125909125910125911125912125913125914125915125916125917125918125919125920125921125922125923125924125925125926125927125928125929125930125931125932125933125934125935125936125937125938125939125940125941125942125943125944125945125946125947125948125949125950125951125952125953125954125955125956125957125958125959125960125961125962125963125964125965125966125967125968125969125970125971125972125973125974125975125976125977125978125979125980125981125982125983125984125985125986125987125988125989125990125991125992125993125994125995125996125997125998125999126000126001126002126003126004126005126006126007126008126009126010126011126012126013126014126015126016126017126018126019126020126021126022126023126024126025126026126027126028126029126030126031126032126033126034126035126036126037126038126039126040126041126042126043126044126045126046126047126048126049126050126051126052126053126054126055126056126057126058126059126060126061126062126063126064126065126066126067126068126069126070126071126072126073126074126075126076126077126078126079126080126081126082126083126084126085126086126087126088126089126090126091126092126093126094126095126096126097126098126099126100126101126102126103126104126105126106126107126108126109126110126111126112126113126114126115126116126117126118126119126120126121126122126123126124126125126126126127126128126129126130126131126132126133126134126135126136126137126138126139126140126141126142126143126144126145126146126147126148126149126150126151126152126153126154126155126156126157126158126159126160126161126162126163126164126165126166126167126168126169126170126171126172126173126174126175126176126177126178126179126180126181126182126183126184126185126186126187126188126189126190126191126192126193126194126195126196126197126198126199126200126201126202126203126204126205126206126207126208126209126210126211126212126213126214126215126216126217126218126219126220126221126222126223126224126225126226126227126228126229126230126231126232126233126234126235126236126237126238126239126240126241126242126243126244126245126246126247126248126249126250126251126252126253126254126255126256126257126258126259126260126261126262126263126264126265126266126267126268126269126270126271126272126273126274126275126276126277126278126279126280126281126282126283126284126285126286126287126288126289126290126291126292126293126294126295126296126297126298126299126300126301126302126303126304126305126306126307126308126309126310126311126312126313126314126315126316126317126318126319126320126321126322126323126324126325126326126327126328126329126330126331126332126333126334126335126336126337126338126339126340126341126342126343126344126345126346126347126348126349126350126351126352126353126354126355126356126357126358126359126360126361126362126363126364126365126366126367126368126369126370126371126372126373126374126375126376126377126378126379126380126381126382126383126384126385126386126387126388126389126390126391126392126393126394126395126396126397126398126399126400126401126402126403126404126405126406126407126408126409126410126411126412126413126414126415126416126417126418126419126420126421126422126423126424126425126426126427126428126429126430126431126432126433126434126435126436126437126438126439126440126441126442126443126444126445126446126447126448126449126450126451126452126453126454126455126456126457126458126459126460126461126462126463126464126465126466126467126468126469126470126471126472126473126474126475126476126477126478126479126480126481126482126483126484126485126486126487126488126489126490126491126492126493126494126495126496126497126498126499126500126501126502126503126504126505126506126507126508126509126510126511126512126513126514126515126516126517126518126519126520126521126522126523126524126525126526126527126528126529126530126531126532126533126534126535126536126537126538126539126540126541126542126543126544126545126546126547126548126549126550126551126552126553126554126555126556126557126558126559126560126561126562126563126564126565126566126567126568126569126570126571126572126573126574126575126576126577126578126579126580126581126582126583126584126585126586126587126588126589126590126591126592126593126594126595126596126597126598126599126600126601126602126603126604126605126606126607126608126609126610126611126612126613126614126615126616126617126618126619126620126621126622126623126624126625126626126627126628126629126630126631126632126633126634126635126636126637126638126639126640126641126642126643126644126645126646126647126648126649126650126651126652126653126654126655126656126657126658126659126660126661126662126663126664126665126666126667126668126669126670126671126672126673126674126675126676126677126678126679126680126681126682126683126684126685126686126687126688126689126690126691126692126693126694126695126696126697126698126699126700126701126702126703126704126705126706126707126708126709126710126711126712126713126714126715126716126717126718126719126720126721126722126723126724126725126726126727126728126729126730126731126732126733126734126735126736126737126738126739126740126741126742126743126744126745126746126747126748126749126750126751126752126753126754126755126756126757126758126759126760126761126762126763126764126765126766126767126768126769126770126771126772126773126774126775126776126777126778126779126780126781126782126783126784126785126786126787126788126789126790126791126792126793126794126795126796126797126798126799126800126801126802126803126804126805126806126807126808126809126810126811126812126813126814126815126816126817126818126819126820126821126822126823126824126825126826126827126828126829126830126831126832126833126834126835126836126837126838126839126840126841126842126843126844126845126846126847126848126849126850126851126852126853126854126855126856126857126858126859126860126861126862126863126864126865126866126867126868126869126870126871126872126873126874126875126876126877126878126879126880126881126882126883126884126885126886126887126888126889126890126891126892126893126894126895126896126897126898126899126900126901126902126903126904126905126906126907126908126909126910126911126912126913126914126915126916126917126918126919126920126921126922126923126924126925126926126927126928126929126930126931126932126933126934126935126936126937126938126939126940126941126942126943126944126945126946126947126948126949126950126951126952126953126954126955126956126957126958126959126960126961126962126963126964126965126966126967126968126969126970126971126972126973126974126975126976126977126978126979126980126981126982126983126984126985126986126987126988126989126990126991126992126993126994126995126996126997126998126999127000127001127002127003127004127005127006127007127008127009127010127011127012127013127014127015127016127017127018127019127020127021127022127023127024127025127026127027127028127029127030127031127032127033127034127035127036127037127038127039127040127041127042127043127044127045127046127047127048127049127050127051127052127053127054127055127056127057127058127059127060127061127062127063127064127065127066127067127068127069127070127071127072127073127074127075127076127077127078127079127080127081127082127083127084127085127086127087127088127089127090127091127092127093127094127095127096127097127098127099127100127101127102127103127104127105127106127107127108127109127110127111127112127113127114127115127116127117127118127119127120127121127122127123127124127125127126127127127128127129127130127131127132127133127134127135127136127137127138127139127140127141127142127143127144127145127146127147127148127149127150127151127152127153127154127155127156127157127158127159127160127161127162127163127164127165127166127167127168127169127170127171127172127173127174127175127176127177127178127179127180127181127182127183127184127185127186127187127188127189127190127191127192127193127194127195127196127197127198127199127200127201127202127203127204127205127206127207127208127209127210127211127212127213127214127215127216127217127218127219127220127221127222127223127224127225127226127227127228127229127230127231127232127233127234127235127236127237127238127239127240127241127242127243127244127245127246127247127248127249127250127251127252127253127254127255127256127257127258127259127260127261127262127263127264127265127266127267127268127269127270127271127272127273127274127275127276127277127278127279127280127281127282127283127284127285127286127287127288127289127290127291127292127293127294127295127296127297127298127299127300127301127302127303127304127305127306127307127308127309127310127311127312127313127314127315127316127317127318127319127320127321127322127323127324127325127326127327127328127329127330127331127332127333127334127335127336127337127338127339127340127341127342127343127344127345127346127347127348127349127350127351127352127353127354127355127356127357127358127359127360127361127362127363127364127365127366127367127368127369127370127371127372127373127374127375127376127377127378127379127380127381127382127383127384127385127386127387127388127389127390127391127392127393127394127395127396127397127398127399127400127401127402127403127404127405127406127407127408127409127410127411127412127413127414127415127416127417127418127419127420127421127422127423127424127425127426127427127428127429127430127431127432127433127434127435127436127437127438127439127440127441127442127443127444127445127446127447127448127449127450127451127452127453127454127455127456127457127458127459127460127461127462127463127464127465127466127467127468127469127470127471127472127473127474127475127476127477127478127479127480127481127482127483127484127485127486127487127488127489127490127491127492127493127494127495127496127497127498127499127500127501127502127503127504127505127506127507127508127509127510127511127512127513127514127515127516127517127518127519127520127521127522127523127524127525127526127527127528127529127530127531127532127533127534127535127536127537127538127539127540127541127542127543127544127545127546127547127548127549127550127551127552127553127554127555127556127557127558127559127560127561127562127563127564127565127566127567127568127569127570127571127572127573127574127575127576127577127578127579127580127581127582127583127584127585127586127587127588127589127590127591127592127593127594127595127596127597127598127599127600127601127602127603127604127605127606127607127608127609127610127611127612127613127614127615127616127617127618127619127620127621127622127623127624127625127626127627127628127629127630127631127632127633127634127635127636127637127638127639127640127641127642127643127644127645127646127647127648127649127650127651127652127653127654127655127656127657127658127659127660127661127662127663127664127665127666127667127668127669127670127671127672127673127674127675127676127677127678127679127680127681127682127683127684127685127686127687127688127689127690127691127692127693127694127695127696127697127698127699127700127701127702127703127704127705127706127707127708127709127710127711127712127713127714127715127716127717127718127719127720127721127722127723127724127725127726127727127728127729127730127731127732127733127734127735127736127737127738127739127740127741127742127743127744127745127746127747127748127749127750127751127752127753127754127755127756127757127758127759127760127761127762127763127764127765127766127767127768127769127770127771127772127773127774127775127776127777127778127779127780127781127782127783127784127785127786127787127788127789127790127791127792127793127794127795127796127797127798127799127800127801127802127803127804127805127806127807127808127809127810127811127812127813127814127815127816127817127818127819127820127821127822127823127824127825127826127827127828127829127830127831127832127833127834127835127836127837127838127839127840127841127842127843127844127845127846127847127848127849127850127851127852127853127854127855127856127857127858127859127860127861127862127863127864127865127866127867127868127869127870127871127872127873127874127875127876127877127878127879127880127881127882127883127884127885127886127887127888127889127890127891127892127893127894127895127896127897127898127899127900127901127902127903127904127905127906127907127908127909127910127911127912127913127914127915127916127917127918127919127920127921127922127923127924127925127926127927127928127929127930127931127932127933127934127935127936127937127938127939127940127941127942127943127944127945127946127947127948127949127950127951127952127953127954127955127956127957127958127959127960127961127962127963127964127965127966127967127968127969127970127971127972127973127974127975127976127977127978127979127980127981127982127983127984127985127986127987127988127989127990127991127992127993127994127995127996127997127998127999128000128001128002128003128004128005128006128007128008128009128010128011128012128013128014128015128016128017128018128019128020128021128022128023128024128025128026128027128028128029128030128031128032128033128034128035128036128037128038128039128040128041128042128043128044128045128046128047128048128049128050128051128052128053128054128055128056128057128058128059128060128061128062128063128064128065128066128067128068128069128070128071128072128073128074128075128076128077128078128079128080128081128082128083128084128085128086128087128088128089128090128091128092128093128094128095128096128097128098128099128100128101128102128103128104128105128106128107128108128109128110128111128112128113128114128115128116128117128118128119128120128121128122128123128124128125128126128127128128128129128130128131128132128133128134128135128136128137128138128139128140128141128142128143128144128145128146128147128148128149128150128151128152128153128154128155128156128157128158128159128160128161128162128163128164128165128166128167128168128169128170128171128172128173128174128175128176128177128178128179128180128181128182128183128184128185128186128187128188128189128190128191128192128193128194128195128196128197128198128199128200128201128202128203128204128205128206128207128208128209128210128211128212128213128214128215128216128217128218128219128220128221128222128223128224128225128226128227128228128229128230128231128232128233128234128235128236128237128238128239128240128241128242128243128244128245128246128247128248128249128250128251128252128253128254128255128256128257128258128259128260128261128262128263128264128265128266128267128268128269128270128271128272128273128274128275128276128277128278128279128280128281128282128283128284128285128286128287128288128289128290128291128292128293128294128295128296128297128298128299128300128301128302128303128304128305128306128307128308128309128310128311128312128313128314128315128316128317128318128319128320128321128322128323128324128325128326128327128328128329128330128331128332128333128334128335128336128337128338128339128340128341128342128343128344128345128346128347128348128349128350128351128352128353128354128355128356128357128358128359128360128361128362128363128364128365128366128367128368128369128370128371128372128373128374128375128376128377128378128379128380128381128382128383128384128385128386128387128388128389128390128391128392128393128394128395128396128397128398128399128400128401128402128403128404128405128406128407128408128409128410128411128412128413128414128415128416128417128418128419128420128421128422128423128424128425128426128427128428128429128430128431128432128433128434128435128436128437128438128439128440128441128442128443128444128445128446128447128448128449128450128451128452128453128454128455128456128457128458128459128460128461128462128463128464128465128466128467128468128469128470128471128472128473128474128475128476128477128478128479128480128481128482128483128484128485128486128487128488128489128490128491128492128493128494128495128496128497128498128499128500128501128502128503128504128505128506128507128508128509128510128511128512128513128514128515128516128517128518128519128520128521128522128523128524128525128526128527128528128529128530128531128532128533128534128535128536128537128538128539128540128541128542128543128544128545128546128547128548128549128550128551128552128553128554128555128556128557128558128559128560128561128562128563128564128565128566128567128568128569128570128571128572128573128574128575128576128577128578128579128580128581128582128583128584128585128586128587128588128589128590128591128592128593128594128595128596128597128598128599128600128601128602128603128604128605128606128607128608128609128610128611128612128613128614128615128616128617128618128619128620128621128622128623128624128625128626128627128628128629128630128631128632128633128634128635128636128637128638128639128640128641128642128643128644128645128646128647128648128649128650128651128652128653128654128655128656128657128658128659128660128661128662128663128664128665128666128667128668128669128670128671128672128673128674128675128676128677128678128679128680128681128682128683128684128685128686128687128688128689128690128691128692128693128694128695128696128697128698128699128700128701128702128703128704128705128706128707128708128709128710128711128712128713128714128715128716128717128718128719128720128721128722128723128724128725128726128727128728128729128730128731128732128733128734128735128736128737128738128739128740128741128742128743128744128745128746128747128748128749128750128751128752128753128754128755128756128757128758128759128760128761128762128763128764128765128766128767128768128769128770128771128772128773128774128775128776128777128778128779128780128781128782128783128784128785128786128787128788128789128790128791128792128793128794128795128796128797128798128799128800128801128802128803128804128805128806128807128808128809128810128811128812128813128814128815128816128817128818128819128820128821128822128823128824128825128826128827128828128829128830128831128832128833128834128835128836128837128838128839128840128841128842128843128844128845128846128847128848128849128850128851128852128853128854128855128856128857128858128859128860128861128862128863128864128865128866128867128868128869128870128871128872128873128874128875128876128877128878128879128880128881128882128883128884128885128886128887128888128889128890128891128892128893128894128895128896128897128898128899128900128901128902128903128904128905128906128907128908128909128910128911128912128913128914128915128916128917128918128919128920128921128922128923128924128925128926128927128928128929128930128931128932128933128934128935128936128937128938128939128940128941128942128943128944128945128946128947128948128949128950128951128952128953128954128955128956128957128958128959128960128961128962128963128964128965128966128967128968128969128970128971128972128973128974128975128976128977128978128979128980128981128982128983128984128985128986128987128988128989128990128991128992128993128994128995128996128997128998128999129000129001129002129003129004129005129006129007129008129009129010129011129012129013129014129015129016129017129018129019129020129021129022129023129024129025129026129027129028129029129030129031129032129033129034129035129036129037129038129039129040129041129042129043129044129045129046129047129048129049129050129051129052129053129054129055129056129057129058129059129060129061129062129063129064129065129066129067129068129069129070129071129072129073129074129075129076129077129078129079129080129081129082129083129084129085129086129087129088129089129090129091129092129093129094129095129096129097129098129099129100129101129102129103129104129105129106129107129108129109129110129111129112129113129114129115129116129117129118129119129120129121129122129123129124129125129126129127129128129129129130129131129132129133129134129135129136129137129138129139129140129141129142129143129144129145129146129147129148129149129150129151129152129153129154129155129156129157129158129159129160129161129162129163129164129165129166129167129168129169129170129171129172129173129174129175129176129177129178129179129180129181129182129183129184129185129186129187129188129189129190129191129192129193129194129195129196129197129198129199129200129201129202129203129204129205129206129207129208129209129210129211129212129213129214129215129216129217129218129219129220129221129222129223129224129225129226129227129228129229129230129231129232129233129234129235129236129237129238129239129240129241129242129243129244129245129246129247129248129249129250129251129252129253129254129255129256129257129258129259129260129261129262129263129264129265129266129267129268129269129270129271129272129273129274129275129276129277129278129279129280129281129282129283129284129285129286129287129288129289129290129291129292129293129294129295129296129297129298129299129300129301129302129303129304129305129306129307129308129309129310129311129312129313129314129315129316129317129318129319129320129321129322129323129324129325129326129327129328129329129330129331129332129333129334129335129336129337129338129339129340129341129342129343129344129345129346129347129348129349129350129351129352129353129354129355129356129357129358129359129360129361129362129363129364129365129366129367129368129369129370129371129372129373129374129375129376129377129378129379129380129381129382129383129384129385129386129387129388129389129390129391129392129393129394129395129396129397129398129399129400129401129402129403129404129405129406129407129408129409129410129411129412129413129414129415129416129417129418129419129420129421129422129423129424129425129426129427129428129429129430129431129432129433129434129435129436129437129438129439129440129441129442129443129444129445129446129447129448129449129450129451129452129453129454129455129456129457129458129459129460129461129462129463129464129465129466129467129468129469129470129471129472129473129474129475129476129477129478129479129480129481129482129483129484129485129486129487129488129489129490129491129492129493129494129495129496129497129498129499129500129501129502129503129504129505129506129507129508129509129510129511129512129513129514129515129516129517129518129519129520129521129522129523129524129525129526129527129528129529129530129531129532129533129534129535129536129537129538129539129540129541129542129543129544129545129546129547129548129549129550129551129552129553129554129555129556129557129558129559129560129561129562129563129564129565129566129567129568129569129570129571129572129573129574129575129576129577129578129579129580129581129582129583129584129585129586129587129588129589129590129591129592129593129594129595129596129597129598129599129600129601129602129603129604129605129606129607129608129609129610129611129612129613129614129615129616129617129618129619129620129621129622129623129624129625129626129627129628129629129630129631129632129633129634129635129636129637129638129639129640129641129642129643129644129645129646129647129648129649129650129651129652129653129654129655129656129657129658129659129660129661129662129663129664129665129666129667129668129669129670129671129672129673129674129675129676129677129678129679129680129681129682129683129684129685129686129687129688129689129690129691129692129693129694129695129696129697129698129699129700129701129702129703129704129705129706129707129708129709129710129711129712129713129714129715129716129717129718129719129720129721129722129723129724129725129726129727129728129729129730129731129732129733129734129735129736129737129738129739129740129741129742129743129744129745129746129747129748129749129750129751129752129753129754129755129756129757129758129759129760129761129762129763129764129765129766129767129768129769129770129771129772129773129774129775129776129777129778129779129780129781129782129783129784129785129786129787129788129789129790129791129792129793129794129795129796129797129798129799129800129801129802129803129804129805129806129807129808129809129810129811129812129813129814129815129816129817129818129819129820129821129822129823129824129825129826129827129828129829129830129831129832129833129834129835129836129837129838129839129840129841129842129843129844129845129846129847129848129849129850129851129852129853129854129855129856129857129858129859129860129861129862129863129864129865129866129867129868129869129870129871129872129873129874129875129876129877129878129879129880129881129882129883129884129885129886129887129888129889129890129891129892129893129894129895129896129897129898129899129900129901129902129903129904129905129906129907129908129909129910129911129912129913129914129915129916129917129918129919129920129921129922129923129924129925129926129927129928129929129930129931129932129933129934129935129936129937129938129939129940129941129942129943129944129945129946129947129948129949129950129951129952129953129954129955129956129957129958129959129960129961129962129963129964129965129966129967129968129969129970129971129972129973129974129975129976129977129978129979129980129981129982129983129984129985129986129987129988129989129990129991129992129993129994129995129996129997129998129999130000130001130002130003130004130005130006130007130008130009130010130011130012130013130014130015130016130017130018130019130020130021130022130023130024130025130026130027130028130029130030130031130032130033130034130035130036130037130038130039130040130041130042130043130044130045130046130047130048130049130050130051130052130053130054130055130056130057130058130059130060130061130062130063130064130065130066130067130068130069130070130071130072130073130074130075130076130077130078130079130080130081130082130083130084130085130086130087130088130089130090130091130092130093130094130095130096130097130098130099130100130101130102130103130104130105130106130107130108130109130110130111130112130113130114130115130116130117130118130119130120130121130122130123130124130125130126130127130128130129130130130131130132130133130134130135130136130137130138130139130140130141130142130143130144130145130146130147130148130149130150130151130152130153130154130155130156130157130158130159130160130161130162130163130164130165130166130167130168130169130170130171130172130173130174130175130176130177130178130179130180130181130182130183130184130185130186130187130188130189130190130191130192130193130194130195130196130197130198130199130200130201130202130203130204130205130206130207130208130209130210130211130212130213130214130215130216130217130218130219130220130221130222130223130224130225130226130227130228130229130230130231130232130233130234130235130236130237130238130239130240130241130242130243130244130245130246130247130248130249130250130251130252130253130254130255130256130257130258130259130260130261130262130263130264130265130266130267130268130269130270130271130272130273130274130275130276130277130278130279130280130281130282130283130284130285130286130287130288130289130290130291130292130293130294130295130296130297130298130299130300130301130302130303130304130305130306130307130308130309130310130311130312130313130314130315130316130317130318130319130320130321130322130323130324130325130326130327130328130329130330130331130332130333130334130335130336130337130338130339130340130341130342130343130344130345130346130347130348130349130350130351130352130353130354130355130356130357130358130359130360130361130362130363130364130365130366130367130368130369130370130371130372130373130374130375130376130377130378130379130380130381130382130383130384130385130386130387130388130389130390130391130392130393130394130395130396130397130398130399130400130401130402130403130404130405130406130407130408130409130410130411130412130413130414130415130416130417130418130419130420130421130422130423130424130425130426130427130428130429130430130431130432130433130434130435130436130437130438130439130440130441130442130443130444130445130446130447130448130449130450130451130452130453130454130455130456130457130458130459130460130461130462130463130464130465130466130467130468130469130470130471130472130473130474130475130476130477130478130479130480130481130482130483130484130485130486130487130488130489130490130491130492130493130494130495130496130497130498130499130500130501130502130503130504130505130506130507130508130509130510130511130512130513130514130515130516130517130518130519130520130521130522130523130524130525130526130527130528130529130530130531130532130533130534130535130536130537130538130539130540130541130542130543130544130545130546130547130548130549130550130551130552130553130554130555130556130557130558130559130560130561130562130563130564130565130566130567130568130569130570130571130572130573130574130575130576130577130578130579130580130581130582130583130584130585130586130587130588130589130590130591130592130593130594130595130596130597130598130599130600130601130602130603130604130605130606130607130608130609130610130611130612130613130614130615130616130617130618130619130620130621130622130623130624130625130626130627130628130629130630130631130632130633130634130635130636130637130638130639130640130641130642130643130644130645130646130647130648130649130650130651130652130653130654130655130656130657130658130659130660130661130662130663130664130665130666130667130668130669130670130671130672130673130674130675130676130677130678130679130680130681130682130683130684130685130686130687130688130689130690130691130692130693130694130695130696130697130698130699130700130701130702130703130704130705130706130707130708130709130710130711130712130713130714130715130716130717130718130719130720130721130722130723130724130725130726130727130728130729130730130731130732130733130734130735130736130737130738130739130740130741130742130743130744130745130746130747130748130749130750130751130752130753130754130755130756130757130758130759130760130761130762130763130764130765130766130767130768130769130770130771130772130773130774130775130776130777130778130779130780130781130782130783130784130785130786130787130788130789130790130791130792130793130794130795130796130797130798130799130800130801130802130803130804130805130806130807130808130809130810130811130812130813130814130815130816130817130818130819130820130821130822130823130824130825130826130827130828130829130830130831130832130833130834130835130836130837130838130839130840130841130842130843130844130845130846130847130848130849130850130851130852130853130854130855130856130857130858130859130860130861130862130863130864130865130866130867130868130869130870130871130872130873130874130875130876130877130878130879130880130881130882130883130884130885130886130887130888130889130890130891130892130893130894130895130896130897130898130899130900130901130902130903130904130905130906130907130908130909130910130911130912130913130914130915130916130917130918130919130920130921130922130923130924130925130926130927130928130929130930130931130932130933130934130935130936130937130938130939130940130941130942130943130944130945130946130947130948130949130950130951130952130953130954130955130956130957130958130959130960130961130962130963130964130965130966130967130968130969130970130971130972130973130974130975130976130977130978130979130980130981130982130983130984130985130986130987130988130989130990130991130992130993130994130995130996130997130998130999131000131001131002131003131004131005131006131007131008131009131010131011131012131013131014131015131016131017131018131019131020131021131022131023131024131025131026131027131028131029131030131031131032131033131034131035131036131037131038131039131040131041131042131043131044131045131046131047131048131049131050131051131052131053131054131055131056131057131058131059131060131061131062131063131064131065131066131067131068131069131070131071131072131073131074131075131076131077131078131079131080131081131082131083131084131085131086131087131088131089131090131091131092131093131094131095131096131097131098131099131100131101131102131103131104131105131106131107131108131109131110131111131112131113131114131115131116131117131118131119131120131121131122131123131124131125131126131127131128131129131130131131131132131133131134131135131136131137131138131139131140131141131142131143131144131145131146131147131148131149131150131151131152131153131154131155131156131157131158131159131160131161131162131163131164131165131166131167131168131169131170131171131172131173131174131175131176131177131178131179131180131181131182131183131184131185131186131187131188131189131190131191131192131193131194131195131196131197131198131199131200131201131202131203131204131205131206131207131208131209131210131211131212131213131214131215131216131217131218131219131220131221131222131223131224131225131226131227131228131229131230131231131232131233131234131235131236131237131238131239131240131241131242131243131244131245131246131247131248131249131250131251131252131253131254131255131256131257131258131259131260131261131262131263131264131265131266131267131268131269131270131271131272131273131274131275131276131277131278131279131280131281131282131283131284131285131286131287131288131289131290131291131292131293131294131295131296131297131298131299131300131301131302131303131304131305131306131307131308131309131310131311131312131313131314131315131316131317131318131319131320131321131322131323131324131325131326131327131328131329131330131331131332131333131334131335131336131337131338131339131340131341131342131343131344131345131346131347131348131349131350131351131352131353131354131355131356131357131358131359131360131361131362131363131364131365131366131367131368131369131370131371131372131373131374131375131376131377131378131379131380131381131382131383131384131385131386131387131388131389131390131391131392131393131394131395131396131397131398131399131400131401131402131403131404131405131406131407131408131409131410131411131412131413131414131415131416131417131418131419131420131421131422131423131424131425131426131427131428131429131430131431131432131433131434131435131436131437131438131439131440131441131442131443131444131445131446131447131448131449131450131451131452131453131454131455131456131457131458131459131460131461131462131463131464131465131466131467131468131469131470131471131472131473131474131475131476131477131478131479131480131481131482131483131484131485131486131487131488131489131490131491131492131493131494131495131496131497131498131499131500131501131502131503131504131505131506131507131508131509131510131511131512131513131514131515131516131517131518131519131520131521131522131523131524131525131526131527131528131529131530131531131532131533131534131535131536131537131538131539131540131541131542131543131544131545131546131547131548131549131550131551131552131553131554131555131556131557131558131559131560131561131562131563131564131565131566131567131568131569131570131571131572131573131574131575131576131577131578131579131580131581131582131583131584131585131586131587131588131589131590131591131592131593131594131595131596131597131598131599131600131601131602131603131604131605131606131607131608131609131610131611131612131613131614131615131616131617131618131619131620131621131622131623131624131625131626131627131628131629131630131631131632131633131634131635131636131637131638131639131640131641131642131643131644131645131646131647131648131649131650131651131652131653131654131655131656131657131658131659131660131661131662131663131664131665131666131667131668131669131670131671131672131673131674131675131676131677131678131679131680131681131682131683131684131685131686131687131688131689131690131691131692131693131694131695131696131697131698131699131700131701131702131703131704131705131706131707131708131709131710131711131712131713131714131715131716131717131718131719131720131721131722131723131724131725131726131727131728131729131730131731131732131733131734131735131736131737131738131739131740131741131742131743131744131745131746131747131748131749131750131751131752131753131754131755131756131757131758131759131760131761131762131763131764131765131766131767131768131769131770131771131772131773131774131775131776131777131778131779131780131781131782131783131784131785131786131787131788131789131790131791131792131793131794131795131796131797131798131799131800131801131802131803131804131805131806131807131808131809131810131811131812131813131814131815131816131817131818131819131820131821131822131823131824131825131826131827131828131829131830131831131832131833131834131835131836131837131838131839131840131841131842131843131844131845131846131847131848131849131850131851131852131853131854131855131856131857131858131859131860131861131862131863131864131865131866131867131868131869131870131871131872131873131874131875131876131877131878131879131880131881131882131883131884131885131886131887131888131889131890131891131892131893131894131895131896131897131898131899131900131901131902131903131904131905131906131907131908131909131910131911131912131913131914131915131916131917131918131919131920131921131922131923131924131925131926131927131928131929131930131931131932131933131934131935131936131937131938131939131940131941131942131943131944131945131946131947131948131949131950131951131952131953131954131955131956131957131958131959131960131961131962131963131964131965131966131967131968131969131970131971131972131973131974131975131976131977131978131979131980131981131982131983131984131985131986131987131988131989131990131991131992131993131994131995131996131997131998131999132000132001132002132003132004132005132006132007132008132009132010132011132012132013132014132015132016132017132018132019132020132021132022132023132024132025132026132027132028132029132030132031132032132033132034132035132036132037132038132039132040132041132042132043132044132045132046132047132048132049132050132051132052132053132054132055132056132057132058132059132060132061132062132063132064132065132066132067132068132069132070132071132072132073132074132075132076132077132078132079132080132081132082132083132084132085132086132087132088132089132090132091132092132093132094132095132096132097132098132099132100132101132102132103132104132105132106132107132108132109132110132111132112132113132114132115132116132117132118132119132120132121132122132123132124132125132126132127132128132129132130132131132132132133132134132135132136132137132138132139132140132141132142132143132144132145132146132147132148132149132150132151132152132153132154132155132156132157132158132159132160132161132162132163132164132165132166132167132168132169132170132171132172132173132174132175132176132177132178132179132180132181132182132183132184132185132186132187132188132189132190132191132192132193132194132195132196132197132198132199132200132201132202132203132204132205132206132207132208132209132210132211132212132213132214132215132216132217132218132219132220132221132222132223132224132225132226132227132228132229132230132231132232132233132234132235132236132237132238132239132240132241132242132243132244132245132246132247132248132249132250132251132252132253132254132255132256132257132258132259132260132261132262132263132264132265132266132267132268132269132270132271132272132273132274132275132276132277132278132279132280132281132282132283132284132285132286132287132288132289132290132291132292132293132294132295132296132297132298132299132300132301132302132303132304132305132306132307132308132309132310132311132312132313132314132315132316132317132318132319132320132321132322132323132324132325132326132327132328132329132330132331132332132333132334132335132336132337132338132339132340132341132342132343132344132345132346132347132348132349132350132351132352132353132354132355132356132357132358132359132360132361132362132363132364132365132366132367132368132369132370132371132372132373132374132375132376132377132378132379132380132381132382132383132384132385132386132387132388132389132390132391132392132393132394132395132396132397132398132399132400132401132402132403132404132405132406132407132408132409132410132411132412132413132414132415132416132417132418132419132420132421132422132423132424132425132426132427132428132429132430132431132432132433132434132435132436132437132438132439132440132441132442132443132444132445132446132447132448132449132450132451132452132453132454132455132456132457132458132459132460132461132462132463132464132465132466132467132468132469132470132471132472132473132474132475132476132477132478132479132480132481132482132483132484132485132486132487132488132489132490132491132492132493132494132495132496132497132498132499132500132501132502132503132504132505132506132507132508132509132510132511132512132513132514132515132516132517132518132519132520132521132522132523132524132525132526132527132528132529132530132531132532132533132534132535132536132537132538132539132540132541132542132543132544132545132546132547132548132549132550132551132552132553132554132555132556132557132558132559132560132561132562132563132564132565132566132567132568132569132570132571132572132573132574132575132576132577132578132579132580132581132582132583132584132585132586132587132588132589132590132591132592132593132594132595132596132597132598132599132600132601132602132603132604132605132606132607132608132609132610132611132612132613132614132615132616132617132618132619132620132621132622132623132624132625132626132627132628132629132630132631132632132633132634132635132636132637132638132639132640132641132642132643132644132645132646132647132648132649132650132651132652132653132654132655132656132657132658132659132660132661132662132663132664132665132666132667132668132669132670132671132672132673132674132675132676132677132678132679132680132681132682132683132684132685132686132687132688132689132690132691132692132693132694132695132696132697132698132699132700132701132702132703132704132705132706132707132708132709132710132711132712132713132714132715132716132717132718132719132720132721132722132723132724132725132726132727132728132729132730132731132732132733132734132735132736132737132738132739132740132741132742132743132744132745132746132747132748132749132750132751132752132753132754132755132756132757132758132759132760132761132762132763132764132765132766132767132768132769132770132771132772132773132774132775132776132777132778132779132780132781132782132783132784132785132786132787132788132789132790132791132792132793132794132795132796132797132798132799132800132801132802132803132804132805132806132807132808132809132810132811132812132813132814132815132816132817132818132819132820132821132822132823132824132825132826132827132828132829132830132831132832132833132834132835132836132837132838132839132840132841132842132843132844132845132846132847132848132849132850132851132852132853132854132855132856132857132858132859132860132861132862132863132864132865132866132867132868132869132870132871132872132873132874132875132876132877132878132879132880132881132882132883132884132885132886132887132888132889132890132891132892132893132894132895132896132897132898132899132900132901132902132903132904132905132906132907132908132909132910132911132912132913132914132915132916132917132918132919132920132921132922132923132924132925132926132927132928132929132930132931132932132933132934132935132936132937132938132939132940132941132942132943132944132945132946132947132948132949132950132951132952132953132954132955132956132957132958132959132960132961132962132963132964132965132966132967132968132969132970132971132972132973132974132975132976132977132978132979132980132981132982132983132984132985132986132987132988132989132990132991132992132993132994132995132996132997132998132999133000133001133002133003133004133005133006133007133008133009133010133011133012133013133014133015133016133017133018133019133020133021133022133023133024133025133026133027133028133029133030133031133032133033133034133035133036133037133038133039133040133041133042133043133044133045133046133047133048133049133050133051133052133053133054133055133056133057133058133059133060133061133062133063133064133065133066133067133068133069133070133071133072133073133074133075133076133077133078133079133080133081133082133083133084133085133086133087133088133089133090133091133092133093133094133095133096133097133098133099133100133101133102133103133104133105133106133107133108133109133110133111133112133113133114133115133116133117133118133119133120133121133122133123133124133125133126133127133128133129133130133131133132133133133134133135133136133137133138133139133140133141133142133143133144133145133146133147133148133149133150133151133152133153133154133155133156133157133158133159133160133161133162133163133164133165133166133167133168133169133170133171133172133173133174133175133176133177133178133179133180133181133182133183133184133185133186133187133188133189133190133191133192133193133194133195133196133197133198133199133200133201133202133203133204133205133206133207133208133209133210133211133212133213133214133215133216133217133218133219133220133221133222133223133224133225133226133227133228133229133230133231133232133233133234133235133236133237133238133239133240133241133242133243133244133245133246133247133248133249133250133251133252133253133254133255133256133257133258133259133260133261133262133263133264133265133266133267133268133269133270133271133272133273133274133275133276133277133278133279133280133281133282133283133284133285133286133287133288133289133290133291133292133293133294133295133296133297133298133299133300133301133302133303133304133305133306133307133308133309133310133311133312133313133314133315133316133317133318133319133320133321133322133323133324133325133326133327133328133329133330133331133332133333133334133335133336133337133338133339133340133341133342133343133344133345133346133347133348133349133350133351133352133353133354133355133356133357133358133359133360133361133362133363133364133365133366133367133368133369133370133371133372133373133374133375133376133377133378133379133380133381133382133383133384133385133386133387133388133389133390133391133392133393133394133395133396133397133398133399133400133401133402133403133404133405133406133407133408133409133410133411133412133413133414133415133416133417133418133419133420133421133422133423133424133425133426133427133428133429133430133431133432133433133434133435133436133437133438133439133440133441133442133443133444133445133446133447133448133449133450133451133452133453133454133455133456133457133458133459133460133461133462133463133464133465133466133467133468133469133470133471133472133473133474133475133476133477133478133479133480133481133482133483133484133485133486133487133488133489133490133491133492133493133494133495133496133497133498133499133500133501133502133503133504133505133506133507133508133509133510133511133512133513133514133515133516133517133518133519133520133521133522133523133524133525133526133527133528133529133530133531133532133533133534133535133536133537133538133539133540133541133542133543133544133545133546133547133548133549133550133551133552133553133554133555133556133557133558133559133560133561133562133563133564133565133566133567133568133569133570133571133572133573133574133575133576133577133578133579133580133581133582133583133584133585133586133587133588133589133590133591133592133593133594133595133596133597133598133599133600133601133602133603133604133605133606133607133608133609133610133611133612133613133614133615133616133617133618133619133620133621133622133623133624133625133626133627133628133629133630133631133632133633133634133635133636133637133638133639133640133641133642133643133644133645133646133647133648133649133650133651133652133653133654133655133656133657133658133659133660133661133662133663133664133665133666133667133668133669133670133671133672133673133674133675133676133677133678133679133680133681133682133683133684133685133686133687133688133689133690133691133692133693133694133695133696133697133698133699133700133701133702133703133704133705133706133707133708133709133710133711133712133713133714133715133716133717133718133719133720133721133722133723133724133725133726133727133728133729133730133731133732133733133734133735133736133737133738133739133740133741133742133743133744133745133746133747133748133749133750133751133752133753133754133755133756133757133758133759133760133761133762133763133764133765133766133767133768133769133770133771133772133773133774133775133776133777133778133779133780133781133782133783133784133785133786133787133788133789133790133791133792133793133794133795133796133797133798133799133800133801133802133803133804133805133806133807133808133809133810133811133812133813133814133815133816133817133818133819133820133821133822133823133824133825133826133827133828133829133830133831133832133833133834133835133836133837133838133839133840133841133842133843133844133845133846133847133848133849133850133851133852133853133854133855133856133857133858133859133860133861133862133863133864133865133866133867133868133869133870133871133872133873133874133875133876133877133878133879133880133881133882133883133884133885133886133887133888133889133890133891133892133893133894133895133896133897133898133899133900133901133902133903133904133905133906133907133908133909133910133911133912133913133914133915133916133917133918133919133920133921133922133923133924133925133926133927133928133929133930133931133932133933133934133935133936133937133938133939133940133941133942133943133944133945133946133947133948133949133950133951133952133953133954133955133956133957133958133959133960133961133962133963133964133965133966133967133968133969133970133971133972133973133974133975133976133977133978133979133980133981133982133983133984133985133986133987133988133989133990133991133992133993133994133995133996133997133998133999134000134001134002134003134004134005134006134007134008134009134010134011134012134013134014134015134016134017134018134019134020134021134022134023134024134025134026134027134028134029134030134031134032134033134034134035134036134037134038134039134040134041134042134043134044134045134046134047134048134049134050134051134052134053134054134055134056134057134058134059134060134061134062134063134064134065134066134067134068134069134070134071134072134073134074134075134076134077134078134079134080134081134082134083134084134085134086134087134088134089134090134091134092134093134094134095134096134097134098134099134100134101134102134103134104134105134106134107134108134109134110134111134112134113134114134115134116134117134118134119134120134121134122134123134124134125134126134127134128134129134130134131134132134133134134134135134136134137134138134139134140134141134142134143134144134145134146134147134148134149134150134151134152134153134154134155134156134157134158134159134160134161134162134163134164134165134166134167134168134169134170134171134172134173134174134175134176134177134178134179134180134181134182134183134184134185134186134187134188134189134190134191134192134193134194134195134196134197134198134199134200134201134202134203134204134205134206134207134208134209134210134211134212134213134214134215134216134217134218134219134220134221134222134223134224134225134226134227134228134229134230134231134232134233134234134235134236134237134238134239134240134241134242134243134244134245134246134247134248134249134250134251134252134253134254134255134256134257134258134259134260134261134262134263134264134265134266134267134268134269134270134271134272134273134274134275134276134277134278134279134280134281134282134283134284134285134286134287134288134289134290134291134292134293134294134295134296134297134298134299134300134301134302134303134304134305134306134307134308134309134310134311134312134313134314134315134316134317134318134319134320134321134322134323134324134325134326134327134328134329134330134331134332134333134334134335134336134337134338134339134340134341134342134343134344134345134346134347134348134349134350134351134352134353134354134355134356134357134358134359134360134361134362134363134364134365134366134367134368134369134370134371134372134373134374134375134376134377134378134379134380134381134382134383134384134385134386134387134388134389134390134391134392134393134394134395134396134397134398134399134400134401134402134403134404134405134406134407134408134409134410134411134412134413134414134415134416134417134418134419134420134421134422134423134424134425134426134427134428134429134430134431134432134433134434134435134436134437134438134439134440134441134442134443134444134445134446134447134448134449134450134451134452134453134454134455134456134457134458134459134460134461134462134463134464134465134466134467134468134469134470134471134472134473134474134475134476134477134478134479134480134481134482134483134484134485134486134487134488134489134490134491134492134493134494134495134496134497134498134499134500134501134502134503134504134505134506134507134508134509134510134511134512134513134514134515134516134517134518134519134520134521134522134523134524134525134526134527134528134529134530134531134532134533134534134535134536134537134538134539134540134541134542134543134544134545134546134547134548134549134550134551134552134553134554134555134556134557134558134559134560134561134562134563134564134565134566134567134568134569134570134571134572134573134574134575134576134577134578134579134580134581134582134583134584134585134586134587134588134589134590134591134592134593134594134595134596134597134598134599134600134601134602134603134604134605134606134607134608134609134610134611134612134613134614134615134616134617134618134619134620134621134622134623134624134625134626134627134628134629134630134631134632134633134634134635134636134637134638134639134640134641134642134643134644134645134646134647134648134649134650134651134652134653134654134655134656134657134658134659134660134661134662134663134664134665134666134667134668134669134670134671134672134673134674134675134676134677134678134679134680134681134682134683134684134685134686134687134688134689134690134691134692134693134694134695134696134697134698134699134700134701134702134703134704134705134706134707134708134709134710134711134712134713134714134715134716134717134718134719134720134721134722134723134724134725134726134727134728134729134730134731134732134733134734134735134736134737134738134739134740134741134742134743134744134745134746134747134748134749134750134751134752134753134754134755134756134757134758134759134760134761134762134763134764134765134766134767134768134769134770134771134772134773134774134775134776134777134778134779134780134781134782134783134784134785134786134787134788134789134790134791134792134793134794134795134796134797134798134799134800134801134802134803134804134805134806134807134808134809134810134811134812134813134814134815134816134817134818134819134820134821134822134823134824134825134826134827134828134829134830134831134832134833134834134835134836134837134838134839134840134841134842134843134844134845134846134847134848134849134850134851134852134853134854134855134856134857134858134859134860134861134862134863134864134865134866134867134868134869134870134871134872134873134874134875134876134877134878134879134880134881134882134883134884134885134886134887134888134889134890134891134892134893134894134895134896134897134898134899134900134901134902134903134904134905134906134907134908134909134910134911134912134913134914134915134916134917134918134919134920134921134922134923134924134925134926134927134928134929134930134931134932134933134934134935134936134937134938134939134940134941134942134943134944134945134946134947134948134949134950134951134952134953134954134955134956134957134958134959134960134961134962134963134964134965134966134967134968134969134970134971134972134973134974134975134976134977134978134979134980134981134982134983134984134985134986134987134988134989134990134991134992134993134994134995134996134997134998134999135000135001135002135003135004135005135006135007135008135009135010135011135012135013135014135015135016135017135018135019135020135021135022135023135024135025135026135027135028135029135030135031135032135033135034135035135036135037135038135039135040135041135042135043135044135045135046135047135048135049135050135051135052135053135054135055135056135057135058135059135060135061135062135063135064135065135066135067135068135069135070135071135072135073135074135075135076135077135078135079135080135081135082135083135084135085135086135087135088135089135090135091135092135093135094135095135096135097135098135099135100135101135102135103135104135105135106135107135108135109135110135111135112135113135114135115135116135117135118135119135120135121135122135123135124135125135126135127135128135129135130135131135132135133135134135135135136135137135138135139135140135141135142135143135144135145135146135147135148135149135150135151135152135153135154135155135156135157135158135159135160135161135162135163135164135165135166135167135168135169135170135171135172135173135174135175135176135177135178135179135180135181135182135183135184135185135186135187135188135189135190135191135192135193135194135195135196135197135198135199135200135201135202135203135204135205135206135207135208135209135210135211135212135213135214135215135216135217135218135219135220135221135222135223135224135225135226135227135228135229135230135231135232135233135234135235135236135237135238135239135240135241135242135243135244135245135246135247135248135249135250135251135252135253135254135255135256135257135258135259135260135261135262135263135264135265135266135267135268135269135270135271135272135273135274135275135276135277135278135279135280135281135282135283135284135285135286135287135288135289135290135291135292135293135294135295135296135297135298135299135300135301135302135303135304135305135306135307135308135309135310135311135312135313135314135315135316135317135318135319135320135321135322135323135324135325135326135327135328135329135330135331135332135333135334135335135336135337135338135339135340135341135342135343135344135345135346135347135348135349135350135351135352135353135354135355135356135357135358135359135360135361135362135363135364135365135366135367135368135369135370135371135372135373135374135375135376135377135378135379135380135381135382135383135384135385135386135387135388135389135390135391135392135393135394135395135396135397135398135399135400135401135402135403135404135405135406135407135408135409135410135411135412135413135414135415135416135417135418135419135420135421135422135423135424135425135426135427135428135429135430135431135432135433135434135435135436135437135438135439135440135441135442135443135444135445135446135447135448135449135450135451135452135453135454135455135456135457135458135459135460135461135462135463135464135465135466135467135468135469135470135471135472135473135474135475135476135477135478135479135480135481135482135483135484135485135486135487135488135489135490135491135492135493135494135495135496135497135498135499135500135501135502135503135504135505135506135507135508135509135510135511135512135513135514135515135516135517135518135519135520135521135522135523135524135525135526135527135528135529135530135531135532135533135534135535135536135537135538135539135540135541135542135543135544135545135546135547135548135549135550135551135552135553135554135555135556135557135558135559135560135561135562135563135564135565135566135567135568135569135570135571135572135573135574135575135576135577135578135579135580135581135582135583135584135585135586135587135588135589135590135591135592135593135594135595135596135597135598135599135600135601135602135603135604135605135606135607135608135609135610135611135612135613135614135615135616135617135618135619135620135621135622135623135624135625135626135627135628135629135630135631135632135633135634135635135636135637135638135639135640135641135642135643135644135645135646135647135648135649135650135651135652135653135654135655135656135657135658135659135660135661135662135663135664135665135666135667135668135669135670135671135672135673135674135675135676135677135678135679135680135681135682135683135684135685135686135687135688135689135690135691135692135693135694135695135696135697135698135699135700135701135702135703135704135705135706135707135708135709135710135711135712135713135714135715135716135717135718135719135720135721135722135723135724135725135726135727135728135729135730135731135732135733135734135735135736135737135738135739135740135741135742135743135744135745135746135747135748135749135750135751135752135753135754135755135756135757135758135759135760135761135762135763135764135765135766135767135768135769135770135771135772135773135774135775135776135777135778135779135780135781135782135783135784135785135786135787135788135789135790135791135792135793135794135795135796135797135798135799135800135801135802135803135804135805135806135807135808135809135810135811135812135813135814135815135816135817135818135819135820135821135822135823135824135825135826135827135828135829135830135831135832135833135834135835135836135837135838135839135840135841135842135843135844135845135846135847135848135849135850135851135852135853135854135855135856135857135858135859135860135861135862135863135864135865135866135867135868135869135870135871135872135873135874135875135876135877135878135879135880135881135882135883135884135885135886135887135888135889135890135891135892135893135894135895135896135897135898135899135900135901135902135903135904135905135906135907135908135909135910135911135912135913135914135915135916135917135918135919135920135921135922135923135924135925135926135927135928135929135930135931135932135933135934135935135936135937135938135939135940135941135942135943135944135945135946135947135948135949135950135951135952135953135954135955135956135957135958135959135960135961135962135963135964135965135966135967135968135969135970135971135972135973135974135975135976135977135978135979135980135981135982135983135984135985135986135987135988135989135990135991135992135993135994135995135996135997135998135999136000136001136002136003136004136005136006136007136008136009136010136011136012136013136014136015136016136017136018136019136020136021136022136023136024136025136026136027136028136029136030136031136032136033136034136035136036136037136038136039136040136041136042136043136044136045136046136047136048136049136050136051136052136053136054136055136056136057136058136059136060136061136062136063136064136065136066136067136068136069136070136071136072136073136074136075136076136077136078136079136080136081136082136083136084136085136086136087136088136089136090136091136092136093136094136095136096136097136098136099136100136101136102136103136104136105136106136107136108136109136110136111136112136113136114136115136116136117136118136119136120136121136122136123136124136125136126136127136128136129136130136131136132136133136134136135136136136137136138136139136140136141136142136143136144136145136146136147136148136149136150136151136152136153136154136155136156136157136158136159136160136161136162136163136164136165136166136167136168136169136170136171136172136173136174136175136176136177136178136179136180136181136182136183136184136185136186136187136188136189136190136191136192136193136194136195136196136197136198136199136200136201136202136203136204136205136206136207136208136209136210136211136212136213136214136215136216136217136218136219136220136221136222136223136224136225136226136227136228136229136230136231136232136233136234136235136236136237136238136239136240136241136242136243136244136245136246136247136248136249136250136251136252136253136254136255136256136257136258136259136260136261136262136263136264136265136266136267136268136269136270136271136272136273136274136275136276136277136278136279136280136281136282136283136284136285136286136287136288136289136290136291136292136293136294136295136296136297136298136299136300136301136302136303136304136305136306136307136308136309136310136311136312136313136314136315136316136317136318136319136320136321136322136323136324136325136326136327136328136329136330136331136332136333136334136335136336136337136338136339136340136341136342136343136344136345136346136347136348136349136350136351136352136353136354136355136356136357136358136359136360136361136362136363136364136365136366136367136368136369136370136371136372136373136374136375136376136377136378136379136380136381136382136383136384136385136386136387136388136389136390136391136392136393136394136395136396136397136398136399136400136401136402136403136404136405136406136407136408136409136410136411136412136413136414136415136416136417136418136419136420136421136422136423136424136425136426136427136428136429136430136431136432136433136434136435136436136437136438136439136440136441136442136443136444136445136446136447136448136449136450136451136452136453136454136455136456136457136458136459136460136461136462136463136464136465136466136467136468136469136470136471136472136473136474136475136476136477136478136479136480136481136482136483136484136485136486136487136488136489136490136491136492136493136494136495136496136497136498136499136500136501136502136503136504136505136506136507136508136509136510136511136512136513136514136515136516136517136518136519136520136521136522136523136524136525136526136527136528136529136530136531136532136533136534136535136536136537136538136539136540136541136542136543136544136545136546136547136548136549136550136551136552136553136554136555136556136557136558136559136560136561136562136563136564136565136566136567136568136569136570136571136572136573136574136575136576136577136578136579136580136581136582136583136584136585136586136587136588136589136590136591136592136593136594136595136596136597136598136599136600136601136602136603136604136605136606136607136608136609136610136611136612136613136614136615136616136617136618136619136620136621136622136623136624136625136626136627136628136629136630136631136632136633136634136635136636136637136638136639136640136641136642136643136644136645136646136647136648136649136650136651136652136653136654136655136656136657136658136659136660136661136662136663136664136665136666136667136668136669136670136671136672136673136674136675136676136677136678136679136680136681136682136683136684136685136686136687136688136689136690136691136692136693136694136695136696136697136698136699136700136701136702136703136704136705136706136707136708136709136710136711136712136713136714136715136716136717136718136719136720136721136722136723136724136725136726136727136728136729136730136731136732136733136734136735136736136737136738136739136740136741136742136743136744136745136746136747136748136749136750136751136752136753136754136755136756136757136758136759136760136761136762136763136764136765136766136767136768136769136770136771136772136773136774136775136776136777136778136779136780136781136782136783136784136785136786136787136788136789136790136791136792136793136794136795136796136797136798136799136800136801136802136803136804136805136806136807136808136809136810136811136812136813136814136815136816136817136818136819136820136821136822136823136824136825136826136827136828136829136830136831136832136833136834136835136836136837136838136839136840136841136842136843136844136845136846136847136848136849136850136851136852136853136854136855136856136857136858136859136860136861136862136863136864136865136866136867136868136869136870136871136872136873136874136875136876136877136878136879136880136881136882136883136884136885136886136887136888136889136890136891136892136893136894136895136896136897136898136899136900136901136902136903136904136905136906136907136908136909136910136911136912136913136914136915136916136917136918136919136920136921136922136923136924136925136926136927136928136929136930136931136932136933136934136935136936136937136938136939136940136941136942136943136944136945136946136947136948136949136950136951136952136953136954136955136956136957136958136959136960136961136962136963136964136965136966136967136968136969136970136971136972136973136974136975136976136977136978136979136980136981136982136983136984136985136986136987136988136989136990136991136992136993136994136995136996136997136998136999137000137001137002137003137004137005137006137007137008137009137010137011137012137013137014137015137016137017137018137019137020137021137022137023137024137025137026137027137028137029137030137031137032137033137034137035137036137037137038137039137040137041137042137043137044137045137046137047137048137049137050137051137052137053137054137055137056137057137058137059137060137061137062137063137064137065137066137067137068137069137070137071137072137073137074137075137076137077137078137079137080137081137082137083137084137085137086137087137088137089137090137091137092137093137094137095137096137097137098137099137100137101137102137103137104137105137106137107137108137109137110137111137112137113137114137115137116137117137118137119137120137121137122137123137124137125137126137127137128137129137130137131137132137133137134137135137136137137137138137139137140137141137142137143137144137145137146137147137148137149137150137151137152137153137154137155137156137157137158137159137160137161137162137163137164137165137166137167137168137169137170137171137172137173137174137175137176137177137178137179137180137181137182137183137184137185137186137187137188137189137190137191137192137193137194137195137196137197137198137199137200137201137202137203137204137205137206137207137208137209137210137211137212137213137214137215137216137217137218137219137220137221137222137223137224137225137226137227137228137229137230137231137232137233137234137235137236137237137238137239137240137241137242137243137244137245137246137247137248137249137250137251137252137253137254137255137256137257137258137259137260137261137262137263137264137265137266137267137268137269137270137271137272137273137274137275137276137277137278137279137280137281137282137283137284137285137286137287137288137289137290137291137292137293137294137295137296137297137298137299137300137301137302137303137304137305137306137307137308137309137310137311137312137313137314137315137316137317137318137319137320137321137322137323137324137325137326137327137328137329137330137331137332137333137334137335137336137337137338137339137340137341137342137343137344137345137346137347137348137349137350137351137352137353137354137355137356137357137358137359137360137361137362137363137364137365137366137367137368137369137370137371137372137373137374137375137376137377137378137379137380137381137382137383137384137385137386137387137388137389137390137391137392137393137394137395137396137397137398137399137400137401137402137403137404137405137406137407137408137409137410137411137412137413137414137415137416137417137418137419137420137421137422137423137424137425137426137427137428137429137430137431137432137433137434137435137436137437137438137439137440137441137442137443137444137445137446137447137448137449137450137451137452137453137454137455137456137457137458137459137460137461137462137463137464137465137466137467137468137469137470137471137472137473137474137475137476137477137478137479137480137481137482137483137484137485137486137487137488137489137490137491137492137493137494137495137496137497137498137499137500137501137502137503137504137505137506137507137508137509137510137511137512137513137514137515137516137517137518137519137520137521137522137523137524137525137526137527137528137529137530137531137532137533137534137535137536137537137538137539137540137541137542137543137544137545137546137547137548137549137550137551137552137553137554137555137556137557137558137559137560137561137562137563137564137565137566137567137568137569137570137571137572137573137574137575137576137577137578137579137580137581137582137583137584137585137586137587137588137589137590137591137592137593137594137595137596137597137598137599137600137601137602137603137604137605137606137607137608137609137610137611137612137613137614137615137616137617137618137619137620137621137622137623137624137625137626137627137628137629137630137631137632137633137634137635137636137637137638137639137640137641137642137643137644137645137646137647137648137649137650137651137652137653137654137655137656137657137658137659137660137661137662137663137664137665137666137667137668137669137670137671137672137673137674137675137676137677137678137679137680137681137682137683137684137685137686137687137688137689137690137691137692137693137694137695137696137697137698137699137700137701137702137703137704137705137706137707137708137709137710137711137712137713137714137715137716137717137718137719137720137721137722137723137724137725137726137727137728137729137730137731137732137733137734137735137736137737137738137739137740137741137742137743137744137745137746137747137748137749137750137751137752137753137754137755137756137757137758137759137760137761137762137763137764137765137766137767137768137769137770137771137772137773137774137775137776137777137778137779137780137781137782137783137784137785137786137787137788137789137790137791137792137793137794137795137796137797137798137799137800137801137802137803137804137805137806137807137808137809137810137811137812137813137814137815137816137817137818137819137820137821137822137823137824137825137826137827137828137829137830137831137832137833137834137835137836137837137838137839137840137841137842137843137844137845137846137847137848137849137850137851137852137853137854137855137856137857137858137859137860137861137862137863137864137865137866137867137868137869137870137871137872137873137874137875137876137877137878137879137880137881137882137883137884137885137886137887137888137889137890137891137892137893137894137895137896137897137898137899137900137901137902137903137904137905137906137907137908137909137910137911137912137913137914137915137916137917137918137919137920137921137922137923137924137925137926137927137928137929137930137931137932137933137934137935137936137937137938137939137940137941137942137943137944137945137946137947137948137949137950137951137952137953137954137955137956137957137958137959137960137961137962137963137964137965137966137967137968137969137970137971137972137973137974137975137976137977137978137979137980137981137982137983137984137985137986137987137988137989137990137991137992137993137994137995137996137997137998137999138000138001138002138003138004138005138006138007138008138009138010138011138012138013138014138015138016138017138018138019138020138021138022138023138024138025138026138027138028138029138030138031138032138033138034138035138036138037138038138039138040138041138042138043138044138045138046138047138048138049138050138051138052138053138054138055138056138057138058138059138060138061138062138063138064138065138066138067138068138069138070138071138072138073138074138075138076138077138078138079138080138081138082138083138084138085138086138087138088138089138090138091138092138093138094138095138096138097138098138099138100138101138102138103138104138105138106138107138108138109138110138111138112138113138114138115138116138117138118138119138120138121138122138123138124138125138126138127138128138129138130138131138132138133138134138135138136138137138138138139138140138141138142138143138144138145138146138147138148138149138150138151138152138153138154138155138156138157138158138159138160138161138162138163138164138165138166138167138168138169138170138171138172138173138174138175138176138177138178138179138180138181138182138183138184138185138186138187138188138189138190138191138192138193138194138195138196138197138198138199138200138201138202138203138204138205138206138207138208138209138210138211138212138213138214138215138216138217138218138219138220138221138222138223138224138225138226138227138228138229138230138231138232138233138234138235138236138237138238138239138240138241138242138243138244138245138246138247138248138249138250138251138252138253138254138255138256138257138258138259138260138261138262138263138264138265138266138267138268138269138270138271138272138273138274138275138276138277138278138279138280138281138282138283138284138285138286138287138288138289138290138291138292138293138294138295138296138297138298138299138300138301138302138303138304138305138306138307138308138309138310138311138312138313138314138315138316138317138318138319138320138321138322138323138324138325138326138327138328138329138330138331138332138333138334138335138336138337138338138339138340138341138342138343138344138345138346138347138348138349138350138351138352138353138354138355138356138357138358138359138360138361138362138363138364138365138366138367138368138369138370138371138372138373138374138375138376138377138378138379138380138381138382138383138384138385138386138387138388138389138390138391138392138393138394138395138396138397138398138399138400138401138402138403138404138405138406138407138408138409138410138411138412138413138414138415138416138417138418138419138420138421138422138423138424138425138426138427138428138429138430138431138432138433138434138435138436138437138438138439138440138441138442138443138444138445138446138447138448138449138450138451138452138453138454138455138456138457138458138459138460138461138462138463138464138465138466138467138468138469138470138471138472138473138474138475138476138477138478138479138480138481138482138483138484138485138486138487138488138489138490138491138492138493138494138495138496138497138498138499138500138501138502138503138504138505138506138507138508138509138510138511138512138513138514138515138516138517138518138519138520138521138522138523138524138525138526138527138528138529138530138531138532138533138534138535138536138537138538138539138540138541138542138543138544138545138546138547138548138549138550138551138552138553138554138555138556138557138558138559138560138561138562138563138564138565138566138567138568138569138570138571138572138573138574138575138576138577138578138579138580138581138582138583138584138585138586138587138588138589138590138591138592138593138594138595138596138597138598138599138600138601138602138603138604138605138606138607138608138609138610138611138612138613138614138615138616138617138618138619138620138621138622138623138624138625138626138627138628138629138630138631138632138633138634138635138636138637138638138639138640138641138642138643138644138645138646138647138648138649138650138651138652138653138654138655138656138657138658138659138660138661138662138663138664138665138666138667138668138669138670138671138672138673138674138675138676138677138678138679138680138681138682138683138684138685138686138687138688138689138690138691138692138693138694138695138696138697138698138699138700138701138702138703138704138705138706138707138708138709138710138711138712138713138714138715138716138717138718138719138720138721138722138723138724138725138726138727138728138729138730138731138732138733138734138735138736138737138738138739138740138741138742138743138744138745138746138747138748138749138750138751138752138753138754138755138756138757138758138759138760138761138762138763138764138765138766138767138768138769138770138771138772138773138774138775138776138777138778138779138780138781138782138783138784138785138786138787138788138789138790138791138792138793138794138795138796138797138798138799138800138801138802138803138804138805138806138807138808138809138810138811138812138813138814138815138816138817138818138819138820138821138822138823138824138825138826138827138828138829138830138831138832138833138834138835138836138837138838138839138840138841138842138843138844138845138846138847138848138849138850138851138852138853138854138855138856138857138858138859138860138861138862138863138864138865138866138867138868138869138870138871138872138873138874138875138876138877138878138879138880138881138882138883138884138885138886138887138888138889138890138891138892138893138894138895138896138897138898138899138900138901138902138903138904138905138906138907138908138909138910138911138912138913138914138915138916138917138918138919138920138921138922138923138924138925138926138927138928138929138930138931138932138933138934138935138936138937138938138939138940138941138942138943138944138945138946138947138948138949138950138951138952138953138954138955138956138957138958138959138960138961138962138963138964138965138966138967138968138969138970138971138972138973138974138975138976138977138978138979138980138981138982138983138984138985138986138987138988138989138990138991138992138993138994138995138996138997138998138999139000139001139002139003139004139005139006139007139008139009139010139011139012139013139014139015139016139017139018139019139020139021139022139023139024139025139026139027139028139029139030139031139032139033139034139035139036139037139038139039139040139041139042139043139044139045139046139047139048139049139050139051139052139053139054139055139056139057139058139059139060139061139062139063139064139065139066139067139068139069139070139071139072139073139074139075139076139077139078139079139080139081139082139083139084139085139086139087139088139089139090139091139092139093139094139095139096139097139098139099139100139101139102139103139104139105139106139107139108139109139110139111139112139113139114139115139116139117139118139119139120139121139122139123139124139125139126139127139128139129139130139131139132139133139134139135139136139137139138139139139140139141139142139143139144139145139146139147139148139149139150139151139152139153139154139155139156139157139158139159139160139161139162139163139164139165139166139167139168139169139170139171139172139173139174139175139176139177139178139179139180139181139182139183139184139185139186139187139188139189139190139191139192139193139194139195139196139197139198139199139200139201139202139203139204139205139206139207139208139209139210139211139212139213139214139215139216139217139218139219139220139221139222139223139224139225139226139227139228139229139230139231139232139233139234139235139236139237139238139239139240139241139242139243139244139245139246139247139248139249139250139251139252139253139254139255139256139257139258139259139260139261139262139263139264139265139266139267139268139269139270139271139272139273139274139275139276139277139278139279139280139281139282139283139284139285139286139287139288139289139290139291139292139293139294139295139296139297139298139299139300139301139302139303139304139305139306139307139308139309139310139311139312139313139314139315139316139317139318139319139320139321139322139323139324139325139326139327139328139329139330139331139332139333139334139335139336139337139338139339139340139341139342139343139344139345139346139347139348139349139350139351139352139353139354139355139356139357139358139359139360139361139362139363139364139365139366139367139368139369139370139371139372139373139374139375139376139377139378139379139380139381139382139383139384139385139386139387139388139389139390139391139392139393139394139395139396139397139398139399139400139401139402139403139404139405139406139407139408139409139410139411139412139413139414139415139416139417139418139419139420139421139422139423139424139425139426139427139428139429139430139431139432139433139434139435139436139437139438139439139440139441139442139443139444139445139446139447139448139449139450139451139452139453139454139455139456139457139458139459139460139461139462139463139464139465139466139467139468139469139470139471139472139473139474139475139476139477139478139479139480139481139482139483139484139485139486139487139488139489139490139491139492139493139494139495139496139497139498139499139500139501139502139503139504139505139506139507139508139509139510139511139512139513139514139515139516139517139518139519139520139521139522139523139524139525139526139527139528139529139530139531139532139533139534139535139536139537139538139539139540139541139542139543139544139545139546139547139548139549139550139551139552139553139554139555139556139557139558139559139560139561139562139563139564139565139566139567139568139569139570139571139572139573139574139575139576139577139578139579139580139581139582139583139584139585139586139587139588139589139590139591139592139593139594139595139596139597139598139599139600139601139602139603139604139605139606139607139608139609139610139611139612139613139614139615139616139617139618139619139620139621139622139623139624139625139626139627139628139629139630139631139632139633139634139635139636139637139638139639139640139641139642139643139644139645139646139647139648139649139650139651139652139653139654139655139656139657139658139659139660139661139662139663139664139665139666139667139668139669139670139671139672139673139674139675139676139677139678139679139680139681139682139683139684139685139686139687139688139689139690139691139692139693139694139695139696139697139698139699139700139701139702139703139704139705139706139707139708139709139710139711139712139713139714139715139716139717139718139719139720139721139722139723139724139725139726139727139728139729139730139731139732139733139734139735139736139737139738139739139740139741139742139743139744139745139746139747139748139749139750139751139752139753139754139755139756139757139758139759139760139761139762139763139764139765139766139767139768139769139770139771139772139773139774139775139776139777139778139779139780139781139782139783139784139785139786139787139788139789139790139791139792139793139794139795139796139797139798139799139800139801139802139803139804139805139806139807139808139809139810139811139812139813139814139815139816139817139818139819139820139821139822139823139824139825139826139827139828139829139830139831139832139833139834139835139836139837139838139839139840139841139842139843139844139845139846139847139848139849139850139851139852139853139854139855139856139857139858139859139860139861139862139863139864139865139866139867139868139869139870139871139872139873139874139875139876139877139878139879139880139881139882139883139884139885139886139887139888139889139890139891139892139893139894139895139896139897139898139899139900139901139902139903139904139905139906139907139908139909139910139911139912139913139914139915139916139917139918139919139920139921139922139923139924139925139926139927139928139929139930139931139932139933139934139935139936139937139938139939139940139941139942139943139944139945139946139947139948139949139950139951139952139953139954139955139956139957139958139959139960139961139962139963139964139965139966139967139968139969139970139971139972139973139974139975139976139977139978139979139980139981139982139983139984139985139986139987139988139989139990139991139992139993139994139995139996139997139998139999140000140001140002140003140004140005140006140007140008140009140010140011140012140013140014140015140016140017140018140019140020140021140022140023140024140025140026140027140028140029140030140031140032140033140034140035140036140037140038140039140040140041140042140043140044140045140046140047140048140049140050140051140052140053140054140055140056140057140058140059140060140061140062140063140064140065140066140067140068140069140070140071140072140073140074140075140076140077140078140079140080140081140082140083140084140085140086140087140088140089140090140091140092140093140094140095140096140097140098140099140100140101140102140103140104140105140106140107140108140109140110140111140112140113140114140115140116140117140118140119140120140121140122140123140124140125140126140127140128140129140130140131140132140133140134140135140136140137140138140139140140140141140142140143140144140145140146140147140148140149140150140151140152140153140154140155140156140157140158140159140160140161140162140163140164140165140166140167140168140169140170140171140172140173140174140175140176140177140178140179140180140181140182140183140184140185140186140187140188140189140190140191140192140193140194140195140196140197140198140199140200140201140202140203140204140205140206140207140208140209140210140211140212140213140214140215140216140217140218140219140220140221140222140223140224140225140226140227140228140229140230140231140232140233140234140235140236140237140238140239140240140241140242140243140244140245140246140247140248140249140250140251140252140253140254140255140256140257140258140259140260140261140262140263140264140265140266140267140268140269140270140271140272140273140274140275140276140277140278140279140280140281140282140283140284140285140286140287140288140289140290140291140292140293140294140295140296140297140298140299140300140301140302140303140304140305140306140307140308140309140310140311140312140313140314140315140316140317140318140319140320140321140322140323140324140325140326140327140328140329140330140331140332140333140334140335140336140337140338140339140340140341140342140343140344140345140346140347140348140349140350140351140352140353140354140355140356140357140358140359140360140361140362140363140364140365140366140367140368140369140370140371140372140373140374140375140376140377140378140379140380140381140382140383140384140385140386140387140388140389140390140391140392140393140394140395140396140397140398140399140400140401140402140403140404140405140406140407140408140409140410140411140412140413140414140415140416140417140418140419140420140421140422140423140424140425140426140427140428140429140430140431140432140433140434140435140436140437140438140439140440140441140442140443140444140445140446140447140448140449140450140451140452140453140454140455140456140457140458140459140460140461140462140463140464140465140466140467140468140469140470140471140472140473140474140475140476140477140478140479140480140481140482140483140484140485140486140487140488140489140490140491140492140493140494140495140496140497140498140499140500140501140502140503140504140505140506140507140508140509140510140511140512140513140514140515140516140517140518140519140520140521140522140523140524140525140526140527140528140529140530140531140532140533140534140535140536140537140538140539140540140541140542140543140544140545140546140547140548140549140550140551140552140553140554140555140556140557140558140559140560140561140562140563140564140565140566140567140568140569140570140571140572140573140574140575140576140577140578140579140580140581140582140583140584140585140586140587140588140589140590140591140592140593140594140595140596140597140598140599140600140601140602140603140604140605140606140607140608140609140610140611140612140613140614140615140616140617140618140619140620140621140622140623140624140625140626140627140628140629140630140631140632140633140634140635140636140637140638140639140640140641140642140643140644140645140646140647140648140649140650140651140652140653140654140655140656140657140658140659140660140661140662140663140664140665140666140667140668140669140670140671140672140673140674140675140676140677140678140679140680140681140682140683140684140685140686140687140688140689140690140691140692140693140694140695140696140697140698140699140700140701140702140703140704140705140706140707140708140709140710140711140712140713140714140715140716140717140718140719140720140721140722140723140724140725140726140727140728140729140730140731140732140733140734140735140736140737140738140739140740140741140742140743140744140745140746140747140748140749140750140751140752140753140754140755140756140757140758140759140760140761140762140763140764140765140766140767140768140769140770140771140772140773140774140775140776140777140778140779140780140781140782140783140784140785140786140787140788140789140790140791140792140793140794140795140796140797140798140799140800140801140802140803140804140805140806140807140808140809140810140811140812140813140814140815140816140817140818140819140820140821140822140823140824140825140826140827140828140829140830140831140832140833140834140835140836140837140838140839140840140841140842140843140844140845140846140847140848140849140850140851140852140853140854140855140856140857140858140859140860140861140862140863140864140865140866140867140868140869140870140871140872140873140874140875140876140877140878140879140880140881140882140883140884140885140886140887140888140889140890140891140892140893140894140895140896140897140898140899140900140901140902140903140904140905140906140907140908140909140910140911140912140913140914140915140916140917140918140919140920140921140922140923140924140925140926140927140928140929140930140931140932140933140934140935140936140937140938140939140940140941140942140943140944140945140946140947140948140949140950140951140952140953140954140955140956140957140958140959140960140961140962140963140964140965140966140967140968140969140970140971140972140973140974140975140976140977140978140979140980140981140982140983140984140985140986140987140988140989140990140991140992140993140994140995140996140997140998140999141000141001141002141003141004141005141006141007141008141009141010141011141012141013141014141015141016141017141018141019141020141021141022141023141024141025141026141027141028141029141030141031141032141033141034141035141036141037141038141039141040141041141042141043141044141045141046141047141048141049141050141051141052141053141054141055141056141057141058141059141060141061141062141063141064141065141066141067141068141069141070141071141072141073141074141075141076141077141078141079141080141081141082141083141084141085141086141087141088141089141090141091141092141093141094141095141096141097141098141099141100141101141102141103141104141105141106141107141108141109141110141111141112141113141114141115141116141117141118141119141120141121141122141123141124141125141126141127141128141129141130141131141132141133141134141135141136141137141138141139141140141141141142141143141144141145141146141147141148141149141150141151141152141153141154141155141156141157141158141159141160141161141162141163141164141165141166141167141168141169141170141171141172141173141174141175141176141177141178141179141180141181141182141183141184141185141186141187141188141189141190141191141192141193141194141195141196141197141198141199141200141201141202141203141204141205141206141207141208141209141210141211141212141213141214141215141216141217141218141219141220141221141222141223141224141225141226141227141228141229141230141231141232141233141234141235141236141237141238141239141240141241141242141243141244141245141246141247141248141249141250141251141252141253141254141255141256141257141258141259141260141261141262141263141264141265141266141267141268141269141270141271141272141273141274141275141276141277141278141279141280141281141282141283141284141285141286141287141288141289141290141291141292141293141294141295141296141297141298141299141300141301141302141303141304141305141306141307141308141309141310141311141312141313141314141315141316141317141318141319141320141321141322141323141324141325141326141327141328141329141330141331141332141333141334141335141336141337141338141339141340141341141342141343141344141345141346141347141348141349141350141351141352141353141354141355141356141357141358141359141360141361141362141363141364141365141366141367141368141369141370141371141372141373141374141375141376141377141378141379141380141381141382141383141384141385141386141387141388141389141390141391141392141393141394141395141396141397141398141399141400141401141402141403141404141405141406141407141408141409141410141411141412141413141414141415141416141417141418141419141420141421141422141423141424141425141426141427141428141429141430141431141432141433141434141435141436141437141438141439141440141441141442141443141444141445141446141447141448141449141450141451141452141453141454141455141456141457141458141459141460141461141462141463141464141465141466141467141468141469141470141471141472141473141474141475141476141477141478141479141480141481141482141483141484141485141486141487141488141489141490141491141492141493141494141495141496141497141498141499141500141501141502141503141504141505141506141507141508141509141510141511141512141513141514141515141516141517141518141519141520141521141522141523141524141525141526141527141528141529141530141531141532141533141534141535141536141537141538141539141540141541141542141543141544141545141546141547141548141549141550141551141552141553141554141555141556141557141558141559141560141561141562141563141564141565141566141567141568141569141570141571141572141573141574141575141576141577141578141579141580141581141582141583141584141585141586141587141588141589141590141591141592141593141594141595141596141597141598141599141600141601141602141603141604141605141606141607141608141609141610141611141612141613141614141615141616141617141618141619141620141621141622141623141624141625141626141627141628141629141630141631141632141633141634141635141636141637141638141639141640141641141642141643141644141645141646141647141648141649141650141651141652141653141654141655141656141657141658141659141660141661141662141663141664141665141666141667141668141669141670141671141672141673141674141675141676141677141678141679141680141681141682141683141684141685141686141687141688141689141690141691141692141693141694141695141696141697141698141699141700141701141702141703141704141705141706141707141708141709141710141711141712141713141714141715141716141717141718141719141720141721141722141723141724141725141726141727141728141729141730141731141732141733141734141735141736141737141738141739141740141741141742141743141744141745141746141747141748141749141750141751141752141753141754141755141756141757141758141759141760141761141762141763141764141765141766141767141768141769141770141771141772141773141774141775141776141777141778141779141780141781141782141783141784141785141786141787141788141789141790141791141792141793141794141795141796141797141798141799141800141801141802141803141804141805141806141807141808141809141810141811141812141813141814141815141816141817141818141819141820141821141822141823141824141825141826141827141828141829141830141831141832141833141834141835141836141837141838141839141840141841141842141843141844141845141846141847141848141849141850141851141852141853141854141855141856141857141858141859141860141861141862141863141864141865141866141867141868141869141870141871141872141873141874141875141876141877141878141879141880141881141882141883141884141885141886141887141888141889141890141891141892141893141894141895141896141897141898141899141900141901141902141903141904141905141906141907141908141909141910141911141912141913141914141915141916141917141918141919141920141921141922141923141924141925141926141927141928141929141930141931141932141933141934141935141936141937141938141939141940141941141942141943141944141945141946141947141948141949141950141951141952141953141954141955141956141957141958141959141960141961141962141963141964141965141966141967141968141969141970141971141972141973141974141975141976141977141978141979141980141981141982141983141984141985141986141987141988141989141990141991141992141993141994141995141996141997141998141999142000142001142002142003142004142005142006142007142008142009142010142011142012142013142014142015142016142017142018142019142020142021142022142023142024142025142026142027142028142029142030142031142032142033142034142035142036142037142038142039142040142041142042142043142044142045142046142047142048142049142050142051142052142053142054142055142056142057142058142059142060142061142062142063142064142065142066142067142068142069142070142071142072142073142074142075142076142077142078142079142080142081142082142083142084142085142086142087142088142089142090142091142092142093142094142095142096142097142098142099142100142101142102142103142104142105142106142107142108142109142110142111142112142113142114142115142116142117142118142119142120142121142122142123142124142125142126142127142128142129142130142131142132142133142134142135142136142137142138142139142140142141142142142143142144142145142146142147142148142149142150142151142152142153142154142155142156142157142158142159142160142161142162142163142164142165142166142167142168142169142170142171142172142173142174142175142176142177142178142179142180142181142182142183142184142185142186142187142188142189142190142191142192142193142194142195142196142197142198142199142200142201142202142203142204142205142206142207142208142209142210142211142212142213142214142215142216142217142218142219142220142221142222142223142224142225142226142227142228142229142230142231142232142233142234142235142236142237142238142239142240142241142242142243142244142245142246142247142248142249142250142251142252142253142254142255142256142257142258142259142260142261142262142263142264142265142266142267142268142269142270142271142272142273142274142275142276142277142278142279142280142281142282142283142284142285142286142287142288142289142290142291142292142293142294142295142296142297142298142299142300142301142302142303142304142305142306142307142308142309142310142311142312142313142314142315142316142317142318142319142320142321142322142323142324142325142326142327142328142329142330142331142332142333142334142335142336142337142338142339142340142341142342142343142344142345142346142347142348142349142350142351142352142353142354142355142356142357142358142359142360142361142362142363142364142365142366142367142368142369142370142371142372142373142374142375142376142377142378142379142380142381142382142383142384142385142386142387142388142389142390142391142392142393142394142395142396142397142398142399142400142401142402142403142404142405142406142407142408142409142410142411142412142413142414142415142416142417142418142419142420142421142422142423142424142425142426142427142428142429142430142431142432142433142434142435142436142437142438142439142440142441142442142443142444142445142446142447142448142449142450142451142452142453142454142455142456142457142458142459142460142461142462142463142464142465142466142467142468142469142470142471142472142473142474142475142476142477142478142479142480142481142482142483142484142485142486142487142488142489142490142491142492142493142494142495142496142497142498142499142500142501142502142503142504142505142506142507142508142509142510142511142512142513142514142515142516142517142518142519142520142521142522142523142524142525142526142527142528142529142530142531142532142533142534142535142536142537142538142539142540142541142542142543142544142545142546142547142548142549142550142551142552142553142554142555142556142557142558142559142560142561142562142563142564142565142566142567142568142569142570142571142572142573142574142575142576142577142578142579142580142581142582142583142584142585142586142587142588142589142590142591142592142593142594142595142596142597142598142599142600142601142602142603142604142605142606142607142608142609142610142611142612142613142614142615142616142617142618142619142620142621142622142623142624142625142626142627142628142629142630142631142632142633142634142635142636142637142638142639142640142641142642142643142644142645142646142647142648142649142650142651142652142653142654142655142656142657142658142659142660142661142662142663142664142665142666142667142668142669142670142671142672142673142674142675142676142677142678142679142680142681142682142683142684142685142686142687142688142689142690142691142692142693142694142695142696142697142698142699142700142701142702142703142704142705142706142707142708142709142710142711142712142713142714142715142716142717142718142719142720142721142722142723142724142725142726142727142728142729142730142731142732142733142734142735142736142737142738142739142740142741142742142743142744142745142746142747142748142749142750142751142752142753142754142755142756142757142758142759142760142761142762142763142764142765142766142767142768142769142770142771142772142773142774142775142776142777142778142779142780142781142782142783142784142785142786142787142788142789142790142791142792142793142794142795142796142797142798142799142800142801142802142803142804142805142806142807142808142809142810142811142812142813142814142815142816142817142818142819142820142821142822142823142824142825142826142827142828142829142830142831142832142833142834142835142836142837142838142839142840142841142842142843142844142845142846142847142848142849142850142851142852142853142854142855142856142857142858142859142860142861142862142863142864142865142866142867142868142869142870142871142872142873142874142875142876142877142878142879142880142881142882142883142884142885142886142887142888142889142890142891142892142893142894142895142896142897142898142899142900142901142902142903142904142905142906142907142908142909142910142911142912142913142914142915142916142917142918142919142920142921142922142923142924142925142926142927142928142929142930142931142932142933142934142935142936142937142938142939142940142941142942142943142944142945142946142947142948142949142950142951142952142953142954142955142956142957142958142959142960142961142962142963142964142965142966142967142968142969142970142971142972142973142974142975142976142977142978142979142980142981142982142983142984142985142986142987142988142989142990142991142992142993142994142995142996142997142998142999143000143001143002143003143004143005143006143007143008143009143010143011143012143013143014143015143016143017143018143019143020143021143022143023143024143025143026143027143028143029143030143031143032143033143034143035143036143037143038143039143040143041143042143043143044143045143046143047143048143049143050143051143052143053143054143055143056143057143058143059143060143061143062143063143064143065143066143067143068143069143070143071143072143073143074143075143076143077143078143079143080143081143082143083143084143085143086143087143088143089143090143091143092143093143094143095143096143097143098143099143100143101143102143103143104143105143106143107143108143109143110143111143112143113143114143115143116143117143118143119143120143121143122143123143124143125143126143127143128143129143130143131143132143133143134143135143136143137143138143139143140143141143142143143143144143145143146143147143148143149143150143151143152143153143154143155143156143157143158143159143160143161143162143163143164143165143166143167143168143169143170143171143172143173143174143175143176143177143178143179143180143181143182143183143184143185143186143187143188143189143190143191143192143193143194143195143196143197143198143199143200143201143202143203143204143205143206143207143208143209143210143211143212143213143214143215143216143217143218143219143220143221143222143223143224143225143226143227143228143229143230143231143232143233143234143235143236143237143238143239143240143241143242143243143244143245143246143247143248143249143250143251143252143253143254143255143256143257143258143259143260143261143262143263143264143265143266143267143268143269143270143271143272143273143274143275143276143277143278143279143280143281143282143283143284143285143286143287143288143289143290143291143292143293143294143295143296143297143298143299143300143301143302143303143304143305143306143307143308143309143310143311143312143313143314143315143316143317143318143319143320143321143322143323143324143325143326143327143328143329143330143331143332143333143334143335143336143337143338143339143340143341143342143343143344143345143346143347143348143349143350143351143352143353143354143355143356143357143358143359143360143361143362143363143364143365143366143367143368143369143370143371143372143373143374143375143376143377143378143379143380143381143382143383143384143385143386143387143388143389143390143391143392143393143394143395143396143397143398143399143400143401143402143403143404143405143406143407143408143409143410143411143412143413143414143415143416143417143418143419143420143421143422143423143424143425143426143427143428143429143430143431143432143433143434143435143436143437143438143439143440143441143442143443143444143445143446143447143448143449143450143451143452143453143454143455143456143457143458143459143460143461143462143463143464143465143466143467143468143469143470143471143472143473143474143475143476143477143478143479143480143481143482143483143484143485143486143487143488143489143490143491143492143493143494143495143496143497143498143499143500143501143502143503143504143505143506143507143508143509143510143511143512143513143514143515143516143517143518143519143520143521143522143523143524143525143526143527143528143529143530143531143532143533143534143535143536143537143538143539143540143541143542143543143544143545143546143547143548143549143550143551143552143553143554143555143556143557143558143559143560143561143562143563143564143565143566143567143568143569143570143571143572143573143574143575143576143577143578143579143580143581143582143583143584143585143586143587143588143589143590143591143592143593143594143595143596143597143598143599143600143601143602143603143604143605143606143607143608143609143610143611143612143613143614143615143616143617143618143619143620143621143622143623143624143625143626143627143628143629143630143631143632143633143634143635143636143637143638143639143640143641143642143643143644143645143646143647143648143649143650143651143652143653143654143655143656143657143658143659143660143661143662143663143664143665143666143667143668143669143670143671143672143673143674143675143676143677143678143679143680143681143682143683143684143685143686143687143688143689143690143691143692143693143694143695143696143697143698143699143700143701143702143703143704143705143706143707143708143709143710143711143712143713143714143715143716143717143718143719143720143721143722143723143724143725143726143727143728143729143730143731143732143733143734143735143736143737143738143739143740143741143742143743143744143745143746143747143748143749143750143751143752143753143754143755143756143757143758143759143760143761143762143763143764143765143766143767143768143769143770143771143772143773143774143775143776143777143778143779143780143781143782143783143784143785143786143787143788143789143790143791143792143793143794143795143796143797143798143799143800143801143802143803143804143805143806143807143808143809143810143811143812143813143814143815143816143817143818143819143820143821143822143823143824143825143826143827143828143829143830143831143832143833143834143835143836143837143838143839143840143841143842143843143844143845143846143847143848143849143850143851143852143853143854143855143856143857143858143859143860143861143862143863143864143865143866143867143868143869143870143871143872143873143874143875143876143877143878143879143880143881143882143883143884143885143886143887143888143889143890143891143892143893143894143895143896143897143898143899143900143901143902143903143904143905143906143907143908143909143910143911143912143913143914143915143916143917143918143919143920143921143922143923143924143925143926143927143928143929143930143931143932143933143934143935143936143937143938143939143940143941143942143943143944143945143946143947143948143949143950143951143952143953143954143955143956143957143958143959143960143961143962143963143964143965143966143967143968143969143970143971143972143973143974143975143976143977143978143979143980143981143982143983143984143985143986143987143988143989143990143991143992143993143994143995143996143997143998143999144000144001144002144003144004144005144006144007144008144009144010144011144012144013144014144015144016144017144018144019144020144021144022144023144024144025144026144027144028144029144030144031144032144033144034144035144036144037144038144039144040144041144042144043144044144045144046144047144048144049144050144051144052144053144054144055144056144057144058144059144060144061144062144063144064144065144066144067144068144069144070144071144072144073144074144075144076144077144078144079144080144081144082144083144084144085144086144087144088144089144090144091144092144093144094144095144096144097144098144099144100144101144102144103144104144105144106144107144108144109144110144111144112144113144114144115144116144117144118144119144120144121144122144123144124144125144126144127144128144129144130144131144132144133144134144135144136144137144138144139144140144141144142144143144144144145144146144147144148144149144150144151144152144153144154144155144156144157144158144159144160144161144162144163144164144165144166144167144168144169144170144171144172144173144174144175144176144177144178144179144180144181144182144183144184144185144186144187144188144189144190144191144192144193144194144195144196144197144198144199144200144201144202144203144204144205144206144207144208144209144210144211144212144213144214144215144216144217144218144219144220144221144222144223144224144225144226144227144228144229144230144231144232144233144234144235144236144237144238144239144240144241144242144243144244144245144246144247144248144249144250144251144252144253144254144255144256144257144258144259144260144261144262144263144264144265144266144267144268144269144270144271144272144273144274144275144276144277144278144279144280144281144282144283144284144285144286144287144288144289144290144291144292144293144294144295144296144297144298144299144300144301144302144303144304144305144306144307144308144309144310144311144312144313144314144315144316144317144318144319144320144321144322144323144324144325144326144327144328144329144330144331144332144333144334144335144336144337144338144339144340144341144342144343144344144345144346144347144348144349144350144351144352144353144354144355144356144357144358144359144360144361144362144363144364144365144366144367144368144369144370144371144372144373144374144375144376144377144378144379144380144381144382144383144384144385144386144387144388144389144390144391144392144393144394144395144396144397144398144399144400144401144402144403144404144405144406144407144408144409144410144411144412144413144414144415144416144417144418144419144420144421144422144423144424144425144426144427144428144429144430144431144432144433144434144435144436144437144438144439144440144441144442144443144444144445144446144447144448144449144450144451144452144453144454144455144456144457144458144459144460144461144462144463144464144465144466144467144468144469144470144471144472144473144474144475144476144477144478144479144480144481144482144483144484144485144486144487144488144489144490144491144492144493144494144495144496144497144498144499144500144501144502144503144504144505144506144507144508144509144510144511144512144513144514144515144516144517144518144519144520144521144522144523144524144525144526144527144528144529144530144531144532144533144534144535144536144537144538144539144540144541144542144543144544144545144546144547144548144549144550144551144552144553144554144555144556144557144558144559144560144561144562144563144564144565144566144567144568144569144570144571144572144573144574144575144576144577144578144579144580144581144582144583144584144585144586144587144588144589144590144591144592144593144594144595144596144597144598144599144600144601144602144603144604144605144606144607144608144609144610144611144612144613144614144615144616144617144618144619144620144621144622144623144624144625144626144627144628144629144630144631144632144633144634144635144636144637144638144639144640144641144642144643144644144645144646144647144648144649144650144651144652144653144654144655144656144657144658144659144660144661144662144663144664144665144666144667144668144669144670144671144672144673144674144675144676144677144678144679144680144681144682144683144684144685144686144687144688144689144690144691144692144693144694144695144696144697144698144699144700144701144702144703144704144705144706144707144708144709144710144711144712144713144714144715144716144717144718144719144720144721144722144723144724144725144726144727144728144729144730144731144732144733144734144735144736144737144738144739144740144741144742144743144744144745144746144747144748144749144750144751144752144753144754144755144756144757144758144759144760144761144762144763144764144765144766144767144768144769144770144771144772144773144774144775144776144777144778144779144780144781144782144783144784144785144786144787144788144789144790144791144792144793144794144795144796144797144798144799144800144801144802144803144804144805144806144807144808144809144810144811144812144813144814144815144816144817144818144819144820144821144822144823144824144825144826144827144828144829144830144831144832144833144834144835144836144837144838144839144840144841144842144843144844144845144846144847144848144849144850144851144852144853144854144855144856144857144858144859144860144861144862144863144864144865144866144867144868144869144870144871144872144873144874144875144876144877144878144879144880144881144882144883144884144885144886144887144888144889144890144891144892144893144894144895144896144897144898144899144900144901144902144903144904144905144906144907144908144909144910144911144912144913144914144915144916144917144918144919144920144921144922144923144924144925144926144927144928144929144930144931144932144933144934144935144936144937144938144939144940144941144942144943144944144945144946144947144948144949144950144951144952144953144954144955144956144957144958144959144960144961144962144963144964144965144966144967144968144969144970144971144972144973144974144975144976144977144978144979144980144981144982144983144984144985144986144987144988144989144990144991144992144993144994144995144996144997144998144999145000145001145002145003145004145005145006145007145008145009145010145011145012145013145014145015145016145017145018145019145020145021145022145023145024145025145026145027145028145029145030145031145032145033145034145035145036145037145038145039145040145041145042145043145044145045145046145047145048145049145050145051145052145053145054145055145056145057145058145059145060145061145062145063145064145065145066145067145068145069145070145071145072145073145074145075145076145077145078145079145080145081145082145083145084145085145086145087145088145089145090145091145092145093145094145095145096145097145098145099145100145101145102145103145104145105145106145107145108145109145110145111145112145113145114145115145116145117145118145119145120145121145122145123145124145125145126145127145128145129145130145131145132145133145134145135145136145137145138145139145140145141145142145143145144145145145146145147145148145149145150145151145152145153145154145155145156145157145158145159145160145161145162145163145164145165145166145167145168145169145170145171145172145173145174145175145176145177145178145179145180145181145182145183145184145185145186145187145188145189145190145191145192145193145194145195145196145197145198145199145200145201145202145203145204145205145206145207145208145209145210145211145212145213145214145215145216145217145218145219145220145221145222145223145224145225145226145227145228145229145230145231145232145233145234145235145236145237145238145239145240145241145242145243145244145245145246145247145248145249145250145251145252145253145254145255145256145257145258145259145260145261145262145263145264145265145266145267145268145269145270145271145272145273145274145275145276145277145278145279145280145281145282145283145284145285145286145287145288145289145290145291145292145293145294145295145296145297145298145299145300145301145302145303145304145305145306145307145308145309145310145311145312145313145314145315145316145317145318145319145320145321145322145323145324145325145326145327145328145329145330145331145332145333145334145335145336145337145338145339145340145341145342145343145344145345145346145347145348145349145350145351145352145353145354145355145356145357145358145359145360145361145362145363145364145365145366145367145368145369145370145371145372145373145374145375145376145377145378145379145380145381145382145383145384145385145386145387145388145389145390145391145392145393145394145395145396145397145398145399145400145401145402145403145404145405145406145407145408145409145410145411145412145413145414145415145416145417145418145419145420145421145422145423145424145425145426145427145428145429145430145431145432145433145434145435145436145437145438145439145440145441145442145443145444145445145446145447145448145449145450145451145452145453145454145455145456145457145458145459145460145461145462145463145464145465145466145467145468145469145470145471145472145473145474145475145476145477145478145479145480145481145482145483145484145485145486145487145488145489145490145491145492145493145494145495145496145497145498145499145500145501145502145503145504145505145506145507145508145509145510145511145512145513145514145515145516145517145518145519145520145521145522145523145524145525145526145527145528145529145530145531145532145533145534145535145536145537145538145539145540145541145542145543145544145545145546145547145548145549145550145551145552145553145554145555145556145557145558145559145560145561145562145563145564145565145566145567145568145569145570145571145572145573145574145575145576145577145578145579145580145581145582145583145584145585145586145587145588145589145590145591145592145593145594145595145596145597145598145599145600145601145602145603145604145605145606145607145608145609145610145611145612145613145614145615145616145617145618145619145620145621145622145623145624145625145626145627145628145629145630145631145632145633145634145635145636145637145638145639145640145641145642145643145644145645145646145647145648145649145650145651145652145653145654145655145656145657145658145659145660145661145662145663145664145665145666145667145668145669145670145671145672145673145674145675145676145677145678145679145680145681145682145683145684145685145686145687145688145689145690145691145692145693145694145695145696145697145698145699145700145701145702145703145704145705145706145707145708145709145710145711145712145713145714145715145716145717145718145719145720145721145722145723145724145725145726145727145728145729145730145731145732145733145734145735145736145737145738145739145740145741145742145743145744145745145746145747145748145749145750145751145752145753145754145755145756145757145758145759145760145761145762145763145764145765145766145767145768145769145770145771145772145773145774145775145776145777145778145779145780145781145782145783145784145785145786145787145788145789145790145791145792145793145794145795145796145797145798145799145800145801145802145803145804145805145806145807145808145809145810145811145812145813145814145815145816145817145818145819145820145821145822145823145824145825145826145827145828145829145830145831145832145833145834145835145836145837145838145839145840145841145842145843145844145845145846145847145848145849145850145851145852145853145854145855145856145857145858145859145860145861145862145863145864145865145866145867145868145869145870145871145872145873145874145875145876145877145878145879145880145881145882145883145884145885145886145887145888145889145890145891145892145893145894145895145896145897145898145899145900145901145902145903145904145905145906145907145908145909145910145911145912145913145914145915145916145917145918145919145920145921145922145923145924145925145926145927145928145929145930145931145932145933145934145935145936145937145938145939145940145941145942145943145944145945145946145947145948145949145950145951145952145953145954145955145956145957145958145959145960145961145962145963145964145965145966145967145968145969145970145971145972145973145974145975145976145977145978145979145980145981145982145983145984145985145986145987145988145989145990145991145992145993145994145995145996145997145998145999146000146001146002146003146004146005146006146007146008146009146010146011146012146013146014146015146016146017146018146019146020146021146022146023146024146025146026146027146028146029146030146031146032146033146034146035146036146037146038146039146040146041146042146043146044146045146046146047146048146049146050146051146052146053146054146055146056146057146058146059146060146061146062146063146064146065146066146067146068146069146070146071146072146073146074146075146076146077146078146079146080146081146082146083146084146085146086146087146088146089146090146091146092146093146094146095146096146097146098146099146100146101146102146103146104146105146106146107146108146109146110146111146112146113146114146115146116146117146118146119146120146121146122146123146124146125146126146127146128146129146130146131146132146133146134146135146136146137146138146139146140146141146142146143146144146145146146146147146148146149146150146151146152146153146154146155146156146157146158146159146160146161146162146163146164146165146166146167146168146169146170146171146172146173146174146175146176146177146178146179146180146181146182146183146184146185146186146187146188146189146190146191146192146193146194146195146196146197146198146199146200146201146202146203146204146205146206146207146208146209146210146211146212146213146214146215146216146217146218146219146220146221146222146223146224146225146226146227146228146229146230146231146232146233146234146235146236146237146238146239146240146241146242146243146244146245146246146247146248146249146250146251146252146253146254146255146256146257146258146259146260146261146262146263146264146265146266146267146268146269146270146271146272146273146274146275146276146277146278146279146280146281146282146283146284146285146286146287146288146289146290146291146292146293146294146295146296146297146298146299146300146301146302146303146304146305146306146307146308146309146310146311146312146313146314146315146316146317146318146319146320146321146322146323146324146325146326146327146328146329146330146331146332146333146334146335146336146337146338146339146340146341146342146343146344146345146346146347146348146349146350146351146352146353146354146355146356146357146358146359146360146361146362146363146364146365146366146367146368146369146370146371146372146373146374146375146376146377146378146379146380146381146382146383146384146385146386146387146388146389146390146391146392146393146394146395146396146397146398146399146400146401146402146403146404146405146406146407146408146409146410146411146412146413146414146415146416146417146418146419146420146421146422146423146424146425146426146427146428146429146430146431146432146433146434146435146436146437146438146439146440146441146442146443146444146445146446146447146448146449146450146451146452146453146454146455146456146457146458146459146460146461146462146463146464146465146466146467146468146469146470146471146472146473146474146475146476146477146478146479146480146481146482146483146484146485146486146487146488146489146490146491146492146493146494146495146496146497146498146499146500146501146502146503146504146505146506146507146508146509146510146511146512146513146514146515146516146517146518146519146520146521146522146523146524146525146526146527146528146529146530146531146532146533146534146535146536146537146538146539146540146541146542146543146544146545146546146547146548146549146550146551146552146553146554146555146556146557146558146559146560146561146562146563146564146565146566146567146568146569146570146571146572146573146574146575146576146577146578146579146580146581146582146583146584146585146586146587146588146589146590146591146592146593146594146595146596146597146598146599146600146601146602146603146604146605146606146607146608146609146610146611146612146613146614146615146616146617146618146619146620146621146622146623146624146625146626146627146628146629146630146631146632146633146634146635146636146637146638146639146640146641146642146643146644146645146646146647146648146649146650146651146652146653146654146655146656146657146658146659146660146661146662146663146664146665146666146667146668146669146670146671146672146673146674146675146676146677146678146679146680146681146682146683146684146685146686146687146688146689146690146691146692146693146694146695146696146697146698146699146700146701146702146703146704146705146706146707146708146709146710146711146712146713146714146715146716146717146718146719146720146721146722146723146724146725146726146727146728146729146730146731146732146733146734146735146736146737146738146739146740146741146742146743146744146745146746146747146748146749146750146751146752146753146754146755146756146757146758146759146760146761146762146763146764146765146766146767146768146769146770146771146772146773146774146775146776146777146778146779146780146781146782146783146784146785146786146787146788146789146790146791146792146793146794146795146796146797146798146799146800146801146802146803146804146805146806146807146808146809146810146811146812146813146814146815146816146817146818146819146820146821146822146823146824146825146826146827146828146829146830146831146832146833146834146835146836146837146838146839146840146841146842146843146844146845146846146847146848146849146850146851146852146853146854146855146856146857146858146859146860146861146862146863146864146865146866146867146868146869146870146871146872146873146874146875146876146877146878146879146880146881146882146883146884146885146886146887146888146889146890146891146892146893146894146895146896146897146898146899146900146901146902146903146904146905146906146907146908146909146910146911146912146913146914146915146916146917146918146919146920146921146922146923146924146925146926146927146928146929146930146931146932146933146934146935146936146937146938146939146940146941146942146943146944146945146946146947146948146949146950146951146952146953146954146955146956146957146958146959146960146961146962146963146964146965146966146967146968146969146970146971146972146973146974146975146976146977146978146979146980146981146982146983146984146985146986146987146988146989146990146991146992146993146994146995146996146997146998146999147000147001147002147003147004147005147006147007147008147009147010147011147012147013147014147015147016147017147018147019147020147021147022147023147024147025147026147027147028147029147030147031147032147033147034147035147036147037147038147039147040147041147042147043147044147045147046147047147048147049147050147051147052147053147054147055147056147057147058147059147060147061147062147063147064147065147066147067147068147069147070147071147072147073147074147075147076147077147078147079147080147081147082147083147084147085147086147087147088147089147090147091147092147093147094147095147096147097147098147099147100147101147102147103147104147105147106147107147108147109147110147111147112147113147114147115147116147117147118147119147120147121147122147123147124147125147126147127147128147129147130147131147132147133147134147135147136147137147138147139147140147141147142147143147144147145147146147147147148147149147150147151147152147153147154147155147156147157147158147159147160147161147162147163147164147165147166147167147168147169147170147171147172147173147174147175147176147177147178147179147180147181147182147183147184147185147186147187147188147189147190147191147192147193147194147195147196147197147198147199147200147201147202147203147204147205147206147207147208147209147210147211147212147213147214147215147216147217147218147219147220147221147222147223147224147225147226147227147228147229147230147231147232147233147234147235147236147237147238147239147240147241147242147243147244147245147246147247147248147249147250147251147252147253147254147255147256147257147258147259147260147261147262147263147264147265147266147267147268147269147270147271147272147273147274147275147276147277147278147279147280147281147282147283147284147285147286147287147288147289147290147291147292147293147294147295147296147297147298147299147300147301147302147303147304147305147306147307147308147309147310147311147312147313147314147315147316147317147318147319147320147321147322147323147324147325147326147327147328147329147330147331147332147333147334147335147336147337147338147339147340147341147342147343147344147345147346147347147348147349147350147351147352147353147354147355147356147357147358147359147360147361147362147363147364147365147366147367147368147369147370147371147372147373147374147375147376147377147378147379147380147381147382147383147384147385147386147387147388147389147390147391147392147393147394147395147396147397147398147399147400147401147402147403147404147405147406147407147408147409147410147411147412147413147414147415147416147417147418147419147420147421147422147423147424147425147426147427147428147429147430147431147432147433147434147435147436147437147438147439147440147441147442147443147444147445147446147447147448147449147450147451147452147453147454147455147456147457147458147459147460147461147462147463147464147465147466147467147468147469147470147471147472147473147474147475147476147477147478147479147480147481147482147483147484147485147486147487147488147489147490147491147492147493147494147495147496147497147498147499147500147501147502147503147504147505147506147507147508147509147510147511147512147513147514147515147516147517147518147519147520147521147522147523147524147525147526147527147528147529147530147531147532147533147534147535147536147537147538147539147540147541147542147543147544147545147546147547147548147549147550147551147552147553147554147555147556147557147558147559147560147561147562147563147564147565147566147567147568147569147570147571147572147573147574147575147576147577147578147579147580147581147582147583147584147585147586147587147588147589147590147591147592147593147594147595147596147597147598147599147600147601147602147603147604147605147606147607147608147609147610147611147612147613147614147615147616147617147618147619147620147621147622147623147624147625147626147627147628147629147630147631147632147633147634147635147636147637147638147639147640147641147642147643147644147645147646147647147648147649147650147651147652147653147654147655147656147657147658147659147660147661147662147663147664147665147666147667147668147669147670147671147672147673147674147675147676147677147678147679147680147681147682147683147684147685147686147687147688147689147690147691147692147693147694147695147696147697147698147699147700147701147702147703147704147705147706147707147708147709147710147711147712147713147714147715147716147717147718147719147720147721147722147723147724147725147726147727147728147729147730147731147732147733147734147735147736147737147738147739147740147741147742147743147744147745147746147747147748147749147750147751147752147753147754147755147756147757147758147759147760147761147762147763147764147765147766147767147768147769147770147771147772147773147774147775147776147777147778147779147780147781147782147783147784147785147786147787147788147789147790147791147792147793147794147795147796147797147798147799147800147801147802147803147804147805147806147807147808147809147810147811147812147813147814147815147816147817147818147819147820147821147822147823147824147825147826147827147828147829147830147831147832147833147834147835147836147837147838147839147840147841147842147843147844147845147846147847147848147849147850147851147852147853147854147855147856147857147858147859147860147861147862147863147864147865147866147867147868147869147870147871147872147873147874147875147876147877147878147879147880147881147882147883147884147885147886147887147888147889147890147891147892147893147894147895147896147897147898147899147900147901147902147903147904147905147906147907147908147909147910147911147912147913147914147915147916147917147918147919147920147921147922147923147924147925147926147927147928147929147930147931147932147933147934147935147936147937147938147939147940147941147942147943147944147945147946147947147948147949147950147951147952147953147954147955147956147957147958147959147960147961147962147963147964147965147966147967147968147969147970147971147972147973147974147975147976147977147978147979147980147981147982147983147984147985147986147987147988147989147990147991147992147993147994147995147996147997147998147999148000148001148002148003148004148005148006148007148008148009148010148011148012148013148014148015148016148017148018148019148020148021148022148023148024148025148026148027148028148029148030148031148032148033148034148035148036148037148038148039148040148041148042148043148044148045148046148047148048148049148050148051148052148053148054148055148056148057148058148059148060148061148062148063148064148065148066148067148068148069148070148071148072148073148074148075148076148077148078148079148080148081148082148083148084148085148086148087148088148089148090148091148092148093148094148095148096148097148098148099148100148101148102148103148104148105148106148107148108148109148110148111148112148113148114148115148116148117148118148119148120148121148122148123148124148125148126148127148128148129148130148131148132148133148134148135148136148137148138148139148140148141148142148143148144148145148146148147148148148149148150148151148152148153148154148155148156148157148158148159148160148161148162148163148164148165148166148167148168148169148170148171148172148173148174148175148176148177148178148179148180148181148182148183148184148185148186148187148188148189148190148191148192148193148194148195148196148197148198148199148200148201148202148203148204148205148206148207148208148209148210148211148212148213148214148215148216148217148218148219148220148221148222148223148224148225148226148227148228148229148230148231148232148233148234148235148236148237148238148239148240148241148242148243148244148245148246148247148248148249148250148251148252148253148254148255148256148257148258148259148260148261148262148263148264148265148266148267148268148269148270148271148272148273148274148275148276148277148278148279148280148281148282148283148284148285148286148287148288148289148290148291148292148293148294148295148296148297148298148299148300148301148302148303148304148305148306148307148308148309148310148311148312148313148314148315148316148317148318148319148320148321148322148323148324148325148326148327148328148329148330148331148332148333148334148335148336148337148338148339148340148341148342148343148344148345148346148347148348148349148350148351148352148353148354148355148356148357148358148359148360148361148362148363148364148365148366148367148368148369148370148371148372148373148374148375148376148377148378148379148380148381148382148383148384148385148386148387148388148389148390148391148392148393148394148395148396148397148398148399148400148401148402148403148404148405148406148407148408148409148410148411148412148413148414148415148416148417148418148419148420148421148422148423148424148425148426148427148428148429148430148431148432148433148434148435148436148437148438148439148440148441148442148443148444148445148446148447148448148449148450148451148452148453148454148455148456148457148458148459148460148461148462148463148464148465148466148467148468148469148470148471148472148473148474148475148476148477148478148479148480148481148482148483148484148485148486148487148488148489148490148491148492148493148494148495148496148497148498148499148500148501148502148503148504148505148506148507148508148509148510148511148512148513148514148515148516148517148518148519148520148521148522148523148524148525148526148527148528148529148530148531148532148533148534148535148536148537148538148539148540148541148542148543148544148545148546148547148548148549148550148551148552148553148554148555148556148557148558148559148560148561148562148563148564148565148566148567148568148569148570148571148572148573148574148575148576148577148578148579148580148581148582148583148584148585148586148587148588148589148590148591148592148593148594148595148596148597148598148599148600148601148602148603148604148605148606148607148608148609148610148611148612148613148614148615148616148617148618148619148620148621148622148623148624148625148626148627148628148629148630148631148632148633148634148635148636148637148638148639148640148641148642148643148644148645148646148647148648148649148650148651148652148653148654148655148656148657148658148659148660148661148662148663148664148665148666148667148668148669148670148671148672148673148674148675148676148677148678148679148680148681148682148683148684148685148686148687148688148689148690148691148692148693148694148695148696148697148698148699148700148701148702148703148704148705148706148707148708148709148710148711148712148713148714148715148716148717148718148719148720148721148722148723148724148725148726148727148728148729148730148731148732148733148734148735148736148737148738148739148740148741148742148743148744148745148746148747148748148749148750148751148752148753148754148755148756148757148758148759148760148761148762148763148764148765148766148767148768148769148770148771148772148773148774148775148776148777148778148779148780148781148782148783148784148785148786148787148788148789148790148791148792148793148794148795148796148797148798148799148800148801148802148803148804148805148806148807148808148809148810148811148812148813148814148815148816148817148818148819148820148821148822148823148824148825148826148827148828148829148830148831148832148833148834148835148836148837148838148839148840148841148842148843148844148845148846148847148848148849148850148851148852148853148854148855148856148857148858148859148860148861148862148863148864148865148866148867148868148869148870148871148872148873148874148875148876148877148878148879148880148881148882148883148884148885148886148887148888148889148890148891148892148893148894148895148896148897148898148899148900148901148902148903148904148905148906148907148908148909148910148911148912148913148914148915148916148917148918148919148920148921148922148923148924148925148926148927148928148929148930148931148932148933148934148935148936148937148938148939148940148941148942148943148944148945148946148947148948148949148950148951148952148953148954148955148956148957148958148959148960148961148962148963148964148965148966148967148968148969148970148971148972148973148974148975148976148977148978148979148980148981148982148983148984148985148986148987148988148989148990148991148992148993148994148995148996148997148998148999149000149001149002149003149004149005149006149007149008149009149010149011149012149013149014149015149016149017149018149019149020149021149022149023149024149025149026149027149028149029149030149031149032149033149034149035149036149037149038149039149040149041149042149043149044149045149046149047149048149049149050149051149052149053149054149055149056149057149058149059149060149061149062149063149064149065149066149067149068149069149070149071149072149073149074149075149076149077149078149079149080149081149082149083149084149085149086149087149088149089149090149091149092149093149094149095149096149097149098149099149100149101149102149103149104149105149106149107149108149109149110149111149112149113149114149115149116149117149118149119149120149121149122149123149124149125149126149127149128149129149130149131149132149133149134149135149136149137149138149139149140149141149142149143149144149145149146149147149148149149149150149151149152149153149154149155149156149157149158149159149160149161149162149163149164149165149166149167149168149169149170149171149172149173149174149175149176149177149178149179149180149181149182149183149184149185149186149187149188149189149190149191149192149193149194149195149196149197149198149199149200149201149202149203149204149205149206149207149208149209149210149211149212149213149214149215149216149217149218149219149220149221149222149223149224149225149226149227149228149229149230149231149232149233149234149235149236149237149238149239149240149241149242149243149244149245149246149247149248149249149250149251149252149253149254149255149256149257149258149259149260149261149262149263149264149265149266149267149268149269149270149271149272149273149274149275149276149277149278149279149280149281149282149283149284149285149286149287149288149289149290149291149292149293149294149295149296149297149298149299149300149301149302149303149304149305149306149307149308149309149310149311149312149313149314149315149316149317149318149319149320149321149322149323149324149325149326149327149328149329149330149331149332149333149334149335149336149337149338149339149340149341149342149343149344149345149346149347149348149349149350149351149352149353149354149355149356149357149358149359149360149361149362149363149364149365149366149367149368149369149370149371149372149373149374149375149376149377149378149379149380149381149382149383149384149385149386149387149388149389149390149391149392149393149394149395149396149397149398149399149400149401149402149403149404149405149406149407149408149409149410149411149412149413149414149415149416149417149418149419149420149421149422149423149424149425149426149427149428149429149430149431149432149433149434149435149436149437149438149439149440149441149442149443149444149445149446149447149448149449149450149451149452149453149454149455149456149457149458149459149460149461149462149463149464149465149466149467149468149469149470149471149472149473149474149475149476149477149478149479149480149481149482149483149484149485149486149487149488149489149490149491149492149493149494149495149496149497149498149499149500149501149502149503149504149505149506149507149508149509149510149511149512149513149514149515149516149517149518149519149520149521149522149523149524149525149526149527149528149529149530149531149532149533149534149535149536149537149538149539149540149541149542149543149544149545149546149547149548149549149550149551149552149553149554149555149556149557149558149559149560149561149562149563149564149565149566149567149568149569149570149571149572149573149574149575149576149577149578149579149580149581149582149583149584149585149586149587149588149589149590149591149592149593149594149595149596149597149598149599149600149601149602149603149604149605149606149607149608149609149610149611149612149613149614149615149616149617149618149619149620149621149622149623149624149625149626149627149628149629149630149631149632149633149634149635149636149637149638149639149640149641149642149643149644149645149646149647149648149649149650149651149652149653149654149655149656149657149658149659149660149661149662149663149664149665149666149667149668149669149670149671149672149673149674149675149676149677149678149679149680149681149682149683149684149685149686149687149688149689149690149691149692149693149694149695149696149697149698149699149700149701149702149703149704149705149706149707149708149709149710149711149712149713149714149715149716149717149718149719149720149721149722149723149724149725149726149727149728149729149730149731149732149733149734149735149736149737149738149739149740149741149742149743149744149745149746149747149748149749149750149751149752149753149754149755149756149757149758149759149760149761149762149763149764149765149766149767149768149769149770149771149772149773149774149775149776149777149778149779149780149781149782149783149784149785149786149787149788149789149790149791149792149793149794149795149796149797149798149799149800149801149802149803149804149805149806149807149808149809149810149811149812149813149814149815149816149817149818149819149820149821149822149823149824149825149826149827149828149829149830149831149832149833149834149835149836149837149838149839149840149841149842149843149844149845149846149847149848149849149850149851149852149853149854149855149856149857149858149859149860149861149862149863149864149865149866149867149868149869149870149871149872149873149874149875149876149877149878149879149880149881149882149883149884149885149886149887149888149889149890149891149892149893149894149895149896149897149898149899149900149901149902149903149904149905149906149907149908149909149910149911149912149913149914149915149916149917149918149919149920149921149922149923149924149925149926149927149928149929149930149931149932149933149934149935149936149937149938149939149940149941149942149943149944149945149946149947149948149949149950149951149952149953149954149955149956149957149958149959149960149961149962149963149964149965149966149967149968149969149970149971149972149973149974149975149976149977149978149979149980149981149982149983149984149985149986149987149988149989149990149991149992149993149994149995149996149997149998149999150000150001150002150003150004150005150006150007150008150009150010150011150012150013150014150015150016150017150018150019150020150021150022150023150024150025150026150027150028150029150030150031150032150033150034150035150036150037150038150039150040150041150042150043150044150045150046150047150048150049150050150051150052150053150054150055150056150057150058150059150060150061150062150063150064150065150066150067150068150069150070150071150072150073150074150075150076150077150078150079150080150081150082150083150084150085150086150087150088150089150090150091150092150093150094150095150096150097150098150099150100150101150102150103150104150105150106150107150108150109150110150111150112150113150114150115150116150117150118150119150120150121150122150123150124150125150126150127150128150129150130150131150132150133150134150135150136150137150138150139150140150141150142150143150144150145150146150147150148150149150150150151150152150153150154150155150156150157150158150159150160150161150162150163150164150165150166150167150168150169150170150171150172150173150174150175150176150177150178150179150180150181150182150183150184150185150186150187150188150189150190150191150192150193150194150195150196150197150198150199150200150201150202150203150204150205150206150207150208150209150210150211150212150213150214150215150216150217150218150219150220150221150222150223150224150225150226150227150228150229150230150231150232150233150234150235150236150237150238150239150240150241150242150243150244150245150246150247150248150249150250150251150252150253150254150255150256150257150258150259150260150261150262150263150264150265150266150267150268150269150270150271150272150273150274150275150276150277150278150279150280150281150282150283150284150285150286150287150288150289150290150291150292150293150294150295150296150297150298150299150300150301150302150303150304150305150306150307150308150309150310150311150312150313150314150315150316150317150318150319150320150321150322150323150324150325150326150327150328150329150330150331150332150333150334150335150336150337150338150339150340150341150342150343150344150345150346150347150348150349150350150351150352150353150354150355150356150357150358150359150360150361150362150363150364150365150366150367150368150369150370150371150372150373150374150375150376150377150378150379150380150381150382150383150384150385150386150387150388150389150390150391150392150393150394150395150396150397150398150399150400150401150402150403150404150405150406150407150408150409150410150411150412150413150414150415150416150417150418150419150420150421150422150423150424150425150426150427150428150429150430150431150432150433150434150435150436150437150438150439150440150441150442150443150444150445150446150447150448150449150450150451150452150453150454150455150456150457150458150459150460150461150462150463150464150465150466150467150468150469150470150471150472150473150474150475150476150477150478150479150480150481150482150483150484150485150486150487150488150489150490150491150492150493150494150495150496150497150498150499150500150501150502150503150504150505150506150507150508150509150510150511150512150513150514150515150516150517150518150519150520150521150522150523150524150525150526150527150528150529150530150531150532150533150534150535150536150537150538150539150540150541150542150543150544150545150546150547150548150549150550150551150552150553150554150555150556150557150558150559150560150561150562150563150564150565150566150567150568150569150570150571150572150573150574150575150576150577150578150579150580150581150582150583150584150585150586150587150588150589150590150591150592150593150594150595150596150597150598150599150600150601150602150603150604150605150606150607150608150609150610150611150612150613150614150615150616150617150618150619150620150621150622150623150624150625150626150627150628150629150630150631150632150633150634150635150636150637150638150639150640150641150642150643150644150645150646150647150648150649150650150651150652150653150654150655150656150657150658150659150660150661150662150663150664150665150666150667150668150669150670150671150672150673150674150675150676150677150678150679150680150681150682150683150684150685150686150687150688150689150690150691150692150693150694150695150696150697150698150699150700150701150702150703150704150705150706150707150708150709150710150711150712150713150714150715150716150717150718150719150720150721150722150723150724150725150726150727150728150729150730150731150732150733150734150735150736150737150738150739150740150741150742150743150744150745150746150747150748150749150750150751150752150753150754150755150756150757150758150759150760150761150762150763150764150765150766150767150768150769150770150771150772150773150774150775150776150777150778150779150780150781150782150783150784150785150786150787150788150789150790150791150792150793150794150795150796150797150798150799150800150801150802150803150804150805150806150807150808150809150810150811150812150813150814150815150816150817150818150819150820150821150822150823150824150825150826150827150828150829150830150831150832150833150834150835150836150837150838150839150840150841150842150843150844150845150846150847150848150849150850150851150852150853150854150855150856150857150858150859150860150861150862150863150864150865150866150867150868150869150870150871150872150873150874150875150876150877150878150879150880150881150882150883150884150885150886150887150888150889150890150891150892150893150894150895150896150897150898150899150900150901150902150903150904150905150906150907150908150909150910150911150912150913150914150915150916150917150918150919150920150921150922150923150924150925150926150927150928150929150930150931150932150933150934150935150936150937150938150939150940150941150942150943150944150945150946150947150948150949150950150951150952150953150954150955150956150957150958150959150960150961150962150963150964150965150966150967150968150969150970150971150972150973150974150975150976150977150978150979150980150981150982150983150984150985150986150987150988150989150990150991150992150993150994150995150996150997150998150999151000151001151002151003151004151005151006151007151008151009151010151011151012151013151014151015151016151017151018151019151020151021151022151023151024151025151026151027151028151029151030151031151032151033151034151035151036151037151038151039151040151041151042151043151044151045151046151047151048151049151050151051151052151053151054151055151056151057151058151059151060151061151062151063151064151065151066151067151068151069151070151071151072151073151074151075151076151077151078151079151080151081151082151083151084151085151086151087151088151089151090151091151092151093151094151095151096151097151098151099151100151101151102151103151104151105151106151107151108151109151110151111151112151113151114151115151116151117151118151119151120151121151122151123151124151125151126151127151128151129151130151131151132151133151134151135151136151137151138151139151140151141151142151143151144151145151146151147151148151149151150151151151152151153151154151155151156151157151158151159151160151161151162151163151164151165151166151167151168151169151170151171151172151173151174151175151176151177151178151179151180151181151182151183151184151185151186151187151188151189151190151191151192151193151194151195151196151197151198151199151200151201151202151203151204151205151206151207151208151209151210151211151212151213151214151215151216151217151218151219151220151221151222151223151224151225151226151227151228151229151230151231151232151233151234151235151236151237151238151239151240151241151242151243151244151245151246151247151248151249151250151251151252151253151254151255151256151257151258151259151260151261151262151263151264151265151266151267151268151269151270151271151272151273151274151275151276151277151278151279151280151281151282151283151284151285151286151287151288151289151290151291151292151293151294151295151296151297151298151299151300151301151302151303151304151305151306151307151308151309151310151311151312151313151314151315151316151317151318151319151320151321151322151323151324151325151326151327151328151329151330151331151332151333151334151335151336151337151338151339151340151341151342151343151344151345151346151347151348151349151350151351151352151353151354151355151356151357151358151359151360151361151362151363151364151365151366151367151368151369151370151371151372151373151374151375151376151377151378151379151380151381151382151383151384151385151386151387151388151389151390151391151392151393151394151395151396151397151398151399151400151401151402151403151404151405151406151407151408151409151410151411151412151413151414151415151416151417151418151419151420151421151422151423151424151425151426151427151428151429151430151431151432151433151434151435151436151437151438151439151440151441151442151443151444151445151446151447151448151449151450151451151452151453151454151455151456151457151458151459151460151461151462151463151464151465151466151467151468151469151470151471151472151473151474151475151476151477151478151479151480151481151482151483151484151485151486151487151488151489151490151491151492151493151494151495151496151497151498151499151500151501151502151503151504151505151506151507151508151509151510151511151512151513151514151515151516151517151518151519151520151521151522151523151524151525151526151527151528151529151530151531151532151533151534151535151536151537151538151539151540151541151542151543151544151545151546151547151548151549151550151551151552151553151554151555151556151557151558151559151560151561151562151563151564151565151566151567151568151569151570151571151572151573151574151575151576151577151578151579151580151581151582151583151584151585151586151587151588151589151590151591151592151593151594151595151596151597151598151599151600151601151602151603151604151605151606151607151608151609151610151611151612151613151614151615151616151617151618151619151620151621151622151623151624151625151626151627151628151629151630151631151632151633151634151635151636151637151638151639151640151641151642151643151644151645151646151647151648151649151650151651151652151653151654151655151656151657151658151659151660151661151662151663151664151665151666151667151668151669151670151671151672151673151674151675151676151677151678151679151680151681151682151683151684151685151686151687151688151689151690151691151692151693151694151695151696151697151698151699151700151701151702151703151704151705151706151707151708151709151710151711151712151713151714151715151716151717151718151719151720151721151722151723151724151725151726151727151728151729151730151731151732151733151734151735151736151737151738151739151740151741151742151743151744151745151746151747151748151749151750151751151752151753151754151755151756151757151758151759151760151761151762151763151764151765151766151767151768151769151770151771151772151773151774151775151776151777151778151779151780151781151782151783151784151785151786151787151788151789151790151791151792151793151794151795151796151797151798151799151800151801151802151803151804151805151806151807151808151809151810151811151812151813151814151815151816151817151818151819151820151821151822151823151824151825151826151827151828151829151830151831151832151833151834151835151836151837151838151839151840151841151842151843151844151845151846151847151848151849151850151851151852151853151854151855151856151857151858151859151860151861151862151863151864151865151866151867151868151869151870151871151872151873151874151875151876151877151878151879151880151881151882151883151884151885151886151887151888151889151890151891151892151893151894151895151896151897151898151899151900151901151902151903151904151905151906151907151908151909151910151911151912151913151914151915151916151917151918151919151920151921151922151923151924151925151926151927151928151929151930151931151932151933151934151935151936151937151938151939151940151941151942151943151944151945151946151947151948151949151950151951151952151953151954151955151956151957151958151959151960151961151962151963151964151965151966151967151968151969151970151971151972151973151974151975151976151977151978151979151980151981151982151983151984151985151986151987151988151989151990151991151992151993151994151995151996151997151998151999152000152001152002152003152004152005152006152007152008152009152010152011152012152013152014152015152016152017152018152019152020152021152022152023152024152025152026152027152028152029152030152031152032152033152034152035152036152037152038152039152040152041152042152043152044152045152046152047152048152049152050152051152052152053152054152055152056152057152058152059152060152061152062152063152064152065152066152067152068152069152070152071152072152073152074152075152076152077152078152079152080152081152082152083152084152085152086152087152088152089152090152091152092152093152094152095152096152097152098152099152100152101152102152103152104152105152106152107152108152109152110152111152112152113152114152115152116152117152118152119152120152121152122152123152124152125152126152127152128152129152130152131152132152133152134152135152136152137152138152139152140152141152142152143152144152145152146152147152148152149152150152151152152152153152154152155152156152157152158152159152160152161152162152163152164152165152166152167152168152169152170152171152172152173152174152175152176152177152178152179152180152181152182152183152184152185152186152187152188152189152190152191152192152193152194152195152196152197152198152199152200152201152202152203152204152205152206152207152208152209152210152211152212152213152214152215152216152217152218152219152220152221152222152223152224152225152226152227152228152229152230152231152232152233152234152235152236152237152238152239152240152241152242152243152244152245152246152247152248152249152250152251152252152253152254152255152256152257152258152259152260152261152262152263152264152265152266152267152268152269152270152271152272152273152274152275152276152277152278152279152280152281152282152283152284152285152286152287152288152289152290152291152292152293152294152295152296152297152298152299152300152301152302152303152304152305152306152307152308152309152310152311152312152313152314152315152316152317152318152319152320152321152322152323152324152325152326152327152328152329152330152331152332152333152334152335152336152337152338152339152340152341152342152343152344152345152346152347152348152349152350152351152352152353152354152355152356152357152358152359152360152361152362152363152364152365152366152367152368152369152370152371152372152373152374152375152376152377152378152379152380152381152382152383152384152385152386152387152388152389152390152391152392152393152394152395152396152397152398152399152400152401152402152403152404152405152406152407152408152409152410152411152412152413152414152415152416152417152418152419152420152421152422152423152424152425152426152427152428152429152430152431152432152433152434152435152436152437152438152439152440152441152442152443152444152445152446152447152448152449152450152451152452152453152454152455152456152457152458152459152460152461152462152463152464152465152466152467152468152469152470152471152472152473152474152475152476152477152478152479152480152481152482152483152484152485152486152487152488152489152490152491152492152493152494152495152496152497152498152499152500152501152502152503152504152505152506152507152508152509152510152511152512152513152514152515152516152517152518152519152520152521152522152523152524152525152526152527152528152529152530152531152532152533152534152535152536152537152538152539152540152541152542152543152544152545152546152547152548152549152550152551152552152553152554152555152556152557152558152559152560152561152562152563152564152565152566152567152568152569152570152571152572152573152574152575152576152577152578152579152580152581152582152583152584152585152586152587152588152589152590152591152592152593152594152595152596152597152598152599152600152601152602152603152604152605152606152607152608152609152610152611152612152613152614152615152616152617152618152619152620152621152622152623152624152625152626152627152628152629152630152631152632152633152634152635152636152637152638152639152640152641152642152643152644152645152646152647152648152649152650152651152652152653152654152655152656152657152658152659152660152661152662152663152664152665152666152667152668152669152670152671152672152673152674152675152676152677152678152679152680152681152682152683152684152685152686152687152688152689152690152691152692152693152694152695152696152697152698152699152700152701152702152703152704152705152706152707152708152709152710152711152712152713152714152715152716152717152718152719152720152721152722152723152724152725152726152727152728152729152730152731152732152733152734152735152736152737152738152739152740152741152742152743152744152745152746152747152748152749152750152751152752152753152754152755152756152757152758152759152760152761152762152763152764152765152766152767152768152769152770152771152772152773152774152775152776152777152778152779152780152781152782152783152784152785152786152787152788152789152790152791152792152793152794152795152796152797152798152799152800152801152802152803152804152805152806152807152808152809152810152811152812152813152814152815152816152817152818152819152820152821152822152823152824152825152826152827152828152829152830152831152832152833152834152835152836152837152838152839152840152841152842152843152844152845152846152847152848152849152850152851152852152853152854152855152856152857152858152859152860152861152862152863152864152865152866152867152868152869152870152871152872152873152874152875152876152877152878152879152880152881152882152883152884152885152886152887152888152889152890152891152892152893152894152895152896152897152898152899152900152901152902152903152904152905152906152907152908152909152910152911152912152913152914152915152916152917152918152919152920152921152922152923152924152925152926152927152928152929152930152931152932152933152934152935152936152937152938152939152940152941152942152943152944152945152946152947152948152949152950152951152952152953152954152955152956152957152958152959152960152961152962152963152964152965152966152967152968152969152970152971152972152973152974152975152976152977152978152979152980152981152982152983152984152985152986152987152988152989152990152991152992152993152994152995152996152997152998152999153000153001153002153003153004153005153006153007153008153009153010153011153012153013153014153015153016153017153018153019153020153021153022153023153024153025153026153027153028153029153030153031153032153033153034153035153036153037153038153039153040153041153042153043153044153045153046153047153048153049153050153051153052153053153054153055153056153057153058153059153060153061153062153063153064153065153066153067153068153069153070153071153072153073153074153075153076153077153078153079153080153081153082153083153084153085153086153087153088153089153090153091153092153093153094153095153096153097153098153099153100153101153102153103153104153105153106153107153108153109153110153111153112153113153114153115153116153117153118153119153120153121153122153123153124153125153126153127153128153129153130153131153132153133153134153135153136153137153138153139153140153141153142153143153144153145153146153147153148153149153150153151153152153153153154153155153156153157153158153159153160153161153162153163153164153165153166153167153168153169153170153171153172153173153174153175153176153177153178153179153180153181153182153183153184153185153186153187153188153189153190153191153192153193153194153195153196153197153198153199153200153201153202153203153204153205153206153207153208153209153210153211153212153213153214153215153216153217153218153219153220153221153222153223153224153225153226153227153228153229153230153231153232153233153234153235153236153237153238153239153240153241153242153243153244153245153246153247153248153249153250153251153252153253153254153255153256153257153258153259153260153261153262153263153264153265153266153267153268153269153270153271153272153273153274153275153276153277153278153279153280153281153282153283153284153285153286153287153288153289153290153291153292153293153294153295153296153297153298153299153300153301153302153303153304153305153306153307153308153309153310153311153312153313153314153315153316153317153318153319153320153321153322153323153324153325153326153327153328153329153330153331153332153333153334153335153336153337153338153339153340153341153342153343153344153345153346153347153348153349153350153351153352153353153354153355153356153357153358153359153360153361153362153363153364153365153366153367153368153369153370153371153372153373153374153375153376153377153378153379153380153381153382153383153384153385153386153387153388153389153390153391153392153393153394153395153396153397153398153399153400153401153402153403153404153405153406153407153408153409153410153411153412153413153414153415153416153417153418153419153420153421153422153423153424153425153426153427153428153429153430153431153432153433153434153435153436153437153438153439153440153441153442153443153444153445153446153447153448153449153450153451153452153453153454153455153456153457153458153459153460153461153462153463153464153465153466153467153468153469153470153471153472153473153474153475153476153477153478153479153480153481153482153483153484153485153486153487153488153489153490153491153492153493153494153495153496153497153498153499153500153501153502153503153504153505153506153507153508153509153510153511153512153513153514153515153516153517153518153519153520153521153522153523153524153525153526153527153528153529153530153531153532153533153534153535153536153537153538153539153540153541153542153543153544153545153546153547153548153549153550153551153552153553153554153555153556153557153558153559153560153561153562153563153564153565153566153567153568153569153570153571153572153573153574153575153576153577153578153579153580153581153582153583153584153585153586153587153588153589153590153591153592153593153594153595153596153597153598153599153600153601153602153603153604153605153606153607153608153609153610153611153612153613153614153615153616153617153618153619153620153621153622153623153624153625153626153627153628153629153630153631153632153633153634153635153636153637153638153639153640153641153642153643153644153645153646153647153648153649153650153651153652153653153654153655153656153657153658153659153660153661153662153663153664153665153666153667153668153669153670153671153672153673153674153675153676153677153678153679153680153681153682153683153684153685153686153687153688153689153690153691153692153693153694153695153696153697153698153699153700153701153702153703153704153705153706153707153708153709153710153711153712153713153714153715153716153717153718153719153720153721153722153723153724153725153726153727153728153729153730153731153732153733153734153735153736153737153738153739153740153741153742153743153744153745153746153747153748153749153750153751153752153753153754153755153756153757153758153759153760153761153762153763153764153765153766153767153768153769153770153771153772153773153774153775153776153777153778153779153780153781153782153783153784153785153786153787153788153789153790153791153792153793153794153795153796153797153798153799153800153801153802153803153804153805153806153807153808153809153810153811153812153813153814153815153816153817153818153819153820153821153822153823153824153825153826153827153828153829153830153831153832153833153834153835153836153837153838153839153840153841153842153843153844153845153846153847153848153849153850153851153852153853153854153855153856153857153858153859153860153861153862153863153864153865153866153867153868153869153870153871153872153873153874153875153876153877153878153879153880153881153882153883153884153885153886153887153888153889153890153891153892153893153894153895153896153897153898153899153900153901153902153903153904153905153906153907153908153909153910153911153912153913153914153915153916153917153918153919153920153921153922153923153924153925153926153927153928153929153930153931153932153933153934153935153936153937153938153939153940153941153942153943153944153945153946153947153948153949153950153951153952153953153954153955153956153957153958153959153960153961153962153963153964153965153966153967153968153969153970153971153972153973153974153975153976153977153978153979153980153981153982153983153984153985153986153987153988153989153990153991153992153993153994153995153996153997153998153999154000154001154002154003154004154005154006154007154008154009154010154011154012154013154014154015154016154017154018154019154020154021154022154023154024154025154026154027154028154029154030154031154032154033154034154035154036154037154038154039154040154041154042154043154044154045154046154047154048154049154050154051154052154053154054154055154056154057154058154059154060154061154062154063154064154065154066154067154068154069154070154071154072154073154074154075154076154077154078154079154080154081154082154083154084154085154086154087154088154089154090154091154092154093154094154095154096154097154098154099154100154101154102154103154104154105154106154107154108154109154110154111154112154113154114154115154116154117154118154119154120154121154122154123154124154125154126154127154128154129154130154131154132154133154134154135154136154137154138154139154140154141154142154143154144154145154146154147154148154149154150154151154152154153154154154155154156154157154158154159154160154161154162154163154164154165154166154167154168154169154170154171154172154173154174154175154176154177154178154179154180154181154182154183154184154185154186154187154188154189154190154191154192154193154194154195154196154197154198154199154200154201154202154203154204154205154206154207154208154209154210154211154212154213154214154215154216154217154218154219154220154221154222154223154224154225154226154227154228154229154230154231154232154233154234154235154236154237154238154239154240154241154242154243154244154245154246154247154248154249154250154251154252154253154254154255154256154257154258154259154260154261154262154263154264154265154266154267154268154269154270154271154272154273154274154275154276154277154278154279154280154281154282154283154284154285154286154287154288154289154290154291154292154293154294154295154296154297154298154299154300154301154302154303154304154305154306154307154308154309154310154311154312154313154314154315154316154317154318154319154320154321154322154323154324154325154326154327154328154329154330154331154332154333154334154335154336154337154338154339154340154341154342154343154344154345154346154347154348154349154350154351154352154353154354154355154356154357154358154359154360154361154362154363154364154365154366154367154368154369154370154371154372154373154374154375154376154377154378154379154380154381154382154383154384154385154386154387154388154389154390154391154392154393154394154395154396154397154398154399154400154401154402154403154404154405154406154407154408154409154410154411154412154413154414154415154416154417154418154419154420154421154422154423154424154425154426154427154428154429154430154431154432154433154434154435154436154437154438154439154440154441154442154443154444154445154446154447154448154449154450154451154452154453154454154455154456154457154458154459154460154461154462154463154464154465154466154467154468154469154470154471154472154473154474154475154476154477154478154479154480154481154482154483154484154485154486154487154488154489154490154491154492154493154494154495154496154497154498154499154500154501154502154503154504154505154506154507154508154509154510154511154512154513154514154515154516154517154518154519154520154521154522154523154524154525154526154527154528154529154530154531154532154533154534154535154536154537154538154539154540154541154542154543154544154545154546154547154548154549154550154551154552154553154554154555154556154557154558154559154560154561154562154563154564154565154566154567154568154569154570154571154572154573154574154575154576154577154578154579154580154581154582154583154584154585154586154587154588154589154590154591154592154593154594154595154596154597154598154599154600154601154602154603154604154605154606154607154608154609154610154611154612154613154614154615154616154617154618154619154620154621154622154623154624154625154626154627154628154629154630154631154632154633154634154635154636154637154638154639154640154641154642154643154644154645154646154647154648154649154650154651154652154653154654154655154656154657154658154659154660154661154662154663154664154665154666154667154668154669154670154671154672154673154674154675154676154677154678154679154680154681154682154683154684154685154686154687154688154689154690154691154692154693154694154695154696154697154698154699154700154701154702154703154704154705154706154707154708154709154710154711154712154713154714154715154716154717154718154719154720154721154722154723154724154725154726154727154728154729154730154731154732154733154734154735154736154737154738154739154740154741154742154743154744154745154746154747154748154749154750154751154752154753154754154755154756154757154758154759154760154761154762154763154764154765154766154767154768154769154770154771154772154773154774154775154776154777154778154779154780154781154782154783154784154785154786154787154788154789154790154791154792154793154794154795154796154797154798154799154800154801154802154803154804154805154806154807154808154809154810154811154812154813154814154815154816154817154818154819154820154821154822154823154824154825154826154827154828154829154830154831154832154833154834154835154836154837154838154839154840154841154842154843154844154845154846154847154848154849154850154851154852154853154854154855154856154857154858154859154860154861154862154863154864154865154866154867154868154869154870154871154872154873154874154875154876154877154878154879154880154881154882154883154884154885154886154887154888154889154890154891154892154893154894154895154896154897154898154899154900154901154902154903154904154905154906154907154908154909154910154911154912154913154914154915154916154917154918154919154920154921154922154923154924154925154926154927154928154929154930154931154932154933154934154935154936154937154938154939154940154941154942154943154944154945154946154947154948154949154950154951154952154953154954154955154956154957154958154959154960154961154962154963154964154965154966154967154968154969154970154971154972154973154974154975154976154977154978154979154980154981154982154983154984154985154986154987154988154989154990154991154992154993154994154995154996154997154998154999155000155001155002155003155004155005155006155007155008155009155010155011155012155013155014155015155016155017155018155019155020155021155022155023155024155025155026155027155028155029155030155031155032155033155034155035155036155037155038155039155040155041155042155043155044155045155046155047155048155049155050155051155052155053155054155055155056155057155058155059155060155061155062155063155064155065155066155067155068155069155070155071155072155073155074155075155076155077155078155079155080155081155082155083155084155085155086155087155088155089155090155091155092155093155094155095155096155097155098155099155100155101155102155103155104155105155106155107155108155109155110155111155112155113155114155115155116155117155118155119155120155121155122155123155124155125155126155127155128155129155130155131155132155133155134155135155136155137155138155139155140155141155142155143155144155145155146155147155148155149155150155151155152155153155154155155155156155157155158155159155160155161155162155163155164155165155166155167155168155169155170155171155172155173155174155175155176155177155178155179155180155181155182155183155184155185155186155187155188155189155190155191155192155193155194155195155196155197155198155199155200155201155202155203155204155205155206155207155208155209155210155211155212155213155214155215155216155217155218155219155220155221155222155223155224155225155226155227155228155229155230155231155232155233155234155235155236155237155238155239155240155241155242155243155244155245155246155247155248155249155250155251155252155253155254155255155256155257155258155259155260155261155262155263155264155265155266155267155268155269155270155271155272155273155274155275155276155277155278155279155280155281155282155283155284155285155286155287155288155289155290155291155292155293155294155295155296155297155298155299155300155301155302155303155304155305155306155307155308155309155310155311155312155313155314155315155316155317155318155319155320155321155322155323155324155325155326155327155328155329155330155331155332155333155334155335155336155337155338155339155340155341155342155343155344155345155346155347155348155349155350155351155352155353155354155355155356155357155358155359155360155361155362155363155364155365155366155367155368155369155370155371155372155373155374155375155376155377155378155379155380155381155382155383155384155385155386155387155388155389155390155391155392155393155394155395155396155397155398155399155400155401155402155403155404155405155406155407155408155409155410155411155412155413155414155415155416155417155418155419155420155421155422155423155424155425155426155427155428155429155430155431155432155433155434155435155436155437155438155439155440155441155442155443155444155445155446155447155448155449155450155451155452155453155454155455155456155457155458155459155460155461155462155463155464155465155466155467155468155469155470155471155472155473155474155475155476155477155478155479155480155481155482155483155484155485155486155487155488155489155490155491155492155493155494155495155496155497155498155499155500155501155502155503155504155505155506155507155508155509155510155511155512155513155514155515155516155517155518155519155520155521155522155523155524155525155526155527155528155529155530155531155532155533155534155535155536155537155538155539155540155541155542155543155544155545155546155547155548155549155550155551155552155553155554155555155556155557155558155559155560155561155562155563155564155565155566155567155568155569155570155571155572155573155574155575155576155577155578155579155580155581155582155583155584155585155586155587155588155589155590155591155592155593155594155595155596155597155598155599155600155601155602155603155604155605155606155607155608155609155610155611155612155613155614155615155616155617155618155619155620155621155622155623155624155625155626155627155628155629155630155631155632155633155634155635155636155637155638155639155640155641155642155643155644155645155646155647155648155649155650155651155652155653155654155655155656155657155658155659155660155661155662155663155664155665155666155667155668155669155670155671155672155673155674155675155676155677155678155679155680155681155682155683155684155685155686155687155688155689155690155691155692155693155694155695155696155697155698155699155700155701155702155703155704155705155706155707155708155709155710155711155712155713155714155715155716155717155718155719155720155721155722155723155724155725155726155727155728155729155730155731155732155733155734155735155736155737155738155739155740155741155742155743155744155745155746155747155748155749155750155751155752155753155754155755155756155757155758155759155760155761155762155763155764155765155766155767155768155769155770155771155772155773155774155775155776155777155778155779155780155781155782155783155784155785155786155787155788155789155790155791155792155793155794155795155796155797155798155799155800155801155802155803155804155805155806155807155808155809155810155811155812155813155814155815155816155817155818155819155820155821155822155823155824155825155826155827155828155829155830155831155832155833155834155835155836155837155838155839155840155841155842155843155844155845155846155847155848155849155850155851155852155853155854155855155856155857155858155859155860155861155862155863155864155865155866155867155868155869155870155871155872155873155874155875155876155877155878155879155880155881155882155883155884155885155886155887155888155889155890155891155892155893155894155895155896155897155898155899155900155901155902155903155904155905155906155907155908155909155910155911155912155913155914155915155916155917155918155919155920155921155922155923155924155925155926155927155928155929155930155931155932155933155934155935155936155937155938155939155940155941155942155943155944155945155946155947155948155949155950155951155952155953155954155955155956155957155958155959155960155961155962155963155964155965155966155967155968155969155970155971155972155973155974155975155976155977155978155979155980155981155982155983155984155985155986155987155988155989155990155991155992155993155994155995155996155997155998155999156000156001156002156003156004156005156006156007156008156009156010156011156012156013156014156015156016156017156018156019156020156021156022156023156024156025156026156027156028156029156030156031156032156033156034156035156036156037156038156039156040156041156042156043156044156045156046156047156048156049156050156051156052156053156054156055156056156057156058156059156060156061156062156063156064156065156066156067156068156069156070156071156072156073156074156075156076156077156078156079156080156081156082156083156084156085156086156087156088156089156090156091156092156093156094156095156096156097156098156099156100156101156102156103156104156105156106156107156108156109156110156111156112156113156114156115156116156117156118156119156120156121156122156123156124156125156126156127156128156129156130156131156132156133156134156135156136156137156138156139156140156141156142156143156144156145156146156147156148156149156150156151156152156153156154156155156156156157156158156159156160156161156162156163156164156165156166156167156168156169156170156171156172156173156174156175156176156177156178156179156180156181156182156183156184156185156186156187156188156189156190156191156192156193156194156195156196156197156198156199156200156201156202156203156204156205156206156207156208156209156210156211156212156213156214156215156216156217156218156219156220156221156222156223156224156225156226156227156228156229156230156231156232156233156234156235156236156237156238156239156240156241156242156243156244156245156246156247156248156249156250156251156252156253156254156255156256156257156258156259156260156261156262156263156264156265156266156267156268156269156270156271156272156273156274156275156276156277156278156279156280156281156282156283156284156285156286156287156288156289156290156291156292156293156294156295156296156297156298156299156300156301156302156303156304156305156306156307156308156309156310156311156312156313156314156315156316156317156318156319156320156321156322156323156324156325156326156327156328156329156330156331156332156333156334156335156336156337156338156339156340156341156342156343156344156345156346156347156348156349156350156351156352156353156354156355156356156357156358156359156360156361156362156363156364156365156366156367156368156369156370156371156372156373156374156375156376156377156378156379156380156381156382156383156384156385156386156387156388156389156390156391156392156393156394156395156396156397156398156399156400156401156402156403156404156405156406156407156408156409156410156411156412156413156414156415156416156417156418156419156420156421156422156423156424156425156426156427156428156429156430156431156432156433156434156435156436156437156438156439156440156441156442156443156444156445156446156447156448156449156450156451156452156453156454156455156456156457156458156459156460156461156462156463156464156465156466156467156468156469156470156471156472156473156474156475156476156477156478156479156480156481156482156483156484156485156486156487156488156489156490156491156492156493156494156495156496156497156498156499156500156501156502156503156504156505156506156507156508156509156510156511156512156513156514156515156516156517156518156519156520156521156522156523156524156525156526156527156528156529156530156531156532156533156534156535156536156537156538156539156540156541156542156543156544156545156546156547156548156549156550156551156552156553156554156555156556156557156558156559156560156561156562156563156564156565156566156567156568156569156570156571156572156573156574156575156576156577156578156579156580156581156582156583156584156585156586156587156588156589156590156591156592156593156594156595156596156597156598156599156600156601156602156603156604156605156606156607156608156609156610156611156612156613156614156615156616156617156618156619156620156621156622156623156624156625156626156627156628156629156630156631156632156633156634156635156636156637156638156639156640156641156642156643156644156645156646156647156648156649156650156651156652156653156654156655156656156657156658156659156660156661156662156663156664156665156666156667156668156669156670156671156672156673156674156675156676156677156678156679156680156681156682156683156684156685156686156687156688156689156690156691156692156693156694156695156696156697156698156699156700156701156702156703156704156705156706156707156708156709156710156711156712156713156714156715156716156717156718156719156720156721156722156723156724156725156726156727156728156729156730156731156732156733156734156735156736156737156738156739156740156741156742156743156744156745156746156747156748156749156750156751156752156753156754156755156756156757156758156759156760156761156762156763156764156765156766156767156768156769156770156771156772156773156774156775156776156777156778156779156780156781156782156783156784156785156786156787156788156789156790156791156792156793156794156795156796156797156798156799156800156801156802156803156804156805156806156807156808156809156810156811156812156813156814156815156816156817156818156819156820156821156822156823156824156825156826156827156828156829156830156831156832156833156834156835156836156837156838156839156840156841156842156843156844156845156846156847156848156849156850156851156852156853156854156855156856156857156858156859156860156861156862156863156864156865156866156867156868156869156870156871156872156873156874156875156876156877156878156879156880156881156882156883156884156885156886156887156888156889156890156891156892156893156894156895156896156897156898156899156900156901156902156903156904156905156906156907156908156909156910156911156912156913156914156915156916156917156918156919156920156921156922156923156924156925156926156927156928156929156930156931156932156933156934156935156936156937156938156939156940156941156942156943156944156945156946156947156948156949156950156951156952156953156954156955156956156957156958156959156960156961156962156963156964156965156966156967156968156969156970156971156972156973156974156975156976156977156978156979156980156981156982156983156984156985156986156987156988156989156990156991156992156993156994156995156996156997156998156999157000157001157002157003157004157005157006157007157008157009157010157011157012157013157014157015157016157017157018157019157020157021157022157023157024157025157026157027157028157029157030157031157032157033157034157035157036157037157038157039157040157041157042157043157044157045157046157047157048157049157050157051157052157053157054157055157056157057157058157059157060157061157062157063157064157065157066157067157068157069157070157071157072157073157074157075157076157077157078157079157080157081157082157083157084157085157086157087157088157089157090157091157092157093157094157095157096157097157098157099157100157101157102157103157104157105157106157107157108157109157110157111157112157113157114157115157116157117157118157119157120157121157122157123157124157125157126157127157128157129157130157131157132157133157134157135157136157137157138157139157140157141157142157143157144157145157146157147157148157149157150157151157152157153157154157155157156157157157158157159157160157161157162157163157164157165157166157167157168157169157170157171157172157173157174157175157176157177157178157179157180157181157182157183157184157185157186157187157188157189157190157191157192157193157194157195157196157197157198157199157200157201157202157203157204157205157206157207157208157209157210157211157212157213157214157215157216157217157218157219157220157221157222157223157224157225157226157227157228157229157230157231157232157233157234157235157236157237157238157239157240157241157242157243157244157245157246157247157248157249157250157251157252157253157254157255157256157257157258157259157260157261157262157263157264157265157266157267157268157269157270157271157272157273157274157275157276157277157278157279157280157281157282157283157284157285157286157287157288157289157290157291157292157293157294157295157296157297157298157299157300157301157302157303157304157305157306157307157308157309157310157311157312157313157314157315157316157317157318157319157320157321157322157323157324157325157326157327157328157329157330157331157332157333157334157335157336157337157338157339157340157341157342157343157344157345157346157347157348157349157350157351157352157353157354157355157356157357157358157359157360157361157362157363157364157365157366157367157368157369157370157371157372157373157374157375157376157377157378157379157380157381157382157383157384157385157386157387157388157389157390157391157392157393157394157395157396157397157398157399157400157401157402157403157404157405157406157407157408157409157410157411157412157413157414157415157416157417157418157419157420157421157422157423157424157425157426157427157428157429157430157431157432157433157434157435157436157437157438157439157440157441157442157443157444157445157446157447157448157449157450157451157452157453157454157455157456157457157458157459157460157461157462157463157464157465157466157467157468157469157470157471157472157473157474157475157476157477157478157479157480157481157482157483157484157485157486157487157488157489157490157491157492157493157494157495157496157497157498157499157500157501157502157503157504157505157506157507157508157509157510157511157512157513157514157515157516157517157518157519157520157521157522157523157524157525157526157527157528157529157530157531157532157533157534157535157536157537157538157539157540157541157542157543157544157545157546157547157548157549157550157551157552157553157554157555157556157557157558157559157560157561157562157563157564157565157566157567157568157569157570157571157572157573157574157575157576157577157578157579157580157581157582157583157584157585157586157587157588157589157590157591157592157593157594157595157596157597157598157599157600157601157602157603157604157605157606157607157608157609157610157611157612157613157614157615157616157617157618157619157620157621157622157623157624157625157626157627157628157629157630157631157632157633157634157635157636157637157638157639157640157641157642157643157644157645157646157647157648157649157650157651157652157653157654157655157656157657157658157659157660157661157662157663157664157665157666157667157668157669157670157671157672157673157674157675157676157677157678157679157680157681157682157683157684157685157686157687157688157689157690157691157692157693157694157695157696157697157698157699157700157701157702157703157704157705157706157707157708157709157710157711157712157713157714157715157716157717157718157719157720157721157722157723157724157725157726157727157728157729157730157731157732157733157734157735157736157737157738157739157740157741157742157743157744157745157746157747157748157749157750157751157752157753157754157755157756157757157758157759157760157761157762157763157764157765157766157767157768157769157770157771157772157773157774157775157776157777157778157779157780157781157782157783157784157785157786157787157788157789157790157791157792157793157794157795157796157797157798157799157800157801157802157803157804157805157806157807157808157809157810157811157812157813157814157815157816157817157818157819157820157821157822157823157824157825157826157827157828157829157830157831157832157833157834157835157836157837157838157839157840157841157842157843157844157845157846157847157848157849157850157851157852157853157854157855157856157857157858157859157860157861157862157863157864157865157866157867157868157869157870157871157872157873157874157875157876157877157878157879157880157881157882157883157884157885157886157887157888157889157890157891157892157893157894157895157896157897157898157899157900157901157902157903157904157905157906157907157908157909157910157911157912157913157914157915157916157917157918157919157920157921157922157923157924157925157926157927157928157929157930157931157932157933157934157935157936157937157938157939157940157941157942157943157944157945157946157947157948157949157950157951157952157953157954157955157956157957157958157959157960157961157962157963157964157965157966157967157968157969157970157971157972157973157974157975157976157977157978157979157980157981157982157983157984157985157986157987157988157989157990157991157992157993157994157995157996157997157998157999158000158001158002158003158004158005158006158007158008158009158010158011158012158013158014158015158016158017158018158019158020158021158022158023158024158025158026158027158028158029158030158031158032158033158034158035158036158037158038158039158040158041158042158043158044158045158046158047158048158049158050158051158052158053158054158055158056158057158058158059158060158061158062158063158064158065158066158067158068158069158070158071158072158073158074158075158076158077158078158079158080158081158082158083158084158085158086158087158088158089158090158091158092158093158094158095158096158097158098158099158100158101158102158103158104158105158106158107158108158109158110158111158112158113158114158115158116158117158118158119158120158121158122158123158124158125158126158127158128158129158130158131158132158133158134158135158136158137158138158139158140158141158142158143158144158145158146158147158148158149158150158151158152158153158154158155158156158157158158158159158160158161158162158163158164158165158166158167158168158169158170158171158172158173158174158175158176158177158178158179158180158181158182158183158184158185158186158187158188158189158190158191158192158193158194158195158196158197158198158199158200158201158202158203158204158205158206158207158208158209158210158211158212158213158214158215158216158217158218158219158220158221158222158223158224158225158226158227158228158229158230158231158232158233158234158235158236158237158238158239158240158241158242158243158244158245158246158247158248158249158250158251158252158253158254158255158256158257158258158259158260158261158262158263158264158265158266158267158268158269158270158271158272158273158274158275158276158277158278158279158280158281158282158283158284158285158286158287158288158289158290158291158292158293158294158295158296158297158298158299158300158301158302158303158304158305158306158307158308158309158310158311158312158313158314158315158316158317158318158319158320158321158322158323158324158325158326158327158328158329158330158331158332158333158334158335158336158337158338158339158340158341158342158343158344158345158346158347158348158349158350158351158352158353158354158355158356158357158358158359158360158361158362158363158364158365158366158367158368158369158370158371158372158373158374158375158376158377158378158379158380158381158382158383158384158385158386158387158388158389158390158391158392158393158394158395158396158397158398158399158400158401158402158403158404158405158406158407158408158409158410158411158412158413158414158415158416158417158418158419158420158421158422158423158424158425158426158427158428158429158430158431158432158433158434158435158436158437158438158439158440158441158442158443158444158445158446158447158448158449158450158451158452158453158454158455158456158457158458158459158460158461158462158463158464158465158466158467158468158469158470158471158472158473158474158475158476158477158478158479158480158481158482158483158484158485158486158487158488158489158490158491158492158493158494158495158496158497158498158499158500158501158502158503158504158505158506158507158508158509158510158511158512158513158514158515158516158517158518158519158520158521158522158523158524158525158526158527158528158529158530158531158532158533158534158535158536158537158538158539158540158541158542158543158544158545158546158547158548158549158550158551158552158553158554158555158556158557158558158559158560158561158562158563158564158565158566158567158568158569158570158571158572158573158574158575158576158577158578158579158580158581158582158583158584158585158586158587158588158589158590158591158592158593158594158595158596158597158598158599158600158601158602158603158604158605158606158607158608158609158610158611158612158613158614158615158616158617158618158619158620158621158622158623158624158625158626158627158628158629158630158631158632158633158634158635158636158637158638158639158640158641158642158643158644158645158646158647158648158649158650158651158652158653158654158655158656158657158658158659158660158661158662158663158664158665158666158667158668158669158670158671158672158673158674158675158676158677158678158679158680158681158682158683158684158685158686158687158688158689158690158691158692158693158694158695158696158697158698158699158700158701158702158703158704158705158706158707158708158709158710158711158712158713158714158715158716158717158718158719158720158721158722158723158724158725158726158727158728158729158730158731158732158733158734158735158736158737158738158739158740158741158742158743158744158745158746158747158748158749158750158751158752158753158754158755158756158757158758158759158760158761158762158763158764158765158766158767158768158769158770158771158772158773158774158775158776158777158778158779158780158781158782158783158784158785158786158787158788158789158790158791158792158793158794158795158796158797158798158799158800158801158802158803158804158805158806158807158808158809158810158811158812158813158814158815158816158817158818158819158820158821158822158823158824158825158826158827158828158829158830158831158832158833158834158835158836158837158838158839158840158841158842158843158844158845158846158847158848158849158850158851158852158853158854158855158856158857158858158859158860158861158862158863158864158865158866158867158868158869158870158871158872158873158874158875158876158877158878158879158880158881158882158883158884158885158886158887158888158889158890158891158892158893158894158895158896158897158898158899158900158901158902158903158904158905158906158907158908158909158910158911158912158913158914158915158916158917158918158919158920158921158922158923158924158925158926158927158928158929158930158931158932158933158934158935158936158937158938158939158940158941158942158943158944158945158946158947158948158949158950158951158952158953158954158955158956158957158958158959158960158961158962158963158964158965158966158967158968158969158970158971158972158973158974158975158976158977158978158979158980158981158982158983158984158985158986158987158988158989158990158991158992158993158994158995158996158997158998158999159000159001159002159003159004159005159006159007159008159009159010159011159012159013159014159015159016159017159018159019159020159021159022159023159024159025159026159027159028159029159030159031159032159033159034159035159036159037159038159039159040159041159042159043159044159045159046159047159048159049159050159051159052159053159054159055159056159057159058159059159060159061159062159063159064159065159066159067159068159069159070159071159072159073159074159075159076159077159078159079159080159081159082159083159084159085159086159087159088159089159090159091159092159093159094159095159096159097159098159099159100159101159102159103159104159105159106159107159108159109159110159111159112159113159114159115159116159117159118159119159120159121159122159123159124159125159126159127159128159129159130159131159132159133159134159135159136159137159138159139159140159141159142159143159144159145159146159147159148159149159150159151159152159153159154159155159156159157159158159159159160159161159162159163159164159165159166159167159168159169159170159171159172159173159174159175159176159177159178159179159180159181159182159183159184159185159186159187159188159189159190159191159192159193159194159195159196159197159198159199159200159201159202159203159204159205159206159207159208159209159210159211159212159213159214159215159216159217159218159219159220159221159222159223159224159225159226159227159228159229159230159231159232159233159234159235159236159237159238159239159240159241159242159243159244159245159246159247159248159249159250159251159252159253159254159255159256159257159258159259159260159261159262159263159264159265159266159267159268159269159270159271159272159273159274159275159276159277159278159279159280159281159282159283159284159285159286159287159288159289159290159291159292159293159294159295159296159297159298159299159300159301159302159303159304159305159306159307159308159309159310159311159312159313159314159315159316159317159318159319159320159321159322159323159324159325159326159327159328159329159330159331159332159333159334159335159336159337159338159339159340159341159342159343159344159345159346159347159348159349159350159351159352159353159354159355159356159357159358159359159360159361159362159363159364159365159366159367159368159369159370159371159372159373159374159375159376159377159378159379159380159381159382159383159384159385159386159387159388159389159390159391159392159393159394159395159396159397159398159399159400159401159402159403159404159405159406159407159408159409159410159411159412159413159414159415159416159417159418159419159420159421159422159423159424159425159426159427159428159429159430159431159432159433159434159435159436159437159438159439159440159441159442159443159444159445159446159447159448159449159450159451159452159453159454159455159456159457159458159459159460159461159462159463159464159465159466159467159468159469159470159471159472159473159474159475159476159477159478159479159480159481159482159483159484159485159486159487159488159489159490159491159492159493159494159495159496159497159498159499159500159501159502159503159504159505159506159507159508159509159510159511159512159513159514159515159516159517159518159519159520159521159522159523159524159525159526159527159528159529159530159531159532159533159534159535159536159537159538159539159540159541159542159543159544159545159546159547159548159549159550159551159552159553159554159555159556159557159558159559159560159561159562159563159564159565159566159567159568159569159570159571159572159573159574159575159576159577159578159579159580159581159582159583159584159585159586159587159588159589159590159591159592159593159594159595159596159597159598159599159600159601159602159603159604159605159606159607159608159609159610159611159612159613159614159615159616159617159618159619159620159621159622159623159624159625159626159627159628159629159630159631159632159633159634159635159636159637159638159639159640159641159642159643159644159645159646159647159648159649159650159651159652159653159654159655159656159657159658159659159660159661159662159663159664159665159666159667159668159669159670159671159672159673159674159675159676159677159678159679159680159681159682159683159684159685159686159687159688159689159690159691159692159693159694159695159696159697159698159699159700159701159702159703159704159705159706159707159708159709159710159711159712159713159714159715159716159717159718159719159720159721159722159723159724159725159726159727159728159729159730159731159732159733159734159735159736159737159738159739159740159741159742159743159744159745159746159747159748159749159750159751159752159753159754159755159756159757159758159759159760159761159762159763159764159765159766159767159768159769159770159771159772159773159774159775159776159777159778159779159780159781159782159783159784159785159786159787159788159789159790159791159792159793159794159795159796159797159798159799159800159801159802159803159804159805159806159807159808159809159810159811159812159813159814159815159816159817159818159819159820159821159822159823159824159825159826159827159828159829159830159831159832159833159834159835159836159837159838159839159840159841159842159843159844159845159846159847159848159849159850159851159852159853159854159855159856159857159858159859159860159861159862159863159864159865159866159867159868159869159870159871159872159873159874159875159876159877159878159879159880159881159882159883159884159885159886159887159888159889159890159891159892159893159894159895159896159897159898159899159900159901159902159903159904159905159906159907159908159909159910159911159912159913159914159915159916159917159918159919159920159921159922159923159924159925159926159927159928159929159930159931159932159933159934159935159936159937159938159939159940159941159942159943159944159945159946159947159948159949159950159951159952159953159954159955159956159957159958159959159960159961159962159963159964159965159966159967159968159969159970159971159972159973159974159975159976159977159978159979159980159981159982159983159984159985159986159987159988159989159990159991159992159993159994159995159996159997159998159999160000160001160002160003160004160005160006160007160008160009160010160011160012160013160014160015160016160017160018160019160020160021160022160023160024160025160026160027160028160029160030160031160032160033160034160035160036160037160038160039160040160041160042160043160044160045160046160047160048160049160050160051160052160053160054160055160056160057160058160059160060160061160062160063160064160065160066160067160068160069160070160071160072160073160074160075160076160077160078160079160080160081160082160083160084160085160086160087160088160089160090160091160092160093160094160095160096160097160098160099160100160101160102160103160104160105160106160107160108160109160110160111160112160113160114160115160116160117160118160119160120160121160122160123160124160125160126160127160128160129160130160131160132160133160134160135160136160137160138160139160140160141160142160143160144160145160146160147160148160149160150160151160152160153160154160155160156160157160158160159160160160161160162160163160164160165160166160167160168160169160170160171160172160173160174160175160176160177160178160179160180160181160182160183160184160185160186160187160188160189160190160191160192160193160194160195160196160197160198160199160200160201160202160203160204160205160206160207160208160209160210160211160212160213160214160215160216160217160218160219160220160221160222160223160224160225160226160227160228160229160230160231160232160233160234160235160236160237160238160239160240160241160242160243160244160245160246160247160248160249160250160251160252160253160254160255160256160257160258160259160260160261160262160263160264160265160266160267160268160269160270160271160272160273160274160275160276160277160278160279160280160281160282160283160284160285160286160287160288160289160290160291160292160293160294160295160296160297160298160299160300160301160302160303160304160305160306160307160308160309160310160311160312160313160314160315160316160317160318160319160320160321160322160323160324160325160326160327160328160329160330160331160332160333160334160335160336160337160338160339160340160341160342160343160344160345160346160347160348160349160350160351160352160353160354160355160356160357160358160359160360160361160362160363160364160365160366160367160368160369160370160371160372160373160374160375160376160377160378160379160380160381160382160383160384160385160386160387160388160389160390160391160392160393160394160395160396160397160398160399160400160401160402160403160404160405160406160407160408160409160410160411160412160413160414160415160416160417160418160419160420160421160422160423160424160425160426160427160428160429160430160431160432160433160434160435160436160437160438160439160440160441160442160443160444160445160446160447160448160449160450160451160452160453160454160455160456160457160458160459160460160461160462160463160464160465160466160467160468160469160470160471160472160473160474160475160476160477160478160479160480160481160482160483160484160485160486160487160488160489160490160491160492160493160494160495160496160497160498160499160500160501160502160503160504160505160506160507160508160509160510160511160512160513160514160515160516160517160518160519160520160521160522160523160524160525160526160527160528160529160530160531160532160533160534160535160536160537160538160539160540160541160542160543160544160545160546160547160548160549160550160551160552160553160554160555160556160557160558160559160560160561160562160563160564160565160566160567160568160569160570160571160572160573160574160575160576160577160578160579160580160581160582160583160584160585160586160587160588160589160590160591160592160593160594160595160596160597160598160599160600160601160602160603160604160605160606160607160608160609160610160611160612160613160614160615160616160617160618160619160620160621160622160623160624160625160626160627160628160629160630160631160632160633160634160635160636160637160638160639160640160641160642160643160644160645160646160647160648160649160650160651160652160653160654160655160656160657160658160659160660160661160662160663160664160665160666160667160668160669160670160671160672160673160674160675160676160677160678160679160680160681160682160683160684160685160686160687160688160689160690160691160692160693160694160695160696160697160698160699160700160701160702160703160704160705160706160707160708160709160710160711160712160713160714160715160716160717160718160719160720160721160722160723160724160725160726160727160728160729160730160731160732160733160734160735160736160737160738160739160740160741160742160743160744160745160746160747160748160749160750160751160752160753160754160755160756160757160758160759160760160761160762160763160764160765160766160767160768160769160770160771160772160773160774160775160776160777160778160779160780160781160782160783160784160785160786160787160788160789160790160791160792160793160794160795160796160797160798160799160800160801160802160803160804160805160806160807160808160809160810160811160812160813160814160815160816160817160818160819160820160821160822160823160824160825160826160827160828160829160830160831160832160833160834160835160836160837160838160839160840160841160842160843160844160845160846160847160848160849160850160851160852160853160854160855160856160857160858160859160860160861160862160863160864160865160866160867160868160869160870160871160872160873160874160875160876160877160878160879160880160881160882160883160884160885160886160887160888160889160890160891160892160893160894160895160896160897160898160899160900160901160902160903160904160905160906160907160908160909160910160911160912160913160914160915160916160917160918160919160920160921160922160923160924160925160926160927160928160929160930160931160932160933160934160935160936160937160938160939160940160941160942160943160944160945160946160947160948160949160950160951160952160953160954160955160956160957160958160959160960160961160962160963160964160965160966160967160968160969160970160971160972160973160974160975160976160977160978160979160980160981160982160983160984160985160986160987160988160989160990160991160992160993160994160995160996160997160998160999161000161001161002161003161004161005161006161007161008161009161010161011161012161013161014161015161016161017161018161019161020161021161022161023161024161025161026161027161028161029161030161031161032161033161034161035161036161037161038161039161040161041161042161043161044161045161046161047161048161049161050161051161052161053161054161055161056161057161058161059161060161061161062161063161064161065161066161067161068161069161070161071161072161073161074161075161076161077161078161079161080161081161082161083161084161085161086161087161088161089161090161091161092161093161094161095161096161097161098161099161100161101161102161103161104161105161106161107161108161109161110161111161112161113161114161115161116161117161118161119161120161121161122161123161124161125161126161127161128161129161130161131161132161133161134161135161136161137161138161139161140161141161142161143161144161145161146161147161148161149161150161151161152161153161154161155161156161157161158161159161160161161161162161163161164161165161166161167161168161169161170161171161172161173161174161175161176161177161178161179161180161181161182161183161184161185161186161187161188161189161190161191161192161193161194161195161196161197161198161199161200161201161202161203161204161205161206161207161208161209161210161211161212161213161214161215161216161217161218161219161220161221161222161223161224161225161226161227161228161229161230161231161232161233161234161235161236161237161238161239161240161241161242161243161244161245161246161247161248161249161250161251161252161253161254161255161256161257161258161259161260161261161262161263161264161265161266161267161268161269161270161271161272161273161274161275161276161277161278161279161280161281161282161283161284161285161286161287161288161289161290161291161292161293161294161295161296161297161298161299161300161301161302161303161304161305161306161307161308161309161310161311161312161313161314161315161316161317161318161319161320161321161322161323161324161325161326161327161328161329161330161331161332161333161334161335161336161337161338161339161340161341161342161343161344161345161346161347161348161349161350161351161352161353161354161355161356161357161358161359161360161361161362161363161364161365161366161367161368161369161370161371161372161373161374161375161376161377161378161379161380161381161382161383161384161385161386161387161388161389161390161391161392161393161394161395161396161397161398161399161400161401161402161403161404161405161406161407161408161409161410161411161412161413161414161415161416161417161418161419161420161421161422161423161424161425161426161427161428161429161430161431161432161433161434161435161436161437161438161439161440161441161442161443161444161445161446161447161448161449161450161451161452161453161454161455161456161457161458161459161460161461161462161463161464161465161466161467161468161469161470161471161472161473161474161475161476161477161478161479161480161481161482161483161484161485161486161487161488161489161490161491161492161493161494161495161496161497161498161499161500161501161502161503161504161505161506161507161508161509161510161511161512161513161514161515161516161517161518161519161520161521161522161523161524161525161526161527161528161529161530161531161532161533161534161535161536161537161538161539161540161541161542161543161544161545161546161547161548161549161550161551161552161553161554161555161556161557161558161559161560161561161562161563161564161565161566161567161568161569161570161571161572161573161574161575161576161577161578
  1. /*
  2. Ext JS 4.1 - JavaScript Library
  3. Copyright (c) 2006-2012, Sencha Inc.
  4. All rights reserved.
  5. licensing@sencha.com
  6. http://www.sencha.com/license
  7. Open Source License
  8. ------------------------------------------------------------------------------------------
  9. This version of Ext JS is licensed under the terms of the Open Source GPL 3.0 license.
  10. http://www.gnu.org/licenses/gpl.html
  11. There are several FLOSS exceptions available for use with this release for
  12. open source applications that are distributed under a license other than GPL.
  13. * Open Source License Exception for Applications
  14. http://www.sencha.com/products/floss-exception.php
  15. * Open Source License Exception for Development
  16. http://www.sencha.com/products/ux-exception.php
  17. Alternate Licensing
  18. ------------------------------------------------------------------------------------------
  19. Commercial and OEM Licenses are available for an alternate download of Ext JS.
  20. This is the appropriate option if you are creating proprietary applications and you are
  21. not prepared to distribute and share the source code of your application under the
  22. GPL v3 license. Please visit http://www.sencha.com/license for more details.
  23. --
  24. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF THIRD-PARTY INTELLECTUAL PROPERTY RIGHTS. See the GNU General Public License for more details.
  25. */
  26. //@tag foundation,core
  27. /**
  28. * @class Ext
  29. * @singleton
  30. */
  31. var Ext = Ext || {};
  32. Ext._startTime = new Date().getTime();
  33. (function() {
  34. var global = this,
  35. objectPrototype = Object.prototype,
  36. toString = objectPrototype.toString,
  37. enumerables = true,
  38. enumerablesTest = { toString: 1 },
  39. emptyFn = function () {},
  40. // This is the "$previous" method of a hook function on an instance. When called, it
  41. // calls through the class prototype by the name of the called method.
  42. callOverrideParent = function () {
  43. var method = callOverrideParent.caller.caller; // skip callParent (our caller)
  44. return method.$owner.prototype[method.$name].apply(this, arguments);
  45. },
  46. i;
  47. Ext.global = global;
  48. for (i in enumerablesTest) {
  49. enumerables = null;
  50. }
  51. if (enumerables) {
  52. enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
  53. 'toLocaleString', 'toString', 'constructor'];
  54. }
  55. /**
  56. * An array containing extra enumerables for old browsers
  57. * @property {String[]}
  58. */
  59. Ext.enumerables = enumerables;
  60. /**
  61. * Copies all the properties of config to the specified object.
  62. * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
  63. * {@link Ext.Object#merge} instead.
  64. * @param {Object} object The receiver of the properties
  65. * @param {Object} config The source of the properties
  66. * @param {Object} [defaults] A different object that will also be applied for default values
  67. * @return {Object} returns obj
  68. */
  69. Ext.apply = function(object, config, defaults) {
  70. if (defaults) {
  71. Ext.apply(object, defaults);
  72. }
  73. if (object && config && typeof config === 'object') {
  74. var i, j, k;
  75. for (i in config) {
  76. object[i] = config[i];
  77. }
  78. if (enumerables) {
  79. for (j = enumerables.length; j--;) {
  80. k = enumerables[j];
  81. if (config.hasOwnProperty(k)) {
  82. object[k] = config[k];
  83. }
  84. }
  85. }
  86. }
  87. return object;
  88. };
  89. Ext.buildSettings = Ext.apply({
  90. baseCSSPrefix: 'x-',
  91. scopeResetCSS: false
  92. }, Ext.buildSettings || {});
  93. Ext.apply(Ext, {
  94. /**
  95. * @property {String} [name='Ext']
  96. * <p>The name of the property in the global namespace (The <code>window</code> in browser environments) which refers to the current instance of Ext.</p>
  97. * <p>This is usually <code>"Ext"</code>, but if a sandboxed build of ExtJS is being used, this will be an alternative name.</p>
  98. * <p>If code is being generated for use by <code>eval</code> or to create a <code>new Function</code>, and the global instance
  99. * of Ext must be referenced, this is the name that should be built into the code.</p>
  100. */
  101. name: Ext.sandboxName || 'Ext',
  102. /**
  103. * A reusable empty function
  104. */
  105. emptyFn: emptyFn,
  106. /**
  107. * A zero length string which will pass a truth test. Useful for passing to methods
  108. * which use a truth test to reject <i>falsy</i> values where a string value must be cleared.
  109. */
  110. emptyString: new String(),
  111. baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
  112. /**
  113. * Copies all the properties of config to object if they don't already exist.
  114. * @param {Object} object The receiver of the properties
  115. * @param {Object} config The source of the properties
  116. * @return {Object} returns obj
  117. */
  118. applyIf: function(object, config) {
  119. var property;
  120. if (object) {
  121. for (property in config) {
  122. if (object[property] === undefined) {
  123. object[property] = config[property];
  124. }
  125. }
  126. }
  127. return object;
  128. },
  129. /**
  130. * Iterates either an array or an object. This method delegates to
  131. * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
  132. *
  133. * @param {Object/Array} object The object or array to be iterated.
  134. * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
  135. * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
  136. * type that is being iterated.
  137. * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
  138. * Defaults to the object being iterated itself.
  139. * @markdown
  140. */
  141. iterate: function(object, fn, scope) {
  142. if (Ext.isEmpty(object)) {
  143. return;
  144. }
  145. if (scope === undefined) {
  146. scope = object;
  147. }
  148. if (Ext.isIterable(object)) {
  149. Ext.Array.each.call(Ext.Array, object, fn, scope);
  150. }
  151. else {
  152. Ext.Object.each.call(Ext.Object, object, fn, scope);
  153. }
  154. }
  155. });
  156. Ext.apply(Ext, {
  157. /**
  158. * This method deprecated. Use {@link Ext#define Ext.define} instead.
  159. * @method
  160. * @param {Function} superclass
  161. * @param {Object} overrides
  162. * @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
  163. * @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
  164. */
  165. extend: (function() {
  166. // inline overrides
  167. var objectConstructor = objectPrototype.constructor,
  168. inlineOverrides = function(o) {
  169. for (var m in o) {
  170. if (!o.hasOwnProperty(m)) {
  171. continue;
  172. }
  173. this[m] = o[m];
  174. }
  175. };
  176. return function(subclass, superclass, overrides) {
  177. // First we check if the user passed in just the superClass with overrides
  178. if (Ext.isObject(superclass)) {
  179. overrides = superclass;
  180. superclass = subclass;
  181. subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
  182. superclass.apply(this, arguments);
  183. };
  184. }
  185. if (!superclass) {
  186. Ext.Error.raise({
  187. sourceClass: 'Ext',
  188. sourceMethod: 'extend',
  189. msg: 'Attempting to extend from a class which has not been loaded on the page.'
  190. });
  191. }
  192. // We create a new temporary class
  193. var F = function() {},
  194. subclassProto, superclassProto = superclass.prototype;
  195. F.prototype = superclassProto;
  196. subclassProto = subclass.prototype = new F();
  197. subclassProto.constructor = subclass;
  198. subclass.superclass = superclassProto;
  199. if (superclassProto.constructor === objectConstructor) {
  200. superclassProto.constructor = superclass;
  201. }
  202. subclass.override = function(overrides) {
  203. Ext.override(subclass, overrides);
  204. };
  205. subclassProto.override = inlineOverrides;
  206. subclassProto.proto = subclassProto;
  207. subclass.override(overrides);
  208. subclass.extend = function(o) {
  209. return Ext.extend(subclass, o);
  210. };
  211. return subclass;
  212. };
  213. }()),
  214. /**
  215. * Overrides members of the specified `target` with the given values.
  216. *
  217. * If the `target` is a class declared using {@link Ext#define Ext.define}, the
  218. * `override` method of that class is called (see {@link Ext.Base#override}) given
  219. * the `overrides`.
  220. *
  221. * If the `target` is a function, it is assumed to be a constructor and the contents
  222. * of `overrides` are applied to its `prototype` using {@link Ext#apply Ext.apply}.
  223. *
  224. * If the `target` is an instance of a class declared using {@link Ext#define Ext.define},
  225. * the `overrides` are applied to only that instance. In this case, methods are
  226. * specially processed to allow them to use {@link Ext.Base#callParent}.
  227. *
  228. * var panel = new Ext.Panel({ ... });
  229. *
  230. * Ext.override(panel, {
  231. * initComponent: function () {
  232. * // extra processing...
  233. *
  234. * this.callParent();
  235. * }
  236. * });
  237. *
  238. * If the `target` is none of these, the `overrides` are applied to the `target`
  239. * using {@link Ext#apply Ext.apply}.
  240. *
  241. * Please refer to {@link Ext#define Ext.define} and {@link Ext.Base#override} for
  242. * further details.
  243. *
  244. * @param {Object} target The target to override.
  245. * @param {Object} overrides The properties to add or replace on `target`.
  246. * @method override
  247. */
  248. override: function (target, overrides) {
  249. if (target.$isClass) {
  250. target.override(overrides);
  251. } else if (typeof target == 'function') {
  252. Ext.apply(target.prototype, overrides);
  253. } else {
  254. var owner = target.self,
  255. name, value;
  256. if (owner && owner.$isClass) { // if (instance of Ext.define'd class)
  257. for (name in overrides) {
  258. if (overrides.hasOwnProperty(name)) {
  259. value = overrides[name];
  260. if (typeof value == 'function') {
  261. if (owner.$className) {
  262. value.displayName = owner.$className + '#' + name;
  263. }
  264. value.$name = name;
  265. value.$owner = owner;
  266. value.$previous = target.hasOwnProperty(name)
  267. ? target[name] // already hooked, so call previous hook
  268. : callOverrideParent; // calls by name on prototype
  269. }
  270. target[name] = value;
  271. }
  272. }
  273. } else {
  274. Ext.apply(target, overrides);
  275. }
  276. }
  277. return target;
  278. }
  279. });
  280. // A full set of static methods to do type checking
  281. Ext.apply(Ext, {
  282. /**
  283. * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
  284. * value (second argument) otherwise.
  285. *
  286. * @param {Object} value The value to test
  287. * @param {Object} defaultValue The value to return if the original value is empty
  288. * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
  289. * @return {Object} value, if non-empty, else defaultValue
  290. */
  291. valueFrom: function(value, defaultValue, allowBlank){
  292. return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
  293. },
  294. /**
  295. * Returns the type of the given variable in string format. List of possible values are:
  296. *
  297. * - `undefined`: If the given value is `undefined`
  298. * - `null`: If the given value is `null`
  299. * - `string`: If the given value is a string
  300. * - `number`: If the given value is a number
  301. * - `boolean`: If the given value is a boolean value
  302. * - `date`: If the given value is a `Date` object
  303. * - `function`: If the given value is a function reference
  304. * - `object`: If the given value is an object
  305. * - `array`: If the given value is an array
  306. * - `regexp`: If the given value is a regular expression
  307. * - `element`: If the given value is a DOM Element
  308. * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
  309. * - `whitespace`: If the given value is a DOM text node and contains only whitespace
  310. *
  311. * @param {Object} value
  312. * @return {String}
  313. * @markdown
  314. */
  315. typeOf: function(value) {
  316. var type,
  317. typeToString;
  318. if (value === null) {
  319. return 'null';
  320. }
  321. type = typeof value;
  322. if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
  323. return type;
  324. }
  325. typeToString = toString.call(value);
  326. switch(typeToString) {
  327. case '[object Array]':
  328. return 'array';
  329. case '[object Date]':
  330. return 'date';
  331. case '[object Boolean]':
  332. return 'boolean';
  333. case '[object Number]':
  334. return 'number';
  335. case '[object RegExp]':
  336. return 'regexp';
  337. }
  338. if (type === 'function') {
  339. return 'function';
  340. }
  341. if (type === 'object') {
  342. if (value.nodeType !== undefined) {
  343. if (value.nodeType === 3) {
  344. return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
  345. }
  346. else {
  347. return 'element';
  348. }
  349. }
  350. return 'object';
  351. }
  352. Ext.Error.raise({
  353. sourceClass: 'Ext',
  354. sourceMethod: 'typeOf',
  355. msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
  356. });
  357. },
  358. /**
  359. * Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
  360. *
  361. * - `null`
  362. * - `undefined`
  363. * - a zero-length array
  364. * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
  365. *
  366. * @param {Object} value The value to test
  367. * @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
  368. * @return {Boolean}
  369. * @markdown
  370. */
  371. isEmpty: function(value, allowEmptyString) {
  372. return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
  373. },
  374. /**
  375. * Returns true if the passed value is a JavaScript Array, false otherwise.
  376. *
  377. * @param {Object} target The target to test
  378. * @return {Boolean}
  379. * @method
  380. */
  381. isArray: ('isArray' in Array) ? Array.isArray : function(value) {
  382. return toString.call(value) === '[object Array]';
  383. },
  384. /**
  385. * Returns true if the passed value is a JavaScript Date object, false otherwise.
  386. * @param {Object} object The object to test
  387. * @return {Boolean}
  388. */
  389. isDate: function(value) {
  390. return toString.call(value) === '[object Date]';
  391. },
  392. /**
  393. * Returns true if the passed value is a JavaScript Object, false otherwise.
  394. * @param {Object} value The value to test
  395. * @return {Boolean}
  396. * @method
  397. */
  398. isObject: (toString.call(null) === '[object Object]') ?
  399. function(value) {
  400. // check ownerDocument here as well to exclude DOM nodes
  401. return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
  402. } :
  403. function(value) {
  404. return toString.call(value) === '[object Object]';
  405. },
  406. /**
  407. * @private
  408. */
  409. isSimpleObject: function(value) {
  410. return value instanceof Object && value.constructor === Object;
  411. },
  412. /**
  413. * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
  414. * @param {Object} value The value to test
  415. * @return {Boolean}
  416. */
  417. isPrimitive: function(value) {
  418. var type = typeof value;
  419. return type === 'string' || type === 'number' || type === 'boolean';
  420. },
  421. /**
  422. * Returns true if the passed value is a JavaScript Function, false otherwise.
  423. * @param {Object} value The value to test
  424. * @return {Boolean}
  425. * @method
  426. */
  427. isFunction:
  428. // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
  429. // Object.prototype.toString (slower)
  430. (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
  431. return toString.call(value) === '[object Function]';
  432. } : function(value) {
  433. return typeof value === 'function';
  434. },
  435. /**
  436. * Returns true if the passed value is a number. Returns false for non-finite numbers.
  437. * @param {Object} value The value to test
  438. * @return {Boolean}
  439. */
  440. isNumber: function(value) {
  441. return typeof value === 'number' && isFinite(value);
  442. },
  443. /**
  444. * Validates that a value is numeric.
  445. * @param {Object} value Examples: 1, '1', '2.34'
  446. * @return {Boolean} True if numeric, false otherwise
  447. */
  448. isNumeric: function(value) {
  449. return !isNaN(parseFloat(value)) && isFinite(value);
  450. },
  451. /**
  452. * Returns true if the passed value is a string.
  453. * @param {Object} value The value to test
  454. * @return {Boolean}
  455. */
  456. isString: function(value) {
  457. return typeof value === 'string';
  458. },
  459. /**
  460. * Returns true if the passed value is a boolean.
  461. *
  462. * @param {Object} value The value to test
  463. * @return {Boolean}
  464. */
  465. isBoolean: function(value) {
  466. return typeof value === 'boolean';
  467. },
  468. /**
  469. * Returns true if the passed value is an HTMLElement
  470. * @param {Object} value The value to test
  471. * @return {Boolean}
  472. */
  473. isElement: function(value) {
  474. return value ? value.nodeType === 1 : false;
  475. },
  476. /**
  477. * Returns true if the passed value is a TextNode
  478. * @param {Object} value The value to test
  479. * @return {Boolean}
  480. */
  481. isTextNode: function(value) {
  482. return value ? value.nodeName === "#text" : false;
  483. },
  484. /**
  485. * Returns true if the passed value is defined.
  486. * @param {Object} value The value to test
  487. * @return {Boolean}
  488. */
  489. isDefined: function(value) {
  490. return typeof value !== 'undefined';
  491. },
  492. /**
  493. * Returns true if the passed value is iterable, false otherwise
  494. * @param {Object} value The value to test
  495. * @return {Boolean}
  496. */
  497. isIterable: function(value) {
  498. var type = typeof value,
  499. checkLength = false;
  500. if (value && type != 'string') {
  501. // Functions have a length property, so we need to filter them out
  502. if (type == 'function') {
  503. // In Safari, NodeList/HTMLCollection both return "function" when using typeof, so we need
  504. // to explicitly check them here.
  505. if (Ext.isSafari) {
  506. checkLength = value instanceof NodeList || value instanceof HTMLCollection;
  507. }
  508. } else {
  509. checkLength = true;
  510. }
  511. }
  512. return checkLength ? value.length !== undefined : false;
  513. }
  514. });
  515. Ext.apply(Ext, {
  516. /**
  517. * Clone simple variables including array, {}-like objects, DOM nodes and Date without keeping the old reference.
  518. * A reference for the object itself is returned if it's not a direct decendant of Object. For model cloning,
  519. * see {@link Ext.data.Model#copy Model.copy}.
  520. *
  521. * @param {Object} item The variable to clone
  522. * @return {Object} clone
  523. */
  524. clone: function(item) {
  525. var type,
  526. i,
  527. j,
  528. k,
  529. clone,
  530. key;
  531. if (item === null || item === undefined) {
  532. return item;
  533. }
  534. // DOM nodes
  535. // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
  536. // recursively
  537. if (item.nodeType && item.cloneNode) {
  538. return item.cloneNode(true);
  539. }
  540. type = toString.call(item);
  541. // Date
  542. if (type === '[object Date]') {
  543. return new Date(item.getTime());
  544. }
  545. // Array
  546. if (type === '[object Array]') {
  547. i = item.length;
  548. clone = [];
  549. while (i--) {
  550. clone[i] = Ext.clone(item[i]);
  551. }
  552. }
  553. // Object
  554. else if (type === '[object Object]' && item.constructor === Object) {
  555. clone = {};
  556. for (key in item) {
  557. clone[key] = Ext.clone(item[key]);
  558. }
  559. if (enumerables) {
  560. for (j = enumerables.length; j--;) {
  561. k = enumerables[j];
  562. clone[k] = item[k];
  563. }
  564. }
  565. }
  566. return clone || item;
  567. },
  568. /**
  569. * @private
  570. * Generate a unique reference of Ext in the global scope, useful for sandboxing
  571. */
  572. getUniqueGlobalNamespace: function() {
  573. var uniqueGlobalNamespace = this.uniqueGlobalNamespace,
  574. i;
  575. if (uniqueGlobalNamespace === undefined) {
  576. i = 0;
  577. do {
  578. uniqueGlobalNamespace = 'ExtBox' + (++i);
  579. } while (Ext.global[uniqueGlobalNamespace] !== undefined);
  580. Ext.global[uniqueGlobalNamespace] = Ext;
  581. this.uniqueGlobalNamespace = uniqueGlobalNamespace;
  582. }
  583. return uniqueGlobalNamespace;
  584. },
  585. /**
  586. * @private
  587. */
  588. functionFactoryCache: {},
  589. cacheableFunctionFactory: function() {
  590. var me = this,
  591. args = Array.prototype.slice.call(arguments),
  592. cache = me.functionFactoryCache,
  593. idx, fn, ln;
  594. if (Ext.isSandboxed) {
  595. ln = args.length;
  596. if (ln > 0) {
  597. ln--;
  598. args[ln] = 'var Ext=window.' + Ext.name + ';' + args[ln];
  599. }
  600. }
  601. idx = args.join('');
  602. fn = cache[idx];
  603. if (!fn) {
  604. fn = Function.prototype.constructor.apply(Function.prototype, args);
  605. cache[idx] = fn;
  606. }
  607. return fn;
  608. },
  609. functionFactory: function() {
  610. var me = this,
  611. args = Array.prototype.slice.call(arguments),
  612. ln;
  613. if (Ext.isSandboxed) {
  614. ln = args.length;
  615. if (ln > 0) {
  616. ln--;
  617. args[ln] = 'var Ext=window.' + Ext.name + ';' + args[ln];
  618. }
  619. }
  620. return Function.prototype.constructor.apply(Function.prototype, args);
  621. },
  622. /**
  623. * @private
  624. * @property
  625. */
  626. Logger: {
  627. verbose: emptyFn,
  628. log: emptyFn,
  629. info: emptyFn,
  630. warn: emptyFn,
  631. error: function(message) {
  632. throw new Error(message);
  633. },
  634. deprecate: emptyFn
  635. }
  636. });
  637. /**
  638. * Old alias to {@link Ext#typeOf}
  639. * @deprecated 4.0.0 Use {@link Ext#typeOf} instead
  640. * @method
  641. * @inheritdoc Ext#typeOf
  642. */
  643. Ext.type = Ext.typeOf;
  644. }());
  645. /*
  646. * This method evaluates the given code free of any local variable. In some browsers this
  647. * will be at global scope, in others it will be in a function.
  648. * @parma {String} code The code to evaluate.
  649. * @private
  650. * @method
  651. */
  652. Ext.globalEval = Ext.global.execScript
  653. ? function(code) {
  654. execScript(code);
  655. }
  656. : function($$code) {
  657. // IMPORTANT: because we use eval we cannot place this in the above function or it
  658. // will break the compressor's ability to rename local variables...
  659. (function(){
  660. eval($$code);
  661. }());
  662. };
  663. //@tag foundation,core
  664. //@require ../Ext.js
  665. /**
  666. * @author Jacky Nguyen <jacky@sencha.com>
  667. * @docauthor Jacky Nguyen <jacky@sencha.com>
  668. * @class Ext.Version
  669. *
  670. * A utility class that wrap around a string version number and provide convenient
  671. * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
  672. *
  673. * var version = new Ext.Version('1.0.2beta');
  674. * console.log("Version is " + version); // Version is 1.0.2beta
  675. *
  676. * console.log(version.getMajor()); // 1
  677. * console.log(version.getMinor()); // 0
  678. * console.log(version.getPatch()); // 2
  679. * console.log(version.getBuild()); // 0
  680. * console.log(version.getRelease()); // beta
  681. *
  682. * console.log(version.isGreaterThan('1.0.1')); // True
  683. * console.log(version.isGreaterThan('1.0.2alpha')); // True
  684. * console.log(version.isGreaterThan('1.0.2RC')); // False
  685. * console.log(version.isGreaterThan('1.0.2')); // False
  686. * console.log(version.isLessThan('1.0.2')); // True
  687. *
  688. * console.log(version.match(1.0)); // True
  689. * console.log(version.match('1.0.2')); // True
  690. *
  691. */
  692. (function() {
  693. // Current core version
  694. var version = '4.1.1.1', Version;
  695. Ext.Version = Version = Ext.extend(Object, {
  696. /**
  697. * @param {String/Number} version The version number in the following standard format:
  698. *
  699. * major[.minor[.patch[.build[release]]]]
  700. *
  701. * Examples:
  702. *
  703. * 1.0
  704. * 1.2.3beta
  705. * 1.2.3.4RC
  706. *
  707. * @return {Ext.Version} this
  708. */
  709. constructor: function(version) {
  710. var parts, releaseStartIndex;
  711. if (version instanceof Version) {
  712. return version;
  713. }
  714. this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
  715. releaseStartIndex = this.version.search(/([^\d\.])/);
  716. if (releaseStartIndex !== -1) {
  717. this.release = this.version.substr(releaseStartIndex, version.length);
  718. this.shortVersion = this.version.substr(0, releaseStartIndex);
  719. }
  720. this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
  721. parts = this.version.split('.');
  722. this.major = parseInt(parts.shift() || 0, 10);
  723. this.minor = parseInt(parts.shift() || 0, 10);
  724. this.patch = parseInt(parts.shift() || 0, 10);
  725. this.build = parseInt(parts.shift() || 0, 10);
  726. return this;
  727. },
  728. /**
  729. * Override the native toString method
  730. * @private
  731. * @return {String} version
  732. */
  733. toString: function() {
  734. return this.version;
  735. },
  736. /**
  737. * Override the native valueOf method
  738. * @private
  739. * @return {String} version
  740. */
  741. valueOf: function() {
  742. return this.version;
  743. },
  744. /**
  745. * Returns the major component value
  746. * @return {Number} major
  747. */
  748. getMajor: function() {
  749. return this.major || 0;
  750. },
  751. /**
  752. * Returns the minor component value
  753. * @return {Number} minor
  754. */
  755. getMinor: function() {
  756. return this.minor || 0;
  757. },
  758. /**
  759. * Returns the patch component value
  760. * @return {Number} patch
  761. */
  762. getPatch: function() {
  763. return this.patch || 0;
  764. },
  765. /**
  766. * Returns the build component value
  767. * @return {Number} build
  768. */
  769. getBuild: function() {
  770. return this.build || 0;
  771. },
  772. /**
  773. * Returns the release component value
  774. * @return {Number} release
  775. */
  776. getRelease: function() {
  777. return this.release || '';
  778. },
  779. /**
  780. * Returns whether this version if greater than the supplied argument
  781. * @param {String/Number} target The version to compare with
  782. * @return {Boolean} True if this version if greater than the target, false otherwise
  783. */
  784. isGreaterThan: function(target) {
  785. return Version.compare(this.version, target) === 1;
  786. },
  787. /**
  788. * Returns whether this version if greater than or equal to the supplied argument
  789. * @param {String/Number} target The version to compare with
  790. * @return {Boolean} True if this version if greater than or equal to the target, false otherwise
  791. */
  792. isGreaterThanOrEqual: function(target) {
  793. return Version.compare(this.version, target) >= 0;
  794. },
  795. /**
  796. * Returns whether this version if smaller than the supplied argument
  797. * @param {String/Number} target The version to compare with
  798. * @return {Boolean} True if this version if smaller than the target, false otherwise
  799. */
  800. isLessThan: function(target) {
  801. return Version.compare(this.version, target) === -1;
  802. },
  803. /**
  804. * Returns whether this version if less than or equal to the supplied argument
  805. * @param {String/Number} target The version to compare with
  806. * @return {Boolean} True if this version if less than or equal to the target, false otherwise
  807. */
  808. isLessThanOrEqual: function(target) {
  809. return Version.compare(this.version, target) <= 0;
  810. },
  811. /**
  812. * Returns whether this version equals to the supplied argument
  813. * @param {String/Number} target The version to compare with
  814. * @return {Boolean} True if this version equals to the target, false otherwise
  815. */
  816. equals: function(target) {
  817. return Version.compare(this.version, target) === 0;
  818. },
  819. /**
  820. * Returns whether this version matches the supplied argument. Example:
  821. *
  822. * var version = new Ext.Version('1.0.2beta');
  823. * console.log(version.match(1)); // True
  824. * console.log(version.match(1.0)); // True
  825. * console.log(version.match('1.0.2')); // True
  826. * console.log(version.match('1.0.2RC')); // False
  827. *
  828. * @param {String/Number} target The version to compare with
  829. * @return {Boolean} True if this version matches the target, false otherwise
  830. */
  831. match: function(target) {
  832. target = String(target);
  833. return this.version.substr(0, target.length) === target;
  834. },
  835. /**
  836. * Returns this format: [major, minor, patch, build, release]. Useful for comparison
  837. * @return {Number[]}
  838. */
  839. toArray: function() {
  840. return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
  841. },
  842. /**
  843. * Returns shortVersion version without dots and release
  844. * @return {String}
  845. */
  846. getShortVersion: function() {
  847. return this.shortVersion;
  848. },
  849. /**
  850. * Convenient alias to {@link Ext.Version#isGreaterThan isGreaterThan}
  851. * @param {String/Number} target
  852. * @return {Boolean}
  853. */
  854. gt: function() {
  855. return this.isGreaterThan.apply(this, arguments);
  856. },
  857. /**
  858. * Convenient alias to {@link Ext.Version#isLessThan isLessThan}
  859. * @param {String/Number} target
  860. * @return {Boolean}
  861. */
  862. lt: function() {
  863. return this.isLessThan.apply(this, arguments);
  864. },
  865. /**
  866. * Convenient alias to {@link Ext.Version#isGreaterThanOrEqual isGreaterThanOrEqual}
  867. * @param {String/Number} target
  868. * @return {Boolean}
  869. */
  870. gtEq: function() {
  871. return this.isGreaterThanOrEqual.apply(this, arguments);
  872. },
  873. /**
  874. * Convenient alias to {@link Ext.Version#isLessThanOrEqual isLessThanOrEqual}
  875. * @param {String/Number} target
  876. * @return {Boolean}
  877. */
  878. ltEq: function() {
  879. return this.isLessThanOrEqual.apply(this, arguments);
  880. }
  881. });
  882. Ext.apply(Version, {
  883. // @private
  884. releaseValueMap: {
  885. 'dev': -6,
  886. 'alpha': -5,
  887. 'a': -5,
  888. 'beta': -4,
  889. 'b': -4,
  890. 'rc': -3,
  891. '#': -2,
  892. 'p': -1,
  893. 'pl': -1
  894. },
  895. /**
  896. * Converts a version component to a comparable value
  897. *
  898. * @static
  899. * @param {Object} value The value to convert
  900. * @return {Object}
  901. */
  902. getComponentValue: function(value) {
  903. return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
  904. },
  905. /**
  906. * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
  907. * they are handled in the following order:
  908. * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
  909. *
  910. * @static
  911. * @param {String} current The current version to compare to
  912. * @param {String} target The target version to compare to
  913. * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent
  914. */
  915. compare: function(current, target) {
  916. var currentValue, targetValue, i;
  917. current = new Version(current).toArray();
  918. target = new Version(target).toArray();
  919. for (i = 0; i < Math.max(current.length, target.length); i++) {
  920. currentValue = this.getComponentValue(current[i]);
  921. targetValue = this.getComponentValue(target[i]);
  922. if (currentValue < targetValue) {
  923. return -1;
  924. } else if (currentValue > targetValue) {
  925. return 1;
  926. }
  927. }
  928. return 0;
  929. }
  930. });
  931. /**
  932. * @class Ext
  933. */
  934. Ext.apply(Ext, {
  935. /**
  936. * @private
  937. */
  938. versions: {},
  939. /**
  940. * @private
  941. */
  942. lastRegisteredVersion: null,
  943. /**
  944. * Set version number for the given package name.
  945. *
  946. * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'
  947. * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'
  948. * @return {Ext}
  949. */
  950. setVersion: function(packageName, version) {
  951. Ext.versions[packageName] = new Version(version);
  952. Ext.lastRegisteredVersion = Ext.versions[packageName];
  953. return this;
  954. },
  955. /**
  956. * Get the version number of the supplied package name; will return the last registered version
  957. * (last Ext.setVersion call) if there's no package name given.
  958. *
  959. * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'
  960. * @return {Ext.Version} The version
  961. */
  962. getVersion: function(packageName) {
  963. if (packageName === undefined) {
  964. return Ext.lastRegisteredVersion;
  965. }
  966. return Ext.versions[packageName];
  967. },
  968. /**
  969. * Create a closure for deprecated code.
  970. *
  971. * // This means Ext.oldMethod is only supported in 4.0.0beta and older.
  972. * // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
  973. * // the closure will not be invoked
  974. * Ext.deprecate('extjs', '4.0.0beta', function() {
  975. * Ext.oldMethod = Ext.newMethod;
  976. *
  977. * ...
  978. * });
  979. *
  980. * @param {String} packageName The package name
  981. * @param {String} since The last version before it's deprecated
  982. * @param {Function} closure The callback function to be executed with the specified version is less than the current version
  983. * @param {Object} scope The execution scope (`this`) if the closure
  984. */
  985. deprecate: function(packageName, since, closure, scope) {
  986. if (Version.compare(Ext.getVersion(packageName), since) < 1) {
  987. closure.call(scope);
  988. }
  989. }
  990. }); // End Versioning
  991. Ext.setVersion('core', version);
  992. }());
  993. //@tag foundation,core
  994. //@require ../version/Version.js
  995. /**
  996. * @class Ext.String
  997. *
  998. * A collection of useful static methods to deal with strings
  999. * @singleton
  1000. */
  1001. Ext.String = (function() {
  1002. var trimRegex = /^[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+|[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+$/g,
  1003. escapeRe = /('|\\)/g,
  1004. formatRe = /\{(\d+)\}/g,
  1005. escapeRegexRe = /([-.*+?\^${}()|\[\]\/\\])/g,
  1006. basicTrimRe = /^\s+|\s+$/g,
  1007. whitespaceRe = /\s+/,
  1008. varReplace = /(^[^a-z]*|[^\w])/gi,
  1009. charToEntity,
  1010. entityToChar,
  1011. charToEntityRegex,
  1012. entityToCharRegex,
  1013. htmlEncodeReplaceFn = function(match, capture) {
  1014. return charToEntity[capture];
  1015. },
  1016. htmlDecodeReplaceFn = function(match, capture) {
  1017. return (capture in entityToChar) ? entityToChar[capture] : String.fromCharCode(parseInt(capture.substr(2), 10));
  1018. };
  1019. return {
  1020. /**
  1021. * Converts a string of characters into a legal, parseable Javascript `var` name as long as the passed
  1022. * string contains at least one alphabetic character. Non alphanumeric characters, and *leading* non alphabetic
  1023. * characters will be removed.
  1024. * @param {String} s A string to be converted into a `var` name.
  1025. * @return {String} A legal Javascript `var` name.
  1026. */
  1027. createVarName: function(s) {
  1028. return s.replace(varReplace, '');
  1029. },
  1030. /**
  1031. * Convert certain characters (&, <, >, ', and ") to their HTML character equivalents for literal display in web pages.
  1032. * @param {String} value The string to encode
  1033. * @return {String} The encoded text
  1034. * @method
  1035. */
  1036. htmlEncode: function(value) {
  1037. return (!value) ? value : String(value).replace(charToEntityRegex, htmlEncodeReplaceFn);
  1038. },
  1039. /**
  1040. * Convert certain characters (&, <, >, ', and ") from their HTML character equivalents.
  1041. * @param {String} value The string to decode
  1042. * @return {String} The decoded text
  1043. * @method
  1044. */
  1045. htmlDecode: function(value) {
  1046. return (!value) ? value : String(value).replace(entityToCharRegex, htmlDecodeReplaceFn);
  1047. },
  1048. /**
  1049. * Adds a set of character entity definitions to the set used by
  1050. * {@link Ext.String#htmlEncode} and {@link Ext.String#htmlDecode}.
  1051. *
  1052. * This object should be keyed by the entity name sequence,
  1053. * with the value being the textual representation of the entity.
  1054. *
  1055. * Ext.String.addCharacterEntities({
  1056. * '&amp;Uuml;':'Ü',
  1057. * '&amp;ccedil;':'ç',
  1058. * '&amp;ntilde;':'ñ',
  1059. * '&amp;egrave;':'è'
  1060. * });
  1061. * var s = Ext.String.htmlEncode("A string with entities: èÜçñ");
  1062. *
  1063. * Note: the values of the character entites defined on this object are expected
  1064. * to be single character values. As such, the actual values represented by the
  1065. * characters are sensitive to the character encoding of the javascript source
  1066. * file when defined in string literal form. Script tasgs referencing server
  1067. * resources with character entities must ensure that the 'charset' attribute
  1068. * of the script node is consistent with the actual character encoding of the
  1069. * server resource.
  1070. *
  1071. * The set of character entities may be reset back to the default state by using
  1072. * the {@link Ext.String#resetCharacterEntities} method
  1073. *
  1074. * @param {Object} entities The set of character entities to add to the current
  1075. * definitions.
  1076. */
  1077. addCharacterEntities: function(newEntities) {
  1078. var charKeys = [],
  1079. entityKeys = [],
  1080. key, echar;
  1081. for (key in newEntities) {
  1082. echar = newEntities[key];
  1083. entityToChar[key] = echar;
  1084. charToEntity[echar] = key;
  1085. charKeys.push(echar);
  1086. entityKeys.push(key);
  1087. }
  1088. charToEntityRegex = new RegExp('(' + charKeys.join('|') + ')', 'g');
  1089. entityToCharRegex = new RegExp('(' + entityKeys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
  1090. },
  1091. /**
  1092. * Resets the set of character entity definitions used by
  1093. * {@link Ext.String#htmlEncode} and {@link Ext.String#htmlDecode} back to the
  1094. * default state.
  1095. */
  1096. resetCharacterEntities: function() {
  1097. charToEntity = {};
  1098. entityToChar = {};
  1099. // add the default set
  1100. this.addCharacterEntities({
  1101. '&amp;' : '&',
  1102. '&gt;' : '>',
  1103. '&lt;' : '<',
  1104. '&quot;' : '"',
  1105. '&#39;' : "'"
  1106. });
  1107. },
  1108. /**
  1109. * Appends content to the query string of a URL, handling logic for whether to place
  1110. * a question mark or ampersand.
  1111. * @param {String} url The URL to append to.
  1112. * @param {String} string The content to append to the URL.
  1113. * @return {String} The resulting URL
  1114. */
  1115. urlAppend : function(url, string) {
  1116. if (!Ext.isEmpty(string)) {
  1117. return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
  1118. }
  1119. return url;
  1120. },
  1121. /**
  1122. * Trims whitespace from either end of a string, leaving spaces within the string intact. Example:
  1123. * @example
  1124. var s = ' foo bar ';
  1125. alert('-' + s + '-'); //alerts "- foo bar -"
  1126. alert('-' + Ext.String.trim(s) + '-'); //alerts "-foo bar-"
  1127. * @param {String} string The string to escape
  1128. * @return {String} The trimmed string
  1129. */
  1130. trim: function(string) {
  1131. return string.replace(trimRegex, "");
  1132. },
  1133. /**
  1134. * Capitalize the given string
  1135. * @param {String} string
  1136. * @return {String}
  1137. */
  1138. capitalize: function(string) {
  1139. return string.charAt(0).toUpperCase() + string.substr(1);
  1140. },
  1141. /**
  1142. * Uncapitalize the given string
  1143. * @param {String} string
  1144. * @return {String}
  1145. */
  1146. uncapitalize: function(string) {
  1147. return string.charAt(0).toLowerCase() + string.substr(1);
  1148. },
  1149. /**
  1150. * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
  1151. * @param {String} value The string to truncate
  1152. * @param {Number} length The maximum length to allow before truncating
  1153. * @param {Boolean} word True to try to find a common word break
  1154. * @return {String} The converted text
  1155. */
  1156. ellipsis: function(value, len, word) {
  1157. if (value && value.length > len) {
  1158. if (word) {
  1159. var vs = value.substr(0, len - 2),
  1160. index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
  1161. if (index !== -1 && index >= (len - 15)) {
  1162. return vs.substr(0, index) + "...";
  1163. }
  1164. }
  1165. return value.substr(0, len - 3) + "...";
  1166. }
  1167. return value;
  1168. },
  1169. /**
  1170. * Escapes the passed string for use in a regular expression
  1171. * @param {String} string
  1172. * @return {String}
  1173. */
  1174. escapeRegex: function(string) {
  1175. return string.replace(escapeRegexRe, "\\$1");
  1176. },
  1177. /**
  1178. * Escapes the passed string for ' and \
  1179. * @param {String} string The string to escape
  1180. * @return {String} The escaped string
  1181. */
  1182. escape: function(string) {
  1183. return string.replace(escapeRe, "\\$1");
  1184. },
  1185. /**
  1186. * Utility function that allows you to easily switch a string between two alternating values. The passed value
  1187. * is compared to the current string, and if they are equal, the other value that was passed in is returned. If
  1188. * they are already different, the first value passed in is returned. Note that this method returns the new value
  1189. * but does not change the current string.
  1190. * <pre><code>
  1191. // alternate sort directions
  1192. sort = Ext.String.toggle(sort, 'ASC', 'DESC');
  1193. // instead of conditional logic:
  1194. sort = (sort == 'ASC' ? 'DESC' : 'ASC');
  1195. </code></pre>
  1196. * @param {String} string The current string
  1197. * @param {String} value The value to compare to the current string
  1198. * @param {String} other The new value to use if the string already equals the first value passed in
  1199. * @return {String} The new value
  1200. */
  1201. toggle: function(string, value, other) {
  1202. return string === value ? other : value;
  1203. },
  1204. /**
  1205. * Pads the left side of a string with a specified character. This is especially useful
  1206. * for normalizing number and date strings. Example usage:
  1207. *
  1208. * <pre><code>
  1209. var s = Ext.String.leftPad('123', 5, '0');
  1210. // s now contains the string: '00123'
  1211. </code></pre>
  1212. * @param {String} string The original string
  1213. * @param {Number} size The total length of the output string
  1214. * @param {String} character (optional) The character with which to pad the original string (defaults to empty string " ")
  1215. * @return {String} The padded string
  1216. */
  1217. leftPad: function(string, size, character) {
  1218. var result = String(string);
  1219. character = character || " ";
  1220. while (result.length < size) {
  1221. result = character + result;
  1222. }
  1223. return result;
  1224. },
  1225. /**
  1226. * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
  1227. * token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
  1228. * <pre><code>
  1229. var cls = 'my-class', text = 'Some text';
  1230. var s = Ext.String.format('&lt;div class="{0}">{1}&lt;/div>', cls, text);
  1231. // s now contains the string: '&lt;div class="my-class">Some text&lt;/div>'
  1232. </code></pre>
  1233. * @param {String} string The tokenized string to be formatted
  1234. * @param {String} value1 The value to replace token {0}
  1235. * @param {String} value2 Etc...
  1236. * @return {String} The formatted string
  1237. */
  1238. format: function(format) {
  1239. var args = Ext.Array.toArray(arguments, 1);
  1240. return format.replace(formatRe, function(m, i) {
  1241. return args[i];
  1242. });
  1243. },
  1244. /**
  1245. * Returns a string with a specified number of repititions a given string pattern.
  1246. * The pattern be separated by a different string.
  1247. *
  1248. * var s = Ext.String.repeat('---', 4); // = '------------'
  1249. * var t = Ext.String.repeat('--', 3, '/'); // = '--/--/--'
  1250. *
  1251. * @param {String} pattern The pattern to repeat.
  1252. * @param {Number} count The number of times to repeat the pattern (may be 0).
  1253. * @param {String} sep An option string to separate each pattern.
  1254. */
  1255. repeat: function(pattern, count, sep) {
  1256. for (var buf = [], i = count; i--; ) {
  1257. buf.push(pattern);
  1258. }
  1259. return buf.join(sep || '');
  1260. },
  1261. /**
  1262. * Splits a string of space separated words into an array, trimming as needed. If the
  1263. * words are already an array, it is returned.
  1264. *
  1265. * @param {String/Array} words
  1266. */
  1267. splitWords: function (words) {
  1268. if (words && typeof words == 'string') {
  1269. return words.replace(basicTrimRe, '').split(whitespaceRe);
  1270. }
  1271. return words || [];
  1272. }
  1273. };
  1274. }());
  1275. // initialize the default encode / decode entities
  1276. Ext.String.resetCharacterEntities();
  1277. /**
  1278. * Old alias to {@link Ext.String#htmlEncode}
  1279. * @deprecated Use {@link Ext.String#htmlEncode} instead
  1280. * @method
  1281. * @member Ext
  1282. * @inheritdoc Ext.String#htmlEncode
  1283. */
  1284. Ext.htmlEncode = Ext.String.htmlEncode;
  1285. /**
  1286. * Old alias to {@link Ext.String#htmlDecode}
  1287. * @deprecated Use {@link Ext.String#htmlDecode} instead
  1288. * @method
  1289. * @member Ext
  1290. * @inheritdoc Ext.String#htmlDecode
  1291. */
  1292. Ext.htmlDecode = Ext.String.htmlDecode;
  1293. /**
  1294. * Old alias to {@link Ext.String#urlAppend}
  1295. * @deprecated Use {@link Ext.String#urlAppend} instead
  1296. * @method
  1297. * @member Ext
  1298. * @inheritdoc Ext.String#urlAppend
  1299. */
  1300. Ext.urlAppend = Ext.String.urlAppend;
  1301. //@tag foundation,core
  1302. //@require String.js
  1303. //@define Ext.Number
  1304. /**
  1305. * @class Ext.Number
  1306. *
  1307. * A collection of useful static methods to deal with numbers
  1308. * @singleton
  1309. */
  1310. Ext.Number = new function() {
  1311. var me = this,
  1312. isToFixedBroken = (0.9).toFixed() !== '1',
  1313. math = Math;
  1314. Ext.apply(this, {
  1315. /**
  1316. * Checks whether or not the passed number is within a desired range. If the number is already within the
  1317. * range it is returned, otherwise the min or max value is returned depending on which side of the range is
  1318. * exceeded. Note that this method returns the constrained value but does not change the current number.
  1319. * @param {Number} number The number to check
  1320. * @param {Number} min The minimum number in the range
  1321. * @param {Number} max The maximum number in the range
  1322. * @return {Number} The constrained value if outside the range, otherwise the current value
  1323. */
  1324. constrain: function(number, min, max) {
  1325. var x = parseFloat(number);
  1326. // Watch out for NaN in Chrome 18
  1327. // V8bug: http://code.google.com/p/v8/issues/detail?id=2056
  1328. // Operators are faster than Math.min/max. See http://jsperf.com/number-constrain
  1329. // ... and (x < Nan) || (x < undefined) == false
  1330. // ... same for (x > NaN) || (x > undefined)
  1331. // so if min or max are undefined or NaN, we never return them... sadly, this
  1332. // is not true of null (but even Math.max(-1,null)==0 and isNaN(null)==false)
  1333. return (x < min) ? min : ((x > max) ? max : x);
  1334. },
  1335. /**
  1336. * Snaps the passed number between stopping points based upon a passed increment value.
  1337. *
  1338. * The difference between this and {@link #snapInRange} is that {@link #snapInRange} uses the minValue
  1339. * when calculating snap points:
  1340. *
  1341. * r = Ext.Number.snap(56, 2, 55, 65); // Returns 56 - snap points are zero based
  1342. *
  1343. * r = Ext.Number.snapInRange(56, 2, 55, 65); // Returns 57 - snap points are based from minValue
  1344. *
  1345. * @param {Number} value The unsnapped value.
  1346. * @param {Number} increment The increment by which the value must move.
  1347. * @param {Number} minValue The minimum value to which the returned value must be constrained. Overrides the increment.
  1348. * @param {Number} maxValue The maximum value to which the returned value must be constrained. Overrides the increment.
  1349. * @return {Number} The value of the nearest snap target.
  1350. */
  1351. snap : function(value, increment, minValue, maxValue) {
  1352. var m;
  1353. // If no value passed, or minValue was passed and value is less than minValue (anything < undefined is false)
  1354. // Then use the minValue (or zero if the value was undefined)
  1355. if (value === undefined || value < minValue) {
  1356. return minValue || 0;
  1357. }
  1358. if (increment) {
  1359. m = value % increment;
  1360. if (m !== 0) {
  1361. value -= m;
  1362. if (m * 2 >= increment) {
  1363. value += increment;
  1364. } else if (m * 2 < -increment) {
  1365. value -= increment;
  1366. }
  1367. }
  1368. }
  1369. return me.constrain(value, minValue, maxValue);
  1370. },
  1371. /**
  1372. * Snaps the passed number between stopping points based upon a passed increment value.
  1373. *
  1374. * The difference between this and {@link #snap} is that {@link #snap} does not use the minValue
  1375. * when calculating snap points:
  1376. *
  1377. * r = Ext.Number.snap(56, 2, 55, 65); // Returns 56 - snap points are zero based
  1378. *
  1379. * r = Ext.Number.snapInRange(56, 2, 55, 65); // Returns 57 - snap points are based from minValue
  1380. *
  1381. * @param {Number} value The unsnapped value.
  1382. * @param {Number} increment The increment by which the value must move.
  1383. * @param {Number} [minValue=0] The minimum value to which the returned value must be constrained.
  1384. * @param {Number} [maxValue=Infinity] The maximum value to which the returned value must be constrained.
  1385. * @return {Number} The value of the nearest snap target.
  1386. */
  1387. snapInRange : function(value, increment, minValue, maxValue) {
  1388. var tween;
  1389. // default minValue to zero
  1390. minValue = (minValue || 0);
  1391. // If value is undefined, or less than minValue, use minValue
  1392. if (value === undefined || value < minValue) {
  1393. return minValue;
  1394. }
  1395. // Calculate how many snap points from the minValue the passed value is.
  1396. if (increment && (tween = ((value - minValue) % increment))) {
  1397. value -= tween;
  1398. tween *= 2;
  1399. if (tween >= increment) {
  1400. value += increment;
  1401. }
  1402. }
  1403. // If constraining within a maximum, ensure the maximum is on a snap point
  1404. if (maxValue !== undefined) {
  1405. if (value > (maxValue = me.snapInRange(maxValue, increment, minValue))) {
  1406. value = maxValue;
  1407. }
  1408. }
  1409. return value;
  1410. },
  1411. /**
  1412. * Formats a number using fixed-point notation
  1413. * @param {Number} value The number to format
  1414. * @param {Number} precision The number of digits to show after the decimal point
  1415. */
  1416. toFixed: isToFixedBroken ? function(value, precision) {
  1417. precision = precision || 0;
  1418. var pow = math.pow(10, precision);
  1419. return (math.round(value * pow) / pow).toFixed(precision);
  1420. } : function(value, precision) {
  1421. return value.toFixed(precision);
  1422. },
  1423. /**
  1424. * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
  1425. * it is not.
  1426. Ext.Number.from('1.23', 1); // returns 1.23
  1427. Ext.Number.from('abc', 1); // returns 1
  1428. * @param {Object} value
  1429. * @param {Number} defaultValue The value to return if the original value is non-numeric
  1430. * @return {Number} value, if numeric, defaultValue otherwise
  1431. */
  1432. from: function(value, defaultValue) {
  1433. if (isFinite(value)) {
  1434. value = parseFloat(value);
  1435. }
  1436. return !isNaN(value) ? value : defaultValue;
  1437. },
  1438. /**
  1439. * Returns a random integer between the specified range (inclusive)
  1440. * @param {Number} from Lowest value to return.
  1441. * @param {Number} to Highst value to return.
  1442. * @return {Number} A random integer within the specified range.
  1443. */
  1444. randomInt: function (from, to) {
  1445. return math.floor(math.random() * (to - from + 1) + from);
  1446. }
  1447. });
  1448. /**
  1449. * @deprecated 4.0.0 Please use {@link Ext.Number#from} instead.
  1450. * @member Ext
  1451. * @method num
  1452. * @inheritdoc Ext.Number#from
  1453. */
  1454. Ext.num = function() {
  1455. return me.from.apply(this, arguments);
  1456. };
  1457. };
  1458. //@tag foundation,core
  1459. //@require Number.js
  1460. /**
  1461. * @class Ext.Array
  1462. * @singleton
  1463. * @author Jacky Nguyen <jacky@sencha.com>
  1464. * @docauthor Jacky Nguyen <jacky@sencha.com>
  1465. *
  1466. * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
  1467. */
  1468. (function() {
  1469. var arrayPrototype = Array.prototype,
  1470. slice = arrayPrototype.slice,
  1471. supportsSplice = (function () {
  1472. var array = [],
  1473. lengthBefore,
  1474. j = 20;
  1475. if (!array.splice) {
  1476. return false;
  1477. }
  1478. // This detects a bug in IE8 splice method:
  1479. // see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/
  1480. while (j--) {
  1481. array.push("A");
  1482. }
  1483. array.splice(15, 0, "F", "F", "F", "F", "F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F");
  1484. lengthBefore = array.length; //41
  1485. array.splice(13, 0, "XXX"); // add one element
  1486. if (lengthBefore+1 != array.length) {
  1487. return false;
  1488. }
  1489. // end IE8 bug
  1490. return true;
  1491. }()),
  1492. supportsForEach = 'forEach' in arrayPrototype,
  1493. supportsMap = 'map' in arrayPrototype,
  1494. supportsIndexOf = 'indexOf' in arrayPrototype,
  1495. supportsEvery = 'every' in arrayPrototype,
  1496. supportsSome = 'some' in arrayPrototype,
  1497. supportsFilter = 'filter' in arrayPrototype,
  1498. supportsSort = (function() {
  1499. var a = [1,2,3,4,5].sort(function(){ return 0; });
  1500. return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
  1501. }()),
  1502. supportsSliceOnNodeList = true,
  1503. ExtArray,
  1504. erase,
  1505. replace,
  1506. splice;
  1507. try {
  1508. // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
  1509. if (typeof document !== 'undefined') {
  1510. slice.call(document.getElementsByTagName('body'));
  1511. }
  1512. } catch (e) {
  1513. supportsSliceOnNodeList = false;
  1514. }
  1515. function fixArrayIndex (array, index) {
  1516. return (index < 0) ? Math.max(0, array.length + index)
  1517. : Math.min(array.length, index);
  1518. }
  1519. /*
  1520. Does the same work as splice, but with a slightly more convenient signature. The splice
  1521. method has bugs in IE8, so this is the implementation we use on that platform.
  1522. The rippling of items in the array can be tricky. Consider two use cases:
  1523. index=2
  1524. removeCount=2
  1525. /=====\
  1526. +---+---+---+---+---+---+---+---+
  1527. | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
  1528. +---+---+---+---+---+---+---+---+
  1529. / \/ \/ \/ \
  1530. / /\ /\ /\ \
  1531. / / \/ \/ \ +--------------------------+
  1532. / / /\ /\ +--------------------------+ \
  1533. / / / \/ +--------------------------+ \ \
  1534. / / / /+--------------------------+ \ \ \
  1535. / / / / \ \ \ \
  1536. v v v v v v v v
  1537. +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
  1538. | 0 | 1 | 4 | 5 | 6 | 7 | | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 |
  1539. +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
  1540. A B \=========/
  1541. insert=[a,b,c]
  1542. In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so
  1543. that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we
  1544. must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4].
  1545. */
  1546. function replaceSim (array, index, removeCount, insert) {
  1547. var add = insert ? insert.length : 0,
  1548. length = array.length,
  1549. pos = fixArrayIndex(array, index),
  1550. remove,
  1551. tailOldPos,
  1552. tailNewPos,
  1553. tailCount,
  1554. lengthAfterRemove,
  1555. i;
  1556. // we try to use Array.push when we can for efficiency...
  1557. if (pos === length) {
  1558. if (add) {
  1559. array.push.apply(array, insert);
  1560. }
  1561. } else {
  1562. remove = Math.min(removeCount, length - pos);
  1563. tailOldPos = pos + remove;
  1564. tailNewPos = tailOldPos + add - remove;
  1565. tailCount = length - tailOldPos;
  1566. lengthAfterRemove = length - remove;
  1567. if (tailNewPos < tailOldPos) { // case A
  1568. for (i = 0; i < tailCount; ++i) {
  1569. array[tailNewPos+i] = array[tailOldPos+i];
  1570. }
  1571. } else if (tailNewPos > tailOldPos) { // case B
  1572. for (i = tailCount; i--; ) {
  1573. array[tailNewPos+i] = array[tailOldPos+i];
  1574. }
  1575. } // else, add == remove (nothing to do)
  1576. if (add && pos === lengthAfterRemove) {
  1577. array.length = lengthAfterRemove; // truncate array
  1578. array.push.apply(array, insert);
  1579. } else {
  1580. array.length = lengthAfterRemove + add; // reserves space
  1581. for (i = 0; i < add; ++i) {
  1582. array[pos+i] = insert[i];
  1583. }
  1584. }
  1585. }
  1586. return array;
  1587. }
  1588. function replaceNative (array, index, removeCount, insert) {
  1589. if (insert && insert.length) {
  1590. if (index < array.length) {
  1591. array.splice.apply(array, [index, removeCount].concat(insert));
  1592. } else {
  1593. array.push.apply(array, insert);
  1594. }
  1595. } else {
  1596. array.splice(index, removeCount);
  1597. }
  1598. return array;
  1599. }
  1600. function eraseSim (array, index, removeCount) {
  1601. return replaceSim(array, index, removeCount);
  1602. }
  1603. function eraseNative (array, index, removeCount) {
  1604. array.splice(index, removeCount);
  1605. return array;
  1606. }
  1607. function spliceSim (array, index, removeCount) {
  1608. var pos = fixArrayIndex(array, index),
  1609. removed = array.slice(index, fixArrayIndex(array, pos+removeCount));
  1610. if (arguments.length < 4) {
  1611. replaceSim(array, pos, removeCount);
  1612. } else {
  1613. replaceSim(array, pos, removeCount, slice.call(arguments, 3));
  1614. }
  1615. return removed;
  1616. }
  1617. function spliceNative (array) {
  1618. return array.splice.apply(array, slice.call(arguments, 1));
  1619. }
  1620. erase = supportsSplice ? eraseNative : eraseSim;
  1621. replace = supportsSplice ? replaceNative : replaceSim;
  1622. splice = supportsSplice ? spliceNative : spliceSim;
  1623. // NOTE: from here on, use erase, replace or splice (not native methods)...
  1624. ExtArray = Ext.Array = {
  1625. /**
  1626. * Iterates an array or an iterable value and invoke the given callback function for each item.
  1627. *
  1628. * var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
  1629. *
  1630. * Ext.Array.each(countries, function(name, index, countriesItSelf) {
  1631. * console.log(name);
  1632. * });
  1633. *
  1634. * var sum = function() {
  1635. * var sum = 0;
  1636. *
  1637. * Ext.Array.each(arguments, function(value) {
  1638. * sum += value;
  1639. * });
  1640. *
  1641. * return sum;
  1642. * };
  1643. *
  1644. * sum(1, 2, 3); // returns 6
  1645. *
  1646. * The iteration can be stopped by returning false in the function callback.
  1647. *
  1648. * Ext.Array.each(countries, function(name, index, countriesItSelf) {
  1649. * if (name === 'Singapore') {
  1650. * return false; // break here
  1651. * }
  1652. * });
  1653. *
  1654. * {@link Ext#each Ext.each} is alias for {@link Ext.Array#each Ext.Array.each}
  1655. *
  1656. * @param {Array/NodeList/Object} iterable The value to be iterated. If this
  1657. * argument is not iterable, the callback function is called once.
  1658. * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
  1659. * the current `index`.
  1660. * @param {Object} fn.item The item at the current `index` in the passed `array`
  1661. * @param {Number} fn.index The current `index` within the `array`
  1662. * @param {Array} fn.allItems The `array` itself which was passed as the first argument
  1663. * @param {Boolean} fn.return Return false to stop iteration.
  1664. * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
  1665. * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
  1666. * Defaults false
  1667. * @return {Boolean} See description for the `fn` parameter.
  1668. */
  1669. each: function(array, fn, scope, reverse) {
  1670. array = ExtArray.from(array);
  1671. var i,
  1672. ln = array.length;
  1673. if (reverse !== true) {
  1674. for (i = 0; i < ln; i++) {
  1675. if (fn.call(scope || array[i], array[i], i, array) === false) {
  1676. return i;
  1677. }
  1678. }
  1679. }
  1680. else {
  1681. for (i = ln - 1; i > -1; i--) {
  1682. if (fn.call(scope || array[i], array[i], i, array) === false) {
  1683. return i;
  1684. }
  1685. }
  1686. }
  1687. return true;
  1688. },
  1689. /**
  1690. * Iterates an array and invoke the given callback function for each item. Note that this will simply
  1691. * delegate to the native Array.prototype.forEach method if supported. It doesn't support stopping the
  1692. * iteration by returning false in the callback function like {@link Ext.Array#each}. However, performance
  1693. * could be much better in modern browsers comparing with {@link Ext.Array#each}
  1694. *
  1695. * @param {Array} array The array to iterate
  1696. * @param {Function} fn The callback function.
  1697. * @param {Object} fn.item The item at the current `index` in the passed `array`
  1698. * @param {Number} fn.index The current `index` within the `array`
  1699. * @param {Array} fn.allItems The `array` itself which was passed as the first argument
  1700. * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
  1701. */
  1702. forEach: supportsForEach ? function(array, fn, scope) {
  1703. return array.forEach(fn, scope);
  1704. } : function(array, fn, scope) {
  1705. var i = 0,
  1706. ln = array.length;
  1707. for (; i < ln; i++) {
  1708. fn.call(scope, array[i], i, array);
  1709. }
  1710. },
  1711. /**
  1712. * Get the index of the provided `item` in the given `array`, a supplement for the
  1713. * missing arrayPrototype.indexOf in Internet Explorer.
  1714. *
  1715. * @param {Array} array The array to check
  1716. * @param {Object} item The item to look for
  1717. * @param {Number} from (Optional) The index at which to begin the search
  1718. * @return {Number} The index of item in the array (or -1 if it is not found)
  1719. */
  1720. indexOf: supportsIndexOf ? function(array, item, from) {
  1721. return array.indexOf(item, from);
  1722. } : function(array, item, from) {
  1723. var i, length = array.length;
  1724. for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
  1725. if (array[i] === item) {
  1726. return i;
  1727. }
  1728. }
  1729. return -1;
  1730. },
  1731. /**
  1732. * Checks whether or not the given `array` contains the specified `item`
  1733. *
  1734. * @param {Array} array The array to check
  1735. * @param {Object} item The item to look for
  1736. * @return {Boolean} True if the array contains the item, false otherwise
  1737. */
  1738. contains: supportsIndexOf ? function(array, item) {
  1739. return array.indexOf(item) !== -1;
  1740. } : function(array, item) {
  1741. var i, ln;
  1742. for (i = 0, ln = array.length; i < ln; i++) {
  1743. if (array[i] === item) {
  1744. return true;
  1745. }
  1746. }
  1747. return false;
  1748. },
  1749. /**
  1750. * Converts any iterable (numeric indices and a length property) into a true array.
  1751. *
  1752. * function test() {
  1753. * var args = Ext.Array.toArray(arguments),
  1754. * fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
  1755. *
  1756. * alert(args.join(' '));
  1757. * alert(fromSecondToLastArgs.join(' '));
  1758. * }
  1759. *
  1760. * test('just', 'testing', 'here'); // alerts 'just testing here';
  1761. * // alerts 'testing here';
  1762. *
  1763. * Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
  1764. * Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
  1765. * Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l']
  1766. *
  1767. * {@link Ext#toArray Ext.toArray} is alias for {@link Ext.Array#toArray Ext.Array.toArray}
  1768. *
  1769. * @param {Object} iterable the iterable object to be turned into a true Array.
  1770. * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
  1771. * @param {Number} end (Optional) a 1-based index that specifies the end of extraction. Defaults to the last
  1772. * index of the iterable value
  1773. * @return {Array} array
  1774. */
  1775. toArray: function(iterable, start, end){
  1776. if (!iterable || !iterable.length) {
  1777. return [];
  1778. }
  1779. if (typeof iterable === 'string') {
  1780. iterable = iterable.split('');
  1781. }
  1782. if (supportsSliceOnNodeList) {
  1783. return slice.call(iterable, start || 0, end || iterable.length);
  1784. }
  1785. var array = [],
  1786. i;
  1787. start = start || 0;
  1788. end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
  1789. for (i = start; i < end; i++) {
  1790. array.push(iterable[i]);
  1791. }
  1792. return array;
  1793. },
  1794. /**
  1795. * Plucks the value of a property from each item in the Array. Example:
  1796. *
  1797. * Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
  1798. *
  1799. * @param {Array/NodeList} array The Array of items to pluck the value from.
  1800. * @param {String} propertyName The property name to pluck from each element.
  1801. * @return {Array} The value from each item in the Array.
  1802. */
  1803. pluck: function(array, propertyName) {
  1804. var ret = [],
  1805. i, ln, item;
  1806. for (i = 0, ln = array.length; i < ln; i++) {
  1807. item = array[i];
  1808. ret.push(item[propertyName]);
  1809. }
  1810. return ret;
  1811. },
  1812. /**
  1813. * Creates a new array with the results of calling a provided function on every element in this array.
  1814. *
  1815. * @param {Array} array
  1816. * @param {Function} fn Callback function for each item
  1817. * @param {Object} scope Callback function scope
  1818. * @return {Array} results
  1819. */
  1820. map: supportsMap ? function(array, fn, scope) {
  1821. if (!fn) {
  1822. Ext.Error.raise('Ext.Array.map must have a callback function passed as second argument.');
  1823. }
  1824. return array.map(fn, scope);
  1825. } : function(array, fn, scope) {
  1826. if (!fn) {
  1827. Ext.Error.raise('Ext.Array.map must have a callback function passed as second argument.');
  1828. }
  1829. var results = [],
  1830. i = 0,
  1831. len = array.length;
  1832. for (; i < len; i++) {
  1833. results[i] = fn.call(scope, array[i], i, array);
  1834. }
  1835. return results;
  1836. },
  1837. /**
  1838. * Executes the specified function for each array element until the function returns a falsy value.
  1839. * If such an item is found, the function will return false immediately.
  1840. * Otherwise, it will return true.
  1841. *
  1842. * @param {Array} array
  1843. * @param {Function} fn Callback function for each item
  1844. * @param {Object} scope Callback function scope
  1845. * @return {Boolean} True if no false value is returned by the callback function.
  1846. */
  1847. every: supportsEvery ? function(array, fn, scope) {
  1848. if (!fn) {
  1849. Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
  1850. }
  1851. return array.every(fn, scope);
  1852. } : function(array, fn, scope) {
  1853. if (!fn) {
  1854. Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
  1855. }
  1856. var i = 0,
  1857. ln = array.length;
  1858. for (; i < ln; ++i) {
  1859. if (!fn.call(scope, array[i], i, array)) {
  1860. return false;
  1861. }
  1862. }
  1863. return true;
  1864. },
  1865. /**
  1866. * Executes the specified function for each array element until the function returns a truthy value.
  1867. * If such an item is found, the function will return true immediately. Otherwise, it will return false.
  1868. *
  1869. * @param {Array} array
  1870. * @param {Function} fn Callback function for each item
  1871. * @param {Object} scope Callback function scope
  1872. * @return {Boolean} True if the callback function returns a truthy value.
  1873. */
  1874. some: supportsSome ? function(array, fn, scope) {
  1875. if (!fn) {
  1876. Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
  1877. }
  1878. return array.some(fn, scope);
  1879. } : function(array, fn, scope) {
  1880. if (!fn) {
  1881. Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
  1882. }
  1883. var i = 0,
  1884. ln = array.length;
  1885. for (; i < ln; ++i) {
  1886. if (fn.call(scope, array[i], i, array)) {
  1887. return true;
  1888. }
  1889. }
  1890. return false;
  1891. },
  1892. /**
  1893. * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
  1894. *
  1895. * See {@link Ext.Array#filter}
  1896. *
  1897. * @param {Array} array
  1898. * @return {Array} results
  1899. */
  1900. clean: function(array) {
  1901. var results = [],
  1902. i = 0,
  1903. ln = array.length,
  1904. item;
  1905. for (; i < ln; i++) {
  1906. item = array[i];
  1907. if (!Ext.isEmpty(item)) {
  1908. results.push(item);
  1909. }
  1910. }
  1911. return results;
  1912. },
  1913. /**
  1914. * Returns a new array with unique items
  1915. *
  1916. * @param {Array} array
  1917. * @return {Array} results
  1918. */
  1919. unique: function(array) {
  1920. var clone = [],
  1921. i = 0,
  1922. ln = array.length,
  1923. item;
  1924. for (; i < ln; i++) {
  1925. item = array[i];
  1926. if (ExtArray.indexOf(clone, item) === -1) {
  1927. clone.push(item);
  1928. }
  1929. }
  1930. return clone;
  1931. },
  1932. /**
  1933. * Creates a new array with all of the elements of this array for which
  1934. * the provided filtering function returns true.
  1935. *
  1936. * @param {Array} array
  1937. * @param {Function} fn Callback function for each item
  1938. * @param {Object} scope Callback function scope
  1939. * @return {Array} results
  1940. */
  1941. filter: supportsFilter ? function(array, fn, scope) {
  1942. if (!fn) {
  1943. Ext.Error.raise('Ext.Array.filter must have a callback function passed as second argument.');
  1944. }
  1945. return array.filter(fn, scope);
  1946. } : function(array, fn, scope) {
  1947. if (!fn) {
  1948. Ext.Error.raise('Ext.Array.filter must have a callback function passed as second argument.');
  1949. }
  1950. var results = [],
  1951. i = 0,
  1952. ln = array.length;
  1953. for (; i < ln; i++) {
  1954. if (fn.call(scope, array[i], i, array)) {
  1955. results.push(array[i]);
  1956. }
  1957. }
  1958. return results;
  1959. },
  1960. /**
  1961. * Converts a value to an array if it's not already an array; returns:
  1962. *
  1963. * - An empty array if given value is `undefined` or `null`
  1964. * - Itself if given value is already an array
  1965. * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
  1966. * - An array with one item which is the given value, otherwise
  1967. *
  1968. * @param {Object} value The value to convert to an array if it's not already is an array
  1969. * @param {Boolean} newReference (Optional) True to clone the given array and return a new reference if necessary,
  1970. * defaults to false
  1971. * @return {Array} array
  1972. */
  1973. from: function(value, newReference) {
  1974. if (value === undefined || value === null) {
  1975. return [];
  1976. }
  1977. if (Ext.isArray(value)) {
  1978. return (newReference) ? slice.call(value) : value;
  1979. }
  1980. var type = typeof value;
  1981. // Both strings and functions will have a length property. In phantomJS, NodeList
  1982. // instances report typeof=='function' but don't have an apply method...
  1983. if (value && value.length !== undefined && type !== 'string' && (type !== 'function' || !value.apply)) {
  1984. return ExtArray.toArray(value);
  1985. }
  1986. return [value];
  1987. },
  1988. /**
  1989. * Removes the specified item from the array if it exists
  1990. *
  1991. * @param {Array} array The array
  1992. * @param {Object} item The item to remove
  1993. * @return {Array} The passed array itself
  1994. */
  1995. remove: function(array, item) {
  1996. var index = ExtArray.indexOf(array, item);
  1997. if (index !== -1) {
  1998. erase(array, index, 1);
  1999. }
  2000. return array;
  2001. },
  2002. /**
  2003. * Push an item into the array only if the array doesn't contain it yet
  2004. *
  2005. * @param {Array} array The array
  2006. * @param {Object} item The item to include
  2007. */
  2008. include: function(array, item) {
  2009. if (!ExtArray.contains(array, item)) {
  2010. array.push(item);
  2011. }
  2012. },
  2013. /**
  2014. * Clone a flat array without referencing the previous one. Note that this is different
  2015. * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
  2016. * for Array.prototype.slice.call(array)
  2017. *
  2018. * @param {Array} array The array
  2019. * @return {Array} The clone array
  2020. */
  2021. clone: function(array) {
  2022. return slice.call(array);
  2023. },
  2024. /**
  2025. * Merge multiple arrays into one with unique items.
  2026. *
  2027. * {@link Ext.Array#union} is alias for {@link Ext.Array#merge}
  2028. *
  2029. * @param {Array} array1
  2030. * @param {Array} array2
  2031. * @param {Array} etc
  2032. * @return {Array} merged
  2033. */
  2034. merge: function() {
  2035. var args = slice.call(arguments),
  2036. array = [],
  2037. i, ln;
  2038. for (i = 0, ln = args.length; i < ln; i++) {
  2039. array = array.concat(args[i]);
  2040. }
  2041. return ExtArray.unique(array);
  2042. },
  2043. /**
  2044. * Merge multiple arrays into one with unique items that exist in all of the arrays.
  2045. *
  2046. * @param {Array} array1
  2047. * @param {Array} array2
  2048. * @param {Array} etc
  2049. * @return {Array} intersect
  2050. */
  2051. intersect: function() {
  2052. var intersection = [],
  2053. arrays = slice.call(arguments),
  2054. arraysLength,
  2055. array,
  2056. arrayLength,
  2057. minArray,
  2058. minArrayIndex,
  2059. minArrayCandidate,
  2060. minArrayLength,
  2061. element,
  2062. elementCandidate,
  2063. elementCount,
  2064. i, j, k;
  2065. if (!arrays.length) {
  2066. return intersection;
  2067. }
  2068. // Find the smallest array
  2069. arraysLength = arrays.length;
  2070. for (i = minArrayIndex = 0; i < arraysLength; i++) {
  2071. minArrayCandidate = arrays[i];
  2072. if (!minArray || minArrayCandidate.length < minArray.length) {
  2073. minArray = minArrayCandidate;
  2074. minArrayIndex = i;
  2075. }
  2076. }
  2077. minArray = ExtArray.unique(minArray);
  2078. erase(arrays, minArrayIndex, 1);
  2079. // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
  2080. // an item in the small array, we're likely to find it before reaching the end
  2081. // of the inner loop and can terminate the search early.
  2082. minArrayLength = minArray.length;
  2083. arraysLength = arrays.length;
  2084. for (i = 0; i < minArrayLength; i++) {
  2085. element = minArray[i];
  2086. elementCount = 0;
  2087. for (j = 0; j < arraysLength; j++) {
  2088. array = arrays[j];
  2089. arrayLength = array.length;
  2090. for (k = 0; k < arrayLength; k++) {
  2091. elementCandidate = array[k];
  2092. if (element === elementCandidate) {
  2093. elementCount++;
  2094. break;
  2095. }
  2096. }
  2097. }
  2098. if (elementCount === arraysLength) {
  2099. intersection.push(element);
  2100. }
  2101. }
  2102. return intersection;
  2103. },
  2104. /**
  2105. * Perform a set difference A-B by subtracting all items in array B from array A.
  2106. *
  2107. * @param {Array} arrayA
  2108. * @param {Array} arrayB
  2109. * @return {Array} difference
  2110. */
  2111. difference: function(arrayA, arrayB) {
  2112. var clone = slice.call(arrayA),
  2113. ln = clone.length,
  2114. i, j, lnB;
  2115. for (i = 0,lnB = arrayB.length; i < lnB; i++) {
  2116. for (j = 0; j < ln; j++) {
  2117. if (clone[j] === arrayB[i]) {
  2118. erase(clone, j, 1);
  2119. j--;
  2120. ln--;
  2121. }
  2122. }
  2123. }
  2124. return clone;
  2125. },
  2126. /**
  2127. * Returns a shallow copy of a part of an array. This is equivalent to the native
  2128. * call "Array.prototype.slice.call(array, begin, end)". This is often used when "array"
  2129. * is "arguments" since the arguments object does not supply a slice method but can
  2130. * be the context object to Array.prototype.slice.
  2131. *
  2132. * @param {Array} array The array (or arguments object).
  2133. * @param {Number} begin The index at which to begin. Negative values are offsets from
  2134. * the end of the array.
  2135. * @param {Number} end The index at which to end. The copied items do not include
  2136. * end. Negative values are offsets from the end of the array. If end is omitted,
  2137. * all items up to the end of the array are copied.
  2138. * @return {Array} The copied piece of the array.
  2139. * @method slice
  2140. */
  2141. // Note: IE6 will return [] on slice.call(x, undefined).
  2142. slice: ([1,2].slice(1, undefined).length ?
  2143. function (array, begin, end) {
  2144. return slice.call(array, begin, end);
  2145. } :
  2146. // at least IE6 uses arguments.length for variadic signature
  2147. function (array, begin, end) {
  2148. // After tested for IE 6, the one below is of the best performance
  2149. // see http://jsperf.com/slice-fix
  2150. if (typeof begin === 'undefined') {
  2151. return slice.call(array);
  2152. }
  2153. if (typeof end === 'undefined') {
  2154. return slice.call(array, begin);
  2155. }
  2156. return slice.call(array, begin, end);
  2157. }
  2158. ),
  2159. /**
  2160. * Sorts the elements of an Array.
  2161. * By default, this method sorts the elements alphabetically and ascending.
  2162. *
  2163. * @param {Array} array The array to sort.
  2164. * @param {Function} sortFn (optional) The comparison function.
  2165. * @return {Array} The sorted array.
  2166. */
  2167. sort: supportsSort ? function(array, sortFn) {
  2168. if (sortFn) {
  2169. return array.sort(sortFn);
  2170. } else {
  2171. return array.sort();
  2172. }
  2173. } : function(array, sortFn) {
  2174. var length = array.length,
  2175. i = 0,
  2176. comparison,
  2177. j, min, tmp;
  2178. for (; i < length; i++) {
  2179. min = i;
  2180. for (j = i + 1; j < length; j++) {
  2181. if (sortFn) {
  2182. comparison = sortFn(array[j], array[min]);
  2183. if (comparison < 0) {
  2184. min = j;
  2185. }
  2186. } else if (array[j] < array[min]) {
  2187. min = j;
  2188. }
  2189. }
  2190. if (min !== i) {
  2191. tmp = array[i];
  2192. array[i] = array[min];
  2193. array[min] = tmp;
  2194. }
  2195. }
  2196. return array;
  2197. },
  2198. /**
  2199. * Recursively flattens into 1-d Array. Injects Arrays inline.
  2200. *
  2201. * @param {Array} array The array to flatten
  2202. * @return {Array} The 1-d array.
  2203. */
  2204. flatten: function(array) {
  2205. var worker = [];
  2206. function rFlatten(a) {
  2207. var i, ln, v;
  2208. for (i = 0, ln = a.length; i < ln; i++) {
  2209. v = a[i];
  2210. if (Ext.isArray(v)) {
  2211. rFlatten(v);
  2212. } else {
  2213. worker.push(v);
  2214. }
  2215. }
  2216. return worker;
  2217. }
  2218. return rFlatten(array);
  2219. },
  2220. /**
  2221. * Returns the minimum value in the Array.
  2222. *
  2223. * @param {Array/NodeList} array The Array from which to select the minimum value.
  2224. * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
  2225. * If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
  2226. * @return {Object} minValue The minimum value
  2227. */
  2228. min: function(array, comparisonFn) {
  2229. var min = array[0],
  2230. i, ln, item;
  2231. for (i = 0, ln = array.length; i < ln; i++) {
  2232. item = array[i];
  2233. if (comparisonFn) {
  2234. if (comparisonFn(min, item) === 1) {
  2235. min = item;
  2236. }
  2237. }
  2238. else {
  2239. if (item < min) {
  2240. min = item;
  2241. }
  2242. }
  2243. }
  2244. return min;
  2245. },
  2246. /**
  2247. * Returns the maximum value in the Array.
  2248. *
  2249. * @param {Array/NodeList} array The Array from which to select the maximum value.
  2250. * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
  2251. * If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
  2252. * @return {Object} maxValue The maximum value
  2253. */
  2254. max: function(array, comparisonFn) {
  2255. var max = array[0],
  2256. i, ln, item;
  2257. for (i = 0, ln = array.length; i < ln; i++) {
  2258. item = array[i];
  2259. if (comparisonFn) {
  2260. if (comparisonFn(max, item) === -1) {
  2261. max = item;
  2262. }
  2263. }
  2264. else {
  2265. if (item > max) {
  2266. max = item;
  2267. }
  2268. }
  2269. }
  2270. return max;
  2271. },
  2272. /**
  2273. * Calculates the mean of all items in the array.
  2274. *
  2275. * @param {Array} array The Array to calculate the mean value of.
  2276. * @return {Number} The mean.
  2277. */
  2278. mean: function(array) {
  2279. return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
  2280. },
  2281. /**
  2282. * Calculates the sum of all items in the given array.
  2283. *
  2284. * @param {Array} array The Array to calculate the sum value of.
  2285. * @return {Number} The sum.
  2286. */
  2287. sum: function(array) {
  2288. var sum = 0,
  2289. i, ln, item;
  2290. for (i = 0,ln = array.length; i < ln; i++) {
  2291. item = array[i];
  2292. sum += item;
  2293. }
  2294. return sum;
  2295. },
  2296. /**
  2297. * Creates a map (object) keyed by the elements of the given array. The values in
  2298. * the map are the index+1 of the array element. For example:
  2299. *
  2300. * var map = Ext.Array.toMap(['a','b','c']);
  2301. *
  2302. * // map = { a: 1, b: 2, c: 3 };
  2303. *
  2304. * Or a key property can be specified:
  2305. *
  2306. * var map = Ext.Array.toMap([
  2307. * { name: 'a' },
  2308. * { name: 'b' },
  2309. * { name: 'c' }
  2310. * ], 'name');
  2311. *
  2312. * // map = { a: 1, b: 2, c: 3 };
  2313. *
  2314. * Lastly, a key extractor can be provided:
  2315. *
  2316. * var map = Ext.Array.toMap([
  2317. * { name: 'a' },
  2318. * { name: 'b' },
  2319. * { name: 'c' }
  2320. * ], function (obj) { return obj.name.toUpperCase(); });
  2321. *
  2322. * // map = { A: 1, B: 2, C: 3 };
  2323. */
  2324. toMap: function(array, getKey, scope) {
  2325. var map = {},
  2326. i = array.length;
  2327. if (!getKey) {
  2328. while (i--) {
  2329. map[array[i]] = i+1;
  2330. }
  2331. } else if (typeof getKey == 'string') {
  2332. while (i--) {
  2333. map[array[i][getKey]] = i+1;
  2334. }
  2335. } else {
  2336. while (i--) {
  2337. map[getKey.call(scope, array[i])] = i+1;
  2338. }
  2339. }
  2340. return map;
  2341. },
  2342. _replaceSim: replaceSim, // for unit testing
  2343. _spliceSim: spliceSim,
  2344. /**
  2345. * Removes items from an array. This is functionally equivalent to the splice method
  2346. * of Array, but works around bugs in IE8's splice method and does not copy the
  2347. * removed elements in order to return them (because very often they are ignored).
  2348. *
  2349. * @param {Array} array The Array on which to replace.
  2350. * @param {Number} index The index in the array at which to operate.
  2351. * @param {Number} removeCount The number of items to remove at index.
  2352. * @return {Array} The array passed.
  2353. * @method
  2354. */
  2355. erase: erase,
  2356. /**
  2357. * Inserts items in to an array.
  2358. *
  2359. * @param {Array} array The Array in which to insert.
  2360. * @param {Number} index The index in the array at which to operate.
  2361. * @param {Array} items The array of items to insert at index.
  2362. * @return {Array} The array passed.
  2363. */
  2364. insert: function (array, index, items) {
  2365. return replace(array, index, 0, items);
  2366. },
  2367. /**
  2368. * Replaces items in an array. This is functionally equivalent to the splice method
  2369. * of Array, but works around bugs in IE8's splice method and is often more convenient
  2370. * to call because it accepts an array of items to insert rather than use a variadic
  2371. * argument list.
  2372. *
  2373. * @param {Array} array The Array on which to replace.
  2374. * @param {Number} index The index in the array at which to operate.
  2375. * @param {Number} removeCount The number of items to remove at index (can be 0).
  2376. * @param {Array} insert (optional) An array of items to insert at index.
  2377. * @return {Array} The array passed.
  2378. * @method
  2379. */
  2380. replace: replace,
  2381. /**
  2382. * Replaces items in an array. This is equivalent to the splice method of Array, but
  2383. * works around bugs in IE8's splice method. The signature is exactly the same as the
  2384. * splice method except that the array is the first argument. All arguments following
  2385. * removeCount are inserted in the array at index.
  2386. *
  2387. * @param {Array} array The Array on which to replace.
  2388. * @param {Number} index The index in the array at which to operate.
  2389. * @param {Number} removeCount The number of items to remove at index (can be 0).
  2390. * @param {Object...} elements The elements to add to the array. If you don't specify
  2391. * any elements, splice simply removes elements from the array.
  2392. * @return {Array} An array containing the removed items.
  2393. * @method
  2394. */
  2395. splice: splice,
  2396. /**
  2397. * Pushes new items onto the end of an Array.
  2398. *
  2399. * Passed parameters may be single items, or arrays of items. If an Array is found in the argument list, all its
  2400. * elements are pushed into the end of the target Array.
  2401. *
  2402. * @param {Array} target The Array onto which to push new items
  2403. * @param {Object...} elements The elements to add to the array. Each parameter may
  2404. * be an Array, in which case all the elements of that Array will be pushed into the end of the
  2405. * destination Array.
  2406. * @return {Array} An array containing all the new items push onto the end.
  2407. *
  2408. */
  2409. push: function(array) {
  2410. var len = arguments.length,
  2411. i = 1,
  2412. newItem;
  2413. if (array === undefined) {
  2414. array = [];
  2415. } else if (!Ext.isArray(array)) {
  2416. array = [array];
  2417. }
  2418. for (; i < len; i++) {
  2419. newItem = arguments[i];
  2420. Array.prototype.push[Ext.isArray(newItem) ? 'apply' : 'call'](array, newItem);
  2421. }
  2422. return array;
  2423. }
  2424. };
  2425. /**
  2426. * @method
  2427. * @member Ext
  2428. * @inheritdoc Ext.Array#each
  2429. */
  2430. Ext.each = ExtArray.each;
  2431. /**
  2432. * @method
  2433. * @member Ext.Array
  2434. * @inheritdoc Ext.Array#merge
  2435. */
  2436. ExtArray.union = ExtArray.merge;
  2437. /**
  2438. * Old alias to {@link Ext.Array#min}
  2439. * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
  2440. * @method
  2441. * @member Ext
  2442. * @inheritdoc Ext.Array#min
  2443. */
  2444. Ext.min = ExtArray.min;
  2445. /**
  2446. * Old alias to {@link Ext.Array#max}
  2447. * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
  2448. * @method
  2449. * @member Ext
  2450. * @inheritdoc Ext.Array#max
  2451. */
  2452. Ext.max = ExtArray.max;
  2453. /**
  2454. * Old alias to {@link Ext.Array#sum}
  2455. * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
  2456. * @method
  2457. * @member Ext
  2458. * @inheritdoc Ext.Array#sum
  2459. */
  2460. Ext.sum = ExtArray.sum;
  2461. /**
  2462. * Old alias to {@link Ext.Array#mean}
  2463. * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
  2464. * @method
  2465. * @member Ext
  2466. * @inheritdoc Ext.Array#mean
  2467. */
  2468. Ext.mean = ExtArray.mean;
  2469. /**
  2470. * Old alias to {@link Ext.Array#flatten}
  2471. * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
  2472. * @method
  2473. * @member Ext
  2474. * @inheritdoc Ext.Array#flatten
  2475. */
  2476. Ext.flatten = ExtArray.flatten;
  2477. /**
  2478. * Old alias to {@link Ext.Array#clean}
  2479. * @deprecated 4.0.0 Use {@link Ext.Array#clean} instead
  2480. * @method
  2481. * @member Ext
  2482. * @inheritdoc Ext.Array#clean
  2483. */
  2484. Ext.clean = ExtArray.clean;
  2485. /**
  2486. * Old alias to {@link Ext.Array#unique}
  2487. * @deprecated 4.0.0 Use {@link Ext.Array#unique} instead
  2488. * @method
  2489. * @member Ext
  2490. * @inheritdoc Ext.Array#unique
  2491. */
  2492. Ext.unique = ExtArray.unique;
  2493. /**
  2494. * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
  2495. * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
  2496. * @method
  2497. * @member Ext
  2498. * @inheritdoc Ext.Array#pluck
  2499. */
  2500. Ext.pluck = ExtArray.pluck;
  2501. /**
  2502. * @method
  2503. * @member Ext
  2504. * @inheritdoc Ext.Array#toArray
  2505. */
  2506. Ext.toArray = function() {
  2507. return ExtArray.toArray.apply(ExtArray, arguments);
  2508. };
  2509. }());
  2510. //@tag foundation,core
  2511. //@require Array.js
  2512. /**
  2513. * @class Ext.Function
  2514. *
  2515. * A collection of useful static methods to deal with function callbacks
  2516. * @singleton
  2517. * @alternateClassName Ext.util.Functions
  2518. */
  2519. Ext.Function = {
  2520. /**
  2521. * A very commonly used method throughout the framework. It acts as a wrapper around another method
  2522. * which originally accepts 2 arguments for `name` and `value`.
  2523. * The wrapped function then allows "flexible" value setting of either:
  2524. *
  2525. * - `name` and `value` as 2 arguments
  2526. * - one single object argument with multiple key - value pairs
  2527. *
  2528. * For example:
  2529. *
  2530. * var setValue = Ext.Function.flexSetter(function(name, value) {
  2531. * this[name] = value;
  2532. * });
  2533. *
  2534. * // Afterwards
  2535. * // Setting a single name - value
  2536. * setValue('name1', 'value1');
  2537. *
  2538. * // Settings multiple name - value pairs
  2539. * setValue({
  2540. * name1: 'value1',
  2541. * name2: 'value2',
  2542. * name3: 'value3'
  2543. * });
  2544. *
  2545. * @param {Function} setter
  2546. * @returns {Function} flexSetter
  2547. */
  2548. flexSetter: function(fn) {
  2549. return function(a, b) {
  2550. var k, i;
  2551. if (a === null) {
  2552. return this;
  2553. }
  2554. if (typeof a !== 'string') {
  2555. for (k in a) {
  2556. if (a.hasOwnProperty(k)) {
  2557. fn.call(this, k, a[k]);
  2558. }
  2559. }
  2560. if (Ext.enumerables) {
  2561. for (i = Ext.enumerables.length; i--;) {
  2562. k = Ext.enumerables[i];
  2563. if (a.hasOwnProperty(k)) {
  2564. fn.call(this, k, a[k]);
  2565. }
  2566. }
  2567. }
  2568. } else {
  2569. fn.call(this, a, b);
  2570. }
  2571. return this;
  2572. };
  2573. },
  2574. /**
  2575. * Create a new function from the provided `fn`, change `this` to the provided scope, optionally
  2576. * overrides arguments for the call. (Defaults to the arguments passed by the caller)
  2577. *
  2578. * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind}
  2579. *
  2580. * @param {Function} fn The function to delegate.
  2581. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
  2582. * **If omitted, defaults to the default global environment object (usually the browser window).**
  2583. * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
  2584. * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
  2585. * if a number the args are inserted at the specified position
  2586. * @return {Function} The new function
  2587. */
  2588. bind: function(fn, scope, args, appendArgs) {
  2589. if (arguments.length === 2) {
  2590. return function() {
  2591. return fn.apply(scope, arguments);
  2592. };
  2593. }
  2594. var method = fn,
  2595. slice = Array.prototype.slice;
  2596. return function() {
  2597. var callArgs = args || arguments;
  2598. if (appendArgs === true) {
  2599. callArgs = slice.call(arguments, 0);
  2600. callArgs = callArgs.concat(args);
  2601. }
  2602. else if (typeof appendArgs == 'number') {
  2603. callArgs = slice.call(arguments, 0); // copy arguments first
  2604. Ext.Array.insert(callArgs, appendArgs, args);
  2605. }
  2606. return method.apply(scope || Ext.global, callArgs);
  2607. };
  2608. },
  2609. /**
  2610. * Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.
  2611. * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
  2612. * This is especially useful when creating callbacks.
  2613. *
  2614. * For example:
  2615. *
  2616. * var originalFunction = function(){
  2617. * alert(Ext.Array.from(arguments).join(' '));
  2618. * };
  2619. *
  2620. * var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
  2621. *
  2622. * callback(); // alerts 'Hello World'
  2623. * callback('by Me'); // alerts 'Hello World by Me'
  2624. *
  2625. * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}
  2626. *
  2627. * @param {Function} fn The original function
  2628. * @param {Array} args The arguments to pass to new callback
  2629. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
  2630. * @return {Function} The new callback function
  2631. */
  2632. pass: function(fn, args, scope) {
  2633. if (!Ext.isArray(args)) {
  2634. if (Ext.isIterable(args)) {
  2635. args = Ext.Array.clone(args);
  2636. } else {
  2637. args = args !== undefined ? [args] : [];
  2638. }
  2639. }
  2640. return function() {
  2641. var fnArgs = [].concat(args);
  2642. fnArgs.push.apply(fnArgs, arguments);
  2643. return fn.apply(scope || this, fnArgs);
  2644. };
  2645. },
  2646. /**
  2647. * Create an alias to the provided method property with name `methodName` of `object`.
  2648. * Note that the execution scope will still be bound to the provided `object` itself.
  2649. *
  2650. * @param {Object/Function} object
  2651. * @param {String} methodName
  2652. * @return {Function} aliasFn
  2653. */
  2654. alias: function(object, methodName) {
  2655. return function() {
  2656. return object[methodName].apply(object, arguments);
  2657. };
  2658. },
  2659. /**
  2660. * Create a "clone" of the provided method. The returned method will call the given
  2661. * method passing along all arguments and the "this" pointer and return its result.
  2662. *
  2663. * @param {Function} method
  2664. * @return {Function} cloneFn
  2665. */
  2666. clone: function(method) {
  2667. return function() {
  2668. return method.apply(this, arguments);
  2669. };
  2670. },
  2671. /**
  2672. * Creates an interceptor function. The passed function is called before the original one. If it returns false,
  2673. * the original one is not called. The resulting function returns the results of the original function.
  2674. * The passed function is called with the parameters of the original function. Example usage:
  2675. *
  2676. * var sayHi = function(name){
  2677. * alert('Hi, ' + name);
  2678. * }
  2679. *
  2680. * sayHi('Fred'); // alerts "Hi, Fred"
  2681. *
  2682. * // create a new function that validates input without
  2683. * // directly modifying the original function:
  2684. * var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
  2685. * return name == 'Brian';
  2686. * });
  2687. *
  2688. * sayHiToFriend('Fred'); // no alert
  2689. * sayHiToFriend('Brian'); // alerts "Hi, Brian"
  2690. *
  2691. * @param {Function} origFn The original function.
  2692. * @param {Function} newFn The function to call before the original
  2693. * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
  2694. * **If omitted, defaults to the scope in which the original function is called or the browser window.**
  2695. * @param {Object} returnValue (optional) The value to return if the passed function return false (defaults to null).
  2696. * @return {Function} The new function
  2697. */
  2698. createInterceptor: function(origFn, newFn, scope, returnValue) {
  2699. var method = origFn;
  2700. if (!Ext.isFunction(newFn)) {
  2701. return origFn;
  2702. }
  2703. else {
  2704. return function() {
  2705. var me = this,
  2706. args = arguments;
  2707. newFn.target = me;
  2708. newFn.method = origFn;
  2709. return (newFn.apply(scope || me || Ext.global, args) !== false) ? origFn.apply(me || Ext.global, args) : returnValue || null;
  2710. };
  2711. }
  2712. },
  2713. /**
  2714. * Creates a delegate (callback) which, when called, executes after a specific delay.
  2715. *
  2716. * @param {Function} fn The function which will be called on a delay when the returned function is called.
  2717. * Optionally, a replacement (or additional) argument list may be specified.
  2718. * @param {Number} delay The number of milliseconds to defer execution by whenever called.
  2719. * @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.
  2720. * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
  2721. * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
  2722. * if a number the args are inserted at the specified position.
  2723. * @return {Function} A function which, when called, executes the original function after the specified delay.
  2724. */
  2725. createDelayed: function(fn, delay, scope, args, appendArgs) {
  2726. if (scope || args) {
  2727. fn = Ext.Function.bind(fn, scope, args, appendArgs);
  2728. }
  2729. return function() {
  2730. var me = this,
  2731. args = Array.prototype.slice.call(arguments);
  2732. setTimeout(function() {
  2733. fn.apply(me, args);
  2734. }, delay);
  2735. };
  2736. },
  2737. /**
  2738. * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
  2739. *
  2740. * var sayHi = function(name){
  2741. * alert('Hi, ' + name);
  2742. * }
  2743. *
  2744. * // executes immediately:
  2745. * sayHi('Fred');
  2746. *
  2747. * // executes after 2 seconds:
  2748. * Ext.Function.defer(sayHi, 2000, this, ['Fred']);
  2749. *
  2750. * // this syntax is sometimes useful for deferring
  2751. * // execution of an anonymous function:
  2752. * Ext.Function.defer(function(){
  2753. * alert('Anonymous');
  2754. * }, 100);
  2755. *
  2756. * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}
  2757. *
  2758. * @param {Function} fn The function to defer.
  2759. * @param {Number} millis The number of milliseconds for the setTimeout call
  2760. * (if less than or equal to 0 the function is executed immediately)
  2761. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
  2762. * **If omitted, defaults to the browser window.**
  2763. * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
  2764. * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
  2765. * if a number the args are inserted at the specified position
  2766. * @return {Number} The timeout id that can be used with clearTimeout
  2767. */
  2768. defer: function(fn, millis, scope, args, appendArgs) {
  2769. fn = Ext.Function.bind(fn, scope, args, appendArgs);
  2770. if (millis > 0) {
  2771. return setTimeout(Ext.supports.TimeoutActualLateness ? function () {
  2772. fn();
  2773. } : fn, millis);
  2774. }
  2775. fn();
  2776. return 0;
  2777. },
  2778. /**
  2779. * Create a combined function call sequence of the original function + the passed function.
  2780. * The resulting function returns the results of the original function.
  2781. * The passed function is called with the parameters of the original function. Example usage:
  2782. *
  2783. * var sayHi = function(name){
  2784. * alert('Hi, ' + name);
  2785. * }
  2786. *
  2787. * sayHi('Fred'); // alerts "Hi, Fred"
  2788. *
  2789. * var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
  2790. * alert('Bye, ' + name);
  2791. * });
  2792. *
  2793. * sayGoodbye('Fred'); // both alerts show
  2794. *
  2795. * @param {Function} originalFn The original function.
  2796. * @param {Function} newFn The function to sequence
  2797. * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
  2798. * If omitted, defaults to the scope in which the original function is called or the default global environment object (usually the browser window).
  2799. * @return {Function} The new function
  2800. */
  2801. createSequence: function(originalFn, newFn, scope) {
  2802. if (!newFn) {
  2803. return originalFn;
  2804. }
  2805. else {
  2806. return function() {
  2807. var result = originalFn.apply(this, arguments);
  2808. newFn.apply(scope || this, arguments);
  2809. return result;
  2810. };
  2811. }
  2812. },
  2813. /**
  2814. * Creates a delegate function, optionally with a bound scope which, when called, buffers
  2815. * the execution of the passed function for the configured number of milliseconds.
  2816. * If called again within that period, the impending invocation will be canceled, and the
  2817. * timeout period will begin again.
  2818. *
  2819. * @param {Function} fn The function to invoke on a buffered timer.
  2820. * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
  2821. * function.
  2822. * @param {Object} scope (optional) The scope (`this` reference) in which
  2823. * the passed function is executed. If omitted, defaults to the scope specified by the caller.
  2824. * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
  2825. * passed by the caller.
  2826. * @return {Function} A function which invokes the passed function after buffering for the specified time.
  2827. */
  2828. createBuffered: function(fn, buffer, scope, args) {
  2829. var timerId;
  2830. return function() {
  2831. var callArgs = args || Array.prototype.slice.call(arguments, 0),
  2832. me = scope || this;
  2833. if (timerId) {
  2834. clearTimeout(timerId);
  2835. }
  2836. timerId = setTimeout(function(){
  2837. fn.apply(me, callArgs);
  2838. }, buffer);
  2839. };
  2840. },
  2841. /**
  2842. * Creates a throttled version of the passed function which, when called repeatedly and
  2843. * rapidly, invokes the passed function only after a certain interval has elapsed since the
  2844. * previous invocation.
  2845. *
  2846. * This is useful for wrapping functions which may be called repeatedly, such as
  2847. * a handler of a mouse move event when the processing is expensive.
  2848. *
  2849. * @param {Function} fn The function to execute at a regular time interval.
  2850. * @param {Number} interval The interval **in milliseconds** on which the passed function is executed.
  2851. * @param {Object} scope (optional) The scope (`this` reference) in which
  2852. * the passed function is executed. If omitted, defaults to the scope specified by the caller.
  2853. * @returns {Function} A function which invokes the passed function at the specified interval.
  2854. */
  2855. createThrottled: function(fn, interval, scope) {
  2856. var lastCallTime, elapsed, lastArgs, timer, execute = function() {
  2857. fn.apply(scope || this, lastArgs);
  2858. lastCallTime = new Date().getTime();
  2859. };
  2860. return function() {
  2861. elapsed = new Date().getTime() - lastCallTime;
  2862. lastArgs = arguments;
  2863. clearTimeout(timer);
  2864. if (!lastCallTime || (elapsed >= interval)) {
  2865. execute();
  2866. } else {
  2867. timer = setTimeout(execute, interval - elapsed);
  2868. }
  2869. };
  2870. },
  2871. /**
  2872. * Adds behavior to an existing method that is executed before the
  2873. * original behavior of the function. For example:
  2874. *
  2875. * var soup = {
  2876. * contents: [],
  2877. * add: function(ingredient) {
  2878. * this.contents.push(ingredient);
  2879. * }
  2880. * };
  2881. * Ext.Function.interceptBefore(soup, "add", function(ingredient){
  2882. * if (!this.contents.length && ingredient !== "water") {
  2883. * // Always add water to start with
  2884. * this.contents.push("water");
  2885. * }
  2886. * });
  2887. * soup.add("onions");
  2888. * soup.add("salt");
  2889. * soup.contents; // will contain: water, onions, salt
  2890. *
  2891. * @param {Object} object The target object
  2892. * @param {String} methodName Name of the method to override
  2893. * @param {Function} fn Function with the new behavior. It will
  2894. * be called with the same arguments as the original method. The
  2895. * return value of this function will be the return value of the
  2896. * new method.
  2897. * @param {Object} [scope] The scope to execute the interceptor function. Defaults to the object.
  2898. * @return {Function} The new function just created.
  2899. */
  2900. interceptBefore: function(object, methodName, fn, scope) {
  2901. var method = object[methodName] || Ext.emptyFn;
  2902. return (object[methodName] = function() {
  2903. var ret = fn.apply(scope || this, arguments);
  2904. method.apply(this, arguments);
  2905. return ret;
  2906. });
  2907. },
  2908. /**
  2909. * Adds behavior to an existing method that is executed after the
  2910. * original behavior of the function. For example:
  2911. *
  2912. * var soup = {
  2913. * contents: [],
  2914. * add: function(ingredient) {
  2915. * this.contents.push(ingredient);
  2916. * }
  2917. * };
  2918. * Ext.Function.interceptAfter(soup, "add", function(ingredient){
  2919. * // Always add a bit of extra salt
  2920. * this.contents.push("salt");
  2921. * });
  2922. * soup.add("water");
  2923. * soup.add("onions");
  2924. * soup.contents; // will contain: water, salt, onions, salt
  2925. *
  2926. * @param {Object} object The target object
  2927. * @param {String} methodName Name of the method to override
  2928. * @param {Function} fn Function with the new behavior. It will
  2929. * be called with the same arguments as the original method. The
  2930. * return value of this function will be the return value of the
  2931. * new method.
  2932. * @param {Object} [scope] The scope to execute the interceptor function. Defaults to the object.
  2933. * @return {Function} The new function just created.
  2934. */
  2935. interceptAfter: function(object, methodName, fn, scope) {
  2936. var method = object[methodName] || Ext.emptyFn;
  2937. return (object[methodName] = function() {
  2938. method.apply(this, arguments);
  2939. return fn.apply(scope || this, arguments);
  2940. });
  2941. }
  2942. };
  2943. /**
  2944. * @method
  2945. * @member Ext
  2946. * @inheritdoc Ext.Function#defer
  2947. */
  2948. Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
  2949. /**
  2950. * @method
  2951. * @member Ext
  2952. * @inheritdoc Ext.Function#pass
  2953. */
  2954. Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
  2955. /**
  2956. * @method
  2957. * @member Ext
  2958. * @inheritdoc Ext.Function#bind
  2959. */
  2960. Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
  2961. //@tag foundation,core
  2962. //@require Function.js
  2963. /**
  2964. * @author Jacky Nguyen <jacky@sencha.com>
  2965. * @docauthor Jacky Nguyen <jacky@sencha.com>
  2966. * @class Ext.Object
  2967. *
  2968. * A collection of useful static methods to deal with objects.
  2969. *
  2970. * @singleton
  2971. */
  2972. (function() {
  2973. // The "constructor" for chain:
  2974. var TemplateClass = function(){},
  2975. ExtObject = Ext.Object = {
  2976. /**
  2977. * Returns a new object with the given object as the prototype chain.
  2978. * @param {Object} object The prototype chain for the new object.
  2979. */
  2980. chain: function (object) {
  2981. TemplateClass.prototype = object;
  2982. var result = new TemplateClass();
  2983. TemplateClass.prototype = null;
  2984. return result;
  2985. },
  2986. /**
  2987. * Converts a `name` - `value` pair to an array of objects with support for nested structures. Useful to construct
  2988. * query strings. For example:
  2989. *
  2990. * var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
  2991. *
  2992. * // objects then equals:
  2993. * [
  2994. * { name: 'hobbies', value: 'reading' },
  2995. * { name: 'hobbies', value: 'cooking' },
  2996. * { name: 'hobbies', value: 'swimming' },
  2997. * ];
  2998. *
  2999. * var objects = Ext.Object.toQueryObjects('dateOfBirth', {
  3000. * day: 3,
  3001. * month: 8,
  3002. * year: 1987,
  3003. * extra: {
  3004. * hour: 4
  3005. * minute: 30
  3006. * }
  3007. * }, true); // Recursive
  3008. *
  3009. * // objects then equals:
  3010. * [
  3011. * { name: 'dateOfBirth[day]', value: 3 },
  3012. * { name: 'dateOfBirth[month]', value: 8 },
  3013. * { name: 'dateOfBirth[year]', value: 1987 },
  3014. * { name: 'dateOfBirth[extra][hour]', value: 4 },
  3015. * { name: 'dateOfBirth[extra][minute]', value: 30 },
  3016. * ];
  3017. *
  3018. * @param {String} name
  3019. * @param {Object/Array} value
  3020. * @param {Boolean} [recursive=false] True to traverse object recursively
  3021. * @return {Array}
  3022. */
  3023. toQueryObjects: function(name, value, recursive) {
  3024. var self = ExtObject.toQueryObjects,
  3025. objects = [],
  3026. i, ln;
  3027. if (Ext.isArray(value)) {
  3028. for (i = 0, ln = value.length; i < ln; i++) {
  3029. if (recursive) {
  3030. objects = objects.concat(self(name + '[' + i + ']', value[i], true));
  3031. }
  3032. else {
  3033. objects.push({
  3034. name: name,
  3035. value: value[i]
  3036. });
  3037. }
  3038. }
  3039. }
  3040. else if (Ext.isObject(value)) {
  3041. for (i in value) {
  3042. if (value.hasOwnProperty(i)) {
  3043. if (recursive) {
  3044. objects = objects.concat(self(name + '[' + i + ']', value[i], true));
  3045. }
  3046. else {
  3047. objects.push({
  3048. name: name,
  3049. value: value[i]
  3050. });
  3051. }
  3052. }
  3053. }
  3054. }
  3055. else {
  3056. objects.push({
  3057. name: name,
  3058. value: value
  3059. });
  3060. }
  3061. return objects;
  3062. },
  3063. /**
  3064. * Takes an object and converts it to an encoded query string.
  3065. *
  3066. * Non-recursive:
  3067. *
  3068. * Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
  3069. * Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
  3070. * Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
  3071. * Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
  3072. * Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
  3073. *
  3074. * Recursive:
  3075. *
  3076. * Ext.Object.toQueryString({
  3077. * username: 'Jacky',
  3078. * dateOfBirth: {
  3079. * day: 1,
  3080. * month: 2,
  3081. * year: 1911
  3082. * },
  3083. * hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
  3084. * }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
  3085. * // username=Jacky
  3086. * // &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
  3087. * // &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
  3088. *
  3089. * @param {Object} object The object to encode
  3090. * @param {Boolean} [recursive=false] Whether or not to interpret the object in recursive format.
  3091. * (PHP / Ruby on Rails servers and similar).
  3092. * @return {String} queryString
  3093. */
  3094. toQueryString: function(object, recursive) {
  3095. var paramObjects = [],
  3096. params = [],
  3097. i, j, ln, paramObject, value;
  3098. for (i in object) {
  3099. if (object.hasOwnProperty(i)) {
  3100. paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
  3101. }
  3102. }
  3103. for (j = 0, ln = paramObjects.length; j < ln; j++) {
  3104. paramObject = paramObjects[j];
  3105. value = paramObject.value;
  3106. if (Ext.isEmpty(value)) {
  3107. value = '';
  3108. }
  3109. else if (Ext.isDate(value)) {
  3110. value = Ext.Date.toString(value);
  3111. }
  3112. params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
  3113. }
  3114. return params.join('&');
  3115. },
  3116. /**
  3117. * Converts a query string back into an object.
  3118. *
  3119. * Non-recursive:
  3120. *
  3121. * Ext.Object.fromQueryString("foo=1&bar=2"); // returns {foo: 1, bar: 2}
  3122. * Ext.Object.fromQueryString("foo=&bar=2"); // returns {foo: null, bar: 2}
  3123. * Ext.Object.fromQueryString("some%20price=%24300"); // returns {'some price': '$300'}
  3124. * Ext.Object.fromQueryString("colors=red&colors=green&colors=blue"); // returns {colors: ['red', 'green', 'blue']}
  3125. *
  3126. * Recursive:
  3127. *
  3128. * Ext.Object.fromQueryString(
  3129. * "username=Jacky&"+
  3130. * "dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911&"+
  3131. * "hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&"+
  3132. * "hobbies[3][0]=nested&hobbies[3][1]=stuff", true);
  3133. *
  3134. * // returns
  3135. * {
  3136. * username: 'Jacky',
  3137. * dateOfBirth: {
  3138. * day: '1',
  3139. * month: '2',
  3140. * year: '1911'
  3141. * },
  3142. * hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
  3143. * }
  3144. *
  3145. * @param {String} queryString The query string to decode
  3146. * @param {Boolean} [recursive=false] Whether or not to recursively decode the string. This format is supported by
  3147. * PHP / Ruby on Rails servers and similar.
  3148. * @return {Object}
  3149. */
  3150. fromQueryString: function(queryString, recursive) {
  3151. var parts = queryString.replace(/^\?/, '').split('&'),
  3152. object = {},
  3153. temp, components, name, value, i, ln,
  3154. part, j, subLn, matchedKeys, matchedName,
  3155. keys, key, nextKey;
  3156. for (i = 0, ln = parts.length; i < ln; i++) {
  3157. part = parts[i];
  3158. if (part.length > 0) {
  3159. components = part.split('=');
  3160. name = decodeURIComponent(components[0]);
  3161. value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
  3162. if (!recursive) {
  3163. if (object.hasOwnProperty(name)) {
  3164. if (!Ext.isArray(object[name])) {
  3165. object[name] = [object[name]];
  3166. }
  3167. object[name].push(value);
  3168. }
  3169. else {
  3170. object[name] = value;
  3171. }
  3172. }
  3173. else {
  3174. matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
  3175. matchedName = name.match(/^([^\[]+)/);
  3176. if (!matchedName) {
  3177. throw new Error('[Ext.Object.fromQueryString] Malformed query string given, failed parsing name from "' + part + '"');
  3178. }
  3179. name = matchedName[0];
  3180. keys = [];
  3181. if (matchedKeys === null) {
  3182. object[name] = value;
  3183. continue;
  3184. }
  3185. for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
  3186. key = matchedKeys[j];
  3187. key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
  3188. keys.push(key);
  3189. }
  3190. keys.unshift(name);
  3191. temp = object;
  3192. for (j = 0, subLn = keys.length; j < subLn; j++) {
  3193. key = keys[j];
  3194. if (j === subLn - 1) {
  3195. if (Ext.isArray(temp) && key === '') {
  3196. temp.push(value);
  3197. }
  3198. else {
  3199. temp[key] = value;
  3200. }
  3201. }
  3202. else {
  3203. if (temp[key] === undefined || typeof temp[key] === 'string') {
  3204. nextKey = keys[j+1];
  3205. temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
  3206. }
  3207. temp = temp[key];
  3208. }
  3209. }
  3210. }
  3211. }
  3212. }
  3213. return object;
  3214. },
  3215. /**
  3216. * Iterates through an object and invokes the given callback function for each iteration.
  3217. * The iteration can be stopped by returning `false` in the callback function. For example:
  3218. *
  3219. * var person = {
  3220. * name: 'Jacky'
  3221. * hairColor: 'black'
  3222. * loves: ['food', 'sleeping', 'wife']
  3223. * };
  3224. *
  3225. * Ext.Object.each(person, function(key, value, myself) {
  3226. * console.log(key + ":" + value);
  3227. *
  3228. * if (key === 'hairColor') {
  3229. * return false; // stop the iteration
  3230. * }
  3231. * });
  3232. *
  3233. * @param {Object} object The object to iterate
  3234. * @param {Function} fn The callback function.
  3235. * @param {String} fn.key
  3236. * @param {Object} fn.value
  3237. * @param {Object} fn.object The object itself
  3238. * @param {Object} [scope] The execution scope (`this`) of the callback function
  3239. */
  3240. each: function(object, fn, scope) {
  3241. for (var property in object) {
  3242. if (object.hasOwnProperty(property)) {
  3243. if (fn.call(scope || object, property, object[property], object) === false) {
  3244. return;
  3245. }
  3246. }
  3247. }
  3248. },
  3249. /**
  3250. * Merges any number of objects recursively without referencing them or their children.
  3251. *
  3252. * var extjs = {
  3253. * companyName: 'Ext JS',
  3254. * products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
  3255. * isSuperCool: true,
  3256. * office: {
  3257. * size: 2000,
  3258. * location: 'Palo Alto',
  3259. * isFun: true
  3260. * }
  3261. * };
  3262. *
  3263. * var newStuff = {
  3264. * companyName: 'Sencha Inc.',
  3265. * products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
  3266. * office: {
  3267. * size: 40000,
  3268. * location: 'Redwood City'
  3269. * }
  3270. * };
  3271. *
  3272. * var sencha = Ext.Object.merge(extjs, newStuff);
  3273. *
  3274. * // extjs and sencha then equals to
  3275. * {
  3276. * companyName: 'Sencha Inc.',
  3277. * products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
  3278. * isSuperCool: true,
  3279. * office: {
  3280. * size: 40000,
  3281. * location: 'Redwood City',
  3282. * isFun: true
  3283. * }
  3284. * }
  3285. *
  3286. * @param {Object} destination The object into which all subsequent objects are merged.
  3287. * @param {Object...} object Any number of objects to merge into the destination.
  3288. * @return {Object} merged The destination object with all passed objects merged in.
  3289. */
  3290. merge: function(destination) {
  3291. var i = 1,
  3292. ln = arguments.length,
  3293. mergeFn = ExtObject.merge,
  3294. cloneFn = Ext.clone,
  3295. object, key, value, sourceKey;
  3296. for (; i < ln; i++) {
  3297. object = arguments[i];
  3298. for (key in object) {
  3299. value = object[key];
  3300. if (value && value.constructor === Object) {
  3301. sourceKey = destination[key];
  3302. if (sourceKey && sourceKey.constructor === Object) {
  3303. mergeFn(sourceKey, value);
  3304. }
  3305. else {
  3306. destination[key] = cloneFn(value);
  3307. }
  3308. }
  3309. else {
  3310. destination[key] = value;
  3311. }
  3312. }
  3313. }
  3314. return destination;
  3315. },
  3316. /**
  3317. * @private
  3318. * @param destination
  3319. */
  3320. mergeIf: function(destination) {
  3321. var i = 1,
  3322. ln = arguments.length,
  3323. cloneFn = Ext.clone,
  3324. object, key, value;
  3325. for (; i < ln; i++) {
  3326. object = arguments[i];
  3327. for (key in object) {
  3328. if (!(key in destination)) {
  3329. value = object[key];
  3330. if (value && value.constructor === Object) {
  3331. destination[key] = cloneFn(value);
  3332. }
  3333. else {
  3334. destination[key] = value;
  3335. }
  3336. }
  3337. }
  3338. }
  3339. return destination;
  3340. },
  3341. /**
  3342. * Returns the first matching key corresponding to the given value.
  3343. * If no matching value is found, null is returned.
  3344. *
  3345. * var person = {
  3346. * name: 'Jacky',
  3347. * loves: 'food'
  3348. * };
  3349. *
  3350. * alert(Ext.Object.getKey(person, 'food')); // alerts 'loves'
  3351. *
  3352. * @param {Object} object
  3353. * @param {Object} value The value to find
  3354. */
  3355. getKey: function(object, value) {
  3356. for (var property in object) {
  3357. if (object.hasOwnProperty(property) && object[property] === value) {
  3358. return property;
  3359. }
  3360. }
  3361. return null;
  3362. },
  3363. /**
  3364. * Gets all values of the given object as an array.
  3365. *
  3366. * var values = Ext.Object.getValues({
  3367. * name: 'Jacky',
  3368. * loves: 'food'
  3369. * }); // ['Jacky', 'food']
  3370. *
  3371. * @param {Object} object
  3372. * @return {Array} An array of values from the object
  3373. */
  3374. getValues: function(object) {
  3375. var values = [],
  3376. property;
  3377. for (property in object) {
  3378. if (object.hasOwnProperty(property)) {
  3379. values.push(object[property]);
  3380. }
  3381. }
  3382. return values;
  3383. },
  3384. /**
  3385. * Gets all keys of the given object as an array.
  3386. *
  3387. * var values = Ext.Object.getKeys({
  3388. * name: 'Jacky',
  3389. * loves: 'food'
  3390. * }); // ['name', 'loves']
  3391. *
  3392. * @param {Object} object
  3393. * @return {String[]} An array of keys from the object
  3394. * @method
  3395. */
  3396. getKeys: (typeof Object.keys == 'function')
  3397. ? function(object){
  3398. if (!object) {
  3399. return [];
  3400. }
  3401. return Object.keys(object);
  3402. }
  3403. : function(object) {
  3404. var keys = [],
  3405. property;
  3406. for (property in object) {
  3407. if (object.hasOwnProperty(property)) {
  3408. keys.push(property);
  3409. }
  3410. }
  3411. return keys;
  3412. },
  3413. /**
  3414. * Gets the total number of this object's own properties
  3415. *
  3416. * var size = Ext.Object.getSize({
  3417. * name: 'Jacky',
  3418. * loves: 'food'
  3419. * }); // size equals 2
  3420. *
  3421. * @param {Object} object
  3422. * @return {Number} size
  3423. */
  3424. getSize: function(object) {
  3425. var size = 0,
  3426. property;
  3427. for (property in object) {
  3428. if (object.hasOwnProperty(property)) {
  3429. size++;
  3430. }
  3431. }
  3432. return size;
  3433. },
  3434. /**
  3435. * @private
  3436. */
  3437. classify: function(object) {
  3438. var prototype = object,
  3439. objectProperties = [],
  3440. propertyClassesMap = {},
  3441. objectClass = function() {
  3442. var i = 0,
  3443. ln = objectProperties.length,
  3444. property;
  3445. for (; i < ln; i++) {
  3446. property = objectProperties[i];
  3447. this[property] = new propertyClassesMap[property]();
  3448. }
  3449. },
  3450. key, value;
  3451. for (key in object) {
  3452. if (object.hasOwnProperty(key)) {
  3453. value = object[key];
  3454. if (value && value.constructor === Object) {
  3455. objectProperties.push(key);
  3456. propertyClassesMap[key] = ExtObject.classify(value);
  3457. }
  3458. }
  3459. }
  3460. objectClass.prototype = prototype;
  3461. return objectClass;
  3462. }
  3463. };
  3464. /**
  3465. * A convenient alias method for {@link Ext.Object#merge}.
  3466. *
  3467. * @member Ext
  3468. * @method merge
  3469. * @inheritdoc Ext.Object#merge
  3470. */
  3471. Ext.merge = Ext.Object.merge;
  3472. /**
  3473. * @private
  3474. * @member Ext
  3475. */
  3476. Ext.mergeIf = Ext.Object.mergeIf;
  3477. /**
  3478. *
  3479. * @member Ext
  3480. * @method urlEncode
  3481. * @inheritdoc Ext.Object#toQueryString
  3482. * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString} instead
  3483. */
  3484. Ext.urlEncode = function() {
  3485. var args = Ext.Array.from(arguments),
  3486. prefix = '';
  3487. // Support for the old `pre` argument
  3488. if ((typeof args[1] === 'string')) {
  3489. prefix = args[1] + '&';
  3490. args[1] = false;
  3491. }
  3492. return prefix + ExtObject.toQueryString.apply(ExtObject, args);
  3493. };
  3494. /**
  3495. * Alias for {@link Ext.Object#fromQueryString}.
  3496. *
  3497. * @member Ext
  3498. * @method urlDecode
  3499. * @inheritdoc Ext.Object#fromQueryString
  3500. * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString} instead
  3501. */
  3502. Ext.urlDecode = function() {
  3503. return ExtObject.fromQueryString.apply(ExtObject, arguments);
  3504. };
  3505. }());
  3506. //@tag foundation,core
  3507. //@require Object.js
  3508. //@define Ext.Date
  3509. /**
  3510. * @class Ext.Date
  3511. * A set of useful static methods to deal with date
  3512. * Note that if Ext.Date is required and loaded, it will copy all methods / properties to
  3513. * this object for convenience
  3514. *
  3515. * The date parsing and formatting syntax contains a subset of
  3516. * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
  3517. * supported will provide results equivalent to their PHP versions.
  3518. *
  3519. * The following is a list of all currently supported formats:
  3520. * <pre class="">
  3521. Format Description Example returned values
  3522. ------ ----------------------------------------------------------------------- -----------------------
  3523. d Day of the month, 2 digits with leading zeros 01 to 31
  3524. D A short textual representation of the day of the week Mon to Sun
  3525. j Day of the month without leading zeros 1 to 31
  3526. l A full textual representation of the day of the week Sunday to Saturday
  3527. N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday)
  3528. S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j
  3529. w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday)
  3530. z The day of the year (starting from 0) 0 to 364 (365 in leap years)
  3531. W ISO-8601 week number of year, weeks starting on Monday 01 to 53
  3532. F A full textual representation of a month, such as January or March January to December
  3533. m Numeric representation of a month, with leading zeros 01 to 12
  3534. M A short textual representation of a month Jan to Dec
  3535. n Numeric representation of a month, without leading zeros 1 to 12
  3536. t Number of days in the given month 28 to 31
  3537. L Whether it&#39;s a leap year 1 if it is a leap year, 0 otherwise.
  3538. o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004
  3539. belongs to the previous or next year, that year is used instead)
  3540. Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
  3541. y A two digit representation of a year Examples: 99 or 03
  3542. a Lowercase Ante meridiem and Post meridiem am or pm
  3543. A Uppercase Ante meridiem and Post meridiem AM or PM
  3544. g 12-hour format of an hour without leading zeros 1 to 12
  3545. G 24-hour format of an hour without leading zeros 0 to 23
  3546. h 12-hour format of an hour with leading zeros 01 to 12
  3547. H 24-hour format of an hour with leading zeros 00 to 23
  3548. i Minutes, with leading zeros 00 to 59
  3549. s Seconds, with leading zeros 00 to 59
  3550. u Decimal fraction of a second Examples:
  3551. (minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or
  3552. 100 (i.e. 0.100s) or
  3553. 999 (i.e. 0.999s) or
  3554. 999876543210 (i.e. 0.999876543210s)
  3555. O Difference to Greenwich time (GMT) in hours and minutes Example: +1030
  3556. P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00
  3557. T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ...
  3558. Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400
  3559. c ISO 8601 date
  3560. Notes: Examples:
  3561. 1) If unspecified, the month / day defaults to the current month / day, 1991 or
  3562. the time defaults to midnight, while the timezone defaults to the 1992-10 or
  3563. browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
  3564. and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or
  3565. are optional. 1995-07-18T17:21:28-02:00 or
  3566. 2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or
  3567. least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or
  3568. of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or
  3569. Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or
  3570. date-time granularity which are supported, or see 2000-02-13T21:25:33
  3571. http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34
  3572. U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463
  3573. MS Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
  3574. \/Date(1238606590509+0800)\/
  3575. </pre>
  3576. *
  3577. * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
  3578. * <pre><code>
  3579. // Sample date:
  3580. // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
  3581. var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
  3582. console.log(Ext.Date.format(dt, 'Y-m-d')); // 2007-01-10
  3583. console.log(Ext.Date.format(dt, 'F j, Y, g:i a')); // January 10, 2007, 3:05 pm
  3584. console.log(Ext.Date.format(dt, 'l, \\t\\he jS \\of F Y h:i:s A')); // Wednesday, the 10th of January 2007 03:05:01 PM
  3585. </code></pre>
  3586. *
  3587. * Here are some standard date/time patterns that you might find helpful. They
  3588. * are not part of the source of Ext.Date, but to use them you can simply copy this
  3589. * block of code into any script that is included after Ext.Date and they will also become
  3590. * globally available on the Date object. Feel free to add or remove patterns as needed in your code.
  3591. * <pre><code>
  3592. Ext.Date.patterns = {
  3593. ISO8601Long:"Y-m-d H:i:s",
  3594. ISO8601Short:"Y-m-d",
  3595. ShortDate: "n/j/Y",
  3596. LongDate: "l, F d, Y",
  3597. FullDateTime: "l, F d, Y g:i:s A",
  3598. MonthDay: "F d",
  3599. ShortTime: "g:i A",
  3600. LongTime: "g:i:s A",
  3601. SortableDateTime: "Y-m-d\\TH:i:s",
  3602. UniversalSortableDateTime: "Y-m-d H:i:sO",
  3603. YearMonth: "F, Y"
  3604. };
  3605. </code></pre>
  3606. *
  3607. * Example usage:
  3608. * <pre><code>
  3609. var dt = new Date();
  3610. console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
  3611. </code></pre>
  3612. * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
  3613. * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
  3614. * @singleton
  3615. */
  3616. /*
  3617. * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
  3618. * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
  3619. * They generate precompiled functions from format patterns instead of parsing and
  3620. * processing each pattern every time a date is formatted. These functions are available
  3621. * on every Date object.
  3622. */
  3623. (function() {
  3624. // create private copy of Ext's Ext.util.Format.format() method
  3625. // - to remove unnecessary dependency
  3626. // - to resolve namespace conflict with MS-Ajax's implementation
  3627. function xf(format) {
  3628. var args = Array.prototype.slice.call(arguments, 1);
  3629. return format.replace(/\{(\d+)\}/g, function(m, i) {
  3630. return args[i];
  3631. });
  3632. }
  3633. Ext.Date = {
  3634. /**
  3635. * Returns the current timestamp.
  3636. * @return {Number} Milliseconds since UNIX epoch.
  3637. * @method
  3638. */
  3639. now: Date.now || function() {
  3640. return +new Date();
  3641. },
  3642. /**
  3643. * @private
  3644. * Private for now
  3645. */
  3646. toString: function(date) {
  3647. var pad = Ext.String.leftPad;
  3648. return date.getFullYear() + "-"
  3649. + pad(date.getMonth() + 1, 2, '0') + "-"
  3650. + pad(date.getDate(), 2, '0') + "T"
  3651. + pad(date.getHours(), 2, '0') + ":"
  3652. + pad(date.getMinutes(), 2, '0') + ":"
  3653. + pad(date.getSeconds(), 2, '0');
  3654. },
  3655. /**
  3656. * Returns the number of milliseconds between two dates
  3657. * @param {Date} dateA The first date
  3658. * @param {Date} dateB (optional) The second date, defaults to now
  3659. * @return {Number} The difference in milliseconds
  3660. */
  3661. getElapsed: function(dateA, dateB) {
  3662. return Math.abs(dateA - (dateB || new Date()));
  3663. },
  3664. /**
  3665. * Global flag which determines if strict date parsing should be used.
  3666. * Strict date parsing will not roll-over invalid dates, which is the
  3667. * default behaviour of javascript Date objects.
  3668. * (see {@link #parse} for more information)
  3669. * Defaults to <tt>false</tt>.
  3670. * @type Boolean
  3671. */
  3672. useStrict: false,
  3673. // private
  3674. formatCodeToRegex: function(character, currentGroup) {
  3675. // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
  3676. var p = utilDate.parseCodes[character];
  3677. if (p) {
  3678. p = typeof p == 'function'? p() : p;
  3679. utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
  3680. }
  3681. return p ? Ext.applyIf({
  3682. c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
  3683. }, p) : {
  3684. g: 0,
  3685. c: null,
  3686. s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals
  3687. };
  3688. },
  3689. /**
  3690. * <p>An object hash in which each property is a date parsing function. The property name is the
  3691. * format string which that function parses.</p>
  3692. * <p>This object is automatically populated with date parsing functions as
  3693. * date formats are requested for Ext standard formatting strings.</p>
  3694. * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
  3695. * may be used as a format string to {@link #parse}.<p>
  3696. * <p>Example:</p><pre><code>
  3697. Ext.Date.parseFunctions['x-date-format'] = myDateParser;
  3698. </code></pre>
  3699. * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
  3700. * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
  3701. * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
  3702. * (i.e. prevent javascript Date "rollover") (The default must be false).
  3703. * Invalid date strings should return null when parsed.</div></li>
  3704. * </ul></div></p>
  3705. * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
  3706. * formatting function must be placed into the {@link #formatFunctions} property.
  3707. * @property parseFunctions
  3708. * @type Object
  3709. */
  3710. parseFunctions: {
  3711. "MS": function(input, strict) {
  3712. // note: the timezone offset is ignored since the MS Ajax server sends
  3713. // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
  3714. var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/'),
  3715. r = (input || '').match(re);
  3716. return r? new Date(((r[1] || '') + r[2]) * 1) : null;
  3717. }
  3718. },
  3719. parseRegexes: [],
  3720. /**
  3721. * <p>An object hash in which each property is a date formatting function. The property name is the
  3722. * format string which corresponds to the produced formatted date string.</p>
  3723. * <p>This object is automatically populated with date formatting functions as
  3724. * date formats are requested for Ext standard formatting strings.</p>
  3725. * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
  3726. * may be used as a format string to {@link #format}. Example:</p><pre><code>
  3727. Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
  3728. </code></pre>
  3729. * <p>A formatting function should return a string representation of the passed Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
  3730. * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
  3731. * </ul></div></p>
  3732. * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
  3733. * parsing function must be placed into the {@link #parseFunctions} property.
  3734. * @property formatFunctions
  3735. * @type Object
  3736. */
  3737. formatFunctions: {
  3738. "MS": function() {
  3739. // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
  3740. return '\\/Date(' + this.getTime() + ')\\/';
  3741. }
  3742. },
  3743. y2kYear : 50,
  3744. /**
  3745. * Date interval constant
  3746. * @type String
  3747. */
  3748. MILLI : "ms",
  3749. /**
  3750. * Date interval constant
  3751. * @type String
  3752. */
  3753. SECOND : "s",
  3754. /**
  3755. * Date interval constant
  3756. * @type String
  3757. */
  3758. MINUTE : "mi",
  3759. /** Date interval constant
  3760. * @type String
  3761. */
  3762. HOUR : "h",
  3763. /**
  3764. * Date interval constant
  3765. * @type String
  3766. */
  3767. DAY : "d",
  3768. /**
  3769. * Date interval constant
  3770. * @type String
  3771. */
  3772. MONTH : "mo",
  3773. /**
  3774. * Date interval constant
  3775. * @type String
  3776. */
  3777. YEAR : "y",
  3778. /**
  3779. * <p>An object hash containing default date values used during date parsing.</p>
  3780. * <p>The following properties are available:<div class="mdetail-params"><ul>
  3781. * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
  3782. * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
  3783. * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
  3784. * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
  3785. * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
  3786. * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
  3787. * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
  3788. * </ul></div></p>
  3789. * <p>Override these properties to customize the default date values used by the {@link #parse} method.</p>
  3790. * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
  3791. * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
  3792. * It is the responsiblity of the developer to account for this.</b></p>
  3793. * Example Usage:
  3794. * <pre><code>
  3795. // set default day value to the first day of the month
  3796. Ext.Date.defaults.d = 1;
  3797. // parse a February date string containing only year and month values.
  3798. // setting the default day value to 1 prevents weird date rollover issues
  3799. // when attempting to parse the following date string on, for example, March 31st 2009.
  3800. Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
  3801. </code></pre>
  3802. * @property defaults
  3803. * @type Object
  3804. */
  3805. defaults: {},
  3806. //<locale type="array">
  3807. /**
  3808. * @property {String[]} dayNames
  3809. * An array of textual day names.
  3810. * Override these values for international dates.
  3811. * Example:
  3812. * <pre><code>
  3813. Ext.Date.dayNames = [
  3814. 'SundayInYourLang',
  3815. 'MondayInYourLang',
  3816. ...
  3817. ];
  3818. </code></pre>
  3819. */
  3820. dayNames : [
  3821. "Sunday",
  3822. "Monday",
  3823. "Tuesday",
  3824. "Wednesday",
  3825. "Thursday",
  3826. "Friday",
  3827. "Saturday"
  3828. ],
  3829. //</locale>
  3830. //<locale type="array">
  3831. /**
  3832. * @property {String[]} monthNames
  3833. * An array of textual month names.
  3834. * Override these values for international dates.
  3835. * Example:
  3836. * <pre><code>
  3837. Ext.Date.monthNames = [
  3838. 'JanInYourLang',
  3839. 'FebInYourLang',
  3840. ...
  3841. ];
  3842. </code></pre>
  3843. */
  3844. monthNames : [
  3845. "January",
  3846. "February",
  3847. "March",
  3848. "April",
  3849. "May",
  3850. "June",
  3851. "July",
  3852. "August",
  3853. "September",
  3854. "October",
  3855. "November",
  3856. "December"
  3857. ],
  3858. //</locale>
  3859. //<locale type="object">
  3860. /**
  3861. * @property {Object} monthNumbers
  3862. * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
  3863. * Override these values for international dates.
  3864. * Example:
  3865. * <pre><code>
  3866. Ext.Date.monthNumbers = {
  3867. 'LongJanNameInYourLang': 0,
  3868. 'ShortJanNameInYourLang':0,
  3869. 'LongFebNameInYourLang':1,
  3870. 'ShortFebNameInYourLang':1,
  3871. ...
  3872. };
  3873. </code></pre>
  3874. */
  3875. monthNumbers : {
  3876. January: 0,
  3877. Jan: 0,
  3878. February: 1,
  3879. Feb: 1,
  3880. March: 2,
  3881. Mar: 2,
  3882. April: 3,
  3883. Apr: 3,
  3884. May: 4,
  3885. June: 5,
  3886. Jun: 5,
  3887. July: 6,
  3888. Jul: 6,
  3889. August: 7,
  3890. Aug: 7,
  3891. September: 8,
  3892. Sep: 8,
  3893. October: 9,
  3894. Oct: 9,
  3895. November: 10,
  3896. Nov: 10,
  3897. December: 11,
  3898. Dec: 11
  3899. },
  3900. //</locale>
  3901. //<locale>
  3902. /**
  3903. * @property {String} defaultFormat
  3904. * <p>The date format string that the {@link Ext.util.Format#dateRenderer}
  3905. * and {@link Ext.util.Format#date} functions use. See {@link Ext.Date} for details.</p>
  3906. * <p>This may be overridden in a locale file.</p>
  3907. */
  3908. defaultFormat : "m/d/Y",
  3909. //</locale>
  3910. //<locale type="function">
  3911. /**
  3912. * Get the short month name for the given month number.
  3913. * Override this function for international dates.
  3914. * @param {Number} month A zero-based javascript month number.
  3915. * @return {String} The short month name.
  3916. */
  3917. getShortMonthName : function(month) {
  3918. return Ext.Date.monthNames[month].substring(0, 3);
  3919. },
  3920. //</locale>
  3921. //<locale type="function">
  3922. /**
  3923. * Get the short day name for the given day number.
  3924. * Override this function for international dates.
  3925. * @param {Number} day A zero-based javascript day number.
  3926. * @return {String} The short day name.
  3927. */
  3928. getShortDayName : function(day) {
  3929. return Ext.Date.dayNames[day].substring(0, 3);
  3930. },
  3931. //</locale>
  3932. //<locale type="function">
  3933. /**
  3934. * Get the zero-based javascript month number for the given short/full month name.
  3935. * Override this function for international dates.
  3936. * @param {String} name The short/full month name.
  3937. * @return {Number} The zero-based javascript month number.
  3938. */
  3939. getMonthNumber : function(name) {
  3940. // handle camel casing for english month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
  3941. return Ext.Date.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
  3942. },
  3943. //</locale>
  3944. /**
  3945. * Checks if the specified format contains hour information
  3946. * @param {String} format The format to check
  3947. * @return {Boolean} True if the format contains hour information
  3948. * @method
  3949. */
  3950. formatContainsHourInfo : (function(){
  3951. var stripEscapeRe = /(\\.)/g,
  3952. hourInfoRe = /([gGhHisucUOPZ]|MS)/;
  3953. return function(format){
  3954. return hourInfoRe.test(format.replace(stripEscapeRe, ''));
  3955. };
  3956. }()),
  3957. /**
  3958. * Checks if the specified format contains information about
  3959. * anything other than the time.
  3960. * @param {String} format The format to check
  3961. * @return {Boolean} True if the format contains information about
  3962. * date/day information.
  3963. * @method
  3964. */
  3965. formatContainsDateInfo : (function(){
  3966. var stripEscapeRe = /(\\.)/g,
  3967. dateInfoRe = /([djzmnYycU]|MS)/;
  3968. return function(format){
  3969. return dateInfoRe.test(format.replace(stripEscapeRe, ''));
  3970. };
  3971. }()),
  3972. /**
  3973. * Removes all escaping for a date format string. In date formats,
  3974. * using a '\' can be used to escape special characters.
  3975. * @param {String} format The format to unescape
  3976. * @return {String} The unescaped format
  3977. * @method
  3978. */
  3979. unescapeFormat: (function() {
  3980. var slashRe = /\\/gi;
  3981. return function(format) {
  3982. // Escape the format, since \ can be used to escape special
  3983. // characters in a date format. For example, in a spanish
  3984. // locale the format may be: 'd \\de F \\de Y'
  3985. return format.replace(slashRe, '');
  3986. }
  3987. }()),
  3988. /**
  3989. * The base format-code to formatting-function hashmap used by the {@link #format} method.
  3990. * Formatting functions are strings (or functions which return strings) which
  3991. * will return the appropriate value when evaluated in the context of the Date object
  3992. * from which the {@link #format} method is called.
  3993. * Add to / override these mappings for custom date formatting.
  3994. * Note: Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
  3995. * Example:
  3996. * <pre><code>
  3997. Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
  3998. console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
  3999. </code></pre>
  4000. * @type Object
  4001. */
  4002. formatCodes : {
  4003. d: "Ext.String.leftPad(this.getDate(), 2, '0')",
  4004. D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
  4005. j: "this.getDate()",
  4006. l: "Ext.Date.dayNames[this.getDay()]",
  4007. N: "(this.getDay() ? this.getDay() : 7)",
  4008. S: "Ext.Date.getSuffix(this)",
  4009. w: "this.getDay()",
  4010. z: "Ext.Date.getDayOfYear(this)",
  4011. W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
  4012. F: "Ext.Date.monthNames[this.getMonth()]",
  4013. m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
  4014. M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
  4015. n: "(this.getMonth() + 1)",
  4016. t: "Ext.Date.getDaysInMonth(this)",
  4017. L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
  4018. o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
  4019. Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
  4020. y: "('' + this.getFullYear()).substring(2, 4)",
  4021. a: "(this.getHours() < 12 ? 'am' : 'pm')",
  4022. A: "(this.getHours() < 12 ? 'AM' : 'PM')",
  4023. g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
  4024. G: "this.getHours()",
  4025. h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
  4026. H: "Ext.String.leftPad(this.getHours(), 2, '0')",
  4027. i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
  4028. s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
  4029. u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
  4030. O: "Ext.Date.getGMTOffset(this)",
  4031. P: "Ext.Date.getGMTOffset(this, true)",
  4032. T: "Ext.Date.getTimezone(this)",
  4033. Z: "(this.getTimezoneOffset() * -60)",
  4034. c: function() { // ISO-8601 -- GMT format
  4035. var c, code, i, l, e;
  4036. for (c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
  4037. e = c.charAt(i);
  4038. code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
  4039. }
  4040. return code.join(" + ");
  4041. },
  4042. /*
  4043. c: function() { // ISO-8601 -- UTC format
  4044. return [
  4045. "this.getUTCFullYear()", "'-'",
  4046. "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
  4047. "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
  4048. "'T'",
  4049. "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
  4050. "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
  4051. "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
  4052. "'Z'"
  4053. ].join(" + ");
  4054. },
  4055. */
  4056. U: "Math.round(this.getTime() / 1000)"
  4057. },
  4058. /**
  4059. * Checks if the passed Date parameters will cause a javascript Date "rollover".
  4060. * @param {Number} year 4-digit year
  4061. * @param {Number} month 1-based month-of-year
  4062. * @param {Number} day Day of month
  4063. * @param {Number} hour (optional) Hour
  4064. * @param {Number} minute (optional) Minute
  4065. * @param {Number} second (optional) Second
  4066. * @param {Number} millisecond (optional) Millisecond
  4067. * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
  4068. */
  4069. isValid : function(y, m, d, h, i, s, ms) {
  4070. // setup defaults
  4071. h = h || 0;
  4072. i = i || 0;
  4073. s = s || 0;
  4074. ms = ms || 0;
  4075. // Special handling for year < 100
  4076. var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
  4077. return y == dt.getFullYear() &&
  4078. m == dt.getMonth() + 1 &&
  4079. d == dt.getDate() &&
  4080. h == dt.getHours() &&
  4081. i == dt.getMinutes() &&
  4082. s == dt.getSeconds() &&
  4083. ms == dt.getMilliseconds();
  4084. },
  4085. /**
  4086. * Parses the passed string using the specified date format.
  4087. * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
  4088. * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
  4089. * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
  4090. * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
  4091. * Keep in mind that the input date string must precisely match the specified format string
  4092. * in order for the parse operation to be successful (failed parse operations return a null value).
  4093. * <p>Example:</p><pre><code>
  4094. //dt = Fri May 25 2007 (current date)
  4095. var dt = new Date();
  4096. //dt = Thu May 25 2006 (today&#39;s month/day in 2006)
  4097. dt = Ext.Date.parse("2006", "Y");
  4098. //dt = Sun Jan 15 2006 (all date parts specified)
  4099. dt = Ext.Date.parse("2006-01-15", "Y-m-d");
  4100. //dt = Sun Jan 15 2006 15:20:01
  4101. dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
  4102. // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
  4103. dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
  4104. </code></pre>
  4105. * @param {String} input The raw date string.
  4106. * @param {String} format The expected date string format.
  4107. * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
  4108. (defaults to false). Invalid date strings will return null when parsed.
  4109. * @return {Date} The parsed Date.
  4110. */
  4111. parse : function(input, format, strict) {
  4112. var p = utilDate.parseFunctions;
  4113. if (p[format] == null) {
  4114. utilDate.createParser(format);
  4115. }
  4116. return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
  4117. },
  4118. // Backwards compat
  4119. parseDate: function(input, format, strict){
  4120. return utilDate.parse(input, format, strict);
  4121. },
  4122. // private
  4123. getFormatCode : function(character) {
  4124. var f = utilDate.formatCodes[character];
  4125. if (f) {
  4126. f = typeof f == 'function'? f() : f;
  4127. utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
  4128. }
  4129. // note: unknown characters are treated as literals
  4130. return f || ("'" + Ext.String.escape(character) + "'");
  4131. },
  4132. // private
  4133. createFormat : function(format) {
  4134. var code = [],
  4135. special = false,
  4136. ch = '',
  4137. i;
  4138. for (i = 0; i < format.length; ++i) {
  4139. ch = format.charAt(i);
  4140. if (!special && ch == "\\") {
  4141. special = true;
  4142. } else if (special) {
  4143. special = false;
  4144. code.push("'" + Ext.String.escape(ch) + "'");
  4145. } else {
  4146. code.push(utilDate.getFormatCode(ch));
  4147. }
  4148. }
  4149. utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
  4150. },
  4151. // private
  4152. createParser : (function() {
  4153. var code = [
  4154. "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
  4155. "def = Ext.Date.defaults,",
  4156. "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
  4157. "if(results){",
  4158. "{1}",
  4159. "if(u != null){", // i.e. unix time is defined
  4160. "v = new Date(u * 1000);", // give top priority to UNIX time
  4161. "}else{",
  4162. // create Date object representing midnight of the current day;
  4163. // this will provide us with our date defaults
  4164. // (note: clearTime() handles Daylight Saving Time automatically)
  4165. "dt = Ext.Date.clearTime(new Date);",
  4166. // date calculations (note: these calculations create a dependency on Ext.Number.from())
  4167. "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
  4168. "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
  4169. "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
  4170. // time calculations (note: these calculations create a dependency on Ext.Number.from())
  4171. "h = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
  4172. "i = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
  4173. "s = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
  4174. "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
  4175. "if(z >= 0 && y >= 0){",
  4176. // both the year and zero-based day of year are defined and >= 0.
  4177. // these 2 values alone provide sufficient info to create a full date object
  4178. // create Date object representing January 1st for the given year
  4179. // handle years < 100 appropriately
  4180. "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
  4181. // then add day of year, checking for Date "rollover" if necessary
  4182. "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
  4183. "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
  4184. "v = null;", // invalid date, so return null
  4185. "}else{",
  4186. // plain old Date object
  4187. // handle years < 100 properly
  4188. "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
  4189. "}",
  4190. "}",
  4191. "}",
  4192. "if(v){",
  4193. // favour UTC offset over GMT offset
  4194. "if(zz != null){",
  4195. // reset to UTC, then add offset
  4196. "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
  4197. "}else if(o){",
  4198. // reset to GMT, then add offset
  4199. "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
  4200. "}",
  4201. "}",
  4202. "return v;"
  4203. ].join('\n');
  4204. return function(format) {
  4205. var regexNum = utilDate.parseRegexes.length,
  4206. currentGroup = 1,
  4207. calc = [],
  4208. regex = [],
  4209. special = false,
  4210. ch = "",
  4211. i = 0,
  4212. len = format.length,
  4213. atEnd = [],
  4214. obj;
  4215. for (; i < len; ++i) {
  4216. ch = format.charAt(i);
  4217. if (!special && ch == "\\") {
  4218. special = true;
  4219. } else if (special) {
  4220. special = false;
  4221. regex.push(Ext.String.escape(ch));
  4222. } else {
  4223. obj = utilDate.formatCodeToRegex(ch, currentGroup);
  4224. currentGroup += obj.g;
  4225. regex.push(obj.s);
  4226. if (obj.g && obj.c) {
  4227. if (obj.calcAtEnd) {
  4228. atEnd.push(obj.c);
  4229. } else {
  4230. calc.push(obj.c);
  4231. }
  4232. }
  4233. }
  4234. }
  4235. calc = calc.concat(atEnd);
  4236. utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
  4237. utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
  4238. };
  4239. }()),
  4240. // private
  4241. parseCodes : {
  4242. /*
  4243. * Notes:
  4244. * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
  4245. * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
  4246. * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
  4247. */
  4248. d: {
  4249. g:1,
  4250. c:"d = parseInt(results[{0}], 10);\n",
  4251. s:"(3[0-1]|[1-2][0-9]|0[1-9])" // day of month with leading zeroes (01 - 31)
  4252. },
  4253. j: {
  4254. g:1,
  4255. c:"d = parseInt(results[{0}], 10);\n",
  4256. s:"(3[0-1]|[1-2][0-9]|[1-9])" // day of month without leading zeroes (1 - 31)
  4257. },
  4258. D: function() {
  4259. for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
  4260. return {
  4261. g:0,
  4262. c:null,
  4263. s:"(?:" + a.join("|") +")"
  4264. };
  4265. },
  4266. l: function() {
  4267. return {
  4268. g:0,
  4269. c:null,
  4270. s:"(?:" + utilDate.dayNames.join("|") + ")"
  4271. };
  4272. },
  4273. N: {
  4274. g:0,
  4275. c:null,
  4276. s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
  4277. },
  4278. //<locale type="object" property="parseCodes">
  4279. S: {
  4280. g:0,
  4281. c:null,
  4282. s:"(?:st|nd|rd|th)"
  4283. },
  4284. //</locale>
  4285. w: {
  4286. g:0,
  4287. c:null,
  4288. s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
  4289. },
  4290. z: {
  4291. g:1,
  4292. c:"z = parseInt(results[{0}], 10);\n",
  4293. s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
  4294. },
  4295. W: {
  4296. g:0,
  4297. c:null,
  4298. s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
  4299. },
  4300. F: function() {
  4301. return {
  4302. g:1,
  4303. c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
  4304. s:"(" + utilDate.monthNames.join("|") + ")"
  4305. };
  4306. },
  4307. M: function() {
  4308. for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
  4309. return Ext.applyIf({
  4310. s:"(" + a.join("|") + ")"
  4311. }, utilDate.formatCodeToRegex("F"));
  4312. },
  4313. m: {
  4314. g:1,
  4315. c:"m = parseInt(results[{0}], 10) - 1;\n",
  4316. s:"(1[0-2]|0[1-9])" // month number with leading zeros (01 - 12)
  4317. },
  4318. n: {
  4319. g:1,
  4320. c:"m = parseInt(results[{0}], 10) - 1;\n",
  4321. s:"(1[0-2]|[1-9])" // month number without leading zeros (1 - 12)
  4322. },
  4323. t: {
  4324. g:0,
  4325. c:null,
  4326. s:"(?:\\d{2})" // no. of days in the month (28 - 31)
  4327. },
  4328. L: {
  4329. g:0,
  4330. c:null,
  4331. s:"(?:1|0)"
  4332. },
  4333. o: function() {
  4334. return utilDate.formatCodeToRegex("Y");
  4335. },
  4336. Y: {
  4337. g:1,
  4338. c:"y = parseInt(results[{0}], 10);\n",
  4339. s:"(\\d{4})" // 4-digit year
  4340. },
  4341. y: {
  4342. g:1,
  4343. c:"var ty = parseInt(results[{0}], 10);\n"
  4344. + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
  4345. s:"(\\d{1,2})"
  4346. },
  4347. /*
  4348. * In the am/pm parsing routines, we allow both upper and lower case
  4349. * even though it doesn't exactly match the spec. It gives much more flexibility
  4350. * in being able to specify case insensitive regexes.
  4351. */
  4352. //<locale type="object" property="parseCodes">
  4353. a: {
  4354. g:1,
  4355. c:"if (/(am)/i.test(results[{0}])) {\n"
  4356. + "if (!h || h == 12) { h = 0; }\n"
  4357. + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
  4358. s:"(am|pm|AM|PM)",
  4359. calcAtEnd: true
  4360. },
  4361. //</locale>
  4362. //<locale type="object" property="parseCodes">
  4363. A: {
  4364. g:1,
  4365. c:"if (/(am)/i.test(results[{0}])) {\n"
  4366. + "if (!h || h == 12) { h = 0; }\n"
  4367. + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
  4368. s:"(AM|PM|am|pm)",
  4369. calcAtEnd: true
  4370. },
  4371. //</locale>
  4372. g: {
  4373. g:1,
  4374. c:"h = parseInt(results[{0}], 10);\n",
  4375. s:"(1[0-2]|[0-9])" // 12-hr format of an hour without leading zeroes (1 - 12)
  4376. },
  4377. G: {
  4378. g:1,
  4379. c:"h = parseInt(results[{0}], 10);\n",
  4380. s:"(2[0-3]|1[0-9]|[0-9])" // 24-hr format of an hour without leading zeroes (0 - 23)
  4381. },
  4382. h: {
  4383. g:1,
  4384. c:"h = parseInt(results[{0}], 10);\n",
  4385. s:"(1[0-2]|0[1-9])" // 12-hr format of an hour with leading zeroes (01 - 12)
  4386. },
  4387. H: {
  4388. g:1,
  4389. c:"h = parseInt(results[{0}], 10);\n",
  4390. s:"(2[0-3]|[0-1][0-9])" // 24-hr format of an hour with leading zeroes (00 - 23)
  4391. },
  4392. i: {
  4393. g:1,
  4394. c:"i = parseInt(results[{0}], 10);\n",
  4395. s:"([0-5][0-9])" // minutes with leading zeros (00 - 59)
  4396. },
  4397. s: {
  4398. g:1,
  4399. c:"s = parseInt(results[{0}], 10);\n",
  4400. s:"([0-5][0-9])" // seconds with leading zeros (00 - 59)
  4401. },
  4402. u: {
  4403. g:1,
  4404. c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
  4405. s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
  4406. },
  4407. O: {
  4408. g:1,
  4409. c:[
  4410. "o = results[{0}];",
  4411. "var sn = o.substring(0,1),", // get + / - sign
  4412. "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
  4413. "mn = o.substring(3,5) % 60;", // get minutes
  4414. "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
  4415. ].join("\n"),
  4416. s: "([+-]\\d{4})" // GMT offset in hrs and mins
  4417. },
  4418. P: {
  4419. g:1,
  4420. c:[
  4421. "o = results[{0}];",
  4422. "var sn = o.substring(0,1),", // get + / - sign
  4423. "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
  4424. "mn = o.substring(4,6) % 60;", // get minutes
  4425. "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
  4426. ].join("\n"),
  4427. s: "([+-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
  4428. },
  4429. T: {
  4430. g:0,
  4431. c:null,
  4432. s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
  4433. },
  4434. Z: {
  4435. g:1,
  4436. c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
  4437. + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
  4438. s:"([+-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
  4439. },
  4440. c: function() {
  4441. var calc = [],
  4442. arr = [
  4443. utilDate.formatCodeToRegex("Y", 1), // year
  4444. utilDate.formatCodeToRegex("m", 2), // month
  4445. utilDate.formatCodeToRegex("d", 3), // day
  4446. utilDate.formatCodeToRegex("H", 4), // hour
  4447. utilDate.formatCodeToRegex("i", 5), // minute
  4448. utilDate.formatCodeToRegex("s", 6), // second
  4449. {c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"}, // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
  4450. {c:[ // allow either "Z" (i.e. UTC) or "-0530" or "+08:00" (i.e. UTC offset) timezone delimiters. assumes local timezone if no timezone is specified
  4451. "if(results[8]) {", // timezone specified
  4452. "if(results[8] == 'Z'){",
  4453. "zz = 0;", // UTC
  4454. "}else if (results[8].indexOf(':') > -1){",
  4455. utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
  4456. "}else{",
  4457. utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
  4458. "}",
  4459. "}"
  4460. ].join('\n')}
  4461. ],
  4462. i,
  4463. l;
  4464. for (i = 0, l = arr.length; i < l; ++i) {
  4465. calc.push(arr[i].c);
  4466. }
  4467. return {
  4468. g:1,
  4469. c:calc.join(""),
  4470. s:[
  4471. arr[0].s, // year (required)
  4472. "(?:", "-", arr[1].s, // month (optional)
  4473. "(?:", "-", arr[2].s, // day (optional)
  4474. "(?:",
  4475. "(?:T| )?", // time delimiter -- either a "T" or a single blank space
  4476. arr[3].s, ":", arr[4].s, // hour AND minute, delimited by a single colon (optional). MUST be preceded by either a "T" or a single blank space
  4477. "(?::", arr[5].s, ")?", // seconds (optional)
  4478. "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
  4479. "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
  4480. ")?",
  4481. ")?",
  4482. ")?"
  4483. ].join("")
  4484. };
  4485. },
  4486. U: {
  4487. g:1,
  4488. c:"u = parseInt(results[{0}], 10);\n",
  4489. s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
  4490. }
  4491. },
  4492. //Old Ext.Date prototype methods.
  4493. // private
  4494. dateFormat: function(date, format) {
  4495. return utilDate.format(date, format);
  4496. },
  4497. /**
  4498. * Compares if two dates are equal by comparing their values.
  4499. * @param {Date} date1
  4500. * @param {Date} date2
  4501. * @return {Boolean} True if the date values are equal
  4502. */
  4503. isEqual: function(date1, date2) {
  4504. // check we have 2 date objects
  4505. if (date1 && date2) {
  4506. return (date1.getTime() === date2.getTime());
  4507. }
  4508. // one or both isn't a date, only equal if both are falsey
  4509. return !(date1 || date2);
  4510. },
  4511. /**
  4512. * Formats a date given the supplied format string.
  4513. * @param {Date} date The date to format
  4514. * @param {String} format The format string
  4515. * @return {String} The formatted date or an empty string if date parameter is not a JavaScript Date object
  4516. */
  4517. format: function(date, format) {
  4518. var formatFunctions = utilDate.formatFunctions;
  4519. if (!Ext.isDate(date)) {
  4520. return '';
  4521. }
  4522. if (formatFunctions[format] == null) {
  4523. utilDate.createFormat(format);
  4524. }
  4525. return formatFunctions[format].call(date) + '';
  4526. },
  4527. /**
  4528. * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
  4529. *
  4530. * Note: The date string returned by the javascript Date object's toString() method varies
  4531. * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
  4532. * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
  4533. * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
  4534. * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
  4535. * from the GMT offset portion of the date string.
  4536. * @param {Date} date The date
  4537. * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
  4538. */
  4539. getTimezone : function(date) {
  4540. // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
  4541. //
  4542. // Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
  4543. // Safari : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone (same as FF)
  4544. // FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
  4545. // IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
  4546. // IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
  4547. //
  4548. // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
  4549. // step 1: (?:\((.*)\) -- find timezone in parentheses
  4550. // step 2: ([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?) -- if nothing was found in step 1, find timezone from timezone offset portion of date string
  4551. // step 3: remove all non uppercase characters found in step 1 and 2
  4552. return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
  4553. },
  4554. /**
  4555. * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
  4556. * @param {Date} date The date
  4557. * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
  4558. * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
  4559. */
  4560. getGMTOffset : function(date, colon) {
  4561. var offset = date.getTimezoneOffset();
  4562. return (offset > 0 ? "-" : "+")
  4563. + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
  4564. + (colon ? ":" : "")
  4565. + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
  4566. },
  4567. /**
  4568. * Get the numeric day number of the year, adjusted for leap year.
  4569. * @param {Date} date The date
  4570. * @return {Number} 0 to 364 (365 in leap years).
  4571. */
  4572. getDayOfYear: function(date) {
  4573. var num = 0,
  4574. d = Ext.Date.clone(date),
  4575. m = date.getMonth(),
  4576. i;
  4577. for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
  4578. num += utilDate.getDaysInMonth(d);
  4579. }
  4580. return num + date.getDate() - 1;
  4581. },
  4582. /**
  4583. * Get the numeric ISO-8601 week number of the year.
  4584. * (equivalent to the format specifier 'W', but without a leading zero).
  4585. * @param {Date} date The date
  4586. * @return {Number} 1 to 53
  4587. * @method
  4588. */
  4589. getWeekOfYear : (function() {
  4590. // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
  4591. var ms1d = 864e5, // milliseconds in a day
  4592. ms7d = 7 * ms1d; // milliseconds in a week
  4593. return function(date) { // return a closure so constants get calculated only once
  4594. var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
  4595. AWN = Math.floor(DC3 / 7), // an Absolute Week Number
  4596. Wyr = new Date(AWN * ms7d).getUTCFullYear();
  4597. return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
  4598. };
  4599. }()),
  4600. /**
  4601. * Checks if the current date falls within a leap year.
  4602. * @param {Date} date The date
  4603. * @return {Boolean} True if the current date falls within a leap year, false otherwise.
  4604. */
  4605. isLeapYear : function(date) {
  4606. var year = date.getFullYear();
  4607. return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
  4608. },
  4609. /**
  4610. * Get the first day of the current month, adjusted for leap year. The returned value
  4611. * is the numeric day index within the week (0-6) which can be used in conjunction with
  4612. * the {@link #monthNames} array to retrieve the textual day name.
  4613. * Example:
  4614. * <pre><code>
  4615. var dt = new Date('1/10/2007'),
  4616. firstDay = Ext.Date.getFirstDayOfMonth(dt);
  4617. console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
  4618. * </code></pre>
  4619. * @param {Date} date The date
  4620. * @return {Number} The day number (0-6).
  4621. */
  4622. getFirstDayOfMonth : function(date) {
  4623. var day = (date.getDay() - (date.getDate() - 1)) % 7;
  4624. return (day < 0) ? (day + 7) : day;
  4625. },
  4626. /**
  4627. * Get the last day of the current month, adjusted for leap year. The returned value
  4628. * is the numeric day index within the week (0-6) which can be used in conjunction with
  4629. * the {@link #monthNames} array to retrieve the textual day name.
  4630. * Example:
  4631. * <pre><code>
  4632. var dt = new Date('1/10/2007'),
  4633. lastDay = Ext.Date.getLastDayOfMonth(dt);
  4634. console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
  4635. * </code></pre>
  4636. * @param {Date} date The date
  4637. * @return {Number} The day number (0-6).
  4638. */
  4639. getLastDayOfMonth : function(date) {
  4640. return utilDate.getLastDateOfMonth(date).getDay();
  4641. },
  4642. /**
  4643. * Get the date of the first day of the month in which this date resides.
  4644. * @param {Date} date The date
  4645. * @return {Date}
  4646. */
  4647. getFirstDateOfMonth : function(date) {
  4648. return new Date(date.getFullYear(), date.getMonth(), 1);
  4649. },
  4650. /**
  4651. * Get the date of the last day of the month in which this date resides.
  4652. * @param {Date} date The date
  4653. * @return {Date}
  4654. */
  4655. getLastDateOfMonth : function(date) {
  4656. return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
  4657. },
  4658. /**
  4659. * Get the number of days in the current month, adjusted for leap year.
  4660. * @param {Date} date The date
  4661. * @return {Number} The number of days in the month.
  4662. * @method
  4663. */
  4664. getDaysInMonth: (function() {
  4665. var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  4666. return function(date) { // return a closure for efficiency
  4667. var m = date.getMonth();
  4668. return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
  4669. };
  4670. }()),
  4671. //<locale type="function">
  4672. /**
  4673. * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
  4674. * @param {Date} date The date
  4675. * @return {String} 'st, 'nd', 'rd' or 'th'.
  4676. */
  4677. getSuffix : function(date) {
  4678. switch (date.getDate()) {
  4679. case 1:
  4680. case 21:
  4681. case 31:
  4682. return "st";
  4683. case 2:
  4684. case 22:
  4685. return "nd";
  4686. case 3:
  4687. case 23:
  4688. return "rd";
  4689. default:
  4690. return "th";
  4691. }
  4692. },
  4693. //</locale>
  4694. /**
  4695. * Creates and returns a new Date instance with the exact same date value as the called instance.
  4696. * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
  4697. * variable will also be changed. When the intention is to create a new variable that will not
  4698. * modify the original instance, you should create a clone.
  4699. *
  4700. * Example of correctly cloning a date:
  4701. * <pre><code>
  4702. //wrong way:
  4703. var orig = new Date('10/1/2006');
  4704. var copy = orig;
  4705. copy.setDate(5);
  4706. console.log(orig); //returns 'Thu Oct 05 2006'!
  4707. //correct way:
  4708. var orig = new Date('10/1/2006'),
  4709. copy = Ext.Date.clone(orig);
  4710. copy.setDate(5);
  4711. console.log(orig); //returns 'Thu Oct 01 2006'
  4712. * </code></pre>
  4713. * @param {Date} date The date
  4714. * @return {Date} The new Date instance.
  4715. */
  4716. clone : function(date) {
  4717. return new Date(date.getTime());
  4718. },
  4719. /**
  4720. * Checks if the current date is affected by Daylight Saving Time (DST).
  4721. * @param {Date} date The date
  4722. * @return {Boolean} True if the current date is affected by DST.
  4723. */
  4724. isDST : function(date) {
  4725. // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
  4726. // courtesy of @geoffrey.mcgill
  4727. return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
  4728. },
  4729. /**
  4730. * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
  4731. * automatically adjusting for Daylight Saving Time (DST) where applicable.
  4732. * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
  4733. * @param {Date} date The date
  4734. * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
  4735. * @return {Date} this or the clone.
  4736. */
  4737. clearTime : function(date, clone) {
  4738. if (clone) {
  4739. return Ext.Date.clearTime(Ext.Date.clone(date));
  4740. }
  4741. // get current date before clearing time
  4742. var d = date.getDate(),
  4743. hr,
  4744. c;
  4745. // clear time
  4746. date.setHours(0);
  4747. date.setMinutes(0);
  4748. date.setSeconds(0);
  4749. date.setMilliseconds(0);
  4750. if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
  4751. // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
  4752. // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
  4753. // increment hour until cloned date == current date
  4754. for (hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
  4755. date.setDate(d);
  4756. date.setHours(c.getHours());
  4757. }
  4758. return date;
  4759. },
  4760. /**
  4761. * Provides a convenient method for performing basic date arithmetic. This method
  4762. * does not modify the Date instance being called - it creates and returns
  4763. * a new Date instance containing the resulting date value.
  4764. *
  4765. * Examples:
  4766. * <pre><code>
  4767. // Basic usage:
  4768. var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
  4769. console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
  4770. // Negative values will be subtracted:
  4771. var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
  4772. console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
  4773. * </code></pre>
  4774. *
  4775. * @param {Date} date The date to modify
  4776. * @param {String} interval A valid date interval enum value.
  4777. * @param {Number} value The amount to add to the current date.
  4778. * @return {Date} The new Date instance.
  4779. */
  4780. add : function(date, interval, value) {
  4781. var d = Ext.Date.clone(date),
  4782. Date = Ext.Date,
  4783. day;
  4784. if (!interval || value === 0) {
  4785. return d;
  4786. }
  4787. switch(interval.toLowerCase()) {
  4788. case Ext.Date.MILLI:
  4789. d.setMilliseconds(d.getMilliseconds() + value);
  4790. break;
  4791. case Ext.Date.SECOND:
  4792. d.setSeconds(d.getSeconds() + value);
  4793. break;
  4794. case Ext.Date.MINUTE:
  4795. d.setMinutes(d.getMinutes() + value);
  4796. break;
  4797. case Ext.Date.HOUR:
  4798. d.setHours(d.getHours() + value);
  4799. break;
  4800. case Ext.Date.DAY:
  4801. d.setDate(d.getDate() + value);
  4802. break;
  4803. case Ext.Date.MONTH:
  4804. day = date.getDate();
  4805. if (day > 28) {
  4806. day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), Ext.Date.MONTH, value)).getDate());
  4807. }
  4808. d.setDate(day);
  4809. d.setMonth(date.getMonth() + value);
  4810. break;
  4811. case Ext.Date.YEAR:
  4812. day = date.getDate();
  4813. if (day > 28) {
  4814. day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), Ext.Date.YEAR, value)).getDate());
  4815. }
  4816. d.setDate(day);
  4817. d.setFullYear(date.getFullYear() + value);
  4818. break;
  4819. }
  4820. return d;
  4821. },
  4822. /**
  4823. * Checks if a date falls on or between the given start and end dates.
  4824. * @param {Date} date The date to check
  4825. * @param {Date} start Start date
  4826. * @param {Date} end End date
  4827. * @return {Boolean} true if this date falls on or between the given start and end dates.
  4828. */
  4829. between : function(date, start, end) {
  4830. var t = date.getTime();
  4831. return start.getTime() <= t && t <= end.getTime();
  4832. },
  4833. //Maintains compatibility with old static and prototype window.Date methods.
  4834. compat: function() {
  4835. var nativeDate = window.Date,
  4836. p, u,
  4837. statics = ['useStrict', 'formatCodeToRegex', 'parseFunctions', 'parseRegexes', 'formatFunctions', 'y2kYear', 'MILLI', 'SECOND', 'MINUTE', 'HOUR', 'DAY', 'MONTH', 'YEAR', 'defaults', 'dayNames', 'monthNames', 'monthNumbers', 'getShortMonthName', 'getShortDayName', 'getMonthNumber', 'formatCodes', 'isValid', 'parseDate', 'getFormatCode', 'createFormat', 'createParser', 'parseCodes'],
  4838. proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'],
  4839. sLen = statics.length,
  4840. pLen = proto.length,
  4841. stat, prot, s;
  4842. //Append statics
  4843. for (s = 0; s < sLen; s++) {
  4844. stat = statics[s];
  4845. nativeDate[stat] = utilDate[stat];
  4846. }
  4847. //Append to prototype
  4848. for (p = 0; p < pLen; p++) {
  4849. prot = proto[p];
  4850. nativeDate.prototype[prot] = function() {
  4851. var args = Array.prototype.slice.call(arguments);
  4852. args.unshift(this);
  4853. return utilDate[prot].apply(utilDate, args);
  4854. };
  4855. }
  4856. }
  4857. };
  4858. var utilDate = Ext.Date;
  4859. }());
  4860. //@tag foundation,core
  4861. //@require ../lang/Date.js
  4862. /**
  4863. * @author Jacky Nguyen <jacky@sencha.com>
  4864. * @docauthor Jacky Nguyen <jacky@sencha.com>
  4865. * @class Ext.Base
  4866. *
  4867. * The root of all classes created with {@link Ext#define}.
  4868. *
  4869. * Ext.Base is the building block of all Ext classes. All classes in Ext inherit from Ext.Base.
  4870. * All prototype and static members of this class are inherited by all other classes.
  4871. */
  4872. (function(flexSetter) {
  4873. var noArgs = [],
  4874. Base = function(){};
  4875. // These static properties will be copied to every newly created class with {@link Ext#define}
  4876. Ext.apply(Base, {
  4877. $className: 'Ext.Base',
  4878. $isClass: true,
  4879. /**
  4880. * Create a new instance of this Class.
  4881. *
  4882. * Ext.define('My.cool.Class', {
  4883. * ...
  4884. * });
  4885. *
  4886. * My.cool.Class.create({
  4887. * someConfig: true
  4888. * });
  4889. *
  4890. * All parameters are passed to the constructor of the class.
  4891. *
  4892. * @return {Object} the created instance.
  4893. * @static
  4894. * @inheritable
  4895. */
  4896. create: function() {
  4897. return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
  4898. },
  4899. /**
  4900. * @private
  4901. * @static
  4902. * @inheritable
  4903. * @param config
  4904. */
  4905. extend: function(parent) {
  4906. var parentPrototype = parent.prototype,
  4907. basePrototype, prototype, i, ln, name, statics;
  4908. prototype = this.prototype = Ext.Object.chain(parentPrototype);
  4909. prototype.self = this;
  4910. this.superclass = prototype.superclass = parentPrototype;
  4911. if (!parent.$isClass) {
  4912. basePrototype = Ext.Base.prototype;
  4913. for (i in basePrototype) {
  4914. if (i in prototype) {
  4915. prototype[i] = basePrototype[i];
  4916. }
  4917. }
  4918. }
  4919. // Statics inheritance
  4920. statics = parentPrototype.$inheritableStatics;
  4921. if (statics) {
  4922. for (i = 0,ln = statics.length; i < ln; i++) {
  4923. name = statics[i];
  4924. if (!this.hasOwnProperty(name)) {
  4925. this[name] = parent[name];
  4926. }
  4927. }
  4928. }
  4929. if (parent.$onExtended) {
  4930. this.$onExtended = parent.$onExtended.slice();
  4931. }
  4932. prototype.config = new prototype.configClass();
  4933. prototype.initConfigList = prototype.initConfigList.slice();
  4934. prototype.initConfigMap = Ext.clone(prototype.initConfigMap);
  4935. prototype.configMap = Ext.Object.chain(prototype.configMap);
  4936. },
  4937. /**
  4938. * @private
  4939. * @static
  4940. * @inheritable
  4941. */
  4942. $onExtended: [],
  4943. /**
  4944. * @private
  4945. * @static
  4946. * @inheritable
  4947. */
  4948. triggerExtended: function() {
  4949. var callbacks = this.$onExtended,
  4950. ln = callbacks.length,
  4951. i, callback;
  4952. if (ln > 0) {
  4953. for (i = 0; i < ln; i++) {
  4954. callback = callbacks[i];
  4955. callback.fn.apply(callback.scope || this, arguments);
  4956. }
  4957. }
  4958. },
  4959. /**
  4960. * @private
  4961. * @static
  4962. * @inheritable
  4963. */
  4964. onExtended: function(fn, scope) {
  4965. this.$onExtended.push({
  4966. fn: fn,
  4967. scope: scope
  4968. });
  4969. return this;
  4970. },
  4971. /**
  4972. * @private
  4973. * @static
  4974. * @inheritable
  4975. * @param config
  4976. */
  4977. addConfig: function(config, fullMerge) {
  4978. var prototype = this.prototype,
  4979. configNameCache = Ext.Class.configNameCache,
  4980. hasConfig = prototype.configMap,
  4981. initConfigList = prototype.initConfigList,
  4982. initConfigMap = prototype.initConfigMap,
  4983. defaultConfig = prototype.config,
  4984. initializedName, name, value;
  4985. for (name in config) {
  4986. if (config.hasOwnProperty(name)) {
  4987. if (!hasConfig[name]) {
  4988. hasConfig[name] = true;
  4989. }
  4990. value = config[name];
  4991. initializedName = configNameCache[name].initialized;
  4992. if (!initConfigMap[name] && value !== null && !prototype[initializedName]) {
  4993. initConfigMap[name] = true;
  4994. initConfigList.push(name);
  4995. }
  4996. }
  4997. }
  4998. if (fullMerge) {
  4999. Ext.merge(defaultConfig, config);
  5000. }
  5001. else {
  5002. Ext.mergeIf(defaultConfig, config);
  5003. }
  5004. prototype.configClass = Ext.Object.classify(defaultConfig);
  5005. },
  5006. /**
  5007. * Add / override static properties of this class.
  5008. *
  5009. * Ext.define('My.cool.Class', {
  5010. * ...
  5011. * });
  5012. *
  5013. * My.cool.Class.addStatics({
  5014. * someProperty: 'someValue', // My.cool.Class.someProperty = 'someValue'
  5015. * method1: function() { ... }, // My.cool.Class.method1 = function() { ... };
  5016. * method2: function() { ... } // My.cool.Class.method2 = function() { ... };
  5017. * });
  5018. *
  5019. * @param {Object} members
  5020. * @return {Ext.Base} this
  5021. * @static
  5022. * @inheritable
  5023. */
  5024. addStatics: function(members) {
  5025. var member, name;
  5026. for (name in members) {
  5027. if (members.hasOwnProperty(name)) {
  5028. member = members[name];
  5029. if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn && member !== Ext.identityFn) {
  5030. member.$owner = this;
  5031. member.$name = name;
  5032. member.displayName = Ext.getClassName(this) + '.' + name;
  5033. }
  5034. this[name] = member;
  5035. }
  5036. }
  5037. return this;
  5038. },
  5039. /**
  5040. * @private
  5041. * @static
  5042. * @inheritable
  5043. * @param {Object} members
  5044. */
  5045. addInheritableStatics: function(members) {
  5046. var inheritableStatics,
  5047. hasInheritableStatics,
  5048. prototype = this.prototype,
  5049. name, member;
  5050. inheritableStatics = prototype.$inheritableStatics;
  5051. hasInheritableStatics = prototype.$hasInheritableStatics;
  5052. if (!inheritableStatics) {
  5053. inheritableStatics = prototype.$inheritableStatics = [];
  5054. hasInheritableStatics = prototype.$hasInheritableStatics = {};
  5055. }
  5056. for (name in members) {
  5057. if (members.hasOwnProperty(name)) {
  5058. member = members[name];
  5059. if (typeof member == 'function') {
  5060. member.displayName = Ext.getClassName(this) + '.' + name;
  5061. }
  5062. this[name] = member;
  5063. if (!hasInheritableStatics[name]) {
  5064. hasInheritableStatics[name] = true;
  5065. inheritableStatics.push(name);
  5066. }
  5067. }
  5068. }
  5069. return this;
  5070. },
  5071. /**
  5072. * Add methods / properties to the prototype of this class.
  5073. *
  5074. * Ext.define('My.awesome.Cat', {
  5075. * constructor: function() {
  5076. * ...
  5077. * }
  5078. * });
  5079. *
  5080. * My.awesome.Cat.addMembers({
  5081. * meow: function() {
  5082. * alert('Meowww...');
  5083. * }
  5084. * });
  5085. *
  5086. * var kitty = new My.awesome.Cat;
  5087. * kitty.meow();
  5088. *
  5089. * @param {Object} members
  5090. * @static
  5091. * @inheritable
  5092. */
  5093. addMembers: function(members) {
  5094. var prototype = this.prototype,
  5095. enumerables = Ext.enumerables,
  5096. names = [],
  5097. i, ln, name, member;
  5098. for (name in members) {
  5099. names.push(name);
  5100. }
  5101. if (enumerables) {
  5102. names.push.apply(names, enumerables);
  5103. }
  5104. for (i = 0,ln = names.length; i < ln; i++) {
  5105. name = names[i];
  5106. if (members.hasOwnProperty(name)) {
  5107. member = members[name];
  5108. if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn) {
  5109. member.$owner = this;
  5110. member.$name = name;
  5111. member.displayName = (this.$className || '') + '#' + name;
  5112. }
  5113. prototype[name] = member;
  5114. }
  5115. }
  5116. return this;
  5117. },
  5118. /**
  5119. * @private
  5120. * @static
  5121. * @inheritable
  5122. * @param name
  5123. * @param member
  5124. */
  5125. addMember: function(name, member) {
  5126. if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn) {
  5127. member.$owner = this;
  5128. member.$name = name;
  5129. member.displayName = (this.$className || '') + '#' + name;
  5130. }
  5131. this.prototype[name] = member;
  5132. return this;
  5133. },
  5134. /**
  5135. * Adds members to class.
  5136. * @static
  5137. * @inheritable
  5138. * @deprecated 4.1 Use {@link #addMembers} instead.
  5139. */
  5140. implement: function() {
  5141. this.addMembers.apply(this, arguments);
  5142. },
  5143. /**
  5144. * Borrow another class' members to the prototype of this class.
  5145. *
  5146. * Ext.define('Bank', {
  5147. * money: '$$$',
  5148. * printMoney: function() {
  5149. * alert('$$$$$$$');
  5150. * }
  5151. * });
  5152. *
  5153. * Ext.define('Thief', {
  5154. * ...
  5155. * });
  5156. *
  5157. * Thief.borrow(Bank, ['money', 'printMoney']);
  5158. *
  5159. * var steve = new Thief();
  5160. *
  5161. * alert(steve.money); // alerts '$$$'
  5162. * steve.printMoney(); // alerts '$$$$$$$'
  5163. *
  5164. * @param {Ext.Base} fromClass The class to borrow members from
  5165. * @param {Array/String} members The names of the members to borrow
  5166. * @return {Ext.Base} this
  5167. * @static
  5168. * @inheritable
  5169. * @private
  5170. */
  5171. borrow: function(fromClass, members) {
  5172. var prototype = this.prototype,
  5173. fromPrototype = fromClass.prototype,
  5174. className = Ext.getClassName(this),
  5175. i, ln, name, fn, toBorrow;
  5176. members = Ext.Array.from(members);
  5177. for (i = 0,ln = members.length; i < ln; i++) {
  5178. name = members[i];
  5179. toBorrow = fromPrototype[name];
  5180. if (typeof toBorrow == 'function') {
  5181. fn = Ext.Function.clone(toBorrow);
  5182. if (className) {
  5183. fn.displayName = className + '#' + name;
  5184. }
  5185. fn.$owner = this;
  5186. fn.$name = name;
  5187. prototype[name] = fn;
  5188. }
  5189. else {
  5190. prototype[name] = toBorrow;
  5191. }
  5192. }
  5193. return this;
  5194. },
  5195. /**
  5196. * Override members of this class. Overridden methods can be invoked via
  5197. * {@link Ext.Base#callParent}.
  5198. *
  5199. * Ext.define('My.Cat', {
  5200. * constructor: function() {
  5201. * alert("I'm a cat!");
  5202. * }
  5203. * });
  5204. *
  5205. * My.Cat.override({
  5206. * constructor: function() {
  5207. * alert("I'm going to be a cat!");
  5208. *
  5209. * this.callParent(arguments);
  5210. *
  5211. * alert("Meeeeoooowwww");
  5212. * }
  5213. * });
  5214. *
  5215. * var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
  5216. * // alerts "I'm a cat!"
  5217. * // alerts "Meeeeoooowwww"
  5218. *
  5219. * As of 4.1, direct use of this method is deprecated. Use {@link Ext#define Ext.define}
  5220. * instead:
  5221. *
  5222. * Ext.define('My.CatOverride', {
  5223. * override: 'My.Cat',
  5224. * constructor: function() {
  5225. * alert("I'm going to be a cat!");
  5226. *
  5227. * this.callParent(arguments);
  5228. *
  5229. * alert("Meeeeoooowwww");
  5230. * }
  5231. * });
  5232. *
  5233. * The above accomplishes the same result but can be managed by the {@link Ext.Loader}
  5234. * which can properly order the override and its target class and the build process
  5235. * can determine whether the override is needed based on the required state of the
  5236. * target class (My.Cat).
  5237. *
  5238. * @param {Object} members The properties to add to this class. This should be
  5239. * specified as an object literal containing one or more properties.
  5240. * @return {Ext.Base} this class
  5241. * @static
  5242. * @inheritable
  5243. * @markdown
  5244. * @deprecated 4.1.0 Use {@link Ext#define Ext.define} instead
  5245. */
  5246. override: function(members) {
  5247. var me = this,
  5248. enumerables = Ext.enumerables,
  5249. target = me.prototype,
  5250. cloneFunction = Ext.Function.clone,
  5251. name, index, member, statics, names, previous;
  5252. if (arguments.length === 2) {
  5253. name = members;
  5254. members = {};
  5255. members[name] = arguments[1];
  5256. enumerables = null;
  5257. }
  5258. do {
  5259. names = []; // clean slate for prototype (1st pass) and static (2nd pass)
  5260. statics = null; // not needed 1st pass, but needs to be cleared for 2nd pass
  5261. for (name in members) { // hasOwnProperty is checked in the next loop...
  5262. if (name == 'statics') {
  5263. statics = members[name];
  5264. } else if (name == 'config') {
  5265. me.addConfig(members[name], true);
  5266. } else {
  5267. names.push(name);
  5268. }
  5269. }
  5270. if (enumerables) {
  5271. names.push.apply(names, enumerables);
  5272. }
  5273. for (index = names.length; index--; ) {
  5274. name = names[index];
  5275. if (members.hasOwnProperty(name)) {
  5276. member = members[name];
  5277. if (typeof member == 'function' && !member.$className && member !== Ext.emptyFn) {
  5278. if (typeof member.$owner != 'undefined') {
  5279. member = cloneFunction(member);
  5280. }
  5281. if (me.$className) {
  5282. member.displayName = me.$className + '#' + name;
  5283. }
  5284. member.$owner = me;
  5285. member.$name = name;
  5286. previous = target[name];
  5287. if (previous) {
  5288. member.$previous = previous;
  5289. }
  5290. }
  5291. target[name] = member;
  5292. }
  5293. }
  5294. target = me; // 2nd pass is for statics
  5295. members = statics; // statics will be null on 2nd pass
  5296. } while (members);
  5297. return this;
  5298. },
  5299. // Documented downwards
  5300. callParent: function(args) {
  5301. var method;
  5302. // This code is intentionally inlined for the least number of debugger stepping
  5303. return (method = this.callParent.caller) && (method.$previous ||
  5304. ((method = method.$owner ? method : method.caller) &&
  5305. method.$owner.superclass.self[method.$name])).apply(this, args || noArgs);
  5306. },
  5307. // Documented downwards
  5308. callSuper: function(args) {
  5309. var method;
  5310. // This code is intentionally inlined for the least number of debugger stepping
  5311. return (method = this.callSuper.caller) &&
  5312. ((method = method.$owner ? method : method.caller) &&
  5313. method.$owner.superclass.self[method.$name]).apply(this, args || noArgs);
  5314. },
  5315. /**
  5316. * Used internally by the mixins pre-processor
  5317. * @private
  5318. * @static
  5319. * @inheritable
  5320. */
  5321. mixin: function(name, mixinClass) {
  5322. var mixin = mixinClass.prototype,
  5323. prototype = this.prototype,
  5324. key;
  5325. if (typeof mixin.onClassMixedIn != 'undefined') {
  5326. mixin.onClassMixedIn.call(mixinClass, this);
  5327. }
  5328. if (!prototype.hasOwnProperty('mixins')) {
  5329. if ('mixins' in prototype) {
  5330. prototype.mixins = Ext.Object.chain(prototype.mixins);
  5331. }
  5332. else {
  5333. prototype.mixins = {};
  5334. }
  5335. }
  5336. for (key in mixin) {
  5337. if (key === 'mixins') {
  5338. Ext.merge(prototype.mixins, mixin[key]);
  5339. }
  5340. else if (typeof prototype[key] == 'undefined' && key != 'mixinId' && key != 'config') {
  5341. prototype[key] = mixin[key];
  5342. }
  5343. }
  5344. if ('config' in mixin) {
  5345. this.addConfig(mixin.config, false);
  5346. }
  5347. prototype.mixins[name] = mixin;
  5348. },
  5349. /**
  5350. * Get the current class' name in string format.
  5351. *
  5352. * Ext.define('My.cool.Class', {
  5353. * constructor: function() {
  5354. * alert(this.self.getName()); // alerts 'My.cool.Class'
  5355. * }
  5356. * });
  5357. *
  5358. * My.cool.Class.getName(); // 'My.cool.Class'
  5359. *
  5360. * @return {String} className
  5361. * @static
  5362. * @inheritable
  5363. */
  5364. getName: function() {
  5365. return Ext.getClassName(this);
  5366. },
  5367. /**
  5368. * Create aliases for existing prototype methods. Example:
  5369. *
  5370. * Ext.define('My.cool.Class', {
  5371. * method1: function() { ... },
  5372. * method2: function() { ... }
  5373. * });
  5374. *
  5375. * var test = new My.cool.Class();
  5376. *
  5377. * My.cool.Class.createAlias({
  5378. * method3: 'method1',
  5379. * method4: 'method2'
  5380. * });
  5381. *
  5382. * test.method3(); // test.method1()
  5383. *
  5384. * My.cool.Class.createAlias('method5', 'method3');
  5385. *
  5386. * test.method5(); // test.method3() -> test.method1()
  5387. *
  5388. * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
  5389. * {@link Ext.Function#flexSetter flexSetter}
  5390. * @param {String/Object} origin The original method name
  5391. * @static
  5392. * @inheritable
  5393. * @method
  5394. */
  5395. createAlias: flexSetter(function(alias, origin) {
  5396. this.override(alias, function() {
  5397. return this[origin].apply(this, arguments);
  5398. });
  5399. }),
  5400. /**
  5401. * @private
  5402. * @static
  5403. * @inheritable
  5404. */
  5405. addXtype: function(xtype) {
  5406. var prototype = this.prototype,
  5407. xtypesMap = prototype.xtypesMap,
  5408. xtypes = prototype.xtypes,
  5409. xtypesChain = prototype.xtypesChain;
  5410. if (!prototype.hasOwnProperty('xtypesMap')) {
  5411. xtypesMap = prototype.xtypesMap = Ext.merge({}, prototype.xtypesMap || {});
  5412. xtypes = prototype.xtypes = prototype.xtypes ? [].concat(prototype.xtypes) : [];
  5413. xtypesChain = prototype.xtypesChain = prototype.xtypesChain ? [].concat(prototype.xtypesChain) : [];
  5414. prototype.xtype = xtype;
  5415. }
  5416. if (!xtypesMap[xtype]) {
  5417. xtypesMap[xtype] = true;
  5418. xtypes.push(xtype);
  5419. xtypesChain.push(xtype);
  5420. Ext.ClassManager.setAlias(this, 'widget.' + xtype);
  5421. }
  5422. return this;
  5423. }
  5424. });
  5425. Base.implement({
  5426. /** @private */
  5427. isInstance: true,
  5428. /** @private */
  5429. $className: 'Ext.Base',
  5430. /** @private */
  5431. configClass: Ext.emptyFn,
  5432. /** @private */
  5433. initConfigList: [],
  5434. /** @private */
  5435. configMap: {},
  5436. /** @private */
  5437. initConfigMap: {},
  5438. /**
  5439. * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
  5440. * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
  5441. * `this` points to during run-time
  5442. *
  5443. * Ext.define('My.Cat', {
  5444. * statics: {
  5445. * totalCreated: 0,
  5446. * speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
  5447. * },
  5448. *
  5449. * constructor: function() {
  5450. * var statics = this.statics();
  5451. *
  5452. * alert(statics.speciesName); // always equals to 'Cat' no matter what 'this' refers to
  5453. * // equivalent to: My.Cat.speciesName
  5454. *
  5455. * alert(this.self.speciesName); // dependent on 'this'
  5456. *
  5457. * statics.totalCreated++;
  5458. * },
  5459. *
  5460. * clone: function() {
  5461. * var cloned = new this.self; // dependent on 'this'
  5462. *
  5463. * cloned.groupName = this.statics().speciesName; // equivalent to: My.Cat.speciesName
  5464. *
  5465. * return cloned;
  5466. * }
  5467. * });
  5468. *
  5469. *
  5470. * Ext.define('My.SnowLeopard', {
  5471. * extend: 'My.Cat',
  5472. *
  5473. * statics: {
  5474. * speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
  5475. * },
  5476. *
  5477. * constructor: function() {
  5478. * this.callParent();
  5479. * }
  5480. * });
  5481. *
  5482. * var cat = new My.Cat(); // alerts 'Cat', then alerts 'Cat'
  5483. *
  5484. * var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
  5485. *
  5486. * var clone = snowLeopard.clone();
  5487. * alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
  5488. * alert(clone.groupName); // alerts 'Cat'
  5489. *
  5490. * alert(My.Cat.totalCreated); // alerts 3
  5491. *
  5492. * @protected
  5493. * @return {Ext.Class}
  5494. */
  5495. statics: function() {
  5496. var method = this.statics.caller,
  5497. self = this.self;
  5498. if (!method) {
  5499. return self;
  5500. }
  5501. return method.$owner;
  5502. },
  5503. /**
  5504. * Call the "parent" method of the current method. That is the method previously
  5505. * overridden by derivation or by an override (see {@link Ext#define}).
  5506. *
  5507. * Ext.define('My.Base', {
  5508. * constructor: function (x) {
  5509. * this.x = x;
  5510. * },
  5511. *
  5512. * statics: {
  5513. * method: function (x) {
  5514. * return x;
  5515. * }
  5516. * }
  5517. * });
  5518. *
  5519. * Ext.define('My.Derived', {
  5520. * extend: 'My.Base',
  5521. *
  5522. * constructor: function () {
  5523. * this.callParent([21]);
  5524. * }
  5525. * });
  5526. *
  5527. * var obj = new My.Derived();
  5528. *
  5529. * alert(obj.x); // alerts 21
  5530. *
  5531. * This can be used with an override as follows:
  5532. *
  5533. * Ext.define('My.DerivedOverride', {
  5534. * override: 'My.Derived',
  5535. *
  5536. * constructor: function (x) {
  5537. * this.callParent([x*2]); // calls original My.Derived constructor
  5538. * }
  5539. * });
  5540. *
  5541. * var obj = new My.Derived();
  5542. *
  5543. * alert(obj.x); // now alerts 42
  5544. *
  5545. * This also works with static methods.
  5546. *
  5547. * Ext.define('My.Derived2', {
  5548. * extend: 'My.Base',
  5549. *
  5550. * statics: {
  5551. * method: function (x) {
  5552. * return this.callParent([x*2]); // calls My.Base.method
  5553. * }
  5554. * }
  5555. * });
  5556. *
  5557. * alert(My.Base.method(10); // alerts 10
  5558. * alert(My.Derived2.method(10); // alerts 20
  5559. *
  5560. * Lastly, it also works with overridden static methods.
  5561. *
  5562. * Ext.define('My.Derived2Override', {
  5563. * override: 'My.Derived2',
  5564. *
  5565. * statics: {
  5566. * method: function (x) {
  5567. * return this.callParent([x*2]); // calls My.Derived2.method
  5568. * }
  5569. * }
  5570. * });
  5571. *
  5572. * alert(My.Derived2.method(10); // now alerts 40
  5573. *
  5574. * To override a method and replace it and also call the superclass method, use
  5575. * {@link #callSuper}. This is often done to patch a method to fix a bug.
  5576. *
  5577. * @protected
  5578. * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
  5579. * from the current method, for example: `this.callParent(arguments)`
  5580. * @return {Object} Returns the result of calling the parent method
  5581. */
  5582. callParent: function(args) {
  5583. // NOTE: this code is deliberately as few expressions (and no function calls)
  5584. // as possible so that a debugger can skip over this noise with the minimum number
  5585. // of steps. Basically, just hit Step Into until you are where you really wanted
  5586. // to be.
  5587. var method,
  5588. superMethod = (method = this.callParent.caller) && (method.$previous ||
  5589. ((method = method.$owner ? method : method.caller) &&
  5590. method.$owner.superclass[method.$name]));
  5591. if (!superMethod) {
  5592. method = this.callParent.caller;
  5593. var parentClass, methodName;
  5594. if (!method.$owner) {
  5595. if (!method.caller) {
  5596. throw new Error("Attempting to call a protected method from the public scope, which is not allowed");
  5597. }
  5598. method = method.caller;
  5599. }
  5600. parentClass = method.$owner.superclass;
  5601. methodName = method.$name;
  5602. if (!(methodName in parentClass)) {
  5603. throw new Error("this.callParent() was called but there's no such method (" + methodName +
  5604. ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")");
  5605. }
  5606. }
  5607. return superMethod.apply(this, args || noArgs);
  5608. },
  5609. /**
  5610. * This method is used by an override to call the superclass method but bypass any
  5611. * overridden method. This is often done to "patch" a method that contains a bug
  5612. * but for whatever reason cannot be fixed directly.
  5613. *
  5614. * Consider:
  5615. *
  5616. * Ext.define('Ext.some.Class', {
  5617. * method: function () {
  5618. * console.log('Good');
  5619. * }
  5620. * });
  5621. *
  5622. * Ext.define('Ext.some.DerivedClass', {
  5623. * method: function () {
  5624. * console.log('Bad');
  5625. *
  5626. * // ... logic but with a bug ...
  5627. *
  5628. * this.callParent();
  5629. * }
  5630. * });
  5631. *
  5632. * To patch the bug in `DerivedClass.method`, the typical solution is to create an
  5633. * override:
  5634. *
  5635. * Ext.define('App.paches.DerivedClass', {
  5636. * override: 'Ext.some.DerivedClass',
  5637. *
  5638. * method: function () {
  5639. * console.log('Fixed');
  5640. *
  5641. * // ... logic but with bug fixed ...
  5642. *
  5643. * this.callSuper();
  5644. * }
  5645. * });
  5646. *
  5647. * The patch method cannot use `callParent` to call the superclass `method` since
  5648. * that would call the overridden method containing the bug. In other words, the
  5649. * above patch would only produce "Fixed" then "Good" in the console log, whereas,
  5650. * using `callParent` would produce "Fixed" then "Bad" then "Good".
  5651. *
  5652. * @protected
  5653. * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
  5654. * from the current method, for example: `this.callSuper(arguments)`
  5655. * @return {Object} Returns the result of calling the superclass method
  5656. */
  5657. callSuper: function(args) {
  5658. // NOTE: this code is deliberately as few expressions (and no function calls)
  5659. // as possible so that a debugger can skip over this noise with the minimum number
  5660. // of steps. Basically, just hit Step Into until you are where you really wanted
  5661. // to be.
  5662. var method,
  5663. superMethod = (method = this.callSuper.caller) &&
  5664. ((method = method.$owner ? method : method.caller) &&
  5665. method.$owner.superclass[method.$name]);
  5666. if (!superMethod) {
  5667. method = this.callSuper.caller;
  5668. var parentClass, methodName;
  5669. if (!method.$owner) {
  5670. if (!method.caller) {
  5671. throw new Error("Attempting to call a protected method from the public scope, which is not allowed");
  5672. }
  5673. method = method.caller;
  5674. }
  5675. parentClass = method.$owner.superclass;
  5676. methodName = method.$name;
  5677. if (!(methodName in parentClass)) {
  5678. throw new Error("this.callSuper() was called but there's no such method (" + methodName +
  5679. ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")");
  5680. }
  5681. }
  5682. return superMethod.apply(this, args || noArgs);
  5683. },
  5684. /**
  5685. * @property {Ext.Class} self
  5686. *
  5687. * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
  5688. * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
  5689. * for a detailed comparison
  5690. *
  5691. * Ext.define('My.Cat', {
  5692. * statics: {
  5693. * speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
  5694. * },
  5695. *
  5696. * constructor: function() {
  5697. * alert(this.self.speciesName); // dependent on 'this'
  5698. * },
  5699. *
  5700. * clone: function() {
  5701. * return new this.self();
  5702. * }
  5703. * });
  5704. *
  5705. *
  5706. * Ext.define('My.SnowLeopard', {
  5707. * extend: 'My.Cat',
  5708. * statics: {
  5709. * speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
  5710. * }
  5711. * });
  5712. *
  5713. * var cat = new My.Cat(); // alerts 'Cat'
  5714. * var snowLeopard = new My.SnowLeopard(); // alerts 'Snow Leopard'
  5715. *
  5716. * var clone = snowLeopard.clone();
  5717. * alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
  5718. *
  5719. * @protected
  5720. */
  5721. self: Base,
  5722. // Default constructor, simply returns `this`
  5723. constructor: function() {
  5724. return this;
  5725. },
  5726. /**
  5727. * Initialize configuration for this class. a typical example:
  5728. *
  5729. * Ext.define('My.awesome.Class', {
  5730. * // The default config
  5731. * config: {
  5732. * name: 'Awesome',
  5733. * isAwesome: true
  5734. * },
  5735. *
  5736. * constructor: function(config) {
  5737. * this.initConfig(config);
  5738. * }
  5739. * });
  5740. *
  5741. * var awesome = new My.awesome.Class({
  5742. * name: 'Super Awesome'
  5743. * });
  5744. *
  5745. * alert(awesome.getName()); // 'Super Awesome'
  5746. *
  5747. * @protected
  5748. * @param {Object} config
  5749. * @return {Ext.Base} this
  5750. */
  5751. initConfig: function(config) {
  5752. var instanceConfig = config,
  5753. configNameCache = Ext.Class.configNameCache,
  5754. defaultConfig = new this.configClass(),
  5755. defaultConfigList = this.initConfigList,
  5756. hasConfig = this.configMap,
  5757. nameMap, i, ln, name, initializedName;
  5758. this.initConfig = Ext.emptyFn;
  5759. this.initialConfig = instanceConfig || {};
  5760. this.config = config = (instanceConfig) ? Ext.merge(defaultConfig, config) : defaultConfig;
  5761. if (instanceConfig) {
  5762. defaultConfigList = defaultConfigList.slice();
  5763. for (name in instanceConfig) {
  5764. if (hasConfig[name]) {
  5765. if (instanceConfig[name] !== null) {
  5766. defaultConfigList.push(name);
  5767. this[configNameCache[name].initialized] = false;
  5768. }
  5769. }
  5770. }
  5771. }
  5772. for (i = 0,ln = defaultConfigList.length; i < ln; i++) {
  5773. name = defaultConfigList[i];
  5774. nameMap = configNameCache[name];
  5775. initializedName = nameMap.initialized;
  5776. if (!this[initializedName]) {
  5777. this[initializedName] = true;
  5778. this[nameMap.set].call(this, config[name]);
  5779. }
  5780. }
  5781. return this;
  5782. },
  5783. /**
  5784. * @private
  5785. * @param config
  5786. */
  5787. hasConfig: function(name) {
  5788. return Boolean(this.configMap[name]);
  5789. },
  5790. /**
  5791. * @private
  5792. */
  5793. setConfig: function(config, applyIfNotSet) {
  5794. if (!config) {
  5795. return this;
  5796. }
  5797. var configNameCache = Ext.Class.configNameCache,
  5798. currentConfig = this.config,
  5799. hasConfig = this.configMap,
  5800. initialConfig = this.initialConfig,
  5801. name, value;
  5802. applyIfNotSet = Boolean(applyIfNotSet);
  5803. for (name in config) {
  5804. if (applyIfNotSet && initialConfig.hasOwnProperty(name)) {
  5805. continue;
  5806. }
  5807. value = config[name];
  5808. currentConfig[name] = value;
  5809. if (hasConfig[name]) {
  5810. this[configNameCache[name].set](value);
  5811. }
  5812. }
  5813. return this;
  5814. },
  5815. /**
  5816. * @private
  5817. * @param name
  5818. */
  5819. getConfig: function(name) {
  5820. var configNameCache = Ext.Class.configNameCache;
  5821. return this[configNameCache[name].get]();
  5822. },
  5823. /**
  5824. * Returns the initial configuration passed to constructor when instantiating
  5825. * this class.
  5826. * @param {String} [name] Name of the config option to return.
  5827. * @return {Object/Mixed} The full config object or a single config value
  5828. * when `name` parameter specified.
  5829. */
  5830. getInitialConfig: function(name) {
  5831. var config = this.config;
  5832. if (!name) {
  5833. return config;
  5834. }
  5835. else {
  5836. return config[name];
  5837. }
  5838. },
  5839. /**
  5840. * @private
  5841. * @param names
  5842. * @param callback
  5843. * @param scope
  5844. */
  5845. onConfigUpdate: function(names, callback, scope) {
  5846. var self = this.self,
  5847. className = self.$className,
  5848. i, ln, name,
  5849. updaterName, updater, newUpdater;
  5850. names = Ext.Array.from(names);
  5851. scope = scope || this;
  5852. for (i = 0,ln = names.length; i < ln; i++) {
  5853. name = names[i];
  5854. updaterName = 'update' + Ext.String.capitalize(name);
  5855. updater = this[updaterName] || Ext.emptyFn;
  5856. newUpdater = function() {
  5857. updater.apply(this, arguments);
  5858. scope[callback].apply(scope, arguments);
  5859. };
  5860. newUpdater.$name = updaterName;
  5861. newUpdater.$owner = self;
  5862. newUpdater.displayName = className + '#' + updaterName;
  5863. this[updaterName] = newUpdater;
  5864. }
  5865. },
  5866. /**
  5867. * @private
  5868. */
  5869. destroy: function() {
  5870. this.destroy = Ext.emptyFn;
  5871. }
  5872. });
  5873. /**
  5874. * Call the original method that was previously overridden with {@link Ext.Base#override}
  5875. *
  5876. * Ext.define('My.Cat', {
  5877. * constructor: function() {
  5878. * alert("I'm a cat!");
  5879. * }
  5880. * });
  5881. *
  5882. * My.Cat.override({
  5883. * constructor: function() {
  5884. * alert("I'm going to be a cat!");
  5885. *
  5886. * this.callOverridden();
  5887. *
  5888. * alert("Meeeeoooowwww");
  5889. * }
  5890. * });
  5891. *
  5892. * var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
  5893. * // alerts "I'm a cat!"
  5894. * // alerts "Meeeeoooowwww"
  5895. *
  5896. * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
  5897. * from the current method, for example: `this.callOverridden(arguments)`
  5898. * @return {Object} Returns the result of calling the overridden method
  5899. * @protected
  5900. * @deprecated as of 4.1. Use {@link #callParent} instead.
  5901. */
  5902. Base.prototype.callOverridden = Base.prototype.callParent;
  5903. Ext.Base = Base;
  5904. }(Ext.Function.flexSetter));
  5905. //@tag foundation,core
  5906. //@require Base.js
  5907. /**
  5908. * @author Jacky Nguyen <jacky@sencha.com>
  5909. * @docauthor Jacky Nguyen <jacky@sencha.com>
  5910. * @class Ext.Class
  5911. *
  5912. * Handles class creation throughout the framework. This is a low level factory that is used by Ext.ClassManager and generally
  5913. * should not be used directly. If you choose to use Ext.Class you will lose out on the namespace, aliasing and depency loading
  5914. * features made available by Ext.ClassManager. The only time you would use Ext.Class directly is to create an anonymous class.
  5915. *
  5916. * If you wish to create a class you should use {@link Ext#define Ext.define} which aliases
  5917. * {@link Ext.ClassManager#create Ext.ClassManager.create} to enable namespacing and dynamic dependency resolution.
  5918. *
  5919. * Ext.Class is the factory and **not** the superclass of everything. For the base class that **all** Ext classes inherit
  5920. * from, see {@link Ext.Base}.
  5921. */
  5922. (function() {
  5923. var ExtClass,
  5924. Base = Ext.Base,
  5925. baseStaticMembers = [],
  5926. baseStaticMember, baseStaticMemberLength;
  5927. for (baseStaticMember in Base) {
  5928. if (Base.hasOwnProperty(baseStaticMember)) {
  5929. baseStaticMembers.push(baseStaticMember);
  5930. }
  5931. }
  5932. baseStaticMemberLength = baseStaticMembers.length;
  5933. // Creates a constructor that has nothing extra in its scope chain.
  5934. function makeCtor (className) {
  5935. function constructor () {
  5936. // Opera has some problems returning from a constructor when Dragonfly isn't running. The || null seems to
  5937. // be sufficient to stop it misbehaving. Known to be required against 10.53, 11.51 and 11.61.
  5938. return this.constructor.apply(this, arguments) || null;
  5939. }
  5940. if (className) {
  5941. constructor.displayName = className;
  5942. }
  5943. return constructor;
  5944. }
  5945. /**
  5946. * @method constructor
  5947. * Create a new anonymous class.
  5948. *
  5949. * @param {Object} data An object represent the properties of this class
  5950. * @param {Function} onCreated Optional, the callback function to be executed when this class is fully created.
  5951. * Note that the creation process can be asynchronous depending on the pre-processors used.
  5952. *
  5953. * @return {Ext.Base} The newly created class
  5954. */
  5955. Ext.Class = ExtClass = function(Class, data, onCreated) {
  5956. if (typeof Class != 'function') {
  5957. onCreated = data;
  5958. data = Class;
  5959. Class = null;
  5960. }
  5961. if (!data) {
  5962. data = {};
  5963. }
  5964. Class = ExtClass.create(Class, data);
  5965. ExtClass.process(Class, data, onCreated);
  5966. return Class;
  5967. };
  5968. Ext.apply(ExtClass, {
  5969. /**
  5970. * @private
  5971. * @param Class
  5972. * @param data
  5973. * @param hooks
  5974. */
  5975. onBeforeCreated: function(Class, data, hooks) {
  5976. Class.addMembers(data);
  5977. hooks.onCreated.call(Class, Class);
  5978. },
  5979. /**
  5980. * @private
  5981. * @param Class
  5982. * @param classData
  5983. * @param onClassCreated
  5984. */
  5985. create: function(Class, data) {
  5986. var name, i;
  5987. if (!Class) {
  5988. Class = makeCtor(
  5989. data.$className
  5990. );
  5991. }
  5992. for (i = 0; i < baseStaticMemberLength; i++) {
  5993. name = baseStaticMembers[i];
  5994. Class[name] = Base[name];
  5995. }
  5996. return Class;
  5997. },
  5998. /**
  5999. * @private
  6000. * @param Class
  6001. * @param data
  6002. * @param onCreated
  6003. */
  6004. process: function(Class, data, onCreated) {
  6005. var preprocessorStack = data.preprocessors || ExtClass.defaultPreprocessors,
  6006. registeredPreprocessors = this.preprocessors,
  6007. hooks = {
  6008. onBeforeCreated: this.onBeforeCreated
  6009. },
  6010. preprocessors = [],
  6011. preprocessor, preprocessorsProperties,
  6012. i, ln, j, subLn, preprocessorProperty, process;
  6013. delete data.preprocessors;
  6014. for (i = 0,ln = preprocessorStack.length; i < ln; i++) {
  6015. preprocessor = preprocessorStack[i];
  6016. if (typeof preprocessor == 'string') {
  6017. preprocessor = registeredPreprocessors[preprocessor];
  6018. preprocessorsProperties = preprocessor.properties;
  6019. if (preprocessorsProperties === true) {
  6020. preprocessors.push(preprocessor.fn);
  6021. }
  6022. else if (preprocessorsProperties) {
  6023. for (j = 0,subLn = preprocessorsProperties.length; j < subLn; j++) {
  6024. preprocessorProperty = preprocessorsProperties[j];
  6025. if (data.hasOwnProperty(preprocessorProperty)) {
  6026. preprocessors.push(preprocessor.fn);
  6027. break;
  6028. }
  6029. }
  6030. }
  6031. }
  6032. else {
  6033. preprocessors.push(preprocessor);
  6034. }
  6035. }
  6036. hooks.onCreated = onCreated ? onCreated : Ext.emptyFn;
  6037. hooks.preprocessors = preprocessors;
  6038. this.doProcess(Class, data, hooks);
  6039. },
  6040. doProcess: function(Class, data, hooks){
  6041. var me = this,
  6042. preprocessor = hooks.preprocessors.shift();
  6043. if (!preprocessor) {
  6044. hooks.onBeforeCreated.apply(me, arguments);
  6045. return;
  6046. }
  6047. if (preprocessor.call(me, Class, data, hooks, me.doProcess) !== false) {
  6048. me.doProcess(Class, data, hooks);
  6049. }
  6050. },
  6051. /** @private */
  6052. preprocessors: {},
  6053. /**
  6054. * Register a new pre-processor to be used during the class creation process
  6055. *
  6056. * @param {String} name The pre-processor's name
  6057. * @param {Function} fn The callback function to be executed. Typical format:
  6058. *
  6059. * function(cls, data, fn) {
  6060. * // Your code here
  6061. *
  6062. * // Execute this when the processing is finished.
  6063. * // Asynchronous processing is perfectly ok
  6064. * if (fn) {
  6065. * fn.call(this, cls, data);
  6066. * }
  6067. * });
  6068. *
  6069. * @param {Function} fn.cls The created class
  6070. * @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor
  6071. * @param {Function} fn.fn The callback function that **must** to be executed when this
  6072. * pre-processor finishes, regardless of whether the processing is synchronous or aynchronous.
  6073. * @return {Ext.Class} this
  6074. * @private
  6075. * @static
  6076. */
  6077. registerPreprocessor: function(name, fn, properties, position, relativeTo) {
  6078. if (!position) {
  6079. position = 'last';
  6080. }
  6081. if (!properties) {
  6082. properties = [name];
  6083. }
  6084. this.preprocessors[name] = {
  6085. name: name,
  6086. properties: properties || false,
  6087. fn: fn
  6088. };
  6089. this.setDefaultPreprocessorPosition(name, position, relativeTo);
  6090. return this;
  6091. },
  6092. /**
  6093. * Retrieve a pre-processor callback function by its name, which has been registered before
  6094. *
  6095. * @param {String} name
  6096. * @return {Function} preprocessor
  6097. * @private
  6098. * @static
  6099. */
  6100. getPreprocessor: function(name) {
  6101. return this.preprocessors[name];
  6102. },
  6103. /**
  6104. * @private
  6105. */
  6106. getPreprocessors: function() {
  6107. return this.preprocessors;
  6108. },
  6109. /**
  6110. * @private
  6111. */
  6112. defaultPreprocessors: [],
  6113. /**
  6114. * Retrieve the array stack of default pre-processors
  6115. * @return {Function[]} defaultPreprocessors
  6116. * @private
  6117. * @static
  6118. */
  6119. getDefaultPreprocessors: function() {
  6120. return this.defaultPreprocessors;
  6121. },
  6122. /**
  6123. * Set the default array stack of default pre-processors
  6124. *
  6125. * @private
  6126. * @param {Array} preprocessors
  6127. * @return {Ext.Class} this
  6128. * @static
  6129. */
  6130. setDefaultPreprocessors: function(preprocessors) {
  6131. this.defaultPreprocessors = Ext.Array.from(preprocessors);
  6132. return this;
  6133. },
  6134. /**
  6135. * Insert this pre-processor at a specific position in the stack, optionally relative to
  6136. * any existing pre-processor. For example:
  6137. *
  6138. * Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
  6139. * // Your code here
  6140. *
  6141. * if (fn) {
  6142. * fn.call(this, cls, data);
  6143. * }
  6144. * }).setDefaultPreprocessorPosition('debug', 'last');
  6145. *
  6146. * @private
  6147. * @param {String} name The pre-processor name. Note that it needs to be registered with
  6148. * {@link Ext.Class#registerPreprocessor registerPreprocessor} before this
  6149. * @param {String} offset The insertion position. Four possible values are:
  6150. * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
  6151. * @param {String} relativeName
  6152. * @return {Ext.Class} this
  6153. * @static
  6154. */
  6155. setDefaultPreprocessorPosition: function(name, offset, relativeName) {
  6156. var defaultPreprocessors = this.defaultPreprocessors,
  6157. index;
  6158. if (typeof offset == 'string') {
  6159. if (offset === 'first') {
  6160. defaultPreprocessors.unshift(name);
  6161. return this;
  6162. }
  6163. else if (offset === 'last') {
  6164. defaultPreprocessors.push(name);
  6165. return this;
  6166. }
  6167. offset = (offset === 'after') ? 1 : -1;
  6168. }
  6169. index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
  6170. if (index !== -1) {
  6171. Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
  6172. }
  6173. return this;
  6174. },
  6175. configNameCache: {},
  6176. getConfigNameMap: function(name) {
  6177. var cache = this.configNameCache,
  6178. map = cache[name],
  6179. capitalizedName;
  6180. if (!map) {
  6181. capitalizedName = name.charAt(0).toUpperCase() + name.substr(1);
  6182. map = cache[name] = {
  6183. internal: name,
  6184. initialized: '_is' + capitalizedName + 'Initialized',
  6185. apply: 'apply' + capitalizedName,
  6186. update: 'update' + capitalizedName,
  6187. 'set': 'set' + capitalizedName,
  6188. 'get': 'get' + capitalizedName,
  6189. doSet : 'doSet' + capitalizedName,
  6190. changeEvent: name.toLowerCase() + 'change'
  6191. };
  6192. }
  6193. return map;
  6194. }
  6195. });
  6196. /**
  6197. * @cfg {String} extend
  6198. * The parent class that this class extends. For example:
  6199. *
  6200. * Ext.define('Person', {
  6201. * say: function(text) { alert(text); }
  6202. * });
  6203. *
  6204. * Ext.define('Developer', {
  6205. * extend: 'Person',
  6206. * say: function(text) { this.callParent(["print "+text]); }
  6207. * });
  6208. */
  6209. ExtClass.registerPreprocessor('extend', function(Class, data) {
  6210. var Base = Ext.Base,
  6211. basePrototype = Base.prototype,
  6212. extend = data.extend,
  6213. Parent, parentPrototype, i;
  6214. delete data.extend;
  6215. if (extend && extend !== Object) {
  6216. Parent = extend;
  6217. }
  6218. else {
  6219. Parent = Base;
  6220. }
  6221. parentPrototype = Parent.prototype;
  6222. if (!Parent.$isClass) {
  6223. for (i in basePrototype) {
  6224. if (!parentPrototype[i]) {
  6225. parentPrototype[i] = basePrototype[i];
  6226. }
  6227. }
  6228. }
  6229. Class.extend(Parent);
  6230. Class.triggerExtended.apply(Class, arguments);
  6231. if (data.onClassExtended) {
  6232. Class.onExtended(data.onClassExtended, Class);
  6233. delete data.onClassExtended;
  6234. }
  6235. }, true);
  6236. /**
  6237. * @cfg {Object} statics
  6238. * List of static methods for this class. For example:
  6239. *
  6240. * Ext.define('Computer', {
  6241. * statics: {
  6242. * factory: function(brand) {
  6243. * // 'this' in static methods refer to the class itself
  6244. * return new this(brand);
  6245. * }
  6246. * },
  6247. *
  6248. * constructor: function() { ... }
  6249. * });
  6250. *
  6251. * var dellComputer = Computer.factory('Dell');
  6252. */
  6253. ExtClass.registerPreprocessor('statics', function(Class, data) {
  6254. Class.addStatics(data.statics);
  6255. delete data.statics;
  6256. });
  6257. /**
  6258. * @cfg {Object} inheritableStatics
  6259. * List of inheritable static methods for this class.
  6260. * Otherwise just like {@link #statics} but subclasses inherit these methods.
  6261. */
  6262. ExtClass.registerPreprocessor('inheritableStatics', function(Class, data) {
  6263. Class.addInheritableStatics(data.inheritableStatics);
  6264. delete data.inheritableStatics;
  6265. });
  6266. /**
  6267. * @cfg {Object} config
  6268. * List of configuration options with their default values, for which automatically
  6269. * accessor methods are generated. For example:
  6270. *
  6271. * Ext.define('SmartPhone', {
  6272. * config: {
  6273. * hasTouchScreen: false,
  6274. * operatingSystem: 'Other',
  6275. * price: 500
  6276. * },
  6277. * constructor: function(cfg) {
  6278. * this.initConfig(cfg);
  6279. * }
  6280. * });
  6281. *
  6282. * var iPhone = new SmartPhone({
  6283. * hasTouchScreen: true,
  6284. * operatingSystem: 'iOS'
  6285. * });
  6286. *
  6287. * iPhone.getPrice(); // 500;
  6288. * iPhone.getOperatingSystem(); // 'iOS'
  6289. * iPhone.getHasTouchScreen(); // true;
  6290. */
  6291. ExtClass.registerPreprocessor('config', function(Class, data) {
  6292. var config = data.config,
  6293. prototype = Class.prototype;
  6294. delete data.config;
  6295. Ext.Object.each(config, function(name, value) {
  6296. var nameMap = ExtClass.getConfigNameMap(name),
  6297. internalName = nameMap.internal,
  6298. initializedName = nameMap.initialized,
  6299. applyName = nameMap.apply,
  6300. updateName = nameMap.update,
  6301. setName = nameMap.set,
  6302. getName = nameMap.get,
  6303. hasOwnSetter = (setName in prototype) || data.hasOwnProperty(setName),
  6304. hasOwnApplier = (applyName in prototype) || data.hasOwnProperty(applyName),
  6305. hasOwnUpdater = (updateName in prototype) || data.hasOwnProperty(updateName),
  6306. optimizedGetter, customGetter;
  6307. if (value === null || (!hasOwnSetter && !hasOwnApplier && !hasOwnUpdater)) {
  6308. prototype[internalName] = value;
  6309. prototype[initializedName] = true;
  6310. }
  6311. else {
  6312. prototype[initializedName] = false;
  6313. }
  6314. if (!hasOwnSetter) {
  6315. data[setName] = function(value) {
  6316. var oldValue = this[internalName],
  6317. applier = this[applyName],
  6318. updater = this[updateName];
  6319. if (!this[initializedName]) {
  6320. this[initializedName] = true;
  6321. }
  6322. if (applier) {
  6323. value = applier.call(this, value, oldValue);
  6324. }
  6325. if (typeof value != 'undefined') {
  6326. this[internalName] = value;
  6327. if (updater && value !== oldValue) {
  6328. updater.call(this, value, oldValue);
  6329. }
  6330. }
  6331. return this;
  6332. };
  6333. }
  6334. if (!(getName in prototype) || data.hasOwnProperty(getName)) {
  6335. customGetter = data[getName] || false;
  6336. if (customGetter) {
  6337. optimizedGetter = function() {
  6338. return customGetter.apply(this, arguments);
  6339. };
  6340. }
  6341. else {
  6342. optimizedGetter = function() {
  6343. return this[internalName];
  6344. };
  6345. }
  6346. data[getName] = function() {
  6347. var currentGetter;
  6348. if (!this[initializedName]) {
  6349. this[initializedName] = true;
  6350. this[setName](this.config[name]);
  6351. }
  6352. currentGetter = this[getName];
  6353. if ('$previous' in currentGetter) {
  6354. currentGetter.$previous = optimizedGetter;
  6355. }
  6356. else {
  6357. this[getName] = optimizedGetter;
  6358. }
  6359. return optimizedGetter.apply(this, arguments);
  6360. };
  6361. }
  6362. });
  6363. Class.addConfig(config, true);
  6364. });
  6365. /**
  6366. * @cfg {String[]/Object} mixins
  6367. * List of classes to mix into this class. For example:
  6368. *
  6369. * Ext.define('CanSing', {
  6370. * sing: function() {
  6371. * alert("I'm on the highway to hell...")
  6372. * }
  6373. * });
  6374. *
  6375. * Ext.define('Musician', {
  6376. * mixins: ['CanSing']
  6377. * })
  6378. *
  6379. * In this case the Musician class will get a `sing` method from CanSing mixin.
  6380. *
  6381. * But what if the Musician already has a `sing` method? Or you want to mix
  6382. * in two classes, both of which define `sing`? In such a cases it's good
  6383. * to define mixins as an object, where you assign a name to each mixin:
  6384. *
  6385. * Ext.define('Musician', {
  6386. * mixins: {
  6387. * canSing: 'CanSing'
  6388. * },
  6389. *
  6390. * sing: function() {
  6391. * // delegate singing operation to mixin
  6392. * this.mixins.canSing.sing.call(this);
  6393. * }
  6394. * })
  6395. *
  6396. * In this case the `sing` method of Musician will overwrite the
  6397. * mixed in `sing` method. But you can access the original mixed in method
  6398. * through special `mixins` property.
  6399. */
  6400. ExtClass.registerPreprocessor('mixins', function(Class, data, hooks) {
  6401. var mixins = data.mixins,
  6402. name, mixin, i, ln;
  6403. delete data.mixins;
  6404. Ext.Function.interceptBefore(hooks, 'onCreated', function() {
  6405. if (mixins instanceof Array) {
  6406. for (i = 0,ln = mixins.length; i < ln; i++) {
  6407. mixin = mixins[i];
  6408. name = mixin.prototype.mixinId || mixin.$className;
  6409. Class.mixin(name, mixin);
  6410. }
  6411. }
  6412. else {
  6413. for (var mixinName in mixins) {
  6414. if (mixins.hasOwnProperty(mixinName)) {
  6415. Class.mixin(mixinName, mixins[mixinName]);
  6416. }
  6417. }
  6418. }
  6419. });
  6420. });
  6421. // Backwards compatible
  6422. Ext.extend = function(Class, Parent, members) {
  6423. if (arguments.length === 2 && Ext.isObject(Parent)) {
  6424. members = Parent;
  6425. Parent = Class;
  6426. Class = null;
  6427. }
  6428. var cls;
  6429. if (!Parent) {
  6430. throw new Error("[Ext.extend] Attempting to extend from a class which has not been loaded on the page.");
  6431. }
  6432. members.extend = Parent;
  6433. members.preprocessors = [
  6434. 'extend'
  6435. ,'statics'
  6436. ,'inheritableStatics'
  6437. ,'mixins'
  6438. ,'config'
  6439. ];
  6440. if (Class) {
  6441. cls = new ExtClass(Class, members);
  6442. // The 'constructor' is given as 'Class' but also needs to be on prototype
  6443. cls.prototype.constructor = Class;
  6444. } else {
  6445. cls = new ExtClass(members);
  6446. }
  6447. cls.prototype.override = function(o) {
  6448. for (var m in o) {
  6449. if (o.hasOwnProperty(m)) {
  6450. this[m] = o[m];
  6451. }
  6452. }
  6453. };
  6454. return cls;
  6455. };
  6456. }());
  6457. //@tag foundation,core
  6458. //@require Class.js
  6459. /**
  6460. * @author Jacky Nguyen <jacky@sencha.com>
  6461. * @docauthor Jacky Nguyen <jacky@sencha.com>
  6462. * @class Ext.ClassManager
  6463. *
  6464. * Ext.ClassManager manages all classes and handles mapping from string class name to
  6465. * actual class objects throughout the whole framework. It is not generally accessed directly, rather through
  6466. * these convenient shorthands:
  6467. *
  6468. * - {@link Ext#define Ext.define}
  6469. * - {@link Ext#create Ext.create}
  6470. * - {@link Ext#widget Ext.widget}
  6471. * - {@link Ext#getClass Ext.getClass}
  6472. * - {@link Ext#getClassName Ext.getClassName}
  6473. *
  6474. * # Basic syntax:
  6475. *
  6476. * Ext.define(className, properties);
  6477. *
  6478. * in which `properties` is an object represent a collection of properties that apply to the class. See
  6479. * {@link Ext.ClassManager#create} for more detailed instructions.
  6480. *
  6481. * Ext.define('Person', {
  6482. * name: 'Unknown',
  6483. *
  6484. * constructor: function(name) {
  6485. * if (name) {
  6486. * this.name = name;
  6487. * }
  6488. * },
  6489. *
  6490. * eat: function(foodType) {
  6491. * alert("I'm eating: " + foodType);
  6492. *
  6493. * return this;
  6494. * }
  6495. * });
  6496. *
  6497. * var aaron = new Person("Aaron");
  6498. * aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
  6499. *
  6500. * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
  6501. * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
  6502. *
  6503. * # Inheritance:
  6504. *
  6505. * Ext.define('Developer', {
  6506. * extend: 'Person',
  6507. *
  6508. * constructor: function(name, isGeek) {
  6509. * this.isGeek = isGeek;
  6510. *
  6511. * // Apply a method from the parent class' prototype
  6512. * this.callParent([name]);
  6513. * },
  6514. *
  6515. * code: function(language) {
  6516. * alert("I'm coding in: " + language);
  6517. *
  6518. * this.eat("Bugs");
  6519. *
  6520. * return this;
  6521. * }
  6522. * });
  6523. *
  6524. * var jacky = new Developer("Jacky", true);
  6525. * jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
  6526. * // alert("I'm eating: Bugs");
  6527. *
  6528. * See {@link Ext.Base#callParent} for more details on calling superclass' methods
  6529. *
  6530. * # Mixins:
  6531. *
  6532. * Ext.define('CanPlayGuitar', {
  6533. * playGuitar: function() {
  6534. * alert("F#...G...D...A");
  6535. * }
  6536. * });
  6537. *
  6538. * Ext.define('CanComposeSongs', {
  6539. * composeSongs: function() { ... }
  6540. * });
  6541. *
  6542. * Ext.define('CanSing', {
  6543. * sing: function() {
  6544. * alert("I'm on the highway to hell...")
  6545. * }
  6546. * });
  6547. *
  6548. * Ext.define('Musician', {
  6549. * extend: 'Person',
  6550. *
  6551. * mixins: {
  6552. * canPlayGuitar: 'CanPlayGuitar',
  6553. * canComposeSongs: 'CanComposeSongs',
  6554. * canSing: 'CanSing'
  6555. * }
  6556. * })
  6557. *
  6558. * Ext.define('CoolPerson', {
  6559. * extend: 'Person',
  6560. *
  6561. * mixins: {
  6562. * canPlayGuitar: 'CanPlayGuitar',
  6563. * canSing: 'CanSing'
  6564. * },
  6565. *
  6566. * sing: function() {
  6567. * alert("Ahem....");
  6568. *
  6569. * this.mixins.canSing.sing.call(this);
  6570. *
  6571. * alert("[Playing guitar at the same time...]");
  6572. *
  6573. * this.playGuitar();
  6574. * }
  6575. * });
  6576. *
  6577. * var me = new CoolPerson("Jacky");
  6578. *
  6579. * me.sing(); // alert("Ahem...");
  6580. * // alert("I'm on the highway to hell...");
  6581. * // alert("[Playing guitar at the same time...]");
  6582. * // alert("F#...G...D...A");
  6583. *
  6584. * # Config:
  6585. *
  6586. * Ext.define('SmartPhone', {
  6587. * config: {
  6588. * hasTouchScreen: false,
  6589. * operatingSystem: 'Other',
  6590. * price: 500
  6591. * },
  6592. *
  6593. * isExpensive: false,
  6594. *
  6595. * constructor: function(config) {
  6596. * this.initConfig(config);
  6597. * },
  6598. *
  6599. * applyPrice: function(price) {
  6600. * this.isExpensive = (price > 500);
  6601. *
  6602. * return price;
  6603. * },
  6604. *
  6605. * applyOperatingSystem: function(operatingSystem) {
  6606. * if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
  6607. * return 'Other';
  6608. * }
  6609. *
  6610. * return operatingSystem;
  6611. * }
  6612. * });
  6613. *
  6614. * var iPhone = new SmartPhone({
  6615. * hasTouchScreen: true,
  6616. * operatingSystem: 'iOS'
  6617. * });
  6618. *
  6619. * iPhone.getPrice(); // 500;
  6620. * iPhone.getOperatingSystem(); // 'iOS'
  6621. * iPhone.getHasTouchScreen(); // true;
  6622. * iPhone.hasTouchScreen(); // true
  6623. *
  6624. * iPhone.isExpensive; // false;
  6625. * iPhone.setPrice(600);
  6626. * iPhone.getPrice(); // 600
  6627. * iPhone.isExpensive; // true;
  6628. *
  6629. * iPhone.setOperatingSystem('AlienOS');
  6630. * iPhone.getOperatingSystem(); // 'Other'
  6631. *
  6632. * # Statics:
  6633. *
  6634. * Ext.define('Computer', {
  6635. * statics: {
  6636. * factory: function(brand) {
  6637. * // 'this' in static methods refer to the class itself
  6638. * return new this(brand);
  6639. * }
  6640. * },
  6641. *
  6642. * constructor: function() { ... }
  6643. * });
  6644. *
  6645. * var dellComputer = Computer.factory('Dell');
  6646. *
  6647. * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
  6648. * static properties within class methods
  6649. *
  6650. * @singleton
  6651. */
  6652. (function(Class, alias, arraySlice, arrayFrom, global) {
  6653. // Creates a constructor that has nothing extra in its scope chain.
  6654. function makeCtor () {
  6655. function constructor () {
  6656. // Opera has some problems returning from a constructor when Dragonfly isn't running. The || null seems to
  6657. // be sufficient to stop it misbehaving. Known to be required against 10.53, 11.51 and 11.61.
  6658. return this.constructor.apply(this, arguments) || null;
  6659. }
  6660. return constructor;
  6661. }
  6662. var Manager = Ext.ClassManager = {
  6663. /**
  6664. * @property {Object} classes
  6665. * All classes which were defined through the ClassManager. Keys are the
  6666. * name of the classes and the values are references to the classes.
  6667. * @private
  6668. */
  6669. classes: {},
  6670. /**
  6671. * @private
  6672. */
  6673. existCache: {},
  6674. /**
  6675. * @private
  6676. */
  6677. namespaceRewrites: [{
  6678. from: 'Ext.',
  6679. to: Ext
  6680. }],
  6681. /**
  6682. * @private
  6683. */
  6684. maps: {
  6685. alternateToName: {},
  6686. aliasToName: {},
  6687. nameToAliases: {},
  6688. nameToAlternates: {}
  6689. },
  6690. /** @private */
  6691. enableNamespaceParseCache: true,
  6692. /** @private */
  6693. namespaceParseCache: {},
  6694. /** @private */
  6695. instantiators: [],
  6696. /**
  6697. * Checks if a class has already been created.
  6698. *
  6699. * @param {String} className
  6700. * @return {Boolean} exist
  6701. */
  6702. isCreated: function(className) {
  6703. var existCache = this.existCache,
  6704. i, ln, part, root, parts;
  6705. if (typeof className != 'string' || className.length < 1) {
  6706. throw new Error("[Ext.ClassManager] Invalid classname, must be a string and must not be empty");
  6707. }
  6708. if (this.classes[className] || existCache[className]) {
  6709. return true;
  6710. }
  6711. root = global;
  6712. parts = this.parseNamespace(className);
  6713. for (i = 0, ln = parts.length; i < ln; i++) {
  6714. part = parts[i];
  6715. if (typeof part != 'string') {
  6716. root = part;
  6717. } else {
  6718. if (!root || !root[part]) {
  6719. return false;
  6720. }
  6721. root = root[part];
  6722. }
  6723. }
  6724. existCache[className] = true;
  6725. this.triggerCreated(className);
  6726. return true;
  6727. },
  6728. /**
  6729. * @private
  6730. */
  6731. createdListeners: [],
  6732. /**
  6733. * @private
  6734. */
  6735. nameCreatedListeners: {},
  6736. /**
  6737. * @private
  6738. */
  6739. triggerCreated: function(className) {
  6740. var listeners = this.createdListeners,
  6741. nameListeners = this.nameCreatedListeners,
  6742. alternateNames = this.maps.nameToAlternates[className],
  6743. names = [className],
  6744. i, ln, j, subLn, listener, name;
  6745. for (i = 0,ln = listeners.length; i < ln; i++) {
  6746. listener = listeners[i];
  6747. listener.fn.call(listener.scope, className);
  6748. }
  6749. if (alternateNames) {
  6750. names.push.apply(names, alternateNames);
  6751. }
  6752. for (i = 0,ln = names.length; i < ln; i++) {
  6753. name = names[i];
  6754. listeners = nameListeners[name];
  6755. if (listeners) {
  6756. for (j = 0,subLn = listeners.length; j < subLn; j++) {
  6757. listener = listeners[j];
  6758. listener.fn.call(listener.scope, name);
  6759. }
  6760. delete nameListeners[name];
  6761. }
  6762. }
  6763. },
  6764. /**
  6765. * @private
  6766. */
  6767. onCreated: function(fn, scope, className) {
  6768. var listeners = this.createdListeners,
  6769. nameListeners = this.nameCreatedListeners,
  6770. listener = {
  6771. fn: fn,
  6772. scope: scope
  6773. };
  6774. if (className) {
  6775. if (this.isCreated(className)) {
  6776. fn.call(scope, className);
  6777. return;
  6778. }
  6779. if (!nameListeners[className]) {
  6780. nameListeners[className] = [];
  6781. }
  6782. nameListeners[className].push(listener);
  6783. }
  6784. else {
  6785. listeners.push(listener);
  6786. }
  6787. },
  6788. /**
  6789. * Supports namespace rewriting
  6790. * @private
  6791. */
  6792. parseNamespace: function(namespace) {
  6793. if (typeof namespace != 'string') {
  6794. throw new Error("[Ext.ClassManager] Invalid namespace, must be a string");
  6795. }
  6796. var cache = this.namespaceParseCache,
  6797. parts,
  6798. rewrites,
  6799. root,
  6800. name,
  6801. rewrite, from, to, i, ln;
  6802. if (this.enableNamespaceParseCache) {
  6803. if (cache.hasOwnProperty(namespace)) {
  6804. return cache[namespace];
  6805. }
  6806. }
  6807. parts = [];
  6808. rewrites = this.namespaceRewrites;
  6809. root = global;
  6810. name = namespace;
  6811. for (i = 0, ln = rewrites.length; i < ln; i++) {
  6812. rewrite = rewrites[i];
  6813. from = rewrite.from;
  6814. to = rewrite.to;
  6815. if (name === from || name.substring(0, from.length) === from) {
  6816. name = name.substring(from.length);
  6817. if (typeof to != 'string') {
  6818. root = to;
  6819. } else {
  6820. parts = parts.concat(to.split('.'));
  6821. }
  6822. break;
  6823. }
  6824. }
  6825. parts.push(root);
  6826. parts = parts.concat(name.split('.'));
  6827. if (this.enableNamespaceParseCache) {
  6828. cache[namespace] = parts;
  6829. }
  6830. return parts;
  6831. },
  6832. /**
  6833. * Creates a namespace and assign the `value` to the created object
  6834. *
  6835. * Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
  6836. *
  6837. * alert(MyCompany.pkg.Example === someObject); // alerts true
  6838. *
  6839. * @param {String} name
  6840. * @param {Object} value
  6841. */
  6842. setNamespace: function(name, value) {
  6843. var root = global,
  6844. parts = this.parseNamespace(name),
  6845. ln = parts.length - 1,
  6846. leaf = parts[ln],
  6847. i, part;
  6848. for (i = 0; i < ln; i++) {
  6849. part = parts[i];
  6850. if (typeof part != 'string') {
  6851. root = part;
  6852. } else {
  6853. if (!root[part]) {
  6854. root[part] = {};
  6855. }
  6856. root = root[part];
  6857. }
  6858. }
  6859. root[leaf] = value;
  6860. return root[leaf];
  6861. },
  6862. /**
  6863. * The new Ext.ns, supports namespace rewriting
  6864. * @private
  6865. */
  6866. createNamespaces: function() {
  6867. var root = global,
  6868. parts, part, i, j, ln, subLn;
  6869. for (i = 0, ln = arguments.length; i < ln; i++) {
  6870. parts = this.parseNamespace(arguments[i]);
  6871. for (j = 0, subLn = parts.length; j < subLn; j++) {
  6872. part = parts[j];
  6873. if (typeof part != 'string') {
  6874. root = part;
  6875. } else {
  6876. if (!root[part]) {
  6877. root[part] = {};
  6878. }
  6879. root = root[part];
  6880. }
  6881. }
  6882. }
  6883. return root;
  6884. },
  6885. /**
  6886. * Sets a name reference to a class.
  6887. *
  6888. * @param {String} name
  6889. * @param {Object} value
  6890. * @return {Ext.ClassManager} this
  6891. */
  6892. set: function(name, value) {
  6893. var me = this,
  6894. maps = me.maps,
  6895. nameToAlternates = maps.nameToAlternates,
  6896. targetName = me.getName(value),
  6897. alternates;
  6898. me.classes[name] = me.setNamespace(name, value);
  6899. if (targetName && targetName !== name) {
  6900. maps.alternateToName[name] = targetName;
  6901. alternates = nameToAlternates[targetName] || (nameToAlternates[targetName] = []);
  6902. alternates.push(name);
  6903. }
  6904. return this;
  6905. },
  6906. /**
  6907. * Retrieve a class by its name.
  6908. *
  6909. * @param {String} name
  6910. * @return {Ext.Class} class
  6911. */
  6912. get: function(name) {
  6913. var classes = this.classes,
  6914. root,
  6915. parts,
  6916. part, i, ln;
  6917. if (classes[name]) {
  6918. return classes[name];
  6919. }
  6920. root = global;
  6921. parts = this.parseNamespace(name);
  6922. for (i = 0, ln = parts.length; i < ln; i++) {
  6923. part = parts[i];
  6924. if (typeof part != 'string') {
  6925. root = part;
  6926. } else {
  6927. if (!root || !root[part]) {
  6928. return null;
  6929. }
  6930. root = root[part];
  6931. }
  6932. }
  6933. return root;
  6934. },
  6935. /**
  6936. * Register the alias for a class.
  6937. *
  6938. * @param {Ext.Class/String} cls a reference to a class or a className
  6939. * @param {String} alias Alias to use when referring to this class
  6940. */
  6941. setAlias: function(cls, alias) {
  6942. var aliasToNameMap = this.maps.aliasToName,
  6943. nameToAliasesMap = this.maps.nameToAliases,
  6944. className;
  6945. if (typeof cls == 'string') {
  6946. className = cls;
  6947. } else {
  6948. className = this.getName(cls);
  6949. }
  6950. if (alias && aliasToNameMap[alias] !== className) {
  6951. if (aliasToNameMap[alias] && Ext.isDefined(global.console)) {
  6952. global.console.log("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
  6953. "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
  6954. }
  6955. aliasToNameMap[alias] = className;
  6956. }
  6957. if (!nameToAliasesMap[className]) {
  6958. nameToAliasesMap[className] = [];
  6959. }
  6960. if (alias) {
  6961. Ext.Array.include(nameToAliasesMap[className], alias);
  6962. }
  6963. return this;
  6964. },
  6965. /**
  6966. * Adds a batch of class name to alias mappings
  6967. * @param {Object} aliases The set of mappings of the form
  6968. * className : [values...]
  6969. */
  6970. addNameAliasMappings: function(aliases){
  6971. var aliasToNameMap = this.maps.aliasToName,
  6972. nameToAliasesMap = this.maps.nameToAliases,
  6973. className, aliasList, alias, i;
  6974. for (className in aliases) {
  6975. aliasList = nameToAliasesMap[className] ||
  6976. (nameToAliasesMap[className] = []);
  6977. for (i = 0; i < aliases[className].length; i++) {
  6978. alias = aliases[className][i];
  6979. if (!aliasToNameMap[alias]) {
  6980. aliasToNameMap[alias] = className;
  6981. aliasList.push(alias);
  6982. }
  6983. }
  6984. }
  6985. return this;
  6986. },
  6987. /**
  6988. *
  6989. * @param {Object} alternates The set of mappings of the form
  6990. * className : [values...]
  6991. */
  6992. addNameAlternateMappings: function(alternates) {
  6993. var alternateToName = this.maps.alternateToName,
  6994. nameToAlternates = this.maps.nameToAlternates,
  6995. className, aliasList, alternate, i;
  6996. for (className in alternates) {
  6997. aliasList = nameToAlternates[className] ||
  6998. (nameToAlternates[className] = []);
  6999. for (i = 0; i < alternates[className].length; i++) {
  7000. alternate = alternates[className];
  7001. if (!alternateToName[alternate]) {
  7002. alternateToName[alternate] = className;
  7003. aliasList.push(alternate);
  7004. }
  7005. }
  7006. }
  7007. return this;
  7008. },
  7009. /**
  7010. * Get a reference to the class by its alias.
  7011. *
  7012. * @param {String} alias
  7013. * @return {Ext.Class} class
  7014. */
  7015. getByAlias: function(alias) {
  7016. return this.get(this.getNameByAlias(alias));
  7017. },
  7018. /**
  7019. * Get the name of a class by its alias.
  7020. *
  7021. * @param {String} alias
  7022. * @return {String} className
  7023. */
  7024. getNameByAlias: function(alias) {
  7025. return this.maps.aliasToName[alias] || '';
  7026. },
  7027. /**
  7028. * Get the name of a class by its alternate name.
  7029. *
  7030. * @param {String} alternate
  7031. * @return {String} className
  7032. */
  7033. getNameByAlternate: function(alternate) {
  7034. return this.maps.alternateToName[alternate] || '';
  7035. },
  7036. /**
  7037. * Get the aliases of a class by the class name
  7038. *
  7039. * @param {String} name
  7040. * @return {Array} aliases
  7041. */
  7042. getAliasesByName: function(name) {
  7043. return this.maps.nameToAliases[name] || [];
  7044. },
  7045. /**
  7046. * Get the name of the class by its reference or its instance;
  7047. * usually invoked by the shorthand {@link Ext#getClassName Ext.getClassName}
  7048. *
  7049. * Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
  7050. *
  7051. * @param {Ext.Class/Object} object
  7052. * @return {String} className
  7053. */
  7054. getName: function(object) {
  7055. return object && object.$className || '';
  7056. },
  7057. /**
  7058. * Get the class of the provided object; returns null if it's not an instance
  7059. * of any class created with Ext.define. This is usually invoked by the shorthand {@link Ext#getClass Ext.getClass}
  7060. *
  7061. * var component = new Ext.Component();
  7062. *
  7063. * Ext.ClassManager.getClass(component); // returns Ext.Component
  7064. *
  7065. * @param {Object} object
  7066. * @return {Ext.Class} class
  7067. */
  7068. getClass: function(object) {
  7069. return object && object.self || null;
  7070. },
  7071. /**
  7072. * Defines a class.
  7073. * @deprecated 4.1.0 Use {@link Ext#define} instead, as that also supports creating overrides.
  7074. */
  7075. create: function(className, data, createdFn) {
  7076. if (className != null && typeof className != 'string') {
  7077. throw new Error("[Ext.define] Invalid class name '" + className + "' specified, must be a non-empty string");
  7078. }
  7079. var ctor = makeCtor();
  7080. if (typeof data == 'function') {
  7081. data = data(ctor);
  7082. }
  7083. if (className) {
  7084. ctor.displayName = className;
  7085. }
  7086. data.$className = className;
  7087. return new Class(ctor, data, function() {
  7088. var postprocessorStack = data.postprocessors || Manager.defaultPostprocessors,
  7089. registeredPostprocessors = Manager.postprocessors,
  7090. postprocessors = [],
  7091. postprocessor, i, ln, j, subLn, postprocessorProperties, postprocessorProperty;
  7092. delete data.postprocessors;
  7093. for (i = 0,ln = postprocessorStack.length; i < ln; i++) {
  7094. postprocessor = postprocessorStack[i];
  7095. if (typeof postprocessor == 'string') {
  7096. postprocessor = registeredPostprocessors[postprocessor];
  7097. postprocessorProperties = postprocessor.properties;
  7098. if (postprocessorProperties === true) {
  7099. postprocessors.push(postprocessor.fn);
  7100. }
  7101. else if (postprocessorProperties) {
  7102. for (j = 0,subLn = postprocessorProperties.length; j < subLn; j++) {
  7103. postprocessorProperty = postprocessorProperties[j];
  7104. if (data.hasOwnProperty(postprocessorProperty)) {
  7105. postprocessors.push(postprocessor.fn);
  7106. break;
  7107. }
  7108. }
  7109. }
  7110. }
  7111. else {
  7112. postprocessors.push(postprocessor);
  7113. }
  7114. }
  7115. data.postprocessors = postprocessors;
  7116. data.createdFn = createdFn;
  7117. Manager.processCreate(className, this, data);
  7118. });
  7119. },
  7120. processCreate: function(className, cls, clsData){
  7121. var me = this,
  7122. postprocessor = clsData.postprocessors.shift(),
  7123. createdFn = clsData.createdFn;
  7124. if (!postprocessor) {
  7125. if (className) {
  7126. me.set(className, cls);
  7127. }
  7128. if (createdFn) {
  7129. createdFn.call(cls, cls);
  7130. }
  7131. if (className) {
  7132. me.triggerCreated(className);
  7133. }
  7134. return;
  7135. }
  7136. if (postprocessor.call(me, className, cls, clsData, me.processCreate) !== false) {
  7137. me.processCreate(className, cls, clsData);
  7138. }
  7139. },
  7140. createOverride: function (className, data, createdFn) {
  7141. var me = this,
  7142. overriddenClassName = data.override,
  7143. requires = data.requires,
  7144. uses = data.uses,
  7145. classReady = function () {
  7146. var cls, temp;
  7147. if (requires) {
  7148. temp = requires;
  7149. requires = null; // do the real thing next time (which may be now)
  7150. // Since the override is going to be used (its target class is now
  7151. // created), we need to fetch the required classes for the override
  7152. // and call us back once they are loaded:
  7153. Ext.Loader.require(temp, classReady);
  7154. } else {
  7155. // The target class and the required classes for this override are
  7156. // ready, so we can apply the override now:
  7157. cls = me.get(overriddenClassName);
  7158. // We don't want to apply these:
  7159. delete data.override;
  7160. delete data.requires;
  7161. delete data.uses;
  7162. Ext.override(cls, data);
  7163. // This pushes the overridding file itself into Ext.Loader.history
  7164. // Hence if the target class never exists, the overriding file will
  7165. // never be included in the build.
  7166. me.triggerCreated(className);
  7167. if (uses) {
  7168. Ext.Loader.addUsedClasses(uses); // get these classes too!
  7169. }
  7170. if (createdFn) {
  7171. createdFn.call(cls); // last but not least!
  7172. }
  7173. }
  7174. };
  7175. me.existCache[className] = true;
  7176. // Override the target class right after it's created
  7177. me.onCreated(classReady, me, overriddenClassName);
  7178. return me;
  7179. },
  7180. /**
  7181. * Instantiate a class by its alias; usually invoked by the convenient shorthand {@link Ext#createByAlias Ext.createByAlias}
  7182. * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
  7183. * attempt to load the class via synchronous loading.
  7184. *
  7185. * var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
  7186. *
  7187. * @param {String} alias
  7188. * @param {Object...} args Additional arguments after the alias will be passed to the
  7189. * class constructor.
  7190. * @return {Object} instance
  7191. */
  7192. instantiateByAlias: function() {
  7193. var alias = arguments[0],
  7194. args = arraySlice.call(arguments),
  7195. className = this.getNameByAlias(alias);
  7196. if (!className) {
  7197. className = this.maps.aliasToName[alias];
  7198. if (!className) {
  7199. throw new Error("[Ext.createByAlias] Cannot create an instance of unrecognized alias: " + alias);
  7200. }
  7201. if (global.console) {
  7202. global.console.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
  7203. "Ext.require('" + alias + "') above Ext.onReady");
  7204. }
  7205. Ext.syncRequire(className);
  7206. }
  7207. args[0] = className;
  7208. return this.instantiate.apply(this, args);
  7209. },
  7210. /**
  7211. * @private
  7212. */
  7213. instantiate: function() {
  7214. var name = arguments[0],
  7215. nameType = typeof name,
  7216. args = arraySlice.call(arguments, 1),
  7217. alias = name,
  7218. possibleName, cls;
  7219. if (nameType != 'function') {
  7220. if (nameType != 'string' && args.length === 0) {
  7221. args = [name];
  7222. name = name.xclass;
  7223. }
  7224. if (typeof name != 'string' || name.length < 1) {
  7225. throw new Error("[Ext.create] Invalid class name or alias '" + name + "' specified, must be a non-empty string");
  7226. }
  7227. cls = this.get(name);
  7228. }
  7229. else {
  7230. cls = name;
  7231. }
  7232. // No record of this class name, it's possibly an alias, so look it up
  7233. if (!cls) {
  7234. possibleName = this.getNameByAlias(name);
  7235. if (possibleName) {
  7236. name = possibleName;
  7237. cls = this.get(name);
  7238. }
  7239. }
  7240. // Still no record of this class name, it's possibly an alternate name, so look it up
  7241. if (!cls) {
  7242. possibleName = this.getNameByAlternate(name);
  7243. if (possibleName) {
  7244. name = possibleName;
  7245. cls = this.get(name);
  7246. }
  7247. }
  7248. // Still not existing at this point, try to load it via synchronous mode as the last resort
  7249. if (!cls) {
  7250. if (global.console) {
  7251. global.console.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding " +
  7252. "Ext.require('" + ((possibleName) ? alias : name) + "') above Ext.onReady");
  7253. }
  7254. Ext.syncRequire(name);
  7255. cls = this.get(name);
  7256. }
  7257. if (!cls) {
  7258. throw new Error("[Ext.create] Cannot create an instance of unrecognized class name / alias: " + alias);
  7259. }
  7260. if (typeof cls != 'function') {
  7261. throw new Error("[Ext.create] '" + name + "' is a singleton and cannot be instantiated");
  7262. }
  7263. return this.getInstantiator(args.length)(cls, args);
  7264. },
  7265. /**
  7266. * @private
  7267. * @param name
  7268. * @param args
  7269. */
  7270. dynInstantiate: function(name, args) {
  7271. args = arrayFrom(args, true);
  7272. args.unshift(name);
  7273. return this.instantiate.apply(this, args);
  7274. },
  7275. /**
  7276. * @private
  7277. * @param length
  7278. */
  7279. getInstantiator: function(length) {
  7280. var instantiators = this.instantiators,
  7281. instantiator,
  7282. i,
  7283. args;
  7284. instantiator = instantiators[length];
  7285. if (!instantiator) {
  7286. i = length;
  7287. args = [];
  7288. for (i = 0; i < length; i++) {
  7289. args.push('a[' + i + ']');
  7290. }
  7291. instantiator = instantiators[length] = new Function('c', 'a', 'return new c(' + args.join(',') + ')');
  7292. instantiator.displayName = "Ext.ClassManager.instantiate" + length;
  7293. }
  7294. return instantiator;
  7295. },
  7296. /**
  7297. * @private
  7298. */
  7299. postprocessors: {},
  7300. /**
  7301. * @private
  7302. */
  7303. defaultPostprocessors: [],
  7304. /**
  7305. * Register a post-processor function.
  7306. *
  7307. * @private
  7308. * @param {String} name
  7309. * @param {Function} postprocessor
  7310. */
  7311. registerPostprocessor: function(name, fn, properties, position, relativeTo) {
  7312. if (!position) {
  7313. position = 'last';
  7314. }
  7315. if (!properties) {
  7316. properties = [name];
  7317. }
  7318. this.postprocessors[name] = {
  7319. name: name,
  7320. properties: properties || false,
  7321. fn: fn
  7322. };
  7323. this.setDefaultPostprocessorPosition(name, position, relativeTo);
  7324. return this;
  7325. },
  7326. /**
  7327. * Set the default post processors array stack which are applied to every class.
  7328. *
  7329. * @private
  7330. * @param {String/Array} The name of a registered post processor or an array of registered names.
  7331. * @return {Ext.ClassManager} this
  7332. */
  7333. setDefaultPostprocessors: function(postprocessors) {
  7334. this.defaultPostprocessors = arrayFrom(postprocessors);
  7335. return this;
  7336. },
  7337. /**
  7338. * Insert this post-processor at a specific position in the stack, optionally relative to
  7339. * any existing post-processor
  7340. *
  7341. * @private
  7342. * @param {String} name The post-processor name. Note that it needs to be registered with
  7343. * {@link Ext.ClassManager#registerPostprocessor} before this
  7344. * @param {String} offset The insertion position. Four possible values are:
  7345. * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
  7346. * @param {String} relativeName
  7347. * @return {Ext.ClassManager} this
  7348. */
  7349. setDefaultPostprocessorPosition: function(name, offset, relativeName) {
  7350. var defaultPostprocessors = this.defaultPostprocessors,
  7351. index;
  7352. if (typeof offset == 'string') {
  7353. if (offset === 'first') {
  7354. defaultPostprocessors.unshift(name);
  7355. return this;
  7356. }
  7357. else if (offset === 'last') {
  7358. defaultPostprocessors.push(name);
  7359. return this;
  7360. }
  7361. offset = (offset === 'after') ? 1 : -1;
  7362. }
  7363. index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
  7364. if (index !== -1) {
  7365. Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
  7366. }
  7367. return this;
  7368. },
  7369. /**
  7370. * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
  7371. * or class names. Expressions support wildcards:
  7372. *
  7373. * // returns ['Ext.window.Window']
  7374. * var window = Ext.ClassManager.getNamesByExpression('widget.window');
  7375. *
  7376. * // returns ['widget.panel', 'widget.window', ...]
  7377. * var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
  7378. *
  7379. * // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
  7380. * var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
  7381. *
  7382. * @param {String} expression
  7383. * @return {String[]} classNames
  7384. */
  7385. getNamesByExpression: function(expression) {
  7386. var nameToAliasesMap = this.maps.nameToAliases,
  7387. names = [],
  7388. name, alias, aliases, possibleName, regex, i, ln;
  7389. if (typeof expression != 'string' || expression.length < 1) {
  7390. throw new Error("[Ext.ClassManager.getNamesByExpression] Expression " + expression + " is invalid, must be a non-empty string");
  7391. }
  7392. if (expression.indexOf('*') !== -1) {
  7393. expression = expression.replace(/\*/g, '(.*?)');
  7394. regex = new RegExp('^' + expression + '$');
  7395. for (name in nameToAliasesMap) {
  7396. if (nameToAliasesMap.hasOwnProperty(name)) {
  7397. aliases = nameToAliasesMap[name];
  7398. if (name.search(regex) !== -1) {
  7399. names.push(name);
  7400. }
  7401. else {
  7402. for (i = 0, ln = aliases.length; i < ln; i++) {
  7403. alias = aliases[i];
  7404. if (alias.search(regex) !== -1) {
  7405. names.push(name);
  7406. break;
  7407. }
  7408. }
  7409. }
  7410. }
  7411. }
  7412. } else {
  7413. possibleName = this.getNameByAlias(expression);
  7414. if (possibleName) {
  7415. names.push(possibleName);
  7416. } else {
  7417. possibleName = this.getNameByAlternate(expression);
  7418. if (possibleName) {
  7419. names.push(possibleName);
  7420. } else {
  7421. names.push(expression);
  7422. }
  7423. }
  7424. }
  7425. return names;
  7426. }
  7427. };
  7428. /**
  7429. * @cfg {String[]} alias
  7430. * @member Ext.Class
  7431. * List of short aliases for class names. Most useful for defining xtypes for widgets:
  7432. *
  7433. * Ext.define('MyApp.CoolPanel', {
  7434. * extend: 'Ext.panel.Panel',
  7435. * alias: ['widget.coolpanel'],
  7436. * title: 'Yeah!'
  7437. * });
  7438. *
  7439. * // Using Ext.create
  7440. * Ext.create('widget.coolpanel');
  7441. *
  7442. * // Using the shorthand for defining widgets by xtype
  7443. * Ext.widget('panel', {
  7444. * items: [
  7445. * {xtype: 'coolpanel', html: 'Foo'},
  7446. * {xtype: 'coolpanel', html: 'Bar'}
  7447. * ]
  7448. * });
  7449. *
  7450. * Besides "widget" for xtype there are alias namespaces like "feature" for ftype and "plugin" for ptype.
  7451. */
  7452. Manager.registerPostprocessor('alias', function(name, cls, data) {
  7453. var aliases = data.alias,
  7454. i, ln;
  7455. for (i = 0,ln = aliases.length; i < ln; i++) {
  7456. alias = aliases[i];
  7457. this.setAlias(cls, alias);
  7458. }
  7459. }, ['xtype', 'alias']);
  7460. /**
  7461. * @cfg {Boolean} singleton
  7462. * @member Ext.Class
  7463. * When set to true, the class will be instantiated as singleton. For example:
  7464. *
  7465. * Ext.define('Logger', {
  7466. * singleton: true,
  7467. * log: function(msg) {
  7468. * console.log(msg);
  7469. * }
  7470. * });
  7471. *
  7472. * Logger.log('Hello');
  7473. */
  7474. Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
  7475. fn.call(this, name, new cls(), data);
  7476. return false;
  7477. });
  7478. /**
  7479. * @cfg {String/String[]} alternateClassName
  7480. * @member Ext.Class
  7481. * Defines alternate names for this class. For example:
  7482. *
  7483. * Ext.define('Developer', {
  7484. * alternateClassName: ['Coder', 'Hacker'],
  7485. * code: function(msg) {
  7486. * alert('Typing... ' + msg);
  7487. * }
  7488. * });
  7489. *
  7490. * var joe = Ext.create('Developer');
  7491. * joe.code('stackoverflow');
  7492. *
  7493. * var rms = Ext.create('Hacker');
  7494. * rms.code('hack hack');
  7495. */
  7496. Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
  7497. var alternates = data.alternateClassName,
  7498. i, ln, alternate;
  7499. if (!(alternates instanceof Array)) {
  7500. alternates = [alternates];
  7501. }
  7502. for (i = 0, ln = alternates.length; i < ln; i++) {
  7503. alternate = alternates[i];
  7504. if (typeof alternate != 'string') {
  7505. throw new Error("[Ext.define] Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string");
  7506. }
  7507. this.set(alternate, cls);
  7508. }
  7509. });
  7510. Ext.apply(Ext, {
  7511. /**
  7512. * Instantiate a class by either full name, alias or alternate name.
  7513. *
  7514. * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has
  7515. * not been defined yet, it will attempt to load the class via synchronous loading.
  7516. *
  7517. * For example, all these three lines return the same result:
  7518. *
  7519. * // alias
  7520. * var window = Ext.create('widget.window', {
  7521. * width: 600,
  7522. * height: 800,
  7523. * ...
  7524. * });
  7525. *
  7526. * // alternate name
  7527. * var window = Ext.create('Ext.Window', {
  7528. * width: 600,
  7529. * height: 800,
  7530. * ...
  7531. * });
  7532. *
  7533. * // full class name
  7534. * var window = Ext.create('Ext.window.Window', {
  7535. * width: 600,
  7536. * height: 800,
  7537. * ...
  7538. * });
  7539. *
  7540. * // single object with xclass property:
  7541. * var window = Ext.create({
  7542. * xclass: 'Ext.window.Window', // any valid value for 'name' (above)
  7543. * width: 600,
  7544. * height: 800,
  7545. * ...
  7546. * });
  7547. *
  7548. * @param {String} [name] The class name or alias. Can be specified as `xclass`
  7549. * property if only one object parameter is specified.
  7550. * @param {Object...} [args] Additional arguments after the name will be passed to
  7551. * the class' constructor.
  7552. * @return {Object} instance
  7553. * @member Ext
  7554. * @method create
  7555. */
  7556. create: alias(Manager, 'instantiate'),
  7557. /**
  7558. * Convenient shorthand to create a widget by its xtype or a config object.
  7559. * See also {@link Ext.ClassManager#instantiateByAlias}.
  7560. *
  7561. * var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button');
  7562. *
  7563. * var panel = Ext.widget('panel', { // Equivalent to Ext.create('widget.panel')
  7564. * title: 'Panel'
  7565. * });
  7566. *
  7567. * var grid = Ext.widget({
  7568. * xtype: 'grid',
  7569. * ...
  7570. * });
  7571. *
  7572. * If a {@link Ext.Component component} instance is passed, it is simply returned.
  7573. *
  7574. * @member Ext
  7575. * @param {String} [name] The xtype of the widget to create.
  7576. * @param {Object} [config] The configuration object for the widget constructor.
  7577. * @return {Object} The widget instance
  7578. */
  7579. widget: function(name, config) {
  7580. // forms:
  7581. // 1: (xtype)
  7582. // 2: (xtype, config)
  7583. // 3: (config)
  7584. // 4: (xtype, component)
  7585. // 5: (component)
  7586. //
  7587. var xtype = name,
  7588. alias, className, T, load;
  7589. if (typeof xtype != 'string') { // if (form 3 or 5)
  7590. // first arg is config or component
  7591. config = name; // arguments[0]
  7592. xtype = config.xtype;
  7593. } else {
  7594. config = config || {};
  7595. }
  7596. if (config.isComponent) {
  7597. return config;
  7598. }
  7599. alias = 'widget.' + xtype;
  7600. className = Manager.getNameByAlias(alias);
  7601. // this is needed to support demand loading of the class
  7602. if (!className) {
  7603. load = true;
  7604. }
  7605. T = Manager.get(className);
  7606. if (load || !T) {
  7607. return Manager.instantiateByAlias(alias, config);
  7608. }
  7609. return new T(config);
  7610. },
  7611. /**
  7612. * Convenient shorthand, see {@link Ext.ClassManager#instantiateByAlias}
  7613. * @member Ext
  7614. * @method createByAlias
  7615. */
  7616. createByAlias: alias(Manager, 'instantiateByAlias'),
  7617. /**
  7618. * @method
  7619. * Defines a class or override. A basic class is defined like this:
  7620. *
  7621. * Ext.define('My.awesome.Class', {
  7622. * someProperty: 'something',
  7623. *
  7624. * someMethod: function(s) {
  7625. * alert(s + this.someProperty);
  7626. * }
  7627. *
  7628. * ...
  7629. * });
  7630. *
  7631. * var obj = new My.awesome.Class();
  7632. *
  7633. * obj.someMethod('Say '); // alerts 'Say something'
  7634. *
  7635. * To create an anonymous class, pass `null` for the `className`:
  7636. *
  7637. * Ext.define(null, {
  7638. * constructor: function () {
  7639. * // ...
  7640. * }
  7641. * });
  7642. *
  7643. * In some cases, it is helpful to create a nested scope to contain some private
  7644. * properties. The best way to do this is to pass a function instead of an object
  7645. * as the second parameter. This function will be called to produce the class
  7646. * body:
  7647. *
  7648. * Ext.define('MyApp.foo.Bar', function () {
  7649. * var id = 0;
  7650. *
  7651. * return {
  7652. * nextId: function () {
  7653. * return ++id;
  7654. * }
  7655. * };
  7656. * });
  7657. *
  7658. * When using this form of `Ext.define`, the function is passed a reference to its
  7659. * class. This can be used as an efficient way to access any static properties you
  7660. * may have:
  7661. *
  7662. * Ext.define('MyApp.foo.Bar', function (Bar) {
  7663. * return {
  7664. * statics: {
  7665. * staticMethod: function () {
  7666. * // ...
  7667. * }
  7668. * },
  7669. *
  7670. * method: function () {
  7671. * return Bar.staticMethod();
  7672. * }
  7673. * };
  7674. * });
  7675. *
  7676. * To define an override, include the `override` property. The content of an
  7677. * override is aggregated with the specified class in order to extend or modify
  7678. * that class. This can be as simple as setting default property values or it can
  7679. * extend and/or replace methods. This can also extend the statics of the class.
  7680. *
  7681. * One use for an override is to break a large class into manageable pieces.
  7682. *
  7683. * // File: /src/app/Panel.js
  7684. *
  7685. * Ext.define('My.app.Panel', {
  7686. * extend: 'Ext.panel.Panel',
  7687. * requires: [
  7688. * 'My.app.PanelPart2',
  7689. * 'My.app.PanelPart3'
  7690. * ]
  7691. *
  7692. * constructor: function (config) {
  7693. * this.callParent(arguments); // calls Ext.panel.Panel's constructor
  7694. * //...
  7695. * },
  7696. *
  7697. * statics: {
  7698. * method: function () {
  7699. * return 'abc';
  7700. * }
  7701. * }
  7702. * });
  7703. *
  7704. * // File: /src/app/PanelPart2.js
  7705. * Ext.define('My.app.PanelPart2', {
  7706. * override: 'My.app.Panel',
  7707. *
  7708. * constructor: function (config) {
  7709. * this.callParent(arguments); // calls My.app.Panel's constructor
  7710. * //...
  7711. * }
  7712. * });
  7713. *
  7714. * Another use of overrides is to provide optional parts of classes that can be
  7715. * independently required. In this case, the class may even be unaware of the
  7716. * override altogether.
  7717. *
  7718. * Ext.define('My.ux.CoolTip', {
  7719. * override: 'Ext.tip.ToolTip',
  7720. *
  7721. * constructor: function (config) {
  7722. * this.callParent(arguments); // calls Ext.tip.ToolTip's constructor
  7723. * //...
  7724. * }
  7725. * });
  7726. *
  7727. * The above override can now be required as normal.
  7728. *
  7729. * Ext.define('My.app.App', {
  7730. * requires: [
  7731. * 'My.ux.CoolTip'
  7732. * ]
  7733. * });
  7734. *
  7735. * Overrides can also contain statics:
  7736. *
  7737. * Ext.define('My.app.BarMod', {
  7738. * override: 'Ext.foo.Bar',
  7739. *
  7740. * statics: {
  7741. * method: function (x) {
  7742. * return this.callParent([x * 2]); // call Ext.foo.Bar.method
  7743. * }
  7744. * }
  7745. * });
  7746. *
  7747. * IMPORTANT: An override is only included in a build if the class it overrides is
  7748. * required. Otherwise, the override, like the target class, is not included.
  7749. *
  7750. * @param {String} className The class name to create in string dot-namespaced format, for example:
  7751. * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
  7752. * It is highly recommended to follow this simple convention:
  7753. * - The root and the class name are 'CamelCased'
  7754. * - Everything else is lower-cased
  7755. * Pass `null` to create an anonymous class.
  7756. * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of any valid
  7757. * strings, except those in the reserved listed below:
  7758. * - `mixins`
  7759. * - `statics`
  7760. * - `config`
  7761. * - `alias`
  7762. * - `self`
  7763. * - `singleton`
  7764. * - `alternateClassName`
  7765. * - `override`
  7766. *
  7767. * @param {Function} createdFn Optional callback to execute after the class is created, the execution scope of which
  7768. * (`this`) will be the newly created class itself.
  7769. * @return {Ext.Base}
  7770. * @markdown
  7771. * @member Ext
  7772. * @method define
  7773. */
  7774. define: function (className, data, createdFn) {
  7775. if (data.override) {
  7776. return Manager.createOverride.apply(Manager, arguments);
  7777. }
  7778. return Manager.create.apply(Manager, arguments);
  7779. },
  7780. /**
  7781. * Convenient shorthand, see {@link Ext.ClassManager#getName}
  7782. * @member Ext
  7783. * @method getClassName
  7784. */
  7785. getClassName: alias(Manager, 'getName'),
  7786. /**
  7787. * Returns the displayName property or className or object. When all else fails, returns "Anonymous".
  7788. * @param {Object} object
  7789. * @return {String}
  7790. */
  7791. getDisplayName: function(object) {
  7792. if (object) {
  7793. if (object.displayName) {
  7794. return object.displayName;
  7795. }
  7796. if (object.$name && object.$class) {
  7797. return Ext.getClassName(object.$class) + '#' + object.$name;
  7798. }
  7799. if (object.$className) {
  7800. return object.$className;
  7801. }
  7802. }
  7803. return 'Anonymous';
  7804. },
  7805. /**
  7806. * Convenient shorthand, see {@link Ext.ClassManager#getClass}
  7807. * @member Ext
  7808. * @method getClass
  7809. */
  7810. getClass: alias(Manager, 'getClass'),
  7811. /**
  7812. * Creates namespaces to be used for scoping variables and classes so that they are not global.
  7813. * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
  7814. *
  7815. * Ext.namespace('Company', 'Company.data');
  7816. *
  7817. * // equivalent and preferable to the above syntax
  7818. * Ext.ns('Company.data');
  7819. *
  7820. * Company.Widget = function() { ... };
  7821. *
  7822. * Company.data.CustomStore = function(config) { ... };
  7823. *
  7824. * @param {String...} namespaces
  7825. * @return {Object} The namespace object.
  7826. * (If multiple arguments are passed, this will be the last namespace created)
  7827. * @member Ext
  7828. * @method namespace
  7829. */
  7830. namespace: alias(Manager, 'createNamespaces')
  7831. });
  7832. /**
  7833. * Old name for {@link Ext#widget}.
  7834. * @deprecated 4.0.0 Use {@link Ext#widget} instead.
  7835. * @method createWidget
  7836. * @member Ext
  7837. */
  7838. Ext.createWidget = Ext.widget;
  7839. /**
  7840. * Convenient alias for {@link Ext#namespace Ext.namespace}.
  7841. * @inheritdoc Ext#namespace
  7842. * @member Ext
  7843. * @method ns
  7844. */
  7845. Ext.ns = Ext.namespace;
  7846. Class.registerPreprocessor('className', function(cls, data) {
  7847. if (data.$className) {
  7848. cls.$className = data.$className;
  7849. cls.displayName = cls.$className;
  7850. }
  7851. }, true, 'first');
  7852. Class.registerPreprocessor('alias', function(cls, data) {
  7853. var prototype = cls.prototype,
  7854. xtypes = arrayFrom(data.xtype),
  7855. aliases = arrayFrom(data.alias),
  7856. widgetPrefix = 'widget.',
  7857. widgetPrefixLength = widgetPrefix.length,
  7858. xtypesChain = Array.prototype.slice.call(prototype.xtypesChain || []),
  7859. xtypesMap = Ext.merge({}, prototype.xtypesMap || {}),
  7860. i, ln, alias, xtype;
  7861. for (i = 0,ln = aliases.length; i < ln; i++) {
  7862. alias = aliases[i];
  7863. if (typeof alias != 'string' || alias.length < 1) {
  7864. throw new Error("[Ext.define] Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string");
  7865. }
  7866. if (alias.substring(0, widgetPrefixLength) === widgetPrefix) {
  7867. xtype = alias.substring(widgetPrefixLength);
  7868. Ext.Array.include(xtypes, xtype);
  7869. }
  7870. }
  7871. cls.xtype = data.xtype = xtypes[0];
  7872. data.xtypes = xtypes;
  7873. for (i = 0,ln = xtypes.length; i < ln; i++) {
  7874. xtype = xtypes[i];
  7875. if (!xtypesMap[xtype]) {
  7876. xtypesMap[xtype] = true;
  7877. xtypesChain.push(xtype);
  7878. }
  7879. }
  7880. data.xtypesChain = xtypesChain;
  7881. data.xtypesMap = xtypesMap;
  7882. Ext.Function.interceptAfter(data, 'onClassCreated', function() {
  7883. var mixins = prototype.mixins,
  7884. key, mixin;
  7885. for (key in mixins) {
  7886. if (mixins.hasOwnProperty(key)) {
  7887. mixin = mixins[key];
  7888. xtypes = mixin.xtypes;
  7889. if (xtypes) {
  7890. for (i = 0,ln = xtypes.length; i < ln; i++) {
  7891. xtype = xtypes[i];
  7892. if (!xtypesMap[xtype]) {
  7893. xtypesMap[xtype] = true;
  7894. xtypesChain.push(xtype);
  7895. }
  7896. }
  7897. }
  7898. }
  7899. }
  7900. });
  7901. for (i = 0,ln = xtypes.length; i < ln; i++) {
  7902. xtype = xtypes[i];
  7903. if (typeof xtype != 'string' || xtype.length < 1) {
  7904. throw new Error("[Ext.define] Invalid xtype of: '" + xtype + "' for class: '" + name + "'; must be a valid non-empty string");
  7905. }
  7906. Ext.Array.include(aliases, widgetPrefix + xtype);
  7907. }
  7908. data.alias = aliases;
  7909. }, ['xtype', 'alias']);
  7910. }(Ext.Class, Ext.Function.alias, Array.prototype.slice, Ext.Array.from, Ext.global));
  7911. //@tag foundation,core
  7912. //@require ClassManager.js
  7913. //@define Ext.Loader
  7914. /**
  7915. * @author Jacky Nguyen <jacky@sencha.com>
  7916. * @docauthor Jacky Nguyen <jacky@sencha.com>
  7917. * @class Ext.Loader
  7918. *
  7919. * Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
  7920. * via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
  7921. * approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons of each approach:
  7922. *
  7923. * # Asynchronous Loading #
  7924. *
  7925. * - Advantages:
  7926. * + Cross-domain
  7927. * + No web server needed: you can run the application via the file system protocol (i.e: `file://path/to/your/index
  7928. * .html`)
  7929. * + Best possible debugging experience: error messages come with the exact file name and line number
  7930. *
  7931. * - Disadvantages:
  7932. * + Dependencies need to be specified before-hand
  7933. *
  7934. * ### Method 1: Explicitly include what you need: ###
  7935. *
  7936. * // Syntax
  7937. * Ext.require({String/Array} expressions);
  7938. *
  7939. * // Example: Single alias
  7940. * Ext.require('widget.window');
  7941. *
  7942. * // Example: Single class name
  7943. * Ext.require('Ext.window.Window');
  7944. *
  7945. * // Example: Multiple aliases / class names mix
  7946. * Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
  7947. *
  7948. * // Wildcards
  7949. * Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
  7950. *
  7951. * ### Method 2: Explicitly exclude what you don't need: ###
  7952. *
  7953. * // Syntax: Note that it must be in this chaining format.
  7954. * Ext.exclude({String/Array} expressions)
  7955. * .require({String/Array} expressions);
  7956. *
  7957. * // Include everything except Ext.data.*
  7958. * Ext.exclude('Ext.data.*').require('*');
  7959. *
  7960. * // Include all widgets except widget.checkbox*,
  7961. * // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
  7962. * Ext.exclude('widget.checkbox*').require('widget.*');
  7963. *
  7964. * # Synchronous Loading on Demand #
  7965. *
  7966. * - Advantages:
  7967. * + There's no need to specify dependencies before-hand, which is always the convenience of including ext-all.js
  7968. * before
  7969. *
  7970. * - Disadvantages:
  7971. * + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
  7972. * + Must be from the same domain due to XHR restriction
  7973. * + Need a web server, same reason as above
  7974. *
  7975. * There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
  7976. *
  7977. * Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
  7978. *
  7979. * Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
  7980. *
  7981. * Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
  7982. *
  7983. * Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
  7984. * existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load the given
  7985. * class and all its dependencies.
  7986. *
  7987. * # Hybrid Loading - The Best of Both Worlds #
  7988. *
  7989. * It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
  7990. *
  7991. * ### Step 1: Start writing your application using synchronous approach.
  7992. *
  7993. * Ext.Loader will automatically fetch all dependencies on demand as they're needed during run-time. For example:
  7994. *
  7995. * Ext.onReady(function(){
  7996. * var window = Ext.widget('window', {
  7997. * width: 500,
  7998. * height: 300,
  7999. * layout: {
  8000. * type: 'border',
  8001. * padding: 5
  8002. * },
  8003. * title: 'Hello Dialog',
  8004. * items: [{
  8005. * title: 'Navigation',
  8006. * collapsible: true,
  8007. * region: 'west',
  8008. * width: 200,
  8009. * html: 'Hello',
  8010. * split: true
  8011. * }, {
  8012. * title: 'TabPanel',
  8013. * region: 'center'
  8014. * }]
  8015. * });
  8016. *
  8017. * window.show();
  8018. * })
  8019. *
  8020. * ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these: ###
  8021. *
  8022. * [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code
  8023. * ClassManager.js:432
  8024. * [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
  8025. *
  8026. * Simply copy and paste the suggested code above `Ext.onReady`, i.e:
  8027. *
  8028. * Ext.require('Ext.window.Window');
  8029. * Ext.require('Ext.layout.container.Border');
  8030. *
  8031. * Ext.onReady(...);
  8032. *
  8033. * Everything should now load via asynchronous mode.
  8034. *
  8035. * # Deployment #
  8036. *
  8037. * It's important to note that dynamic loading should only be used during development on your local machines.
  8038. * During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
  8039. * the whole process of transitioning from / to between development / maintenance and production as easy as
  8040. * possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies your application
  8041. * needs in the exact loading sequence. It's as simple as concatenating all files in this array into one,
  8042. * then include it on top of your application.
  8043. *
  8044. * This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
  8045. *
  8046. * @singleton
  8047. */
  8048. Ext.Loader = new function() {
  8049. var Loader = this,
  8050. Manager = Ext.ClassManager,
  8051. Class = Ext.Class,
  8052. flexSetter = Ext.Function.flexSetter,
  8053. alias = Ext.Function.alias,
  8054. pass = Ext.Function.pass,
  8055. defer = Ext.Function.defer,
  8056. arrayErase = Ext.Array.erase,
  8057. dependencyProperties = ['extend', 'mixins', 'requires'],
  8058. isInHistory = {},
  8059. history = [],
  8060. slashDotSlashRe = /\/\.\//g,
  8061. dotRe = /\./g;
  8062. Ext.apply(Loader, {
  8063. /**
  8064. * @private
  8065. */
  8066. isInHistory: isInHistory,
  8067. /**
  8068. * An array of class names to keep track of the dependency loading order.
  8069. * This is not guaranteed to be the same everytime due to the asynchronous
  8070. * nature of the Loader.
  8071. *
  8072. * @property {Array} history
  8073. */
  8074. history: history,
  8075. /**
  8076. * Configuration
  8077. * @private
  8078. */
  8079. config: {
  8080. /**
  8081. * @cfg {Boolean} enabled
  8082. * Whether or not to enable the dynamic dependency loading feature.
  8083. */
  8084. enabled: false,
  8085. /**
  8086. * @cfg {Boolean} scriptChainDelay
  8087. * millisecond delay between asynchronous script injection (prevents stack overflow on some user agents)
  8088. * 'false' disables delay but potentially increases stack load.
  8089. */
  8090. scriptChainDelay : false,
  8091. /**
  8092. * @cfg {Boolean} disableCaching
  8093. * Appends current timestamp to script files to prevent caching.
  8094. */
  8095. disableCaching: true,
  8096. /**
  8097. * @cfg {String} disableCachingParam
  8098. * The get parameter name for the cache buster's timestamp.
  8099. */
  8100. disableCachingParam: '_dc',
  8101. /**
  8102. * @cfg {Boolean} garbageCollect
  8103. * True to prepare an asynchronous script tag for garbage collection (effective only
  8104. * if {@link #preserveScripts preserveScripts} is false)
  8105. */
  8106. garbageCollect : false,
  8107. /**
  8108. * @cfg {Object} paths
  8109. * The mapping from namespaces to file paths
  8110. *
  8111. * {
  8112. * 'Ext': '.', // This is set by default, Ext.layout.container.Container will be
  8113. * // loaded from ./layout/Container.js
  8114. *
  8115. * 'My': './src/my_own_folder' // My.layout.Container will be loaded from
  8116. * // ./src/my_own_folder/layout/Container.js
  8117. * }
  8118. *
  8119. * Note that all relative paths are relative to the current HTML document.
  8120. * If not being specified, for example, <code>Other.awesome.Class</code>
  8121. * will simply be loaded from <code>./Other/awesome/Class.js</code>
  8122. */
  8123. paths: {
  8124. 'Ext': '.'
  8125. },
  8126. /**
  8127. * @cfg {Boolean} preserveScripts
  8128. * False to remove and optionally {@link #garbageCollect garbage-collect} asynchronously loaded scripts,
  8129. * True to retain script element for browser debugger compatibility and improved load performance.
  8130. */
  8131. preserveScripts : true,
  8132. /**
  8133. * @cfg {String} scriptCharset
  8134. * Optional charset to specify encoding of dynamic script content.
  8135. */
  8136. scriptCharset : undefined
  8137. },
  8138. /**
  8139. * Set the configuration for the loader. This should be called right after ext-(debug).js
  8140. * is included in the page, and before Ext.onReady. i.e:
  8141. *
  8142. * <script type="text/javascript" src="ext-core-debug.js"></script>
  8143. * <script type="text/javascript">
  8144. * Ext.Loader.setConfig({
  8145. * enabled: true,
  8146. * paths: {
  8147. * 'My': 'my_own_path'
  8148. * }
  8149. * });
  8150. * </script>
  8151. * <script type="text/javascript">
  8152. * Ext.require(...);
  8153. *
  8154. * Ext.onReady(function() {
  8155. * // application code here
  8156. * });
  8157. * </script>
  8158. *
  8159. * Refer to config options of {@link Ext.Loader} for the list of possible properties
  8160. *
  8161. * @param {Object} config The config object to override the default values
  8162. * @return {Ext.Loader} this
  8163. */
  8164. setConfig: function(name, value) {
  8165. if (Ext.isObject(name) && arguments.length === 1) {
  8166. Ext.merge(Loader.config, name);
  8167. }
  8168. else {
  8169. Loader.config[name] = (Ext.isObject(value)) ? Ext.merge(Loader.config[name], value) : value;
  8170. }
  8171. return Loader;
  8172. },
  8173. /**
  8174. * Get the config value corresponding to the specified name. If no name is given, will return the config object
  8175. * @param {String} name The config property name
  8176. * @return {Object}
  8177. */
  8178. getConfig: function(name) {
  8179. if (name) {
  8180. return Loader.config[name];
  8181. }
  8182. return Loader.config;
  8183. },
  8184. /**
  8185. * Sets the path of a namespace.
  8186. * For Example:
  8187. *
  8188. * Ext.Loader.setPath('Ext', '.');
  8189. *
  8190. * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
  8191. * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
  8192. * @return {Ext.Loader} this
  8193. * @method
  8194. */
  8195. setPath: flexSetter(function(name, path) {
  8196. Loader.config.paths[name] = path;
  8197. return Loader;
  8198. }),
  8199. /**
  8200. * Sets a batch of path entries
  8201. *
  8202. * @param {Object } paths a set of className: path mappings
  8203. * @return {Ext.Loader} this
  8204. */
  8205. addClassPathMappings: function(paths) {
  8206. var name;
  8207. for(name in paths){
  8208. Loader.config.paths[name] = paths[name];
  8209. }
  8210. return Loader;
  8211. },
  8212. /**
  8213. * Translates a className to a file path by adding the
  8214. * the proper prefix and converting the .'s to /'s. For example:
  8215. *
  8216. * Ext.Loader.setPath('My', '/path/to/My');
  8217. *
  8218. * alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
  8219. *
  8220. * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
  8221. *
  8222. * Ext.Loader.setPath({
  8223. * 'My': '/path/to/lib',
  8224. * 'My.awesome': '/other/path/for/awesome/stuff',
  8225. * 'My.awesome.more': '/more/awesome/path'
  8226. * });
  8227. *
  8228. * alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
  8229. *
  8230. * alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
  8231. *
  8232. * alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
  8233. *
  8234. * alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
  8235. *
  8236. * @param {String} className
  8237. * @return {String} path
  8238. */
  8239. getPath: function(className) {
  8240. var path = '',
  8241. paths = Loader.config.paths,
  8242. prefix = Loader.getPrefix(className);
  8243. if (prefix.length > 0) {
  8244. if (prefix === className) {
  8245. return paths[prefix];
  8246. }
  8247. path = paths[prefix];
  8248. className = className.substring(prefix.length + 1);
  8249. }
  8250. if (path.length > 0) {
  8251. path += '/';
  8252. }
  8253. return path.replace(slashDotSlashRe, '/') + className.replace(dotRe, "/") + '.js';
  8254. },
  8255. /**
  8256. * @private
  8257. * @param {String} className
  8258. */
  8259. getPrefix: function(className) {
  8260. var paths = Loader.config.paths,
  8261. prefix, deepestPrefix = '';
  8262. if (paths.hasOwnProperty(className)) {
  8263. return className;
  8264. }
  8265. for (prefix in paths) {
  8266. if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
  8267. if (prefix.length > deepestPrefix.length) {
  8268. deepestPrefix = prefix;
  8269. }
  8270. }
  8271. }
  8272. return deepestPrefix;
  8273. },
  8274. /**
  8275. * @private
  8276. * @param {String} className
  8277. */
  8278. isAClassNameWithAKnownPrefix: function(className) {
  8279. var prefix = Loader.getPrefix(className);
  8280. // we can only say it's really a class if className is not equal to any known namespace
  8281. return prefix !== '' && prefix !== className;
  8282. },
  8283. /**
  8284. * Loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when
  8285. * finishes, within the optional scope. This method is aliased by {@link Ext#require Ext.require} for convenience
  8286. * @param {String/Array} expressions Can either be a string or an array of string
  8287. * @param {Function} fn (Optional) The callback function
  8288. * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
  8289. * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
  8290. */
  8291. require: function(expressions, fn, scope, excludes) {
  8292. if (fn) {
  8293. fn.call(scope);
  8294. }
  8295. },
  8296. /**
  8297. * Synchronously loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when finishes, within the optional scope. This method is aliased by {@link Ext#syncRequire} for convenience
  8298. * @param {String/Array} expressions Can either be a string or an array of string
  8299. * @param {Function} fn (Optional) The callback function
  8300. * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
  8301. * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
  8302. */
  8303. syncRequire: function() {},
  8304. /**
  8305. * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
  8306. * Can be chained with more `require` and `exclude` methods, eg:
  8307. *
  8308. * Ext.exclude('Ext.data.*').require('*');
  8309. *
  8310. * Ext.exclude('widget.button*').require('widget.*');
  8311. *
  8312. * @param {Array} excludes
  8313. * @return {Object} object contains `require` method for chaining
  8314. */
  8315. exclude: function(excludes) {
  8316. return {
  8317. require: function(expressions, fn, scope) {
  8318. return Loader.require(expressions, fn, scope, excludes);
  8319. },
  8320. syncRequire: function(expressions, fn, scope) {
  8321. return Loader.syncRequire(expressions, fn, scope, excludes);
  8322. }
  8323. };
  8324. },
  8325. /**
  8326. * Add a new listener to be executed when all required scripts are fully loaded
  8327. *
  8328. * @param {Function} fn The function callback to be executed
  8329. * @param {Object} scope The execution scope (<code>this</code>) of the callback function
  8330. * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
  8331. */
  8332. onReady: function(fn, scope, withDomReady, options) {
  8333. var oldFn;
  8334. if (withDomReady !== false && Ext.onDocumentReady) {
  8335. oldFn = fn;
  8336. fn = function() {
  8337. Ext.onDocumentReady(oldFn, scope, options);
  8338. };
  8339. }
  8340. fn.call(scope);
  8341. }
  8342. });
  8343. var queue = [],
  8344. isClassFileLoaded = {},
  8345. isFileLoaded = {},
  8346. classNameToFilePathMap = {},
  8347. scriptElements = {},
  8348. readyListeners = [],
  8349. usedClasses = [],
  8350. requiresMap = {};
  8351. Ext.apply(Loader, {
  8352. /**
  8353. * @private
  8354. */
  8355. documentHead: typeof document != 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
  8356. /**
  8357. * Flag indicating whether there are still files being loaded
  8358. * @private
  8359. */
  8360. isLoading: false,
  8361. /**
  8362. * Maintain the queue for all dependencies. Each item in the array is an object of the format:
  8363. *
  8364. * {
  8365. * requires: [...], // The required classes for this queue item
  8366. * callback: function() { ... } // The function to execute when all classes specified in requires exist
  8367. * }
  8368. *
  8369. * @private
  8370. */
  8371. queue: queue,
  8372. /**
  8373. * Maintain the list of files that have already been handled so that they never get double-loaded
  8374. * @private
  8375. */
  8376. isClassFileLoaded: isClassFileLoaded,
  8377. /**
  8378. * @private
  8379. */
  8380. isFileLoaded: isFileLoaded,
  8381. /**
  8382. * Maintain the list of listeners to execute when all required scripts are fully loaded
  8383. * @private
  8384. */
  8385. readyListeners: readyListeners,
  8386. /**
  8387. * Contains classes referenced in `uses` properties.
  8388. * @private
  8389. */
  8390. optionalRequires: usedClasses,
  8391. /**
  8392. * Map of fully qualified class names to an array of dependent classes.
  8393. * @private
  8394. */
  8395. requiresMap: requiresMap,
  8396. /**
  8397. * @private
  8398. */
  8399. numPendingFiles: 0,
  8400. /**
  8401. * @private
  8402. */
  8403. numLoadedFiles: 0,
  8404. /** @private */
  8405. hasFileLoadError: false,
  8406. /**
  8407. * @private
  8408. */
  8409. classNameToFilePathMap: classNameToFilePathMap,
  8410. /**
  8411. * The number of scripts loading via loadScript.
  8412. * @private
  8413. */
  8414. scriptsLoading: 0,
  8415. /**
  8416. * @private
  8417. */
  8418. syncModeEnabled: false,
  8419. scriptElements: scriptElements,
  8420. /**
  8421. * Refresh all items in the queue. If all dependencies for an item exist during looping,
  8422. * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
  8423. * empty
  8424. * @private
  8425. */
  8426. refreshQueue: function() {
  8427. var ln = queue.length,
  8428. i, item, j, requires;
  8429. // When the queue of loading classes reaches zero, trigger readiness
  8430. if (!ln && !Loader.scriptsLoading) {
  8431. return Loader.triggerReady();
  8432. }
  8433. for (i = 0; i < ln; i++) {
  8434. item = queue[i];
  8435. if (item) {
  8436. requires = item.requires;
  8437. // Don't bother checking when the number of files loaded
  8438. // is still less than the array length
  8439. if (requires.length > Loader.numLoadedFiles) {
  8440. continue;
  8441. }
  8442. // Remove any required classes that are loaded
  8443. for (j = 0; j < requires.length; ) {
  8444. if (Manager.isCreated(requires[j])) {
  8445. // Take out from the queue
  8446. arrayErase(requires, j, 1);
  8447. }
  8448. else {
  8449. j++;
  8450. }
  8451. }
  8452. // If we've ended up with no required classes, call the callback
  8453. if (item.requires.length === 0) {
  8454. arrayErase(queue, i, 1);
  8455. item.callback.call(item.scope);
  8456. Loader.refreshQueue();
  8457. break;
  8458. }
  8459. }
  8460. }
  8461. return Loader;
  8462. },
  8463. /**
  8464. * Inject a script element to document's head, call onLoad and onError accordingly
  8465. * @private
  8466. */
  8467. injectScriptElement: function(url, onLoad, onError, scope, charset) {
  8468. var script = document.createElement('script'),
  8469. dispatched = false,
  8470. config = Loader.config,
  8471. onLoadFn = function() {
  8472. if(!dispatched) {
  8473. dispatched = true;
  8474. script.onload = script.onreadystatechange = script.onerror = null;
  8475. if (typeof config.scriptChainDelay == 'number') {
  8476. //free the stack (and defer the next script)
  8477. defer(onLoad, config.scriptChainDelay, scope);
  8478. } else {
  8479. onLoad.call(scope);
  8480. }
  8481. Loader.cleanupScriptElement(script, config.preserveScripts === false, config.garbageCollect);
  8482. }
  8483. },
  8484. onErrorFn = function(arg) {
  8485. defer(onError, 1, scope); //free the stack
  8486. Loader.cleanupScriptElement(script, config.preserveScripts === false, config.garbageCollect);
  8487. };
  8488. script.type = 'text/javascript';
  8489. script.onerror = onErrorFn;
  8490. charset = charset || config.scriptCharset;
  8491. if (charset) {
  8492. script.charset = charset;
  8493. }
  8494. /*
  8495. * IE9 Standards mode (and others) SHOULD follow the load event only
  8496. * (Note: IE9 supports both onload AND readystatechange events)
  8497. */
  8498. if ('addEventListener' in script ) {
  8499. script.onload = onLoadFn;
  8500. } else if ('readyState' in script) { // for <IE9 Compatability
  8501. script.onreadystatechange = function() {
  8502. if ( this.readyState == 'loaded' || this.readyState == 'complete' ) {
  8503. onLoadFn();
  8504. }
  8505. };
  8506. } else {
  8507. script.onload = onLoadFn;
  8508. }
  8509. script.src = url;
  8510. (Loader.documentHead || document.getElementsByTagName('head')[0]).appendChild(script);
  8511. return script;
  8512. },
  8513. /**
  8514. * @private
  8515. */
  8516. removeScriptElement: function(url) {
  8517. if (scriptElements[url]) {
  8518. Loader.cleanupScriptElement(scriptElements[url], true, !!Loader.getConfig('garbageCollect'));
  8519. delete scriptElements[url];
  8520. }
  8521. return Loader;
  8522. },
  8523. /**
  8524. * @private
  8525. */
  8526. cleanupScriptElement: function(script, remove, collect) {
  8527. var prop;
  8528. script.onload = script.onreadystatechange = script.onerror = null;
  8529. if (remove) {
  8530. Ext.removeNode(script); // Remove, since its useless now
  8531. if (collect) {
  8532. for (prop in script) {
  8533. try {
  8534. script[prop] = null;
  8535. delete script[prop]; // and prepare for GC
  8536. } catch (cleanEx) {
  8537. //ignore
  8538. }
  8539. }
  8540. }
  8541. }
  8542. return Loader;
  8543. },
  8544. /**
  8545. * Loads the specified script URL and calls the supplied callbacks. If this method
  8546. * is called before {@link Ext#isReady}, the script's load will delay the transition
  8547. * to ready. This can be used to load arbitrary scripts that may contain further
  8548. * {@link Ext#require Ext.require} calls.
  8549. *
  8550. * @param {Object/String} options The options object or simply the URL to load.
  8551. * @param {String} options.url The URL from which to load the script.
  8552. * @param {Function} [options.onLoad] The callback to call on successful load.
  8553. * @param {Function} [options.onError] The callback to call on failure to load.
  8554. * @param {Object} [options.scope] The scope (`this`) for the supplied callbacks.
  8555. */
  8556. loadScript: function (options) {
  8557. var config = Loader.getConfig(),
  8558. isString = typeof options == 'string',
  8559. url = isString ? options : options.url,
  8560. onError = !isString && options.onError,
  8561. onLoad = !isString && options.onLoad,
  8562. scope = !isString && options.scope,
  8563. onScriptError = function() {
  8564. Loader.numPendingFiles--;
  8565. Loader.scriptsLoading--;
  8566. if (onError) {
  8567. onError.call(scope, "Failed loading '" + url + "', please verify that the file exists");
  8568. }
  8569. if (Loader.numPendingFiles + Loader.scriptsLoading === 0) {
  8570. Loader.refreshQueue();
  8571. }
  8572. },
  8573. onScriptLoad = function () {
  8574. Loader.numPendingFiles--;
  8575. Loader.scriptsLoading--;
  8576. if (onLoad) {
  8577. onLoad.call(scope);
  8578. }
  8579. if (Loader.numPendingFiles + Loader.scriptsLoading === 0) {
  8580. Loader.refreshQueue();
  8581. }
  8582. },
  8583. src;
  8584. Loader.isLoading = true;
  8585. Loader.numPendingFiles++;
  8586. Loader.scriptsLoading++;
  8587. src = config.disableCaching ?
  8588. (url + '?' + config.disableCachingParam + '=' + Ext.Date.now()) : url;
  8589. scriptElements[url] = Loader.injectScriptElement(src, onScriptLoad, onScriptError);
  8590. },
  8591. /**
  8592. * Load a script file, supports both asynchronous and synchronous approaches
  8593. * @private
  8594. */
  8595. loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
  8596. if (isFileLoaded[url]) {
  8597. return Loader;
  8598. }
  8599. var config = Loader.getConfig(),
  8600. noCacheUrl = url + (config.disableCaching ? ('?' + config.disableCachingParam + '=' + Ext.Date.now()) : ''),
  8601. isCrossOriginRestricted = false,
  8602. xhr, status, onScriptError,
  8603. debugSourceURL = "";
  8604. scope = scope || Loader;
  8605. Loader.isLoading = true;
  8606. if (!synchronous) {
  8607. onScriptError = function() {
  8608. onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
  8609. };
  8610. scriptElements[url] = Loader.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
  8611. } else {
  8612. if (typeof XMLHttpRequest != 'undefined') {
  8613. xhr = new XMLHttpRequest();
  8614. } else {
  8615. xhr = new ActiveXObject('Microsoft.XMLHTTP');
  8616. }
  8617. try {
  8618. xhr.open('GET', noCacheUrl, false);
  8619. xhr.send(null);
  8620. } catch (e) {
  8621. isCrossOriginRestricted = true;
  8622. }
  8623. status = (xhr.status === 1223) ? 204 :
  8624. (xhr.status === 0 && (self.location || {}).protocol == 'file:') ? 200 : xhr.status;
  8625. isCrossOriginRestricted = isCrossOriginRestricted || (status === 0);
  8626. if (isCrossOriginRestricted
  8627. ) {
  8628. onError.call(Loader, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
  8629. "being loaded from a different domain or from the local file system whereby cross origin " +
  8630. "requests are not allowed due to security reasons. Use asynchronous loading with " +
  8631. "Ext.require instead.", synchronous);
  8632. }
  8633. else if ((status >= 200 && status < 300) || (status === 304)
  8634. ) {
  8635. // Debugger friendly, file names are still shown even though they're eval'ed code
  8636. // Breakpoints work on both Firebug and Chrome's Web Inspector
  8637. if (!Ext.isIE) {
  8638. debugSourceURL = "\n//@ sourceURL=" + url;
  8639. }
  8640. Ext.globalEval(xhr.responseText + debugSourceURL);
  8641. onLoad.call(scope);
  8642. }
  8643. else {
  8644. onError.call(Loader, "Failed loading synchronously via XHR: '" + url + "'; please " +
  8645. "verify that the file exists. " +
  8646. "XHR status code: " + status, synchronous);
  8647. }
  8648. // Prevent potential IE memory leak
  8649. xhr = null;
  8650. }
  8651. },
  8652. // documented above
  8653. syncRequire: function() {
  8654. var syncModeEnabled = Loader.syncModeEnabled;
  8655. if (!syncModeEnabled) {
  8656. Loader.syncModeEnabled = true;
  8657. }
  8658. Loader.require.apply(Loader, arguments);
  8659. if (!syncModeEnabled) {
  8660. Loader.syncModeEnabled = false;
  8661. }
  8662. Loader.refreshQueue();
  8663. },
  8664. // documented above
  8665. require: function(expressions, fn, scope, excludes) {
  8666. var excluded = {},
  8667. included = {},
  8668. excludedClassNames = [],
  8669. possibleClassNames = [],
  8670. classNames = [],
  8671. references = [],
  8672. callback,
  8673. syncModeEnabled,
  8674. filePath, expression, exclude, className,
  8675. possibleClassName, i, j, ln, subLn;
  8676. if (excludes) {
  8677. // Convert possible single string to an array.
  8678. excludes = (typeof excludes === 'string') ? [ excludes ] : excludes;
  8679. for (i = 0,ln = excludes.length; i < ln; i++) {
  8680. exclude = excludes[i];
  8681. if (typeof exclude == 'string' && exclude.length > 0) {
  8682. excludedClassNames = Manager.getNamesByExpression(exclude);
  8683. for (j = 0,subLn = excludedClassNames.length; j < subLn; j++) {
  8684. excluded[excludedClassNames[j]] = true;
  8685. }
  8686. }
  8687. }
  8688. }
  8689. // Convert possible single string to an array.
  8690. expressions = (typeof expressions === 'string') ? [ expressions ] : (expressions ? expressions : []);
  8691. if (fn) {
  8692. if (fn.length > 0) {
  8693. callback = function() {
  8694. var classes = [],
  8695. i, ln;
  8696. for (i = 0,ln = references.length; i < ln; i++) {
  8697. classes.push(Manager.get(references[i]));
  8698. }
  8699. return fn.apply(this, classes);
  8700. };
  8701. }
  8702. else {
  8703. callback = fn;
  8704. }
  8705. }
  8706. else {
  8707. callback = Ext.emptyFn;
  8708. }
  8709. scope = scope || Ext.global;
  8710. for (i = 0,ln = expressions.length; i < ln; i++) {
  8711. expression = expressions[i];
  8712. if (typeof expression == 'string' && expression.length > 0) {
  8713. possibleClassNames = Manager.getNamesByExpression(expression);
  8714. subLn = possibleClassNames.length;
  8715. for (j = 0; j < subLn; j++) {
  8716. possibleClassName = possibleClassNames[j];
  8717. if (excluded[possibleClassName] !== true) {
  8718. references.push(possibleClassName);
  8719. if (!Manager.isCreated(possibleClassName) && !included[possibleClassName]) {
  8720. included[possibleClassName] = true;
  8721. classNames.push(possibleClassName);
  8722. }
  8723. }
  8724. }
  8725. }
  8726. }
  8727. // If the dynamic dependency feature is not being used, throw an error
  8728. // if the dependencies are not defined
  8729. if (classNames.length > 0) {
  8730. if (!Loader.config.enabled) {
  8731. throw new Error("Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
  8732. "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', '));
  8733. }
  8734. }
  8735. else {
  8736. callback.call(scope);
  8737. return Loader;
  8738. }
  8739. syncModeEnabled = Loader.syncModeEnabled;
  8740. if (!syncModeEnabled) {
  8741. queue.push({
  8742. requires: classNames.slice(), // this array will be modified as the queue is processed,
  8743. // so we need a copy of it
  8744. callback: callback,
  8745. scope: scope
  8746. });
  8747. }
  8748. ln = classNames.length;
  8749. for (i = 0; i < ln; i++) {
  8750. className = classNames[i];
  8751. filePath = Loader.getPath(className);
  8752. // If we are synchronously loading a file that has already been asychronously loaded before
  8753. // we need to destroy the script tag and revert the count
  8754. // This file will then be forced loaded in synchronous
  8755. if (syncModeEnabled && isClassFileLoaded.hasOwnProperty(className)) {
  8756. Loader.numPendingFiles--;
  8757. Loader.removeScriptElement(filePath);
  8758. delete isClassFileLoaded[className];
  8759. }
  8760. if (!isClassFileLoaded.hasOwnProperty(className)) {
  8761. isClassFileLoaded[className] = false;
  8762. classNameToFilePathMap[className] = filePath;
  8763. Loader.numPendingFiles++;
  8764. Loader.loadScriptFile(
  8765. filePath,
  8766. pass(Loader.onFileLoaded, [className, filePath], Loader),
  8767. pass(Loader.onFileLoadError, [className, filePath], Loader),
  8768. Loader,
  8769. syncModeEnabled
  8770. );
  8771. }
  8772. }
  8773. if (syncModeEnabled) {
  8774. callback.call(scope);
  8775. if (ln === 1) {
  8776. return Manager.get(className);
  8777. }
  8778. }
  8779. return Loader;
  8780. },
  8781. /**
  8782. * @private
  8783. * @param {String} className
  8784. * @param {String} filePath
  8785. */
  8786. onFileLoaded: function(className, filePath) {
  8787. Loader.numLoadedFiles++;
  8788. isClassFileLoaded[className] = true;
  8789. isFileLoaded[filePath] = true;
  8790. Loader.numPendingFiles--;
  8791. if (Loader.numPendingFiles === 0) {
  8792. Loader.refreshQueue();
  8793. }
  8794. if (!Loader.syncModeEnabled && Loader.numPendingFiles === 0 && Loader.isLoading && !Loader.hasFileLoadError) {
  8795. var missingClasses = [],
  8796. missingPaths = [],
  8797. requires,
  8798. i, ln, j, subLn;
  8799. for (i = 0,ln = queue.length; i < ln; i++) {
  8800. requires = queue[i].requires;
  8801. for (j = 0,subLn = requires.length; j < subLn; j++) {
  8802. if (isClassFileLoaded[requires[j]]) {
  8803. missingClasses.push(requires[j]);
  8804. }
  8805. }
  8806. }
  8807. if (missingClasses.length < 1) {
  8808. return;
  8809. }
  8810. missingClasses = Ext.Array.filter(Ext.Array.unique(missingClasses), function(item) {
  8811. return !requiresMap.hasOwnProperty(item);
  8812. }, Loader);
  8813. for (i = 0,ln = missingClasses.length; i < ln; i++) {
  8814. missingPaths.push(classNameToFilePathMap[missingClasses[i]]);
  8815. }
  8816. throw new Error("The following classes are not declared even if their files have been " +
  8817. "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
  8818. "corresponding files for possible typos: '" + missingPaths.join("', '"));
  8819. }
  8820. },
  8821. /**
  8822. * @private
  8823. */
  8824. onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
  8825. Loader.numPendingFiles--;
  8826. Loader.hasFileLoadError = true;
  8827. throw new Error("[Ext.Loader] " + errorMessage);
  8828. },
  8829. /**
  8830. * @private
  8831. * Ensure that any classes referenced in the `uses` property are loaded.
  8832. */
  8833. addUsedClasses: function (classes) {
  8834. var cls, i, ln;
  8835. if (classes) {
  8836. classes = (typeof classes == 'string') ? [classes] : classes;
  8837. for (i = 0, ln = classes.length; i < ln; i++) {
  8838. cls = classes[i];
  8839. if (typeof cls == 'string' && !Ext.Array.contains(usedClasses, cls)) {
  8840. usedClasses.push(cls);
  8841. }
  8842. }
  8843. }
  8844. return Loader;
  8845. },
  8846. /**
  8847. * @private
  8848. */
  8849. triggerReady: function() {
  8850. var listener,
  8851. i, refClasses = usedClasses;
  8852. if (Loader.isLoading) {
  8853. Loader.isLoading = false;
  8854. if (refClasses.length !== 0) {
  8855. // Clone then empty the array to eliminate potential recursive loop issue
  8856. refClasses = refClasses.slice();
  8857. usedClasses.length = 0;
  8858. // this may immediately call us back if all 'uses' classes
  8859. // have been loaded
  8860. Loader.require(refClasses, Loader.triggerReady, Loader);
  8861. return Loader;
  8862. }
  8863. }
  8864. // this method can be called with Loader.isLoading either true or false
  8865. // (can be called with false when all 'uses' classes are already loaded)
  8866. // this may bypass the above if condition
  8867. while (readyListeners.length && !Loader.isLoading) {
  8868. // calls to refreshQueue may re-enter triggerReady
  8869. // so we cannot necessarily iterate the readyListeners array
  8870. listener = readyListeners.shift();
  8871. listener.fn.call(listener.scope);
  8872. }
  8873. return Loader;
  8874. },
  8875. // Documented above already
  8876. onReady: function(fn, scope, withDomReady, options) {
  8877. var oldFn;
  8878. if (withDomReady !== false && Ext.onDocumentReady) {
  8879. oldFn = fn;
  8880. fn = function() {
  8881. Ext.onDocumentReady(oldFn, scope, options);
  8882. };
  8883. }
  8884. if (!Loader.isLoading) {
  8885. fn.call(scope);
  8886. }
  8887. else {
  8888. readyListeners.push({
  8889. fn: fn,
  8890. scope: scope
  8891. });
  8892. }
  8893. },
  8894. /**
  8895. * @private
  8896. * @param {String} className
  8897. */
  8898. historyPush: function(className) {
  8899. if (className && isClassFileLoaded.hasOwnProperty(className) && !isInHistory[className]) {
  8900. isInHistory[className] = true;
  8901. history.push(className);
  8902. }
  8903. return Loader;
  8904. }
  8905. });
  8906. /**
  8907. * Turns on or off the "cache buster" applied to dynamically loaded scripts. Normally
  8908. * dynamically loaded scripts have an extra query parameter appended to avoid stale
  8909. * cached scripts. This method can be used to disable this mechanism, and is primarily
  8910. * useful for testing. This is done using a cookie.
  8911. * @param {Boolean} disable True to disable the cache buster.
  8912. * @param {String} [path="/"] An optional path to scope the cookie.
  8913. * @private
  8914. */
  8915. Ext.disableCacheBuster = function (disable, path) {
  8916. var date = new Date();
  8917. date.setTime(date.getTime() + (disable ? 10*365 : -1) * 24*60*60*1000);
  8918. date = date.toGMTString();
  8919. document.cookie = 'ext-cache=1; expires=' + date + '; path='+(path || '/');
  8920. };
  8921. /**
  8922. * Convenient alias of {@link Ext.Loader#require}. Please see the introduction documentation of
  8923. * {@link Ext.Loader} for examples.
  8924. * @member Ext
  8925. * @method require
  8926. */
  8927. Ext.require = alias(Loader, 'require');
  8928. /**
  8929. * Synchronous version of {@link Ext#require}, convenient alias of {@link Ext.Loader#syncRequire}.
  8930. *
  8931. * @member Ext
  8932. * @method syncRequire
  8933. */
  8934. Ext.syncRequire = alias(Loader, 'syncRequire');
  8935. /**
  8936. * Convenient shortcut to {@link Ext.Loader#exclude}
  8937. * @member Ext
  8938. * @method exclude
  8939. */
  8940. Ext.exclude = alias(Loader, 'exclude');
  8941. /**
  8942. * @member Ext
  8943. * @method onReady
  8944. * @ignore
  8945. */
  8946. Ext.onReady = function(fn, scope, options) {
  8947. Loader.onReady(fn, scope, true, options);
  8948. };
  8949. /**
  8950. * @cfg {String[]} requires
  8951. * @member Ext.Class
  8952. * List of classes that have to be loaded before instantiating this class.
  8953. * For example:
  8954. *
  8955. * Ext.define('Mother', {
  8956. * requires: ['Child'],
  8957. * giveBirth: function() {
  8958. * // we can be sure that child class is available.
  8959. * return new Child();
  8960. * }
  8961. * });
  8962. */
  8963. Class.registerPreprocessor('loader', function(cls, data, hooks, continueFn) {
  8964. var me = this,
  8965. dependencies = [],
  8966. dependency,
  8967. className = Manager.getName(cls),
  8968. i, j, ln, subLn, value, propertyName, propertyValue,
  8969. requiredMap, requiredDep;
  8970. /*
  8971. Loop through the dependencyProperties, look for string class names and push
  8972. them into a stack, regardless of whether the property's value is a string, array or object. For example:
  8973. {
  8974. extend: 'Ext.MyClass',
  8975. requires: ['Ext.some.OtherClass'],
  8976. mixins: {
  8977. observable: 'Ext.util.Observable';
  8978. }
  8979. }
  8980. which will later be transformed into:
  8981. {
  8982. extend: Ext.MyClass,
  8983. requires: [Ext.some.OtherClass],
  8984. mixins: {
  8985. observable: Ext.util.Observable;
  8986. }
  8987. }
  8988. */
  8989. for (i = 0,ln = dependencyProperties.length; i < ln; i++) {
  8990. propertyName = dependencyProperties[i];
  8991. if (data.hasOwnProperty(propertyName)) {
  8992. propertyValue = data[propertyName];
  8993. if (typeof propertyValue == 'string') {
  8994. dependencies.push(propertyValue);
  8995. }
  8996. else if (propertyValue instanceof Array) {
  8997. for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
  8998. value = propertyValue[j];
  8999. if (typeof value == 'string') {
  9000. dependencies.push(value);
  9001. }
  9002. }
  9003. }
  9004. else if (typeof propertyValue != 'function') {
  9005. for (j in propertyValue) {
  9006. if (propertyValue.hasOwnProperty(j)) {
  9007. value = propertyValue[j];
  9008. if (typeof value == 'string') {
  9009. dependencies.push(value);
  9010. }
  9011. }
  9012. }
  9013. }
  9014. }
  9015. }
  9016. if (dependencies.length === 0) {
  9017. return;
  9018. }
  9019. var deadlockPath = [],
  9020. detectDeadlock;
  9021. /*
  9022. Automatically detect deadlocks before-hand,
  9023. will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
  9024. - A extends B, then B extends A
  9025. - A requires B, B requires C, then C requires A
  9026. The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
  9027. no matter how deep the path is.
  9028. */
  9029. if (className) {
  9030. requiresMap[className] = dependencies;
  9031. requiredMap = Loader.requiredByMap || (Loader.requiredByMap = {});
  9032. for (i = 0,ln = dependencies.length; i < ln; i++) {
  9033. dependency = dependencies[i];
  9034. (requiredMap[dependency] || (requiredMap[dependency] = [])).push(className);
  9035. }
  9036. detectDeadlock = function(cls) {
  9037. deadlockPath.push(cls);
  9038. if (requiresMap[cls]) {
  9039. if (Ext.Array.contains(requiresMap[cls], className)) {
  9040. throw new Error("Deadlock detected while loading dependencies! '" + className + "' and '" +
  9041. deadlockPath[1] + "' " + "mutually require each other. Path: " +
  9042. deadlockPath.join(' -> ') + " -> " + deadlockPath[0]);
  9043. }
  9044. for (i = 0,ln = requiresMap[cls].length; i < ln; i++) {
  9045. detectDeadlock(requiresMap[cls][i]);
  9046. }
  9047. }
  9048. };
  9049. detectDeadlock(className);
  9050. }
  9051. Loader.require(dependencies, function() {
  9052. for (i = 0,ln = dependencyProperties.length; i < ln; i++) {
  9053. propertyName = dependencyProperties[i];
  9054. if (data.hasOwnProperty(propertyName)) {
  9055. propertyValue = data[propertyName];
  9056. if (typeof propertyValue == 'string') {
  9057. data[propertyName] = Manager.get(propertyValue);
  9058. }
  9059. else if (propertyValue instanceof Array) {
  9060. for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
  9061. value = propertyValue[j];
  9062. if (typeof value == 'string') {
  9063. data[propertyName][j] = Manager.get(value);
  9064. }
  9065. }
  9066. }
  9067. else if (typeof propertyValue != 'function') {
  9068. for (var k in propertyValue) {
  9069. if (propertyValue.hasOwnProperty(k)) {
  9070. value = propertyValue[k];
  9071. if (typeof value == 'string') {
  9072. data[propertyName][k] = Manager.get(value);
  9073. }
  9074. }
  9075. }
  9076. }
  9077. }
  9078. }
  9079. continueFn.call(me, cls, data, hooks);
  9080. });
  9081. return false;
  9082. }, true, 'after', 'className');
  9083. /**
  9084. * @cfg {String[]} uses
  9085. * @member Ext.Class
  9086. * List of optional classes to load together with this class. These aren't neccessarily loaded before
  9087. * this class is created, but are guaranteed to be available before Ext.onReady listeners are
  9088. * invoked. For example:
  9089. *
  9090. * Ext.define('Mother', {
  9091. * uses: ['Child'],
  9092. * giveBirth: function() {
  9093. * // This code might, or might not work:
  9094. * // return new Child();
  9095. *
  9096. * // Instead use Ext.create() to load the class at the spot if not loaded already:
  9097. * return Ext.create('Child');
  9098. * }
  9099. * });
  9100. */
  9101. Manager.registerPostprocessor('uses', function(name, cls, data) {
  9102. var uses = data.uses;
  9103. if (uses) {
  9104. Loader.addUsedClasses(uses);
  9105. }
  9106. });
  9107. Manager.onCreated(Loader.historyPush);
  9108. };
  9109. // simple mechanism for automated means of injecting large amounts of dependency info
  9110. // at the appropriate time in the load cycle
  9111. if (Ext._classPathMetadata) {
  9112. Ext.Loader.addClassPathMappings(Ext._classPathMetadata);
  9113. Ext._classPathMetadata = null;
  9114. }
  9115. // initalize the default path of the framework
  9116. (function() {
  9117. var scripts = document.getElementsByTagName('script'),
  9118. currentScript = scripts[scripts.length - 1],
  9119. src = currentScript.src,
  9120. path = src.substring(0, src.lastIndexOf('/') + 1),
  9121. Loader = Ext.Loader;
  9122. if(src.indexOf("/platform/core/src/class/") != -1) {
  9123. path = path + "../../../../extjs/";
  9124. } else if(src.indexOf("/core/src/class/") != -1) {
  9125. path = path + "../../../";
  9126. }
  9127. Loader.setConfig({
  9128. enabled: true,
  9129. disableCaching: true,
  9130. paths: {
  9131. 'Ext': path + 'src'
  9132. }
  9133. });
  9134. })();
  9135. // allows a tools like dynatrace to deterministically detect onReady state by invoking
  9136. // a callback (intended for external consumption)
  9137. Ext._endTime = new Date().getTime();
  9138. if (Ext._beforereadyhandler){
  9139. Ext._beforereadyhandler();
  9140. }
  9141. //@tag foundation,core
  9142. //@require ../class/Loader.js
  9143. /**
  9144. * @author Brian Moeskau <brian@sencha.com>
  9145. * @docauthor Brian Moeskau <brian@sencha.com>
  9146. *
  9147. * A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
  9148. * errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
  9149. * uses the Ext 4 class system, the Error class can automatically add the source class and method from which
  9150. * the error was raised. It also includes logic to automatically log the eroor to the console, if available,
  9151. * with additional metadata about the error. In all cases, the error will always be thrown at the end so that
  9152. * execution will halt.
  9153. *
  9154. * Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to
  9155. * handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
  9156. * although in a real application it's usually a better idea to override the handling function and perform
  9157. * logging or some other method of reporting the errors in a way that is meaningful to the application.
  9158. *
  9159. * At its simplest you can simply raise an error as a simple string from within any code:
  9160. *
  9161. * Example usage:
  9162. *
  9163. * Ext.Error.raise('Something bad happened!');
  9164. *
  9165. * If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
  9166. * displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
  9167. * additional metadata about the error being raised. The {@link #raise} method can also take a config object.
  9168. * In this form the `msg` attribute becomes the error description, and any other data added to the config gets
  9169. * added to the error object and, if the console is available, logged to the console for inspection.
  9170. *
  9171. * Example usage:
  9172. *
  9173. * Ext.define('Ext.Foo', {
  9174. * doSomething: function(option){
  9175. * if (someCondition === false) {
  9176. * Ext.Error.raise({
  9177. * msg: 'You cannot do that!',
  9178. * option: option, // whatever was passed into the method
  9179. * 'error code': 100 // other arbitrary info
  9180. * });
  9181. * }
  9182. * }
  9183. * });
  9184. *
  9185. * If a console is available (that supports the `console.dir` function) you'll see console output like:
  9186. *
  9187. * An error was raised with the following data:
  9188. * option: Object { foo: "bar"}
  9189. * foo: "bar"
  9190. * error code: 100
  9191. * msg: "You cannot do that!"
  9192. * sourceClass: "Ext.Foo"
  9193. * sourceMethod: "doSomething"
  9194. *
  9195. * uncaught exception: You cannot do that!
  9196. *
  9197. * As you can see, the error will report exactly where it was raised and will include as much information as the
  9198. * raising code can usefully provide.
  9199. *
  9200. * If you want to handle all application errors globally you can simply override the static {@link #handle} method
  9201. * and provide whatever handling logic you need. If the method returns true then the error is considered handled
  9202. * and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
  9203. *
  9204. * Example usage:
  9205. *
  9206. * Ext.Error.handle = function(err) {
  9207. * if (err.someProperty == 'NotReallyAnError') {
  9208. * // maybe log something to the application here if applicable
  9209. * return true;
  9210. * }
  9211. * // any non-true return value (including none) will cause the error to be thrown
  9212. * }
  9213. *
  9214. */
  9215. Ext.Error = Ext.extend(Error, {
  9216. statics: {
  9217. /**
  9218. * @property {Boolean} ignore
  9219. * Static flag that can be used to globally disable error reporting to the browser if set to true
  9220. * (defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
  9221. * and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
  9222. * be preferable to supply a custom error {@link #handle handling} function instead.
  9223. *
  9224. * Example usage:
  9225. *
  9226. * Ext.Error.ignore = true;
  9227. *
  9228. * @static
  9229. */
  9230. ignore: false,
  9231. /**
  9232. * @property {Boolean} notify
  9233. * Static flag that can be used to globally control error notification to the user. Unlike
  9234. * Ex.Error.ignore, this does not effect exceptions. They are still thrown. This value can be
  9235. * set to false to disable the alert notification (default is true for IE6 and IE7).
  9236. *
  9237. * Only the first error will generate an alert. Internally this flag is set to false when the
  9238. * first error occurs prior to displaying the alert.
  9239. *
  9240. * This flag is not used in a release build.
  9241. *
  9242. * Example usage:
  9243. *
  9244. * Ext.Error.notify = false;
  9245. *
  9246. * @static
  9247. */
  9248. //notify: Ext.isIE6 || Ext.isIE7,
  9249. /**
  9250. * Raise an error that can include additional data and supports automatic console logging if available.
  9251. * You can pass a string error message or an object with the `msg` attribute which will be used as the
  9252. * error message. The object can contain any other name-value attributes (or objects) to be logged
  9253. * along with the error.
  9254. *
  9255. * Note that after displaying the error message a JavaScript error will ultimately be thrown so that
  9256. * execution will halt.
  9257. *
  9258. * Example usage:
  9259. *
  9260. * Ext.Error.raise('A simple string error message');
  9261. *
  9262. * // or...
  9263. *
  9264. * Ext.define('Ext.Foo', {
  9265. * doSomething: function(option){
  9266. * if (someCondition === false) {
  9267. * Ext.Error.raise({
  9268. * msg: 'You cannot do that!',
  9269. * option: option, // whatever was passed into the method
  9270. * 'error code': 100 // other arbitrary info
  9271. * });
  9272. * }
  9273. * }
  9274. * });
  9275. *
  9276. * @param {String/Object} err The error message string, or an object containing the attribute "msg" that will be
  9277. * used as the error message. Any other data included in the object will also be logged to the browser console,
  9278. * if available.
  9279. * @static
  9280. */
  9281. raise: function(err){
  9282. err = err || {};
  9283. if (Ext.isString(err)) {
  9284. err = { msg: err };
  9285. }
  9286. var method = this.raise.caller,
  9287. msg;
  9288. if (method) {
  9289. if (method.$name) {
  9290. err.sourceMethod = method.$name;
  9291. }
  9292. if (method.$owner) {
  9293. err.sourceClass = method.$owner.$className;
  9294. }
  9295. }
  9296. if (Ext.Error.handle(err) !== true) {
  9297. msg = Ext.Error.prototype.toString.call(err);
  9298. Ext.log({
  9299. msg: msg,
  9300. level: 'error',
  9301. dump: err,
  9302. stack: true
  9303. });
  9304. throw new Ext.Error(err);
  9305. }
  9306. },
  9307. /**
  9308. * Globally handle any Ext errors that may be raised, optionally providing custom logic to
  9309. * handle different errors individually. Return true from the function to bypass throwing the
  9310. * error to the browser, otherwise the error will be thrown and execution will halt.
  9311. *
  9312. * Example usage:
  9313. *
  9314. * Ext.Error.handle = function(err) {
  9315. * if (err.someProperty == 'NotReallyAnError') {
  9316. * // maybe log something to the application here if applicable
  9317. * return true;
  9318. * }
  9319. * // any non-true return value (including none) will cause the error to be thrown
  9320. * }
  9321. *
  9322. * @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes that were originally
  9323. * raised with it, plus properties about the method and class from which the error originated (if raised from a
  9324. * class that uses the Ext 4 class system).
  9325. * @static
  9326. */
  9327. handle: function(){
  9328. return Ext.Error.ignore;
  9329. }
  9330. },
  9331. // This is the standard property that is the name of the constructor.
  9332. name: 'Ext.Error',
  9333. /**
  9334. * Creates new Error object.
  9335. * @param {String/Object} config The error message string, or an object containing the
  9336. * attribute "msg" that will be used as the error message. Any other data included in
  9337. * the object will be applied to the error instance and logged to the browser console, if available.
  9338. */
  9339. constructor: function(config){
  9340. if (Ext.isString(config)) {
  9341. config = { msg: config };
  9342. }
  9343. var me = this;
  9344. Ext.apply(me, config);
  9345. me.message = me.message || me.msg; // 'message' is standard ('msg' is non-standard)
  9346. // note: the above does not work in old WebKit (me.message is readonly) (Safari 4)
  9347. },
  9348. /**
  9349. * Provides a custom string representation of the error object. This is an override of the base JavaScript
  9350. * `Object.toString` method, which is useful so that when logged to the browser console, an error object will
  9351. * be displayed with a useful message instead of `[object Object]`, the default `toString` result.
  9352. *
  9353. * The default implementation will include the error message along with the raising class and method, if available,
  9354. * but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
  9355. * a particular error instance, if you want to provide a custom description that will show up in the console.
  9356. * @return {String} The error message. If raised from within the Ext 4 class system, the error message will also
  9357. * include the raising class and method names, if available.
  9358. */
  9359. toString: function(){
  9360. var me = this,
  9361. className = me.sourceClass ? me.sourceClass : '',
  9362. methodName = me.sourceMethod ? '.' + me.sourceMethod + '(): ' : '',
  9363. msg = me.msg || '(No description provided)';
  9364. return className + methodName + msg;
  9365. }
  9366. });
  9367. /*
  9368. * Create a function that will throw an error if called (in debug mode) with a message that
  9369. * indicates the method has been removed.
  9370. * @param {String} suggestion Optional text to include in the message (a workaround perhaps).
  9371. * @return {Function} The generated function.
  9372. * @private
  9373. */
  9374. Ext.deprecated = function (suggestion) {
  9375. if (!suggestion) {
  9376. suggestion = '';
  9377. }
  9378. function fail () {
  9379. Ext.Error.raise('The method "' + fail.$owner.$className + '.' + fail.$name +
  9380. '" has been removed. ' + suggestion);
  9381. }
  9382. return fail;
  9383. return Ext.emptyFn;
  9384. };
  9385. /*
  9386. * This mechanism is used to notify the user of the first error encountered on the page. This
  9387. * was previously internal to Ext.Error.raise and is a desirable feature since errors often
  9388. * slip silently under the radar. It cannot live in Ext.Error.raise since there are times
  9389. * where exceptions are handled in a try/catch.
  9390. */
  9391. (function () {
  9392. var timer, errors = 0,
  9393. win = Ext.global,
  9394. msg;
  9395. if (typeof window === 'undefined') {
  9396. return; // build system or some such environment...
  9397. }
  9398. // This method is called to notify the user of the current error status.
  9399. function notify () {
  9400. var counters = Ext.log.counters,
  9401. supports = Ext.supports,
  9402. hasOnError = supports && supports.WindowOnError; // TODO - timing
  9403. // Put log counters to the status bar (for most browsers):
  9404. if (counters && (counters.error + counters.warn + counters.info + counters.log)) {
  9405. msg = [ 'Logged Errors:',counters.error, 'Warnings:',counters.warn,
  9406. 'Info:',counters.info, 'Log:',counters.log].join(' ');
  9407. if (errors) {
  9408. msg = '*** Errors: ' + errors + ' - ' + msg;
  9409. } else if (counters.error) {
  9410. msg = '*** ' + msg;
  9411. }
  9412. win.status = msg;
  9413. }
  9414. // Display an alert on the first error:
  9415. if (!Ext.isDefined(Ext.Error.notify)) {
  9416. Ext.Error.notify = Ext.isIE6 || Ext.isIE7; // TODO - timing
  9417. }
  9418. if (Ext.Error.notify && (hasOnError ? errors : (counters && counters.error))) {
  9419. Ext.Error.notify = false;
  9420. if (timer) {
  9421. win.clearInterval(timer); // ticks can queue up so stop...
  9422. timer = null;
  9423. }
  9424. alert('Unhandled error on page: See console or log');
  9425. poll();
  9426. }
  9427. }
  9428. // Sets up polling loop. This is the only way to know about errors in some browsers
  9429. // (Opera/Safari) and is the only way to update the status bar for warnings and other
  9430. // non-errors.
  9431. function poll () {
  9432. timer = win.setInterval(notify, 1000);
  9433. }
  9434. // window.onerror sounds ideal but it prevents the built-in error dialog from doing
  9435. // its (better) thing.
  9436. poll();
  9437. }());
  9438. //@tag extras,core
  9439. //@require ../lang/Error.js
  9440. /**
  9441. * Modified version of [Douglas Crockford's JSON.js][dc] that doesn't
  9442. * mess with the Object prototype.
  9443. *
  9444. * [dc]: http://www.json.org/js.html
  9445. *
  9446. * @singleton
  9447. */
  9448. Ext.JSON = (new(function() {
  9449. var me = this,
  9450. encodingFunction,
  9451. decodingFunction,
  9452. useNative = null,
  9453. useHasOwn = !! {}.hasOwnProperty,
  9454. isNative = function() {
  9455. if (useNative === null) {
  9456. useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
  9457. }
  9458. return useNative;
  9459. },
  9460. pad = function(n) {
  9461. return n < 10 ? "0" + n : n;
  9462. },
  9463. doDecode = function(json) {
  9464. return eval("(" + json + ')');
  9465. },
  9466. doEncode = function(o, newline) {
  9467. // http://jsperf.com/is-undefined
  9468. if (o === null || o === undefined) {
  9469. return "null";
  9470. } else if (Ext.isDate(o)) {
  9471. return Ext.JSON.encodeDate(o);
  9472. } else if (Ext.isString(o)) {
  9473. return Ext.JSON.encodeString(o);
  9474. } else if (typeof o == "number") {
  9475. //don't use isNumber here, since finite checks happen inside isNumber
  9476. return isFinite(o) ? String(o) : "null";
  9477. } else if (Ext.isBoolean(o)) {
  9478. return String(o);
  9479. }
  9480. // Allow custom zerialization by adding a toJSON method to any object type.
  9481. // Date/String have a toJSON in some environments, so check these first.
  9482. else if (o.toJSON) {
  9483. return o.toJSON();
  9484. } else if (Ext.isArray(o)) {
  9485. return encodeArray(o, newline);
  9486. } else if (Ext.isObject(o)) {
  9487. return encodeObject(o, newline);
  9488. } else if (typeof o === "function") {
  9489. return "null";
  9490. }
  9491. return 'undefined';
  9492. },
  9493. m = {
  9494. "\b": '\\b',
  9495. "\t": '\\t',
  9496. "\n": '\\n',
  9497. "\f": '\\f',
  9498. "\r": '\\r',
  9499. '"': '\\"',
  9500. "\\": '\\\\',
  9501. '\x0b': '\\u000b' //ie doesn't handle \v
  9502. },
  9503. charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g,
  9504. encodeString = function(s) {
  9505. return '"' + s.replace(charToReplace, function(a) {
  9506. var c = m[a];
  9507. return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  9508. }) + '"';
  9509. },
  9510. encodeArrayPretty = function(o, newline) {
  9511. var len = o.length,
  9512. cnewline = newline + ' ',
  9513. sep = ',' + cnewline,
  9514. a = ["[", cnewline], // Note newline in case there are no members
  9515. i;
  9516. for (i = 0; i < len; i += 1) {
  9517. a.push(Ext.JSON.encodeValue(o[i], cnewline), sep);
  9518. }
  9519. // Overwrite trailing comma (or empty string)
  9520. a[a.length - 1] = newline + ']';
  9521. return a.join('');
  9522. },
  9523. encodeObjectPretty = function(o, newline) {
  9524. var cnewline = newline + ' ',
  9525. sep = ',' + cnewline,
  9526. a = ["{", cnewline], // Note newline in case there are no members
  9527. i;
  9528. for (i in o) {
  9529. if (!useHasOwn || o.hasOwnProperty(i)) {
  9530. a.push(Ext.JSON.encodeValue(i) + ': ' + Ext.JSON.encodeValue(o[i], cnewline), sep);
  9531. }
  9532. }
  9533. // Overwrite trailing comma (or empty string)
  9534. a[a.length - 1] = newline + '}';
  9535. return a.join('');
  9536. },
  9537. encodeArray = function(o, newline) {
  9538. if (newline) {
  9539. return encodeArrayPretty(o, newline);
  9540. }
  9541. var a = ["[", ""], // Note empty string in case there are no serializable members.
  9542. len = o.length,
  9543. i;
  9544. for (i = 0; i < len; i += 1) {
  9545. a.push(Ext.JSON.encodeValue(o[i]), ',');
  9546. }
  9547. // Overwrite trailing comma (or empty string)
  9548. a[a.length - 1] = ']';
  9549. return a.join("");
  9550. },
  9551. encodeObject = function(o, newline) {
  9552. if (newline) {
  9553. return encodeObjectPretty(o, newline);
  9554. }
  9555. var a = ["{", ""], // Note empty string in case there are no serializable members.
  9556. i;
  9557. for (i in o) {
  9558. if (!useHasOwn || o.hasOwnProperty(i)) {
  9559. a.push(Ext.JSON.encodeValue(i), ":", Ext.JSON.encodeValue(o[i]), ',');
  9560. }
  9561. }
  9562. // Overwrite trailing comma (or empty string)
  9563. a[a.length - 1] = '}';
  9564. return a.join("");
  9565. };
  9566. /**
  9567. * Encodes a String. This returns the actual string which is inserted into the JSON string as the literal
  9568. * expression. **The returned value includes enclosing double quotation marks.**
  9569. *
  9570. * To override this:
  9571. *
  9572. * Ext.JSON.encodeString = function(s) {
  9573. * return 'Foo' + s;
  9574. * };
  9575. *
  9576. * @param {String} s The String to encode
  9577. * @return {String} The string literal to use in a JSON string.
  9578. * @method
  9579. */
  9580. me.encodeString = encodeString;
  9581. /**
  9582. * The function which {@link #encode} uses to encode all javascript values to their JSON representations
  9583. * when {@link Ext#USE_NATIVE_JSON} is `false`.
  9584. *
  9585. * This is made public so that it can be replaced with a custom implementation.
  9586. *
  9587. * @param {Object} o Any javascript value to be converted to its JSON representation
  9588. * @return {String} The JSON representation of the passed value.
  9589. * @method
  9590. */
  9591. me.encodeValue = doEncode;
  9592. /**
  9593. * Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal
  9594. * expression. **The returned value includes enclosing double quotation marks.**
  9595. *
  9596. * The default return format is `"yyyy-mm-ddThh:mm:ss"`.
  9597. *
  9598. * To override this:
  9599. *
  9600. * Ext.JSON.encodeDate = function(d) {
  9601. * return Ext.Date.format(d, '"Y-m-d"');
  9602. * };
  9603. *
  9604. * @param {Date} d The Date to encode
  9605. * @return {String} The string literal to use in a JSON string.
  9606. */
  9607. me.encodeDate = function(o) {
  9608. return '"' + o.getFullYear() + "-"
  9609. + pad(o.getMonth() + 1) + "-"
  9610. + pad(o.getDate()) + "T"
  9611. + pad(o.getHours()) + ":"
  9612. + pad(o.getMinutes()) + ":"
  9613. + pad(o.getSeconds()) + '"';
  9614. };
  9615. /**
  9616. * Encodes an Object, Array or other value.
  9617. *
  9618. * If the environment's native JSON encoding is not being used ({@link Ext#USE_NATIVE_JSON} is not set,
  9619. * or the environment does not support it), then ExtJS's encoding will be used. This allows the developer
  9620. * to add a `toJSON` method to their classes which need serializing to return a valid JSON representation
  9621. * of the object.
  9622. *
  9623. * @param {Object} o The variable to encode
  9624. * @return {String} The JSON string
  9625. */
  9626. me.encode = function(o) {
  9627. if (!encodingFunction) {
  9628. // setup encoding function on first access
  9629. encodingFunction = isNative() ? JSON.stringify : me.encodeValue;
  9630. }
  9631. return encodingFunction(o);
  9632. };
  9633. /**
  9634. * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws
  9635. * a SyntaxError unless the safe option is set.
  9636. *
  9637. * @param {String} json The JSON string
  9638. * @param {Boolean} [safe=false] True to return null, false to throw an exception if the JSON is invalid.
  9639. * @return {Object} The resulting object
  9640. */
  9641. me.decode = function(json, safe) {
  9642. if (!decodingFunction) {
  9643. // setup decoding function on first access
  9644. decodingFunction = isNative() ? JSON.parse : doDecode;
  9645. }
  9646. try {
  9647. return decodingFunction(json);
  9648. } catch (e) {
  9649. if (safe === true) {
  9650. return null;
  9651. }
  9652. Ext.Error.raise({
  9653. sourceClass: "Ext.JSON",
  9654. sourceMethod: "decode",
  9655. msg: "You're trying to decode an invalid JSON String: " + json
  9656. });
  9657. }
  9658. };
  9659. })());
  9660. /**
  9661. * Shorthand for {@link Ext.JSON#encode}
  9662. * @member Ext
  9663. * @method encode
  9664. * @inheritdoc Ext.JSON#encode
  9665. */
  9666. Ext.encode = Ext.JSON.encode;
  9667. /**
  9668. * Shorthand for {@link Ext.JSON#decode}
  9669. * @member Ext
  9670. * @method decode
  9671. * @inheritdoc Ext.JSON#decode
  9672. */
  9673. Ext.decode = Ext.JSON.decode;
  9674. //@tag extras,core
  9675. //@require misc/JSON.js
  9676. /**
  9677. * @class Ext
  9678. *
  9679. * The Ext namespace (global object) encapsulates all classes, singletons, and
  9680. * utility methods provided by Sencha's libraries.
  9681. *
  9682. * Most user interface Components are at a lower level of nesting in the namespace,
  9683. * but many common utility functions are provided as direct properties of the Ext namespace.
  9684. *
  9685. * Also many frequently used methods from other classes are provided as shortcuts
  9686. * within the Ext namespace. For example {@link Ext#getCmp Ext.getCmp} aliases
  9687. * {@link Ext.ComponentManager#get Ext.ComponentManager.get}.
  9688. *
  9689. * Many applications are initiated with {@link Ext#onReady Ext.onReady} which is
  9690. * called once the DOM is ready. This ensures all scripts have been loaded,
  9691. * preventing dependency issues. For example:
  9692. *
  9693. * Ext.onReady(function(){
  9694. * new Ext.Component({
  9695. * renderTo: document.body,
  9696. * html: 'DOM ready!'
  9697. * });
  9698. * });
  9699. *
  9700. * For more information about how to use the Ext classes, see:
  9701. *
  9702. * - <a href="http://www.sencha.com/learn/">The Learning Center</a>
  9703. * - <a href="http://www.sencha.com/learn/Ext_FAQ">The FAQ</a>
  9704. * - <a href="http://www.sencha.com/forum/">The forums</a>
  9705. *
  9706. * @singleton
  9707. */
  9708. Ext.apply(Ext, {
  9709. userAgent: navigator.userAgent.toLowerCase(),
  9710. cache: {},
  9711. idSeed: 1000,
  9712. windowId: 'ext-window',
  9713. documentId: 'ext-document',
  9714. /**
  9715. * True when the document is fully initialized and ready for action
  9716. */
  9717. isReady: false,
  9718. /**
  9719. * True to automatically uncache orphaned Ext.Elements periodically
  9720. */
  9721. enableGarbageCollector: true,
  9722. /**
  9723. * True to automatically purge event listeners during garbageCollection.
  9724. */
  9725. enableListenerCollection: true,
  9726. addCacheEntry: function(id, el, dom) {
  9727. dom = dom || el.dom;
  9728. if (!dom) {
  9729. // Without the DOM node we can't GC the entry
  9730. Ext.Error.raise('Cannot add an entry to the element cache without the DOM node');
  9731. }
  9732. var key = id || (el && el.id) || dom.id,
  9733. entry = Ext.cache[key] || (Ext.cache[key] = {
  9734. data: {},
  9735. events: {},
  9736. dom: dom,
  9737. // Skip garbage collection for special elements (window, document, iframes)
  9738. skipGarbageCollection: !!(dom.getElementById || dom.navigator)
  9739. });
  9740. if (el) {
  9741. el.$cache = entry;
  9742. // Inject the back link from the cache in case the cache entry
  9743. // had already been created by Ext.fly. Ext.fly creates a cache entry with no el link.
  9744. entry.el = el;
  9745. }
  9746. return entry;
  9747. },
  9748. updateCacheEntry: function(cacheItem, dom){
  9749. cacheItem.dom = dom;
  9750. if (cacheItem.el) {
  9751. cacheItem.el.dom = dom;
  9752. }
  9753. return cacheItem;
  9754. },
  9755. /**
  9756. * Generates unique ids. If the element already has an id, it is unchanged
  9757. * @param {HTMLElement/Ext.Element} [el] The element to generate an id for
  9758. * @param {String} prefix (optional) Id prefix (defaults "ext-gen")
  9759. * @return {String} The generated Id.
  9760. */
  9761. id: function(el, prefix) {
  9762. var me = this,
  9763. sandboxPrefix = '';
  9764. el = Ext.getDom(el, true) || {};
  9765. if (el === document) {
  9766. el.id = me.documentId;
  9767. }
  9768. else if (el === window) {
  9769. el.id = me.windowId;
  9770. }
  9771. if (!el.id) {
  9772. if (me.isSandboxed) {
  9773. sandboxPrefix = Ext.sandboxName.toLowerCase() + '-';
  9774. }
  9775. el.id = sandboxPrefix + (prefix || "ext-gen") + (++Ext.idSeed);
  9776. }
  9777. return el.id;
  9778. },
  9779. escapeId: (function(){
  9780. var validIdRe = /^[a-zA-Z_][a-zA-Z0-9_\-]*$/i,
  9781. escapeRx = /([\W]{1})/g,
  9782. leadingNumRx = /^(\d)/g,
  9783. escapeFn = function(match, capture){
  9784. return "\\" + capture;
  9785. },
  9786. numEscapeFn = function(match, capture){
  9787. return '\\00' + capture.charCodeAt(0).toString(16) + ' ';
  9788. };
  9789. return function(id) {
  9790. return validIdRe.test(id)
  9791. ? id
  9792. // replace the number portion last to keep the trailing ' '
  9793. // from being escaped
  9794. : id.replace(escapeRx, escapeFn)
  9795. .replace(leadingNumRx, numEscapeFn);
  9796. };
  9797. }()),
  9798. /**
  9799. * Returns the current document body as an {@link Ext.Element}.
  9800. * @return Ext.Element The document body
  9801. */
  9802. getBody: (function() {
  9803. var body;
  9804. return function() {
  9805. return body || (body = Ext.get(document.body));
  9806. };
  9807. }()),
  9808. /**
  9809. * Returns the current document head as an {@link Ext.Element}.
  9810. * @return Ext.Element The document head
  9811. * @method
  9812. */
  9813. getHead: (function() {
  9814. var head;
  9815. return function() {
  9816. return head || (head = Ext.get(document.getElementsByTagName("head")[0]));
  9817. };
  9818. }()),
  9819. /**
  9820. * Returns the current HTML document object as an {@link Ext.Element}.
  9821. * @return Ext.Element The document
  9822. */
  9823. getDoc: (function() {
  9824. var doc;
  9825. return function() {
  9826. return doc || (doc = Ext.get(document));
  9827. };
  9828. }()),
  9829. /**
  9830. * This is shorthand reference to {@link Ext.ComponentManager#get}.
  9831. * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#id id}
  9832. *
  9833. * @param {String} id The component {@link Ext.Component#id id}
  9834. * @return Ext.Component The Component, `undefined` if not found, or `null` if a
  9835. * Class was found.
  9836. */
  9837. getCmp: function(id) {
  9838. return Ext.ComponentManager.get(id);
  9839. },
  9840. /**
  9841. * Returns the current orientation of the mobile device
  9842. * @return {String} Either 'portrait' or 'landscape'
  9843. */
  9844. getOrientation: function() {
  9845. return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape';
  9846. },
  9847. /**
  9848. * Attempts to destroy any objects passed to it by removing all event listeners, removing them from the
  9849. * DOM (if applicable) and calling their destroy functions (if available). This method is primarily
  9850. * intended for arguments of type {@link Ext.Element} and {@link Ext.Component}, but any subclass of
  9851. * {@link Ext.util.Observable} can be passed in. Any number of elements and/or components can be
  9852. * passed into this function in a single call as separate arguments.
  9853. *
  9854. * @param {Ext.Element/Ext.Component/Ext.Element[]/Ext.Component[]...} args
  9855. * An {@link Ext.Element}, {@link Ext.Component}, or an Array of either of these to destroy
  9856. */
  9857. destroy: function() {
  9858. var ln = arguments.length,
  9859. i, arg;
  9860. for (i = 0; i < ln; i++) {
  9861. arg = arguments[i];
  9862. if (arg) {
  9863. if (Ext.isArray(arg)) {
  9864. this.destroy.apply(this, arg);
  9865. }
  9866. else if (Ext.isFunction(arg.destroy)) {
  9867. arg.destroy();
  9868. }
  9869. else if (arg.dom) {
  9870. arg.remove();
  9871. }
  9872. }
  9873. }
  9874. },
  9875. /**
  9876. * Execute a callback function in a particular scope. If no function is passed the call is ignored.
  9877. *
  9878. * For example, these lines are equivalent:
  9879. *
  9880. * Ext.callback(myFunc, this, [arg1, arg2]);
  9881. * Ext.isFunction(myFunc) && myFunc.apply(this, [arg1, arg2]);
  9882. *
  9883. * @param {Function} callback The callback to execute
  9884. * @param {Object} [scope] The scope to execute in
  9885. * @param {Array} [args] The arguments to pass to the function
  9886. * @param {Number} [delay] Pass a number to delay the call by a number of milliseconds.
  9887. */
  9888. callback: function(callback, scope, args, delay){
  9889. if(Ext.isFunction(callback)){
  9890. args = args || [];
  9891. scope = scope || window;
  9892. if (delay) {
  9893. Ext.defer(callback, delay, scope, args);
  9894. } else {
  9895. callback.apply(scope, args);
  9896. }
  9897. }
  9898. },
  9899. /**
  9900. * Alias for {@link Ext.String#htmlEncode}.
  9901. * @inheritdoc Ext.String#htmlEncode
  9902. * @ignore
  9903. */
  9904. htmlEncode : function(value) {
  9905. return Ext.String.htmlEncode(value);
  9906. },
  9907. /**
  9908. * Alias for {@link Ext.String#htmlDecode}.
  9909. * @inheritdoc Ext.String#htmlDecode
  9910. * @ignore
  9911. */
  9912. htmlDecode : function(value) {
  9913. return Ext.String.htmlDecode(value);
  9914. },
  9915. /**
  9916. * Alias for {@link Ext.String#urlAppend}.
  9917. * @inheritdoc Ext.String#urlAppend
  9918. * @ignore
  9919. */
  9920. urlAppend : function(url, s) {
  9921. return Ext.String.urlAppend(url, s);
  9922. }
  9923. });
  9924. Ext.ns = Ext.namespace;
  9925. // for old browsers
  9926. window.undefined = window.undefined;
  9927. /**
  9928. * @class Ext
  9929. */
  9930. (function(){
  9931. /*
  9932. FF 3.6 - Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17
  9933. FF 4.0.1 - Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
  9934. FF 5.0 - Mozilla/5.0 (Windows NT 6.1; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0
  9935. IE6 - Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1;)
  9936. IE7 - Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1;)
  9937. IE8 - Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)
  9938. IE9 - Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)
  9939. Chrome 11 - Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.60 Safari/534.24
  9940. Safari 5 - Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1
  9941. Opera 11.11 - Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11
  9942. */
  9943. var check = function(regex){
  9944. return regex.test(Ext.userAgent);
  9945. },
  9946. isStrict = document.compatMode == "CSS1Compat",
  9947. version = function (is, regex) {
  9948. var m;
  9949. return (is && (m = regex.exec(Ext.userAgent))) ? parseFloat(m[1]) : 0;
  9950. },
  9951. docMode = document.documentMode,
  9952. isOpera = check(/opera/),
  9953. isOpera10_5 = isOpera && check(/version\/10\.5/),
  9954. isChrome = check(/\bchrome\b/),
  9955. isWebKit = check(/webkit/),
  9956. isSafari = !isChrome && check(/safari/),
  9957. isSafari2 = isSafari && check(/applewebkit\/4/), // unique to Safari 2
  9958. isSafari3 = isSafari && check(/version\/3/),
  9959. isSafari4 = isSafari && check(/version\/4/),
  9960. isSafari5_0 = isSafari && check(/version\/5\.0/),
  9961. isSafari5 = isSafari && check(/version\/5/),
  9962. isIE = !isOpera && check(/msie/),
  9963. isIE7 = isIE && ((check(/msie 7/) && docMode != 8 && docMode != 9) || docMode == 7),
  9964. isIE8 = isIE && ((check(/msie 8/) && docMode != 7 && docMode != 9) || docMode == 8),
  9965. isIE9 = isIE && ((check(/msie 9/) && docMode != 7 && docMode != 8) || docMode == 9),
  9966. isIE6 = isIE && check(/msie 6/),
  9967. isGecko = !isWebKit && check(/gecko/),
  9968. isGecko3 = isGecko && check(/rv:1\.9/),
  9969. isGecko4 = isGecko && check(/rv:2\.0/),
  9970. isGecko5 = isGecko && check(/rv:5\./),
  9971. isGecko10 = isGecko && check(/rv:10\./),
  9972. isFF3_0 = isGecko3 && check(/rv:1\.9\.0/),
  9973. isFF3_5 = isGecko3 && check(/rv:1\.9\.1/),
  9974. isFF3_6 = isGecko3 && check(/rv:1\.9\.2/),
  9975. isWindows = check(/windows|win32/),
  9976. isMac = check(/macintosh|mac os x/),
  9977. isLinux = check(/linux/),
  9978. scrollbarSize = null,
  9979. chromeVersion = version(true, /\bchrome\/(\d+\.\d+)/),
  9980. firefoxVersion = version(true, /\bfirefox\/(\d+\.\d+)/),
  9981. ieVersion = version(isIE, /msie (\d+\.\d+)/),
  9982. operaVersion = version(isOpera, /version\/(\d+\.\d+)/),
  9983. safariVersion = version(isSafari, /version\/(\d+\.\d+)/),
  9984. webKitVersion = version(isWebKit, /webkit\/(\d+\.\d+)/),
  9985. isSecure = /^https/i.test(window.location.protocol),
  9986. nullLog;
  9987. // remove css image flicker
  9988. try {
  9989. document.execCommand("BackgroundImageCache", false, true);
  9990. } catch(e) {}
  9991. var primitiveRe = /string|number|boolean/;
  9992. function dumpObject (object) {
  9993. var member, type, value, name,
  9994. members = [];
  9995. // Cannot use Ext.encode since it can recurse endlessly (if we're lucky)
  9996. // ...and the data could be prettier!
  9997. for (name in object) {
  9998. if (object.hasOwnProperty(name)) {
  9999. value = object[name];
  10000. type = typeof value;
  10001. if (type == "function") {
  10002. continue;
  10003. }
  10004. if (type == 'undefined') {
  10005. member = type;
  10006. } else if (value === null || primitiveRe.test(type) || Ext.isDate(value)) {
  10007. member = Ext.encode(value);
  10008. } else if (Ext.isArray(value)) {
  10009. member = '[ ]';
  10010. } else if (Ext.isObject(value)) {
  10011. member = '{ }';
  10012. } else {
  10013. member = type;
  10014. }
  10015. members.push(Ext.encode(name) + ': ' + member);
  10016. }
  10017. }
  10018. if (members.length) {
  10019. return ' \nData: {\n ' + members.join(',\n ') + '\n}';
  10020. }
  10021. return '';
  10022. }
  10023. function log (message) {
  10024. var options, dump,
  10025. con = Ext.global.console,
  10026. level = 'log',
  10027. indent = log.indent || 0,
  10028. stack,
  10029. out,
  10030. max;
  10031. log.indent = indent;
  10032. if (typeof message != 'string') {
  10033. options = message;
  10034. message = options.msg || '';
  10035. level = options.level || level;
  10036. dump = options.dump;
  10037. stack = options.stack;
  10038. if (options.indent) {
  10039. ++log.indent;
  10040. } else if (options.outdent) {
  10041. log.indent = indent = Math.max(indent - 1, 0);
  10042. }
  10043. if (dump && !(con && con.dir)) {
  10044. message += dumpObject(dump);
  10045. dump = null;
  10046. }
  10047. }
  10048. if (arguments.length > 1) {
  10049. message += Array.prototype.slice.call(arguments, 1).join('');
  10050. }
  10051. message = indent ? Ext.String.repeat(' ', log.indentSize * indent) + message : message;
  10052. // w/o console, all messages are equal, so munge the level into the message:
  10053. if (level != 'log') {
  10054. message = '[' + level.charAt(0).toUpperCase() + '] ' + message;
  10055. }
  10056. // Not obvious, but 'console' comes and goes when Firebug is turned on/off, so
  10057. // an early test may fail either direction if Firebug is toggled.
  10058. //
  10059. if (con) { // if (Firebug-like console)
  10060. if (con[level]) {
  10061. con[level](message);
  10062. } else {
  10063. con.log(message);
  10064. }
  10065. if (dump) {
  10066. con.dir(dump);
  10067. }
  10068. if (stack && con.trace) {
  10069. // Firebug's console.error() includes a trace already...
  10070. if (!con.firebug || level != 'error') {
  10071. con.trace();
  10072. }
  10073. }
  10074. } else {
  10075. if (Ext.isOpera) {
  10076. opera.postError(message);
  10077. } else {
  10078. out = log.out;
  10079. max = log.max;
  10080. if (out.length >= max) {
  10081. // this formula allows out.max to change (via debugger), where the
  10082. // more obvious "max/4" would not quite be the same
  10083. Ext.Array.erase(out, 0, out.length - 3 * Math.floor(max / 4)); // keep newest 75%
  10084. }
  10085. out.push(message);
  10086. }
  10087. }
  10088. // Mostly informational, but the Ext.Error notifier uses them:
  10089. ++log.count;
  10090. ++log.counters[level];
  10091. }
  10092. function logx (level, args) {
  10093. if (typeof args[0] == 'string') {
  10094. args.unshift({});
  10095. }
  10096. args[0].level = level;
  10097. log.apply(this, args);
  10098. }
  10099. log.error = function () {
  10100. logx('error', Array.prototype.slice.call(arguments));
  10101. };
  10102. log.info = function () {
  10103. logx('info', Array.prototype.slice.call(arguments));
  10104. };
  10105. log.warn = function () {
  10106. logx('warn', Array.prototype.slice.call(arguments));
  10107. };
  10108. log.count = 0;
  10109. log.counters = { error: 0, warn: 0, info: 0, log: 0 };
  10110. log.indentSize = 2;
  10111. log.out = [];
  10112. log.max = 750;
  10113. log.show = function () {
  10114. window.open('','extlog').document.write([
  10115. '<html><head><script type="text/javascript">',
  10116. 'var lastCount = 0;',
  10117. 'function update () {',
  10118. 'var ext = window.opener.Ext,',
  10119. 'extlog = ext && ext.log;',
  10120. 'if (extlog && extlog.out && lastCount != extlog.count) {',
  10121. 'lastCount = extlog.count;',
  10122. 'var s = "<tt>" + extlog.out.join("~~~").replace(/[&]/g, "&amp;").replace(/[<]/g, "&lt;").replace(/[ ]/g, "&#160;").replace(/\\~\\~\\~/g, "<br/>") + "</tt>";',
  10123. 'document.body.innerHTML = s;',
  10124. '}',
  10125. 'setTimeout(update, 1000);',
  10126. '}',
  10127. 'setTimeout(update, 1000);',
  10128. '</script></head><body></body></html>'].join(''));
  10129. };
  10130. nullLog = function () {};
  10131. nullLog.info = nullLog.warn = nullLog.error = Ext.emptyFn;
  10132. Ext.setVersion('extjs', '4.1.1.1');
  10133. Ext.apply(Ext, {
  10134. /**
  10135. * @property {String} SSL_SECURE_URL
  10136. * URL to a blank file used by Ext when in secure mode for iframe src and onReady src
  10137. * to prevent the IE insecure content warning (`'about:blank'`, except for IE
  10138. * in secure mode, which is `'javascript:""'`).
  10139. */
  10140. SSL_SECURE_URL : isSecure && isIE ? 'javascript:\'\'' : 'about:blank',
  10141. /**
  10142. * @property {Boolean} enableFx
  10143. * True if the {@link Ext.fx.Anim} Class is available.
  10144. */
  10145. /**
  10146. * @property {Boolean} scopeResetCSS
  10147. * True to scope the reset CSS to be just applied to Ext components. Note that this
  10148. * wraps root containers with an additional element. Also remember that when you turn
  10149. * on this option, you have to use ext-all-scoped (unless you use the bootstrap.js to
  10150. * load your javascript, in which case it will be handled for you).
  10151. */
  10152. scopeResetCSS : Ext.buildSettings.scopeResetCSS,
  10153. /**
  10154. * @property {String} resetCls
  10155. * The css class used to wrap Ext components when the {@link #scopeResetCSS} option
  10156. * is used.
  10157. */
  10158. resetCls: Ext.buildSettings.baseCSSPrefix + 'reset',
  10159. /**
  10160. * @property {Boolean} enableNestedListenerRemoval
  10161. * **Experimental.** True to cascade listener removal to child elements when an element
  10162. * is removed. Currently not optimized for performance.
  10163. */
  10164. enableNestedListenerRemoval : false,
  10165. /**
  10166. * @property {Boolean} USE_NATIVE_JSON
  10167. * Indicates whether to use native browser parsing for JSON methods.
  10168. * This option is ignored if the browser does not support native JSON methods.
  10169. *
  10170. * **Note:** Native JSON methods will not work with objects that have functions.
  10171. * Also, property names must be quoted, otherwise the data will not parse.
  10172. */
  10173. USE_NATIVE_JSON : false,
  10174. /**
  10175. * Returns the dom node for the passed String (id), dom node, or Ext.Element.
  10176. * Optional 'strict' flag is needed for IE since it can return 'name' and
  10177. * 'id' elements by using getElementById.
  10178. *
  10179. * Here are some examples:
  10180. *
  10181. * // gets dom node based on id
  10182. * var elDom = Ext.getDom('elId');
  10183. * // gets dom node based on the dom node
  10184. * var elDom1 = Ext.getDom(elDom);
  10185. *
  10186. * // If we don&#39;t know if we are working with an
  10187. * // Ext.Element or a dom node use Ext.getDom
  10188. * function(el){
  10189. * var dom = Ext.getDom(el);
  10190. * // do something with the dom node
  10191. * }
  10192. *
  10193. * **Note:** the dom node to be found actually needs to exist (be rendered, etc)
  10194. * when this method is called to be successful.
  10195. *
  10196. * @param {String/HTMLElement/Ext.Element} el
  10197. * @return HTMLElement
  10198. */
  10199. getDom : function(el, strict) {
  10200. if (!el || !document) {
  10201. return null;
  10202. }
  10203. if (el.dom) {
  10204. return el.dom;
  10205. } else {
  10206. if (typeof el == 'string') {
  10207. var e = Ext.getElementById(el);
  10208. // IE returns elements with the 'name' and 'id' attribute.
  10209. // we do a strict check to return the element with only the id attribute
  10210. if (e && isIE && strict) {
  10211. if (el == e.getAttribute('id')) {
  10212. return e;
  10213. } else {
  10214. return null;
  10215. }
  10216. }
  10217. return e;
  10218. } else {
  10219. return el;
  10220. }
  10221. }
  10222. },
  10223. /**
  10224. * Removes a DOM node from the document.
  10225. *
  10226. * Removes this element from the document, removes all DOM event listeners, and
  10227. * deletes the cache reference. All DOM event listeners are removed from this element.
  10228. * If {@link Ext#enableNestedListenerRemoval Ext.enableNestedListenerRemoval} is
  10229. * `true`, then DOM event listeners are also removed from all child nodes.
  10230. * The body node will be ignored if passed in.
  10231. *
  10232. * @param {HTMLElement} node The node to remove
  10233. * @method
  10234. */
  10235. removeNode : isIE6 || isIE7 || isIE8
  10236. ? (function() {
  10237. var d;
  10238. return function(n){
  10239. if(n && n.tagName.toUpperCase() != 'BODY'){
  10240. (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
  10241. var cache = Ext.cache,
  10242. id = n.id;
  10243. if (cache[id]) {
  10244. delete cache[id].dom;
  10245. delete cache[id];
  10246. }
  10247. if (isIE8 && n.parentNode) {
  10248. n.parentNode.removeChild(n);
  10249. }
  10250. d = d || document.createElement('div');
  10251. d.appendChild(n);
  10252. d.innerHTML = '';
  10253. }
  10254. };
  10255. }())
  10256. : function(n) {
  10257. if (n && n.parentNode && n.tagName.toUpperCase() != 'BODY') {
  10258. (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
  10259. var cache = Ext.cache,
  10260. id = n.id;
  10261. if (cache[id]) {
  10262. delete cache[id].dom;
  10263. delete cache[id];
  10264. }
  10265. n.parentNode.removeChild(n);
  10266. }
  10267. },
  10268. isStrict: isStrict,
  10269. isIEQuirks: isIE && !isStrict,
  10270. /**
  10271. * True if the detected browser is Opera.
  10272. * @type Boolean
  10273. */
  10274. isOpera : isOpera,
  10275. /**
  10276. * True if the detected browser is Opera 10.5x.
  10277. * @type Boolean
  10278. */
  10279. isOpera10_5 : isOpera10_5,
  10280. /**
  10281. * True if the detected browser uses WebKit.
  10282. * @type Boolean
  10283. */
  10284. isWebKit : isWebKit,
  10285. /**
  10286. * True if the detected browser is Chrome.
  10287. * @type Boolean
  10288. */
  10289. isChrome : isChrome,
  10290. /**
  10291. * True if the detected browser is Safari.
  10292. * @type Boolean
  10293. */
  10294. isSafari : isSafari,
  10295. /**
  10296. * True if the detected browser is Safari 3.x.
  10297. * @type Boolean
  10298. */
  10299. isSafari3 : isSafari3,
  10300. /**
  10301. * True if the detected browser is Safari 4.x.
  10302. * @type Boolean
  10303. */
  10304. isSafari4 : isSafari4,
  10305. /**
  10306. * True if the detected browser is Safari 5.x.
  10307. * @type Boolean
  10308. */
  10309. isSafari5 : isSafari5,
  10310. /**
  10311. * True if the detected browser is Safari 5.0.x.
  10312. * @type Boolean
  10313. */
  10314. isSafari5_0 : isSafari5_0,
  10315. /**
  10316. * True if the detected browser is Safari 2.x.
  10317. * @type Boolean
  10318. */
  10319. isSafari2 : isSafari2,
  10320. /**
  10321. * True if the detected browser is Internet Explorer.
  10322. * @type Boolean
  10323. */
  10324. isIE : isIE,
  10325. /**
  10326. * True if the detected browser is Internet Explorer 6.x.
  10327. * @type Boolean
  10328. */
  10329. isIE6 : isIE6,
  10330. /**
  10331. * True if the detected browser is Internet Explorer 7.x.
  10332. * @type Boolean
  10333. */
  10334. isIE7 : isIE7,
  10335. /**
  10336. * True if the detected browser is Internet Explorer 8.x.
  10337. * @type Boolean
  10338. */
  10339. isIE8 : isIE8,
  10340. /**
  10341. * True if the detected browser is Internet Explorer 9.x.
  10342. * @type Boolean
  10343. */
  10344. isIE9 : isIE9,
  10345. /**
  10346. * True if the detected browser uses the Gecko layout engine (e.g. Mozilla, Firefox).
  10347. * @type Boolean
  10348. */
  10349. isGecko : isGecko,
  10350. /**
  10351. * True if the detected browser uses a Gecko 1.9+ layout engine (e.g. Firefox 3.x).
  10352. * @type Boolean
  10353. */
  10354. isGecko3 : isGecko3,
  10355. /**
  10356. * True if the detected browser uses a Gecko 2.0+ layout engine (e.g. Firefox 4.x).
  10357. * @type Boolean
  10358. */
  10359. isGecko4 : isGecko4,
  10360. /**
  10361. * True if the detected browser uses a Gecko 5.0+ layout engine (e.g. Firefox 5.x).
  10362. * @type Boolean
  10363. */
  10364. isGecko5 : isGecko5,
  10365. /**
  10366. * True if the detected browser uses a Gecko 5.0+ layout engine (e.g. Firefox 5.x).
  10367. * @type Boolean
  10368. */
  10369. isGecko10 : isGecko10,
  10370. /**
  10371. * True if the detected browser uses FireFox 3.0
  10372. * @type Boolean
  10373. */
  10374. isFF3_0 : isFF3_0,
  10375. /**
  10376. * True if the detected browser uses FireFox 3.5
  10377. * @type Boolean
  10378. */
  10379. isFF3_5 : isFF3_5,
  10380. /**
  10381. * True if the detected browser uses FireFox 3.6
  10382. * @type Boolean
  10383. */
  10384. isFF3_6 : isFF3_6,
  10385. /**
  10386. * True if the detected browser uses FireFox 4
  10387. * @type Boolean
  10388. */
  10389. isFF4 : 4 <= firefoxVersion && firefoxVersion < 5,
  10390. /**
  10391. * True if the detected browser uses FireFox 5
  10392. * @type Boolean
  10393. */
  10394. isFF5 : 5 <= firefoxVersion && firefoxVersion < 6,
  10395. /**
  10396. * True if the detected browser uses FireFox 10
  10397. * @type Boolean
  10398. */
  10399. isFF10 : 10 <= firefoxVersion && firefoxVersion < 11,
  10400. /**
  10401. * True if the detected platform is Linux.
  10402. * @type Boolean
  10403. */
  10404. isLinux : isLinux,
  10405. /**
  10406. * True if the detected platform is Windows.
  10407. * @type Boolean
  10408. */
  10409. isWindows : isWindows,
  10410. /**
  10411. * True if the detected platform is Mac OS.
  10412. * @type Boolean
  10413. */
  10414. isMac : isMac,
  10415. /**
  10416. * The current version of Chrome (0 if the browser is not Chrome).
  10417. * @type Number
  10418. */
  10419. chromeVersion: chromeVersion,
  10420. /**
  10421. * The current version of Firefox (0 if the browser is not Firefox).
  10422. * @type Number
  10423. */
  10424. firefoxVersion: firefoxVersion,
  10425. /**
  10426. * The current version of IE (0 if the browser is not IE). This does not account
  10427. * for the documentMode of the current page, which is factored into {@link #isIE7},
  10428. * {@link #isIE8} and {@link #isIE9}. Thus this is not always true:
  10429. *
  10430. * Ext.isIE8 == (Ext.ieVersion == 8)
  10431. *
  10432. * @type Number
  10433. */
  10434. ieVersion: ieVersion,
  10435. /**
  10436. * The current version of Opera (0 if the browser is not Opera).
  10437. * @type Number
  10438. */
  10439. operaVersion: operaVersion,
  10440. /**
  10441. * The current version of Safari (0 if the browser is not Safari).
  10442. * @type Number
  10443. */
  10444. safariVersion: safariVersion,
  10445. /**
  10446. * The current version of WebKit (0 if the browser does not use WebKit).
  10447. * @type Number
  10448. */
  10449. webKitVersion: webKitVersion,
  10450. /**
  10451. * True if the page is running over SSL
  10452. * @type Boolean
  10453. */
  10454. isSecure: isSecure,
  10455. /**
  10456. * URL to a 1x1 transparent gif image used by Ext to create inline icons with
  10457. * CSS background images. In older versions of IE, this defaults to
  10458. * "http://sencha.com/s.gif" and you should change this to a URL on your server.
  10459. * For other browsers it uses an inline data URL.
  10460. * @type String
  10461. */
  10462. BLANK_IMAGE_URL : (isIE6 || isIE7) ? '/' + '/www.sencha.com/s.gif' : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
  10463. /**
  10464. * Utility method for returning a default value if the passed value is empty.
  10465. *
  10466. * The value is deemed to be empty if it is:
  10467. *
  10468. * - null
  10469. * - undefined
  10470. * - an empty array
  10471. * - a zero length string (Unless the `allowBlank` parameter is `true`)
  10472. *
  10473. * @param {Object} value The value to test
  10474. * @param {Object} defaultValue The value to return if the original value is empty
  10475. * @param {Boolean} [allowBlank=false] true to allow zero length strings to qualify as non-empty.
  10476. * @return {Object} value, if non-empty, else defaultValue
  10477. * @deprecated 4.0.0 Use {@link Ext#valueFrom} instead
  10478. */
  10479. value : function(v, defaultValue, allowBlank){
  10480. return Ext.isEmpty(v, allowBlank) ? defaultValue : v;
  10481. },
  10482. /**
  10483. * Escapes the passed string for use in a regular expression.
  10484. * @param {String} str
  10485. * @return {String}
  10486. * @deprecated 4.0.0 Use {@link Ext.String#escapeRegex} instead
  10487. */
  10488. escapeRe : function(s) {
  10489. return s.replace(/([-.*+?\^${}()|\[\]\/\\])/g, "\\$1");
  10490. },
  10491. /**
  10492. * Applies event listeners to elements by selectors when the document is ready.
  10493. * The event name is specified with an `@` suffix.
  10494. *
  10495. * Ext.addBehaviors({
  10496. * // add a listener for click on all anchors in element with id foo
  10497. * '#foo a@click' : function(e, t){
  10498. * // do something
  10499. * },
  10500. *
  10501. * // add the same listener to multiple selectors (separated by comma BEFORE the @)
  10502. * '#foo a, #bar span.some-class@mouseover' : function(){
  10503. * // do something
  10504. * }
  10505. * });
  10506. *
  10507. * @param {Object} obj The list of behaviors to apply
  10508. */
  10509. addBehaviors : function(o){
  10510. if(!Ext.isReady){
  10511. Ext.onReady(function(){
  10512. Ext.addBehaviors(o);
  10513. });
  10514. } else {
  10515. var cache = {}, // simple cache for applying multiple behaviors to same selector does query multiple times
  10516. parts,
  10517. b,
  10518. s;
  10519. for (b in o) {
  10520. if ((parts = b.split('@'))[1]) { // for Object prototype breakers
  10521. s = parts[0];
  10522. if(!cache[s]){
  10523. cache[s] = Ext.select(s);
  10524. }
  10525. cache[s].on(parts[1], o[b]);
  10526. }
  10527. }
  10528. cache = null;
  10529. }
  10530. },
  10531. /**
  10532. * Returns the size of the browser scrollbars. This can differ depending on
  10533. * operating system settings, such as the theme or font size.
  10534. * @param {Boolean} [force] true to force a recalculation of the value.
  10535. * @return {Object} An object containing scrollbar sizes.
  10536. * @return.width {Number} The width of the vertical scrollbar.
  10537. * @return.height {Number} The height of the horizontal scrollbar.
  10538. */
  10539. getScrollbarSize: function (force) {
  10540. if (!Ext.isReady) {
  10541. return {};
  10542. }
  10543. if (force || !scrollbarSize) {
  10544. var db = document.body,
  10545. div = document.createElement('div');
  10546. div.style.width = div.style.height = '100px';
  10547. div.style.overflow = 'scroll';
  10548. div.style.position = 'absolute';
  10549. db.appendChild(div); // now we can measure the div...
  10550. // at least in iE9 the div is not 100px - the scrollbar size is removed!
  10551. scrollbarSize = {
  10552. width: div.offsetWidth - div.clientWidth,
  10553. height: div.offsetHeight - div.clientHeight
  10554. };
  10555. db.removeChild(div);
  10556. }
  10557. return scrollbarSize;
  10558. },
  10559. /**
  10560. * Utility method for getting the width of the browser's vertical scrollbar. This
  10561. * can differ depending on operating system settings, such as the theme or font size.
  10562. *
  10563. * This method is deprected in favor of {@link #getScrollbarSize}.
  10564. *
  10565. * @param {Boolean} [force] true to force a recalculation of the value.
  10566. * @return {Number} The width of a vertical scrollbar.
  10567. * @deprecated
  10568. */
  10569. getScrollBarWidth: function(force){
  10570. var size = Ext.getScrollbarSize(force);
  10571. return size.width + 2; // legacy fudge factor
  10572. },
  10573. /**
  10574. * Copies a set of named properties fom the source object to the destination object.
  10575. *
  10576. * Example:
  10577. *
  10578. * ImageComponent = Ext.extend(Ext.Component, {
  10579. * initComponent: function() {
  10580. * this.autoEl = { tag: 'img' };
  10581. * MyComponent.superclass.initComponent.apply(this, arguments);
  10582. * this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height');
  10583. * }
  10584. * });
  10585. *
  10586. * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
  10587. *
  10588. * @param {Object} dest The destination object.
  10589. * @param {Object} source The source object.
  10590. * @param {String/String[]} names Either an Array of property names, or a comma-delimited list
  10591. * of property names to copy.
  10592. * @param {Boolean} [usePrototypeKeys] Defaults to false. Pass true to copy keys off of the
  10593. * prototype as well as the instance.
  10594. * @return {Object} The modified object.
  10595. */
  10596. copyTo : function(dest, source, names, usePrototypeKeys){
  10597. if(typeof names == 'string'){
  10598. names = names.split(/[,;\s]/);
  10599. }
  10600. var n,
  10601. nLen = names.length,
  10602. name;
  10603. for(n = 0; n < nLen; n++) {
  10604. name = names[n];
  10605. if(usePrototypeKeys || source.hasOwnProperty(name)){
  10606. dest[name] = source[name];
  10607. }
  10608. }
  10609. return dest;
  10610. },
  10611. /**
  10612. * Attempts to destroy and then remove a set of named properties of the passed object.
  10613. * @param {Object} o The object (most likely a Component) who's properties you wish to destroy.
  10614. * @param {String...} args One or more names of the properties to destroy and remove from the object.
  10615. */
  10616. destroyMembers : function(o){
  10617. for (var i = 1, a = arguments, len = a.length; i < len; i++) {
  10618. Ext.destroy(o[a[i]]);
  10619. delete o[a[i]];
  10620. }
  10621. },
  10622. /**
  10623. * Logs a message. If a console is present it will be used. On Opera, the method
  10624. * "opera.postError" is called. In other cases, the message is logged to an array
  10625. * "Ext.log.out". An attached debugger can watch this array and view the log. The
  10626. * log buffer is limited to a maximum of "Ext.log.max" entries (defaults to 250).
  10627. * The `Ext.log.out` array can also be written to a popup window by entering the
  10628. * following in the URL bar (a "bookmarklet"):
  10629. *
  10630. * javascript:void(Ext.log.show());
  10631. *
  10632. * If additional parameters are passed, they are joined and appended to the message.
  10633. * A technique for tracing entry and exit of a function is this:
  10634. *
  10635. * function foo () {
  10636. * Ext.log({ indent: 1 }, '>> foo');
  10637. *
  10638. * // log statements in here or methods called from here will be indented
  10639. * // by one step
  10640. *
  10641. * Ext.log({ outdent: 1 }, '<< foo');
  10642. * }
  10643. *
  10644. * This method does nothing in a release build.
  10645. *
  10646. * @param {String/Object} [options] The message to log or an options object with any
  10647. * of the following properties:
  10648. *
  10649. * - `msg`: The message to log (required).
  10650. * - `level`: One of: "error", "warn", "info" or "log" (the default is "log").
  10651. * - `dump`: An object to dump to the log as part of the message.
  10652. * - `stack`: True to include a stack trace in the log.
  10653. * - `indent`: Cause subsequent log statements to be indented one step.
  10654. * - `outdent`: Cause this and following statements to be one step less indented.
  10655. *
  10656. * @param {String...} [message] The message to log (required unless specified in
  10657. * options object).
  10658. *
  10659. * @method
  10660. */
  10661. log :
  10662. log ||
  10663. nullLog,
  10664. /**
  10665. * Partitions the set into two sets: a true set and a false set.
  10666. *
  10667. * Example 1:
  10668. *
  10669. * Ext.partition([true, false, true, true, false]);
  10670. * // returns [[true, true, true], [false, false]]
  10671. *
  10672. * Example 2:
  10673. *
  10674. * Ext.partition(
  10675. * Ext.query("p"),
  10676. * function(val){
  10677. * return val.className == "class1"
  10678. * }
  10679. * );
  10680. * // true are those paragraph elements with a className of "class1",
  10681. * // false set are those that do not have that className.
  10682. *
  10683. * @param {Array/NodeList} arr The array to partition
  10684. * @param {Function} truth (optional) a function to determine truth.
  10685. * If this is omitted the element itself must be able to be evaluated for its truthfulness.
  10686. * @return {Array} [array of truish values, array of falsy values]
  10687. * @deprecated 4.0.0 Will be removed in the next major version
  10688. */
  10689. partition : function(arr, truth){
  10690. var ret = [[],[]],
  10691. a, v,
  10692. aLen = arr.length;
  10693. for (a = 0; a < aLen; a++) {
  10694. v = arr[a];
  10695. ret[ (truth && truth(v, a, arr)) || (!truth && v) ? 0 : 1].push(v);
  10696. }
  10697. return ret;
  10698. },
  10699. /**
  10700. * Invokes a method on each item in an Array.
  10701. *
  10702. * Example:
  10703. *
  10704. * Ext.invoke(Ext.query("p"), "getAttribute", "id");
  10705. * // [el1.getAttribute("id"), el2.getAttribute("id"), ..., elN.getAttribute("id")]
  10706. *
  10707. * @param {Array/NodeList} arr The Array of items to invoke the method on.
  10708. * @param {String} methodName The method name to invoke.
  10709. * @param {Object...} args Arguments to send into the method invocation.
  10710. * @return {Array} The results of invoking the method on each item in the array.
  10711. * @deprecated 4.0.0 Will be removed in the next major version
  10712. */
  10713. invoke : function(arr, methodName){
  10714. var ret = [],
  10715. args = Array.prototype.slice.call(arguments, 2),
  10716. a, v,
  10717. aLen = arr.length;
  10718. for (a = 0; a < aLen; a++) {
  10719. v = arr[a];
  10720. if (v && typeof v[methodName] == 'function') {
  10721. ret.push(v[methodName].apply(v, args));
  10722. } else {
  10723. ret.push(undefined);
  10724. }
  10725. }
  10726. return ret;
  10727. },
  10728. /**
  10729. * Zips N sets together.
  10730. *
  10731. * Example 1:
  10732. *
  10733. * Ext.zip([1,2,3],[4,5,6]); // [[1,4],[2,5],[3,6]]
  10734. *
  10735. * Example 2:
  10736. *
  10737. * Ext.zip(
  10738. * [ "+", "-", "+"],
  10739. * [ 12, 10, 22],
  10740. * [ 43, 15, 96],
  10741. * function(a, b, c){
  10742. * return "$" + a + "" + b + "." + c
  10743. * }
  10744. * ); // ["$+12.43", "$-10.15", "$+22.96"]
  10745. *
  10746. * @param {Array/NodeList...} arr This argument may be repeated. Array(s)
  10747. * to contribute values.
  10748. * @param {Function} zipper (optional) The last item in the argument list.
  10749. * This will drive how the items are zipped together.
  10750. * @return {Array} The zipped set.
  10751. * @deprecated 4.0.0 Will be removed in the next major version
  10752. */
  10753. zip : function(){
  10754. var parts = Ext.partition(arguments, function( val ){ return typeof val != 'function'; }),
  10755. arrs = parts[0],
  10756. fn = parts[1][0],
  10757. len = Ext.max(Ext.pluck(arrs, "length")),
  10758. ret = [],
  10759. i,
  10760. j,
  10761. aLen;
  10762. for (i = 0; i < len; i++) {
  10763. ret[i] = [];
  10764. if(fn){
  10765. ret[i] = fn.apply(fn, Ext.pluck(arrs, i));
  10766. }else{
  10767. for (j = 0, aLen = arrs.length; j < aLen; j++){
  10768. ret[i].push( arrs[j][i] );
  10769. }
  10770. }
  10771. }
  10772. return ret;
  10773. },
  10774. /**
  10775. * Turns an array into a sentence, joined by a specified connector - e.g.:
  10776. *
  10777. * Ext.toSentence(['Adama', 'Tigh', 'Roslin']); //'Adama, Tigh and Roslin'
  10778. * Ext.toSentence(['Adama', 'Tigh', 'Roslin'], 'or'); //'Adama, Tigh or Roslin'
  10779. *
  10780. * @param {String[]} items The array to create a sentence from
  10781. * @param {String} connector The string to use to connect the last two words.
  10782. * Usually 'and' or 'or' - defaults to 'and'.
  10783. * @return {String} The sentence string
  10784. * @deprecated 4.0.0 Will be removed in the next major version
  10785. */
  10786. toSentence: function(items, connector) {
  10787. var length = items.length,
  10788. head,
  10789. tail;
  10790. if (length <= 1) {
  10791. return items[0];
  10792. } else {
  10793. head = items.slice(0, length - 1);
  10794. tail = items[length - 1];
  10795. return Ext.util.Format.format("{0} {1} {2}", head.join(", "), connector || 'and', tail);
  10796. }
  10797. },
  10798. /**
  10799. * @property {Boolean} useShims
  10800. * By default, Ext intelligently decides whether floating elements should be shimmed.
  10801. * If you are using flash, you may want to set this to true.
  10802. */
  10803. useShims: isIE6
  10804. });
  10805. }());
  10806. /**
  10807. * Loads Ext.app.Application class and starts it up with given configuration after the page is ready.
  10808. *
  10809. * See Ext.app.Application for details.
  10810. *
  10811. * @param {Object} config
  10812. */
  10813. Ext.application = function(config) {
  10814. Ext.require('Ext.app.Application');
  10815. Ext.onReady(function() {
  10816. new Ext.app.Application(config);
  10817. });
  10818. };
  10819. //@tag extras,core
  10820. //@require ../Ext-more.js
  10821. //@define Ext.util.Format
  10822. /**
  10823. * @class Ext.util.Format
  10824. *
  10825. * This class is a centralized place for formatting functions. It includes
  10826. * functions to format various different types of data, such as text, dates and numeric values.
  10827. *
  10828. * ## Localization
  10829. *
  10830. * This class contains several options for localization. These can be set once the library has loaded,
  10831. * all calls to the functions from that point will use the locale settings that were specified.
  10832. *
  10833. * Options include:
  10834. *
  10835. * - thousandSeparator
  10836. * - decimalSeparator
  10837. * - currenyPrecision
  10838. * - currencySign
  10839. * - currencyAtEnd
  10840. *
  10841. * This class also uses the default date format defined here: {@link Ext.Date#defaultFormat}.
  10842. *
  10843. * ## Using with renderers
  10844. *
  10845. * There are two helper functions that return a new function that can be used in conjunction with
  10846. * grid renderers:
  10847. *
  10848. * columns: [{
  10849. * dataIndex: 'date',
  10850. * renderer: Ext.util.Format.dateRenderer('Y-m-d')
  10851. * }, {
  10852. * dataIndex: 'time',
  10853. * renderer: Ext.util.Format.numberRenderer('0.000')
  10854. * }]
  10855. *
  10856. * Functions that only take a single argument can also be passed directly:
  10857. *
  10858. * columns: [{
  10859. * dataIndex: 'cost',
  10860. * renderer: Ext.util.Format.usMoney
  10861. * }, {
  10862. * dataIndex: 'productCode',
  10863. * renderer: Ext.util.Format.uppercase
  10864. * }]
  10865. *
  10866. * ## Using with XTemplates
  10867. *
  10868. * XTemplates can also directly use Ext.util.Format functions:
  10869. *
  10870. * new Ext.XTemplate([
  10871. * 'Date: {startDate:date("Y-m-d")}',
  10872. * 'Cost: {cost:usMoney}'
  10873. * ]);
  10874. *
  10875. * @singleton
  10876. */
  10877. (function() {
  10878. Ext.ns('Ext.util');
  10879. Ext.util.Format = {};
  10880. var UtilFormat = Ext.util.Format,
  10881. stripTagsRE = /<\/?[^>]+>/gi,
  10882. stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
  10883. nl2brRe = /\r?\n/g,
  10884. // A RegExp to remove from a number format string, all characters except digits and '.'
  10885. formatCleanRe = /[^\d\.]/g,
  10886. // A RegExp to remove from a number format string, all characters except digits and the local decimal separator.
  10887. // Created on first use. The local decimal separator character must be initialized for this to be created.
  10888. I18NFormatCleanRe;
  10889. Ext.apply(UtilFormat, {
  10890. //<locale>
  10891. /**
  10892. * @property {String} thousandSeparator
  10893. * The character that the {@link #number} function uses as a thousand separator.
  10894. *
  10895. * This may be overridden in a locale file.
  10896. */
  10897. thousandSeparator: ',',
  10898. //</locale>
  10899. //<locale>
  10900. /**
  10901. * @property {String} decimalSeparator
  10902. * The character that the {@link #number} function uses as a decimal point.
  10903. *
  10904. * This may be overridden in a locale file.
  10905. */
  10906. decimalSeparator: '.',
  10907. //</locale>
  10908. //<locale>
  10909. /**
  10910. * @property {Number} currencyPrecision
  10911. * The number of decimal places that the {@link #currency} function displays.
  10912. *
  10913. * This may be overridden in a locale file.
  10914. */
  10915. currencyPrecision: 2,
  10916. //</locale>
  10917. //<locale>
  10918. /**
  10919. * @property {String} currencySign
  10920. * The currency sign that the {@link #currency} function displays.
  10921. *
  10922. * This may be overridden in a locale file.
  10923. */
  10924. currencySign: '$',
  10925. //</locale>
  10926. //<locale>
  10927. /**
  10928. * @property {Boolean} currencyAtEnd
  10929. * This may be set to <code>true</code> to make the {@link #currency} function
  10930. * append the currency sign to the formatted value.
  10931. *
  10932. * This may be overridden in a locale file.
  10933. */
  10934. currencyAtEnd: false,
  10935. //</locale>
  10936. /**
  10937. * Checks a reference and converts it to empty string if it is undefined.
  10938. * @param {Object} value Reference to check
  10939. * @return {Object} Empty string if converted, otherwise the original value
  10940. */
  10941. undef : function(value) {
  10942. return value !== undefined ? value : "";
  10943. },
  10944. /**
  10945. * Checks a reference and converts it to the default value if it's empty.
  10946. * @param {Object} value Reference to check
  10947. * @param {String} [defaultValue=""] The value to insert of it's undefined.
  10948. * @return {String}
  10949. */
  10950. defaultValue : function(value, defaultValue) {
  10951. return value !== undefined && value !== '' ? value : defaultValue;
  10952. },
  10953. /**
  10954. * Returns a substring from within an original string.
  10955. * @param {String} value The original text
  10956. * @param {Number} start The start index of the substring
  10957. * @param {Number} length The length of the substring
  10958. * @return {String} The substring
  10959. * @method
  10960. */
  10961. substr : 'ab'.substr(-1) != 'b'
  10962. ? function (value, start, length) {
  10963. var str = String(value);
  10964. return (start < 0)
  10965. ? str.substr(Math.max(str.length + start, 0), length)
  10966. : str.substr(start, length);
  10967. }
  10968. : function(value, start, length) {
  10969. return String(value).substr(start, length);
  10970. },
  10971. /**
  10972. * Converts a string to all lower case letters.
  10973. * @param {String} value The text to convert
  10974. * @return {String} The converted text
  10975. */
  10976. lowercase : function(value) {
  10977. return String(value).toLowerCase();
  10978. },
  10979. /**
  10980. * Converts a string to all upper case letters.
  10981. * @param {String} value The text to convert
  10982. * @return {String} The converted text
  10983. */
  10984. uppercase : function(value) {
  10985. return String(value).toUpperCase();
  10986. },
  10987. /**
  10988. * Format a number as US currency.
  10989. * @param {Number/String} value The numeric value to format
  10990. * @return {String} The formatted currency string
  10991. */
  10992. usMoney : function(v) {
  10993. return UtilFormat.currency(v, '$', 2);
  10994. },
  10995. /**
  10996. * Format a number as a currency.
  10997. * @param {Number/String} value The numeric value to format
  10998. * @param {String} [sign] The currency sign to use (defaults to {@link #currencySign})
  10999. * @param {Number} [decimals] The number of decimals to use for the currency
  11000. * (defaults to {@link #currencyPrecision})
  11001. * @param {Boolean} [end] True if the currency sign should be at the end of the string
  11002. * (defaults to {@link #currencyAtEnd})
  11003. * @return {String} The formatted currency string
  11004. */
  11005. currency: function(v, currencySign, decimals, end) {
  11006. var negativeSign = '',
  11007. format = ",0",
  11008. i = 0;
  11009. v = v - 0;
  11010. if (v < 0) {
  11011. v = -v;
  11012. negativeSign = '-';
  11013. }
  11014. decimals = Ext.isDefined(decimals) ? decimals : UtilFormat.currencyPrecision;
  11015. format += format + (decimals > 0 ? '.' : '');
  11016. for (; i < decimals; i++) {
  11017. format += '0';
  11018. }
  11019. v = UtilFormat.number(v, format);
  11020. if ((end || UtilFormat.currencyAtEnd) === true) {
  11021. return Ext.String.format("{0}{1}{2}", negativeSign, v, currencySign || UtilFormat.currencySign);
  11022. } else {
  11023. return Ext.String.format("{0}{1}{2}", negativeSign, currencySign || UtilFormat.currencySign, v);
  11024. }
  11025. },
  11026. /**
  11027. * Formats the passed date using the specified format pattern.
  11028. * @param {String/Date} value The value to format. If a string is passed, it is converted to a Date
  11029. * by the Javascript's built-in Date#parse method.
  11030. * @param {String} [format] Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
  11031. * @return {String} The formatted date string.
  11032. */
  11033. date: function(v, format) {
  11034. if (!v) {
  11035. return "";
  11036. }
  11037. if (!Ext.isDate(v)) {
  11038. v = new Date(Date.parse(v));
  11039. }
  11040. return Ext.Date.dateFormat(v, format || Ext.Date.defaultFormat);
  11041. },
  11042. /**
  11043. * Returns a date rendering function that can be reused to apply a date format multiple times efficiently.
  11044. * @param {String} format Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
  11045. * @return {Function} The date formatting function
  11046. */
  11047. dateRenderer : function(format) {
  11048. return function(v) {
  11049. return UtilFormat.date(v, format);
  11050. };
  11051. },
  11052. /**
  11053. * Strips all HTML tags.
  11054. * @param {Object} value The text from which to strip tags
  11055. * @return {String} The stripped text
  11056. */
  11057. stripTags : function(v) {
  11058. return !v ? v : String(v).replace(stripTagsRE, "");
  11059. },
  11060. /**
  11061. * Strips all script tags.
  11062. * @param {Object} value The text from which to strip script tags
  11063. * @return {String} The stripped text
  11064. */
  11065. stripScripts : function(v) {
  11066. return !v ? v : String(v).replace(stripScriptsRe, "");
  11067. },
  11068. /**
  11069. * Simple format for a file size (xxx bytes, xxx KB, xxx MB).
  11070. * @param {Number/String} size The numeric value to format
  11071. * @return {String} The formatted file size
  11072. */
  11073. fileSize : function(size) {
  11074. if (size < 1024) {
  11075. return size + " bytes";
  11076. } else if (size < 1048576) {
  11077. return (Math.round(((size*10) / 1024))/10) + " KB";
  11078. } else {
  11079. return (Math.round(((size*10) / 1048576))/10) + " MB";
  11080. }
  11081. },
  11082. /**
  11083. * It does simple math for use in a template, for example:
  11084. *
  11085. * var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}');
  11086. *
  11087. * @return {Function} A function that operates on the passed value.
  11088. * @method
  11089. */
  11090. math : (function(){
  11091. var fns = {};
  11092. return function(v, a){
  11093. if (!fns[a]) {
  11094. fns[a] = Ext.functionFactory('v', 'return v ' + a + ';');
  11095. }
  11096. return fns[a](v);
  11097. };
  11098. }()),
  11099. /**
  11100. * Rounds the passed number to the required decimal precision.
  11101. * @param {Number/String} value The numeric value to round.
  11102. * @param {Number} precision The number of decimal places to which to round the first parameter's value.
  11103. * @return {Number} The rounded value.
  11104. */
  11105. round : function(value, precision) {
  11106. var result = Number(value);
  11107. if (typeof precision == 'number') {
  11108. precision = Math.pow(10, precision);
  11109. result = Math.round(value * precision) / precision;
  11110. }
  11111. return result;
  11112. },
  11113. /**
  11114. * Formats the passed number according to the passed format string.
  11115. *
  11116. * The number of digits after the decimal separator character specifies the number of
  11117. * decimal places in the resulting string. The *local-specific* decimal character is
  11118. * used in the result.
  11119. *
  11120. * The *presence* of a thousand separator character in the format string specifies that
  11121. * the *locale-specific* thousand separator (if any) is inserted separating thousand groups.
  11122. *
  11123. * By default, "," is expected as the thousand separator, and "." is expected as the decimal separator.
  11124. *
  11125. * ## New to Ext JS 4
  11126. *
  11127. * Locale-specific characters are always used in the formatted output when inserting
  11128. * thousand and decimal separators.
  11129. *
  11130. * The format string must specify separator characters according to US/UK conventions ("," as the
  11131. * thousand separator, and "." as the decimal separator)
  11132. *
  11133. * To allow specification of format strings according to local conventions for separator characters, add
  11134. * the string `/i` to the end of the format string.
  11135. *
  11136. * examples (123456.789):
  11137. *
  11138. * - `0` - (123456) show only digits, no precision
  11139. * - `0.00` - (123456.78) show only digits, 2 precision
  11140. * - `0.0000` - (123456.7890) show only digits, 4 precision
  11141. * - `0,000` - (123,456) show comma and digits, no precision
  11142. * - `0,000.00` - (123,456.78) show comma and digits, 2 precision
  11143. * - `0,0.00` - (123,456.78) shortcut method, show comma and digits, 2 precision
  11144. *
  11145. * To allow specification of the formatting string using UK/US grouping characters (,) and
  11146. * decimal (.) for international numbers, add /i to the end. For example: 0.000,00/i
  11147. *
  11148. * @param {Number} v The number to format.
  11149. * @param {String} format The way you would like to format this text.
  11150. * @return {String} The formatted number.
  11151. */
  11152. number : function(v, formatString) {
  11153. if (!formatString) {
  11154. return v;
  11155. }
  11156. v = Ext.Number.from(v, NaN);
  11157. if (isNaN(v)) {
  11158. return '';
  11159. }
  11160. var comma = UtilFormat.thousandSeparator,
  11161. dec = UtilFormat.decimalSeparator,
  11162. i18n = false,
  11163. neg = v < 0,
  11164. hasComma,
  11165. psplit,
  11166. fnum,
  11167. cnum,
  11168. parr,
  11169. j,
  11170. m,
  11171. n,
  11172. i;
  11173. v = Math.abs(v);
  11174. // The "/i" suffix allows caller to use a locale-specific formatting string.
  11175. // Clean the format string by removing all but numerals and the decimal separator.
  11176. // Then split the format string into pre and post decimal segments according to *what* the
  11177. // decimal separator is. If they are specifying "/i", they are using the local convention in the format string.
  11178. if (formatString.substr(formatString.length - 2) == '/i') {
  11179. if (!I18NFormatCleanRe) {
  11180. I18NFormatCleanRe = new RegExp('[^\\d\\' + UtilFormat.decimalSeparator + ']','g');
  11181. }
  11182. formatString = formatString.substr(0, formatString.length - 2);
  11183. i18n = true;
  11184. hasComma = formatString.indexOf(comma) != -1;
  11185. psplit = formatString.replace(I18NFormatCleanRe, '').split(dec);
  11186. } else {
  11187. hasComma = formatString.indexOf(',') != -1;
  11188. psplit = formatString.replace(formatCleanRe, '').split('.');
  11189. }
  11190. if (psplit.length > 2) {
  11191. Ext.Error.raise({
  11192. sourceClass: "Ext.util.Format",
  11193. sourceMethod: "number",
  11194. value: v,
  11195. formatString: formatString,
  11196. msg: "Invalid number format, should have no more than 1 decimal"
  11197. });
  11198. } else if (psplit.length > 1) {
  11199. v = Ext.Number.toFixed(v, psplit[1].length);
  11200. } else {
  11201. v = Ext.Number.toFixed(v, 0);
  11202. }
  11203. fnum = v.toString();
  11204. psplit = fnum.split('.');
  11205. if (hasComma) {
  11206. cnum = psplit[0];
  11207. parr = [];
  11208. j = cnum.length;
  11209. m = Math.floor(j / 3);
  11210. n = cnum.length % 3 || 3;
  11211. for (i = 0; i < j; i += n) {
  11212. if (i !== 0) {
  11213. n = 3;
  11214. }
  11215. parr[parr.length] = cnum.substr(i, n);
  11216. m -= 1;
  11217. }
  11218. fnum = parr.join(comma);
  11219. if (psplit[1]) {
  11220. fnum += dec + psplit[1];
  11221. }
  11222. } else {
  11223. if (psplit[1]) {
  11224. fnum = psplit[0] + dec + psplit[1];
  11225. }
  11226. }
  11227. if (neg) {
  11228. /*
  11229. * Edge case. If we have a very small negative number it will get rounded to 0,
  11230. * however the initial check at the top will still report as negative. Replace
  11231. * everything but 1-9 and check if the string is empty to determine a 0 value.
  11232. */
  11233. neg = fnum.replace(/[^1-9]/g, '') !== '';
  11234. }
  11235. return (neg ? '-' : '') + formatString.replace(/[\d,?\.?]+/, fnum);
  11236. },
  11237. /**
  11238. * Returns a number rendering function that can be reused to apply a number format multiple
  11239. * times efficiently.
  11240. *
  11241. * @param {String} format Any valid number format string for {@link #number}
  11242. * @return {Function} The number formatting function
  11243. */
  11244. numberRenderer : function(format) {
  11245. return function(v) {
  11246. return UtilFormat.number(v, format);
  11247. };
  11248. },
  11249. /**
  11250. * Selectively do a plural form of a word based on a numeric value. For example, in a template,
  11251. * `{commentCount:plural("Comment")}` would result in `"1 Comment"` if commentCount was 1 or
  11252. * would be `"x Comments"` if the value is 0 or greater than 1.
  11253. *
  11254. * @param {Number} value The value to compare against
  11255. * @param {String} singular The singular form of the word
  11256. * @param {String} [plural] The plural form of the word (defaults to the singular with an "s")
  11257. */
  11258. plural : function(v, s, p) {
  11259. return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
  11260. },
  11261. /**
  11262. * Converts newline characters to the HTML tag `<br/>`
  11263. *
  11264. * @param {String} The string value to format.
  11265. * @return {String} The string with embedded `<br/>` tags in place of newlines.
  11266. */
  11267. nl2br : function(v) {
  11268. return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
  11269. },
  11270. /**
  11271. * Alias for {@link Ext.String#capitalize}.
  11272. * @method
  11273. * @inheritdoc Ext.String#capitalize
  11274. */
  11275. capitalize: Ext.String.capitalize,
  11276. /**
  11277. * Alias for {@link Ext.String#ellipsis}.
  11278. * @method
  11279. * @inheritdoc Ext.String#ellipsis
  11280. */
  11281. ellipsis: Ext.String.ellipsis,
  11282. /**
  11283. * Alias for {@link Ext.String#format}.
  11284. * @method
  11285. * @inheritdoc Ext.String#format
  11286. */
  11287. format: Ext.String.format,
  11288. /**
  11289. * Alias for {@link Ext.String#htmlDecode}.
  11290. * @method
  11291. * @inheritdoc Ext.String#htmlDecode
  11292. */
  11293. htmlDecode: Ext.String.htmlDecode,
  11294. /**
  11295. * Alias for {@link Ext.String#htmlEncode}.
  11296. * @method
  11297. * @inheritdoc Ext.String#htmlEncode
  11298. */
  11299. htmlEncode: Ext.String.htmlEncode,
  11300. /**
  11301. * Alias for {@link Ext.String#leftPad}.
  11302. * @method
  11303. * @inheritdoc Ext.String#leftPad
  11304. */
  11305. leftPad: Ext.String.leftPad,
  11306. /**
  11307. * Alias for {@link Ext.String#trim}.
  11308. * @method
  11309. * @inheritdoc Ext.String#trim
  11310. */
  11311. trim : Ext.String.trim,
  11312. /**
  11313. * Parses a number or string representing margin sizes into an object.
  11314. * Supports CSS-style margin declarations (e.g. 10, "10", "10 10", "10 10 10" and
  11315. * "10 10 10 10" are all valid options and would return the same result).
  11316. *
  11317. * @param {Number/String} v The encoded margins
  11318. * @return {Object} An object with margin sizes for top, right, bottom and left
  11319. */
  11320. parseBox : function(box) {
  11321. box = Ext.isEmpty(box) ? '' : box;
  11322. if (Ext.isNumber(box)) {
  11323. box = box.toString();
  11324. }
  11325. var parts = box.split(' '),
  11326. ln = parts.length;
  11327. if (ln == 1) {
  11328. parts[1] = parts[2] = parts[3] = parts[0];
  11329. }
  11330. else if (ln == 2) {
  11331. parts[2] = parts[0];
  11332. parts[3] = parts[1];
  11333. }
  11334. else if (ln == 3) {
  11335. parts[3] = parts[1];
  11336. }
  11337. return {
  11338. top :parseInt(parts[0], 10) || 0,
  11339. right :parseInt(parts[1], 10) || 0,
  11340. bottom:parseInt(parts[2], 10) || 0,
  11341. left :parseInt(parts[3], 10) || 0
  11342. };
  11343. },
  11344. /**
  11345. * Escapes the passed string for use in a regular expression.
  11346. * @param {String} str
  11347. * @return {String}
  11348. */
  11349. escapeRegex : function(s) {
  11350. return s.replace(/([\-.*+?\^${}()|\[\]\/\\])/g, "\\$1");
  11351. }
  11352. });
  11353. }());
  11354. //@tag extras,core
  11355. //@require Format.js
  11356. //@define Ext.util.TaskManager
  11357. //@define Ext.TaskManager
  11358. /**
  11359. * Provides the ability to execute one or more arbitrary tasks in a asynchronous manner.
  11360. * Generally, you can use the singleton {@link Ext.TaskManager} instead, but if needed,
  11361. * you can create separate instances of TaskRunner. Any number of separate tasks can be
  11362. * started at any time and will run independently of each other.
  11363. *
  11364. * Example usage:
  11365. *
  11366. * // Start a simple clock task that updates a div once per second
  11367. * var updateClock = function () {
  11368. * Ext.fly('clock').update(new Date().format('g:i:s A'));
  11369. * }
  11370. *
  11371. * var runner = new Ext.util.TaskRunner();
  11372. * var task = runner.start({
  11373. * run: updateClock,
  11374. * interval: 1000
  11375. * }
  11376. *
  11377. * The equivalent using TaskManager:
  11378. *
  11379. * var task = Ext.TaskManager.start({
  11380. * run: updateClock,
  11381. * interval: 1000
  11382. * });
  11383. *
  11384. * To end a running task:
  11385. *
  11386. * Ext.TaskManager.stop(task);
  11387. *
  11388. * If a task needs to be started and stopped repeated over time, you can create a
  11389. * {@link Ext.util.TaskRunner.Task Task} instance.
  11390. *
  11391. * var task = runner.newTask({
  11392. * run: function () {
  11393. * // useful code
  11394. * },
  11395. * interval: 1000
  11396. * });
  11397. *
  11398. * task.start();
  11399. *
  11400. * // ...
  11401. *
  11402. * task.stop();
  11403. *
  11404. * // ...
  11405. *
  11406. * task.start();
  11407. *
  11408. * A re-usable, one-shot task can be managed similar to the above:
  11409. *
  11410. * var task = runner.newTask({
  11411. * run: function () {
  11412. * // useful code to run once
  11413. * },
  11414. * repeat: 1
  11415. * });
  11416. *
  11417. * task.start();
  11418. *
  11419. * // ...
  11420. *
  11421. * task.start();
  11422. *
  11423. * See the {@link #start} method for details about how to configure a task object.
  11424. *
  11425. * Also see {@link Ext.util.DelayedTask}.
  11426. *
  11427. * @constructor
  11428. * @param {Number/Object} [interval=10] The minimum precision in milliseconds supported by this
  11429. * TaskRunner instance. Alternatively, a config object to apply to the new instance.
  11430. */
  11431. Ext.define('Ext.util.TaskRunner', {
  11432. /**
  11433. * @cfg interval
  11434. * The timer resolution.
  11435. */
  11436. interval: 10,
  11437. /**
  11438. * @property timerId
  11439. * The id of the current timer.
  11440. * @private
  11441. */
  11442. timerId: null,
  11443. constructor: function (interval) {
  11444. var me = this;
  11445. if (typeof interval == 'number') {
  11446. me.interval = interval;
  11447. } else if (interval) {
  11448. Ext.apply(me, interval);
  11449. }
  11450. me.tasks = [];
  11451. me.timerFn = Ext.Function.bind(me.onTick, me);
  11452. },
  11453. /**
  11454. * Creates a new {@link Ext.util.TaskRunner.Task Task} instance. These instances can
  11455. * be easily started and stopped.
  11456. * @param {Object} config The config object. For details on the supported properties,
  11457. * see {@link #start}.
  11458. */
  11459. newTask: function (config) {
  11460. var task = new Ext.util.TaskRunner.Task(config);
  11461. task.manager = this;
  11462. return task;
  11463. },
  11464. /**
  11465. * Starts a new task.
  11466. *
  11467. * Before each invocation, Ext injects the property `taskRunCount` into the task object
  11468. * so that calculations based on the repeat count can be performed.
  11469. *
  11470. * The returned task will contain a `destroy` method that can be used to destroy the
  11471. * task and cancel further calls. This is equivalent to the {@link #stop} method.
  11472. *
  11473. * @param {Object} task A config object that supports the following properties:
  11474. * @param {Function} task.run The function to execute each time the task is invoked. The
  11475. * function will be called at each interval and passed the `args` argument if specified,
  11476. * and the current invocation count if not.
  11477. *
  11478. * If a particular scope (`this` reference) is required, be sure to specify it using
  11479. * the `scope` argument.
  11480. *
  11481. * @param {Function} task.onError The function to execute in case of unhandled
  11482. * error on task.run.
  11483. *
  11484. * @param {Boolean} task.run.return `false` from this function to terminate the task.
  11485. *
  11486. * @param {Number} task.interval The frequency in milliseconds with which the task
  11487. * should be invoked.
  11488. *
  11489. * @param {Object[]} task.args An array of arguments to be passed to the function
  11490. * specified by `run`. If not specified, the current invocation count is passed.
  11491. *
  11492. * @param {Object} task.scope The scope (`this` reference) in which to execute the
  11493. * `run` function. Defaults to the task config object.
  11494. *
  11495. * @param {Number} task.duration The length of time in milliseconds to invoke the task
  11496. * before stopping automatically (defaults to indefinite).
  11497. *
  11498. * @param {Number} task.repeat The number of times to invoke the task before stopping
  11499. * automatically (defaults to indefinite).
  11500. * @return {Object} The task
  11501. */
  11502. start: function(task) {
  11503. var me = this,
  11504. now = new Date().getTime();
  11505. if (!task.pending) {
  11506. me.tasks.push(task);
  11507. task.pending = true; // don't allow the task to be added to me.tasks again
  11508. }
  11509. task.stopped = false; // might have been previously stopped...
  11510. task.taskStartTime = now;
  11511. task.taskRunTime = task.fireOnStart !== false ? 0 : task.taskStartTime;
  11512. task.taskRunCount = 0;
  11513. if (!me.firing) {
  11514. if (task.fireOnStart !== false) {
  11515. me.startTimer(0, now);
  11516. } else {
  11517. me.startTimer(task.interval, now);
  11518. }
  11519. }
  11520. return task;
  11521. },
  11522. /**
  11523. * Stops an existing running task.
  11524. * @param {Object} task The task to stop
  11525. * @return {Object} The task
  11526. */
  11527. stop: function(task) {
  11528. // NOTE: we don't attempt to remove the task from me.tasks at this point because
  11529. // this could be called from inside a task which would then corrupt the state of
  11530. // the loop in onTick
  11531. if (!task.stopped) {
  11532. task.stopped = true;
  11533. if (task.onStop) {
  11534. task.onStop.call(task.scope || task, task);
  11535. }
  11536. }
  11537. return task;
  11538. },
  11539. /**
  11540. * Stops all tasks that are currently running.
  11541. */
  11542. stopAll: function() {
  11543. // onTick will take care of cleaning up the mess after this point...
  11544. Ext.each(this.tasks, this.stop, this);
  11545. },
  11546. //-------------------------------------------------------------------------
  11547. firing: false,
  11548. nextExpires: 1e99,
  11549. // private
  11550. onTick: function () {
  11551. var me = this,
  11552. tasks = me.tasks,
  11553. now = new Date().getTime(),
  11554. nextExpires = 1e99,
  11555. len = tasks.length,
  11556. expires, newTasks, i, task, rt, remove;
  11557. me.timerId = null;
  11558. me.firing = true; // ensure we don't startTimer during this loop...
  11559. // tasks.length can be > len if start is called during a task.run call... so we
  11560. // first check len to avoid tasks.length reference but eventually we need to also
  11561. // check tasks.length. we avoid repeating use of tasks.length by setting len at
  11562. // that time (to help the next loop)
  11563. for (i = 0; i < len || i < (len = tasks.length); ++i) {
  11564. task = tasks[i];
  11565. if (!(remove = task.stopped)) {
  11566. expires = task.taskRunTime + task.interval;
  11567. if (expires <= now) {
  11568. rt = 1; // otherwise we have a stale "rt"
  11569. try {
  11570. rt = task.run.apply(task.scope || task, task.args || [++task.taskRunCount]);
  11571. } catch (taskError) {
  11572. try {
  11573. if (task.onError) {
  11574. rt = task.onError.call(task.scope || task, task, taskError);
  11575. }
  11576. } catch (ignore) { }
  11577. }
  11578. task.taskRunTime = now;
  11579. if (rt === false || task.taskRunCount === task.repeat) {
  11580. me.stop(task);
  11581. remove = true;
  11582. } else {
  11583. remove = task.stopped; // in case stop was called by run
  11584. expires = now + task.interval;
  11585. }
  11586. }
  11587. if (!remove && task.duration && task.duration <= (now - task.taskStartTime)) {
  11588. me.stop(task);
  11589. remove = true;
  11590. }
  11591. }
  11592. if (remove) {
  11593. task.pending = false; // allow the task to be added to me.tasks again
  11594. // once we detect that a task needs to be removed, we copy the tasks that
  11595. // will carry forward into newTasks... this way we avoid O(N*N) to remove
  11596. // each task from the tasks array (and ripple the array down) and also the
  11597. // potentially wasted effort of making a new tasks[] even if all tasks are
  11598. // going into the next wave.
  11599. if (!newTasks) {
  11600. newTasks = tasks.slice(0, i);
  11601. // we don't set me.tasks here because callbacks can also start tasks,
  11602. // which get added to me.tasks... so we will visit them in this loop
  11603. // and account for their expirations in nextExpires...
  11604. }
  11605. } else {
  11606. if (newTasks) {
  11607. newTasks.push(task); // we've cloned the tasks[], so keep this one...
  11608. }
  11609. if (nextExpires > expires) {
  11610. nextExpires = expires; // track the nearest expiration time
  11611. }
  11612. }
  11613. }
  11614. if (newTasks) {
  11615. // only now can we copy the newTasks to me.tasks since no user callbacks can
  11616. // take place
  11617. me.tasks = newTasks;
  11618. }
  11619. me.firing = false; // we're done, so allow startTimer afterwards
  11620. if (me.tasks.length) {
  11621. // we create a new Date here because all the callbacks could have taken a long
  11622. // time... we want to base the next timeout on the current time (after the
  11623. // callback storm):
  11624. me.startTimer(nextExpires - now, new Date().getTime());
  11625. }
  11626. },
  11627. // private
  11628. startTimer: function (timeout, now) {
  11629. var me = this,
  11630. expires = now + timeout,
  11631. timerId = me.timerId;
  11632. // Check to see if this request is enough in advance of the current timer. If so,
  11633. // we reschedule the timer based on this new expiration.
  11634. if (timerId && me.nextExpires - expires > me.interval) {
  11635. clearTimeout(timerId);
  11636. timerId = null;
  11637. }
  11638. if (!timerId) {
  11639. if (timeout < me.interval) {
  11640. timeout = me.interval;
  11641. }
  11642. me.timerId = setTimeout(me.timerFn, timeout);
  11643. me.nextExpires = expires;
  11644. }
  11645. }
  11646. },
  11647. function () {
  11648. var me = this,
  11649. proto = me.prototype;
  11650. /**
  11651. * Destroys this instance, stopping all tasks that are currently running.
  11652. * @method destroy
  11653. */
  11654. proto.destroy = proto.stopAll;
  11655. /**
  11656. * @class Ext.TaskManager
  11657. * @extends Ext.util.TaskRunner
  11658. * @singleton
  11659. *
  11660. * A static {@link Ext.util.TaskRunner} instance that can be used to start and stop
  11661. * arbitrary tasks. See {@link Ext.util.TaskRunner} for supported methods and task
  11662. * config properties.
  11663. *
  11664. * // Start a simple clock task that updates a div once per second
  11665. * var task = {
  11666. * run: function(){
  11667. * Ext.fly('clock').update(new Date().format('g:i:s A'));
  11668. * },
  11669. * interval: 1000 //1 second
  11670. * }
  11671. *
  11672. * Ext.TaskManager.start(task);
  11673. *
  11674. * See the {@link #start} method for details about how to configure a task object.
  11675. */
  11676. Ext.util.TaskManager = Ext.TaskManager = new me();
  11677. /**
  11678. * Instances of this class are created by {@link Ext.util.TaskRunner#newTask} method.
  11679. *
  11680. * For details on config properties, see {@link Ext.util.TaskRunner#start}.
  11681. * @class Ext.util.TaskRunner.Task
  11682. */
  11683. me.Task = new Ext.Class({
  11684. isTask: true,
  11685. /**
  11686. * This flag is set to `true` by {@link #stop}.
  11687. * @private
  11688. */
  11689. stopped: true, // this avoids the odd combination of !stopped && !pending
  11690. /**
  11691. * Override default behavior
  11692. */
  11693. fireOnStart: false,
  11694. constructor: function (config) {
  11695. Ext.apply(this, config);
  11696. },
  11697. /**
  11698. * Restarts this task, clearing it duration, expiration and run count.
  11699. * @param {Number} [interval] Optionally reset this task's interval.
  11700. */
  11701. restart: function (interval) {
  11702. if (interval !== undefined) {
  11703. this.interval = interval;
  11704. }
  11705. this.manager.start(this);
  11706. },
  11707. /**
  11708. * Starts this task if it is not already started.
  11709. * @param {Number} [interval] Optionally reset this task's interval.
  11710. */
  11711. start: function (interval) {
  11712. if (this.stopped) {
  11713. this.restart(interval);
  11714. }
  11715. },
  11716. /**
  11717. * Stops this task.
  11718. */
  11719. stop: function () {
  11720. this.manager.stop(this);
  11721. }
  11722. });
  11723. proto = me.Task.prototype;
  11724. /**
  11725. * Destroys this instance, stopping this task's execution.
  11726. * @method destroy
  11727. */
  11728. proto.destroy = proto.stop;
  11729. });
  11730. //@tag extras,core
  11731. //@require ../util/TaskManager.js
  11732. /**
  11733. * @class Ext.perf.Accumulator
  11734. * @private
  11735. */
  11736. Ext.define('Ext.perf.Accumulator', (function () {
  11737. var currentFrame = null,
  11738. khrome = Ext.global['chrome'],
  11739. formatTpl,
  11740. // lazy init on first request for timestamp (avoids infobar in IE until needed)
  11741. // Also avoids kicking off Chrome's microsecond timer until first needed
  11742. getTimestamp = function () {
  11743. getTimestamp = function () {
  11744. return new Date().getTime();
  11745. };
  11746. var interval, toolbox;
  11747. // If Chrome is started with the --enable-benchmarking switch
  11748. if (Ext.isChrome && khrome && khrome.Interval) {
  11749. interval = new khrome.Interval();
  11750. interval.start();
  11751. getTimestamp = function () {
  11752. return interval.microseconds() / 1000;
  11753. };
  11754. } else if (window.ActiveXObject) {
  11755. try {
  11756. // the above technique is not very accurate for small intervals...
  11757. toolbox = new ActiveXObject('SenchaToolbox.Toolbox');
  11758. Ext.senchaToolbox = toolbox; // export for other uses
  11759. getTimestamp = function () {
  11760. return toolbox.milliseconds;
  11761. };
  11762. } catch (e) {
  11763. // ignore
  11764. }
  11765. } else if (Date.now) {
  11766. getTimestamp = Date.now;
  11767. }
  11768. Ext.perf.getTimestamp = Ext.perf.Accumulator.getTimestamp = getTimestamp;
  11769. return getTimestamp();
  11770. };
  11771. function adjustSet (set, time) {
  11772. set.sum += time;
  11773. set.min = Math.min(set.min, time);
  11774. set.max = Math.max(set.max, time);
  11775. }
  11776. function leaveFrame (time) {
  11777. var totalTime = time ? time : (getTimestamp() - this.time), // do this first
  11778. me = this, // me = frame
  11779. accum = me.accum;
  11780. ++accum.count;
  11781. if (! --accum.depth) {
  11782. adjustSet(accum.total, totalTime);
  11783. }
  11784. adjustSet(accum.pure, totalTime - me.childTime);
  11785. currentFrame = me.parent;
  11786. if (currentFrame) {
  11787. ++currentFrame.accum.childCount;
  11788. currentFrame.childTime += totalTime;
  11789. }
  11790. }
  11791. function makeSet () {
  11792. return {
  11793. min: Number.MAX_VALUE,
  11794. max: 0,
  11795. sum: 0
  11796. };
  11797. }
  11798. function makeTap (me, fn) {
  11799. return function () {
  11800. var frame = me.enter(),
  11801. ret = fn.apply(this, arguments);
  11802. frame.leave();
  11803. return ret;
  11804. };
  11805. }
  11806. function round (x) {
  11807. return Math.round(x * 100) / 100;
  11808. }
  11809. function setToJSON (count, childCount, calibration, set) {
  11810. var data = {
  11811. avg: 0,
  11812. min: set.min,
  11813. max: set.max,
  11814. sum: 0
  11815. };
  11816. if (count) {
  11817. calibration = calibration || 0;
  11818. data.sum = set.sum - childCount * calibration;
  11819. data.avg = data.sum / count;
  11820. // min and max cannot be easily corrected since we don't know the number of
  11821. // child calls for them.
  11822. }
  11823. return data;
  11824. }
  11825. return {
  11826. constructor: function (name) {
  11827. var me = this;
  11828. me.count = me.childCount = me.depth = me.maxDepth = 0;
  11829. me.pure = makeSet();
  11830. me.total = makeSet();
  11831. me.name = name;
  11832. },
  11833. statics: {
  11834. getTimestamp: getTimestamp
  11835. },
  11836. format: function (calibration) {
  11837. if (!formatTpl) {
  11838. formatTpl = new Ext.XTemplate([
  11839. '{name} - {count} call(s)',
  11840. '<tpl if="count">',
  11841. '<tpl if="childCount">',
  11842. ' ({childCount} children)',
  11843. '</tpl>',
  11844. '<tpl if="depth - 1">',
  11845. ' ({depth} deep)',
  11846. '</tpl>',
  11847. '<tpl for="times">',
  11848. ', {type}: {[this.time(values.sum)]} msec (',
  11849. //'min={[this.time(values.min)]}, ',
  11850. 'avg={[this.time(values.sum / parent.count)]}',
  11851. //', max={[this.time(values.max)]}',
  11852. ')',
  11853. '</tpl>',
  11854. '</tpl>'
  11855. ].join(''), {
  11856. time: function (t) {
  11857. return Math.round(t * 100) / 100;
  11858. }
  11859. });
  11860. }
  11861. var data = this.getData(calibration);
  11862. data.name = this.name;
  11863. data.pure.type = 'Pure';
  11864. data.total.type = 'Total';
  11865. data.times = [data.pure, data.total];
  11866. return formatTpl.apply(data);
  11867. },
  11868. getData: function (calibration) {
  11869. var me = this;
  11870. return {
  11871. count: me.count,
  11872. childCount: me.childCount,
  11873. depth: me.maxDepth,
  11874. pure: setToJSON(me.count, me.childCount, calibration, me.pure),
  11875. total: setToJSON(me.count, me.childCount, calibration, me.total)
  11876. };
  11877. },
  11878. enter: function () {
  11879. var me = this,
  11880. frame = {
  11881. accum: me,
  11882. leave: leaveFrame,
  11883. childTime: 0,
  11884. parent: currentFrame
  11885. };
  11886. ++me.depth;
  11887. if (me.maxDepth < me.depth) {
  11888. me.maxDepth = me.depth;
  11889. }
  11890. currentFrame = frame;
  11891. frame.time = getTimestamp(); // do this last
  11892. return frame;
  11893. },
  11894. monitor: function (fn, scope, args) {
  11895. var frame = this.enter();
  11896. if (args) {
  11897. fn.apply(scope, args);
  11898. } else {
  11899. fn.call(scope);
  11900. }
  11901. frame.leave();
  11902. },
  11903. report: function () {
  11904. Ext.log(this.format());
  11905. },
  11906. tap: function (className, methodName) {
  11907. var me = this,
  11908. methods = typeof methodName == 'string' ? [methodName] : methodName,
  11909. klass, statik, i, parts, length, name, src,
  11910. tapFunc;
  11911. tapFunc = function(){
  11912. if (typeof className == 'string') {
  11913. klass = Ext.global;
  11914. parts = className.split('.');
  11915. for (i = 0, length = parts.length; i < length; ++i) {
  11916. klass = klass[parts[i]];
  11917. }
  11918. } else {
  11919. klass = className;
  11920. }
  11921. for (i = 0, length = methods.length; i < length; ++i) {
  11922. name = methods[i];
  11923. statik = name.charAt(0) == '!';
  11924. if (statik) {
  11925. name = name.substring(1);
  11926. } else {
  11927. statik = !(name in klass.prototype);
  11928. }
  11929. src = statik ? klass : klass.prototype;
  11930. src[name] = makeTap(me, src[name]);
  11931. }
  11932. };
  11933. Ext.ClassManager.onCreated(tapFunc, me, className);
  11934. return me;
  11935. }
  11936. };
  11937. }()),
  11938. function () {
  11939. Ext.perf.getTimestamp = this.getTimestamp;
  11940. });
  11941. //@tag extras,core
  11942. //@require Accumulator.js
  11943. /**
  11944. * @class Ext.perf.Monitor
  11945. * @singleton
  11946. * @private
  11947. */
  11948. Ext.define('Ext.perf.Monitor', {
  11949. singleton: true,
  11950. alternateClassName: 'Ext.Perf',
  11951. requires: [
  11952. 'Ext.perf.Accumulator'
  11953. ],
  11954. constructor: function () {
  11955. this.accumulators = [];
  11956. this.accumulatorsByName = {};
  11957. },
  11958. calibrate: function () {
  11959. var accum = new Ext.perf.Accumulator('$'),
  11960. total = accum.total,
  11961. getTimestamp = Ext.perf.Accumulator.getTimestamp,
  11962. count = 0,
  11963. frame,
  11964. endTime,
  11965. startTime;
  11966. startTime = getTimestamp();
  11967. do {
  11968. frame = accum.enter();
  11969. frame.leave();
  11970. ++count;
  11971. } while (total.sum < 100);
  11972. endTime = getTimestamp();
  11973. return (endTime - startTime) / count;
  11974. },
  11975. get: function (name) {
  11976. var me = this,
  11977. accum = me.accumulatorsByName[name];
  11978. if (!accum) {
  11979. me.accumulatorsByName[name] = accum = new Ext.perf.Accumulator(name);
  11980. me.accumulators.push(accum);
  11981. }
  11982. return accum;
  11983. },
  11984. enter: function (name) {
  11985. return this.get(name).enter();
  11986. },
  11987. monitor: function (name, fn, scope) {
  11988. this.get(name).monitor(fn, scope);
  11989. },
  11990. report: function () {
  11991. var me = this,
  11992. accumulators = me.accumulators,
  11993. calibration = me.calibrate();
  11994. accumulators.sort(function (a, b) {
  11995. return (a.name < b.name) ? -1 : ((b.name < a.name) ? 1 : 0);
  11996. });
  11997. me.updateGC();
  11998. Ext.log('Calibration: ' + Math.round(calibration * 100) / 100 + ' msec/sample');
  11999. Ext.each(accumulators, function (accum) {
  12000. Ext.log(accum.format(calibration));
  12001. });
  12002. },
  12003. getData: function (all) {
  12004. var ret = {},
  12005. accumulators = this.accumulators;
  12006. Ext.each(accumulators, function (accum) {
  12007. if (all || accum.count) {
  12008. ret[accum.name] = accum.getData();
  12009. }
  12010. });
  12011. return ret;
  12012. },
  12013. reset: function(){
  12014. Ext.each(this.accumulators, function(accum){
  12015. var me = accum;
  12016. me.count = me.childCount = me.depth = me.maxDepth = 0;
  12017. me.pure = {
  12018. min: Number.MAX_VALUE,
  12019. max: 0,
  12020. sum: 0
  12021. };
  12022. me.total = {
  12023. min: Number.MAX_VALUE,
  12024. max: 0,
  12025. sum: 0
  12026. };
  12027. });
  12028. },
  12029. updateGC: function () {
  12030. var accumGC = this.accumulatorsByName.GC,
  12031. toolbox = Ext.senchaToolbox,
  12032. bucket;
  12033. if (accumGC) {
  12034. accumGC.count = toolbox.garbageCollectionCounter || 0;
  12035. if (accumGC.count) {
  12036. bucket = accumGC.pure;
  12037. accumGC.total.sum = bucket.sum = toolbox.garbageCollectionMilliseconds;
  12038. bucket.min = bucket.max = bucket.sum / accumGC.count;
  12039. bucket = accumGC.total;
  12040. bucket.min = bucket.max = bucket.sum / accumGC.count;
  12041. }
  12042. }
  12043. },
  12044. watchGC: function () {
  12045. Ext.perf.getTimestamp(); // initializes SenchaToolbox (if available)
  12046. var toolbox = Ext.senchaToolbox;
  12047. if (toolbox) {
  12048. this.get("GC");
  12049. toolbox.watchGarbageCollector(false); // no logging, just totals
  12050. }
  12051. },
  12052. setup: function (config) {
  12053. if (!config) {
  12054. config = {
  12055. /*insertHtml: {
  12056. 'Ext.dom.Helper': 'insertHtml'
  12057. },*/
  12058. /*xtplCompile: {
  12059. 'Ext.XTemplateCompiler': 'compile'
  12060. },*/
  12061. // doInsert: {
  12062. // 'Ext.Template': 'doInsert'
  12063. // },
  12064. // applyOut: {
  12065. // 'Ext.XTemplate': 'applyOut'
  12066. // },
  12067. render: {
  12068. 'Ext.AbstractComponent': 'render'
  12069. },
  12070. // fnishRender: {
  12071. // 'Ext.AbstractComponent': 'finishRender'
  12072. // },
  12073. // renderSelectors: {
  12074. // 'Ext.AbstractComponent': 'applyRenderSelectors'
  12075. // },
  12076. // compAddCls: {
  12077. // 'Ext.AbstractComponent': 'addCls'
  12078. // },
  12079. // compRemoveCls: {
  12080. // 'Ext.AbstractComponent': 'removeCls'
  12081. // },
  12082. // getStyle: {
  12083. // 'Ext.core.Element': 'getStyle'
  12084. // },
  12085. // setStyle: {
  12086. // 'Ext.core.Element': 'setStyle'
  12087. // },
  12088. // addCls: {
  12089. // 'Ext.core.Element': 'addCls'
  12090. // },
  12091. // removeCls: {
  12092. // 'Ext.core.Element': 'removeCls'
  12093. // },
  12094. // measure: {
  12095. // 'Ext.layout.component.Component': 'measureAutoDimensions'
  12096. // },
  12097. // moveItem: {
  12098. // 'Ext.layout.Layout': 'moveItem'
  12099. // },
  12100. // layoutFlush: {
  12101. // 'Ext.layout.Context': 'flush'
  12102. // },
  12103. layout: {
  12104. 'Ext.layout.Context': 'run'
  12105. }
  12106. };
  12107. }
  12108. this.currentConfig = config;
  12109. var key, prop,
  12110. accum, className, methods;
  12111. for (key in config) {
  12112. if (config.hasOwnProperty(key)) {
  12113. prop = config[key];
  12114. accum = Ext.Perf.get(key);
  12115. for (className in prop) {
  12116. if (prop.hasOwnProperty(className)) {
  12117. methods = prop[className];
  12118. accum.tap(className, methods);
  12119. }
  12120. }
  12121. }
  12122. }
  12123. this.watchGC();
  12124. }
  12125. });
  12126. //@tag extras,core
  12127. //@require perf/Monitor.js
  12128. //@define Ext.Supports
  12129. /**
  12130. * @class Ext.is
  12131. *
  12132. * Determines information about the current platform the application is running on.
  12133. *
  12134. * @singleton
  12135. */
  12136. Ext.is = {
  12137. init : function(navigator) {
  12138. var platforms = this.platforms,
  12139. ln = platforms.length,
  12140. i, platform;
  12141. navigator = navigator || window.navigator;
  12142. for (i = 0; i < ln; i++) {
  12143. platform = platforms[i];
  12144. this[platform.identity] = platform.regex.test(navigator[platform.property]);
  12145. }
  12146. /**
  12147. * @property Desktop True if the browser is running on a desktop machine
  12148. * @type {Boolean}
  12149. */
  12150. this.Desktop = this.Mac || this.Windows || (this.Linux && !this.Android);
  12151. /**
  12152. * @property Tablet True if the browser is running on a tablet (iPad)
  12153. */
  12154. this.Tablet = this.iPad;
  12155. /**
  12156. * @property Phone True if the browser is running on a phone.
  12157. * @type {Boolean}
  12158. */
  12159. this.Phone = !this.Desktop && !this.Tablet;
  12160. /**
  12161. * @property iOS True if the browser is running on iOS
  12162. * @type {Boolean}
  12163. */
  12164. this.iOS = this.iPhone || this.iPad || this.iPod;
  12165. /**
  12166. * @property Standalone Detects when application has been saved to homescreen.
  12167. * @type {Boolean}
  12168. */
  12169. this.Standalone = !!window.navigator.standalone;
  12170. },
  12171. /**
  12172. * @property iPhone True when the browser is running on a iPhone
  12173. * @type {Boolean}
  12174. */
  12175. platforms: [{
  12176. property: 'platform',
  12177. regex: /iPhone/i,
  12178. identity: 'iPhone'
  12179. },
  12180. /**
  12181. * @property iPod True when the browser is running on a iPod
  12182. * @type {Boolean}
  12183. */
  12184. {
  12185. property: 'platform',
  12186. regex: /iPod/i,
  12187. identity: 'iPod'
  12188. },
  12189. /**
  12190. * @property iPad True when the browser is running on a iPad
  12191. * @type {Boolean}
  12192. */
  12193. {
  12194. property: 'userAgent',
  12195. regex: /iPad/i,
  12196. identity: 'iPad'
  12197. },
  12198. /**
  12199. * @property Blackberry True when the browser is running on a Blackberry
  12200. * @type {Boolean}
  12201. */
  12202. {
  12203. property: 'userAgent',
  12204. regex: /Blackberry/i,
  12205. identity: 'Blackberry'
  12206. },
  12207. /**
  12208. * @property Android True when the browser is running on an Android device
  12209. * @type {Boolean}
  12210. */
  12211. {
  12212. property: 'userAgent',
  12213. regex: /Android/i,
  12214. identity: 'Android'
  12215. },
  12216. /**
  12217. * @property Mac True when the browser is running on a Mac
  12218. * @type {Boolean}
  12219. */
  12220. {
  12221. property: 'platform',
  12222. regex: /Mac/i,
  12223. identity: 'Mac'
  12224. },
  12225. /**
  12226. * @property Windows True when the browser is running on Windows
  12227. * @type {Boolean}
  12228. */
  12229. {
  12230. property: 'platform',
  12231. regex: /Win/i,
  12232. identity: 'Windows'
  12233. },
  12234. /**
  12235. * @property Linux True when the browser is running on Linux
  12236. * @type {Boolean}
  12237. */
  12238. {
  12239. property: 'platform',
  12240. regex: /Linux/i,
  12241. identity: 'Linux'
  12242. }]
  12243. };
  12244. Ext.is.init();
  12245. /**
  12246. * @class Ext.supports
  12247. *
  12248. * Determines information about features are supported in the current environment
  12249. *
  12250. * @singleton
  12251. */
  12252. (function(){
  12253. // this is a local copy of certain logic from (Abstract)Element.getStyle
  12254. // to break a dependancy between the supports mechanism and Element
  12255. // use this instead of element references to check for styling info
  12256. var getStyle = function(element, styleName){
  12257. var view = element.ownerDocument.defaultView,
  12258. style = (view ? view.getComputedStyle(element, null) : element.currentStyle) || element.style;
  12259. return style[styleName];
  12260. };
  12261. Ext.supports = {
  12262. /**
  12263. * Runs feature detection routines and sets the various flags. This is called when
  12264. * the scripts loads (very early) and again at {@link Ext#onReady}. Some detections
  12265. * are flagged as `early` and run immediately. Others that require the document body
  12266. * will not run until ready.
  12267. *
  12268. * Each test is run only once, so calling this method from an onReady function is safe
  12269. * and ensures that all flags have been set.
  12270. * @markdown
  12271. * @private
  12272. */
  12273. init : function() {
  12274. var me = this,
  12275. doc = document,
  12276. tests = me.tests,
  12277. n = tests.length,
  12278. div = n && Ext.isReady && doc.createElement('div'),
  12279. test, notRun = [];
  12280. if (div) {
  12281. div.innerHTML = [
  12282. '<div style="height:30px;width:50px;">',
  12283. '<div style="height:20px;width:20px;"></div>',
  12284. '</div>',
  12285. '<div style="width: 200px; height: 200px; position: relative; padding: 5px;">',
  12286. '<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>',
  12287. '</div>',
  12288. '<div style="position: absolute; left: 10%; top: 10%;"></div>',
  12289. '<div style="float:left; background-color:transparent;"></div>'
  12290. ].join('');
  12291. doc.body.appendChild(div);
  12292. }
  12293. while (n--) {
  12294. test = tests[n];
  12295. if (div || test.early) {
  12296. me[test.identity] = test.fn.call(me, doc, div);
  12297. } else {
  12298. notRun.push(test);
  12299. }
  12300. }
  12301. if (div) {
  12302. doc.body.removeChild(div);
  12303. }
  12304. me.tests = notRun;
  12305. },
  12306. /**
  12307. * @property PointerEvents True if document environment supports the CSS3 pointer-events style.
  12308. * @type {Boolean}
  12309. */
  12310. PointerEvents: 'pointerEvents' in document.documentElement.style,
  12311. /**
  12312. * @property CSS3BoxShadow True if document environment supports the CSS3 box-shadow style.
  12313. * @type {Boolean}
  12314. */
  12315. CSS3BoxShadow: 'boxShadow' in document.documentElement.style || 'WebkitBoxShadow' in document.documentElement.style || 'MozBoxShadow' in document.documentElement.style,
  12316. /**
  12317. * @property ClassList True if document environment supports the HTML5 classList API.
  12318. * @type {Boolean}
  12319. */
  12320. ClassList: !!document.documentElement.classList,
  12321. /**
  12322. * @property OrientationChange True if the device supports orientation change
  12323. * @type {Boolean}
  12324. */
  12325. OrientationChange: ((typeof window.orientation != 'undefined') && ('onorientationchange' in window)),
  12326. /**
  12327. * @property DeviceMotion True if the device supports device motion (acceleration and rotation rate)
  12328. * @type {Boolean}
  12329. */
  12330. DeviceMotion: ('ondevicemotion' in window),
  12331. /**
  12332. * @property Touch True if the device supports touch
  12333. * @type {Boolean}
  12334. */
  12335. // is.Desktop is needed due to the bug in Chrome 5.0.375, Safari 3.1.2
  12336. // and Safari 4.0 (they all have 'ontouchstart' in the window object).
  12337. Touch: ('ontouchstart' in window) && (!Ext.is.Desktop),
  12338. /**
  12339. * @property TimeoutActualLateness True if the browser passes the "actualLateness" parameter to
  12340. * setTimeout. See: https://developer.mozilla.org/en/DOM/window.setTimeout
  12341. * @type {Boolean}
  12342. */
  12343. TimeoutActualLateness: (function(){
  12344. setTimeout(function(){
  12345. Ext.supports.TimeoutActualLateness = arguments.length !== 0;
  12346. }, 0);
  12347. }()),
  12348. tests: [
  12349. /**
  12350. * @property Transitions True if the device supports CSS3 Transitions
  12351. * @type {Boolean}
  12352. */
  12353. {
  12354. identity: 'Transitions',
  12355. fn: function(doc, div) {
  12356. var prefix = [
  12357. 'webkit',
  12358. 'Moz',
  12359. 'o',
  12360. 'ms',
  12361. 'khtml'
  12362. ],
  12363. TE = 'TransitionEnd',
  12364. transitionEndName = [
  12365. prefix[0] + TE,
  12366. 'transitionend', //Moz bucks the prefixing convention
  12367. prefix[2] + TE,
  12368. prefix[3] + TE,
  12369. prefix[4] + TE
  12370. ],
  12371. ln = prefix.length,
  12372. i = 0,
  12373. out = false;
  12374. for (; i < ln; i++) {
  12375. if (getStyle(div, prefix[i] + "TransitionProperty")) {
  12376. Ext.supports.CSS3Prefix = prefix[i];
  12377. Ext.supports.CSS3TransitionEnd = transitionEndName[i];
  12378. out = true;
  12379. break;
  12380. }
  12381. }
  12382. return out;
  12383. }
  12384. },
  12385. /**
  12386. * @property RightMargin True if the device supports right margin.
  12387. * See https://bugs.webkit.org/show_bug.cgi?id=13343 for why this is needed.
  12388. * @type {Boolean}
  12389. */
  12390. {
  12391. identity: 'RightMargin',
  12392. fn: function(doc, div) {
  12393. var view = doc.defaultView;
  12394. return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px');
  12395. }
  12396. },
  12397. /**
  12398. * @property DisplayChangeInputSelectionBug True if INPUT elements lose their
  12399. * selection when their display style is changed. Essentially, if a text input
  12400. * has focus and its display style is changed, the I-beam disappears.
  12401. *
  12402. * This bug is encountered due to the work around in place for the {@link #RightMargin}
  12403. * bug. This has been observed in Safari 4.0.4 and older, and appears to be fixed
  12404. * in Safari 5. It's not clear if Safari 4.1 has the bug, but it has the same WebKit
  12405. * version number as Safari 5 (according to http://unixpapa.com/js/gecko.html).
  12406. */
  12407. {
  12408. identity: 'DisplayChangeInputSelectionBug',
  12409. early: true,
  12410. fn: function() {
  12411. var webKitVersion = Ext.webKitVersion;
  12412. // WebKit but older than Safari 5 or Chrome 6:
  12413. return 0 < webKitVersion && webKitVersion < 533;
  12414. }
  12415. },
  12416. /**
  12417. * @property DisplayChangeTextAreaSelectionBug True if TEXTAREA elements lose their
  12418. * selection when their display style is changed. Essentially, if a text area has
  12419. * focus and its display style is changed, the I-beam disappears.
  12420. *
  12421. * This bug is encountered due to the work around in place for the {@link #RightMargin}
  12422. * bug. This has been observed in Chrome 10 and Safari 5 and older, and appears to
  12423. * be fixed in Chrome 11.
  12424. */
  12425. {
  12426. identity: 'DisplayChangeTextAreaSelectionBug',
  12427. early: true,
  12428. fn: function() {
  12429. var webKitVersion = Ext.webKitVersion;
  12430. /*
  12431. Has bug w/textarea:
  12432. (Chrome) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US)
  12433. AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127
  12434. Safari/534.16
  12435. (Safari) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-us)
  12436. AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5
  12437. Safari/533.21.1
  12438. No bug:
  12439. (Chrome) Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7)
  12440. AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.57
  12441. Safari/534.24
  12442. */
  12443. return 0 < webKitVersion && webKitVersion < 534.24;
  12444. }
  12445. },
  12446. /**
  12447. * @property TransparentColor True if the device supports transparent color
  12448. * @type {Boolean}
  12449. */
  12450. {
  12451. identity: 'TransparentColor',
  12452. fn: function(doc, div, view) {
  12453. view = doc.defaultView;
  12454. return !(view && view.getComputedStyle(div.lastChild, null).backgroundColor != 'transparent');
  12455. }
  12456. },
  12457. /**
  12458. * @property ComputedStyle True if the browser supports document.defaultView.getComputedStyle()
  12459. * @type {Boolean}
  12460. */
  12461. {
  12462. identity: 'ComputedStyle',
  12463. fn: function(doc, div, view) {
  12464. view = doc.defaultView;
  12465. return view && view.getComputedStyle;
  12466. }
  12467. },
  12468. /**
  12469. * @property Svg True if the device supports SVG
  12470. * @type {Boolean}
  12471. */
  12472. {
  12473. identity: 'Svg',
  12474. fn: function(doc) {
  12475. return !!doc.createElementNS && !!doc.createElementNS( "http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect;
  12476. }
  12477. },
  12478. /**
  12479. * @property Canvas True if the device supports Canvas
  12480. * @type {Boolean}
  12481. */
  12482. {
  12483. identity: 'Canvas',
  12484. fn: function(doc) {
  12485. return !!doc.createElement('canvas').getContext;
  12486. }
  12487. },
  12488. /**
  12489. * @property Vml True if the device supports VML
  12490. * @type {Boolean}
  12491. */
  12492. {
  12493. identity: 'Vml',
  12494. fn: function(doc) {
  12495. var d = doc.createElement("div");
  12496. d.innerHTML = "<!--[if vml]><br/><br/><![endif]-->";
  12497. return (d.childNodes.length == 2);
  12498. }
  12499. },
  12500. /**
  12501. * @property Float True if the device supports CSS float
  12502. * @type {Boolean}
  12503. */
  12504. {
  12505. identity: 'Float',
  12506. fn: function(doc, div) {
  12507. return !!div.lastChild.style.cssFloat;
  12508. }
  12509. },
  12510. /**
  12511. * @property AudioTag True if the device supports the HTML5 audio tag
  12512. * @type {Boolean}
  12513. */
  12514. {
  12515. identity: 'AudioTag',
  12516. fn: function(doc) {
  12517. return !!doc.createElement('audio').canPlayType;
  12518. }
  12519. },
  12520. /**
  12521. * @property History True if the device supports HTML5 history
  12522. * @type {Boolean}
  12523. */
  12524. {
  12525. identity: 'History',
  12526. fn: function() {
  12527. var history = window.history;
  12528. return !!(history && history.pushState);
  12529. }
  12530. },
  12531. /**
  12532. * @property CSS3DTransform True if the device supports CSS3DTransform
  12533. * @type {Boolean}
  12534. */
  12535. {
  12536. identity: 'CSS3DTransform',
  12537. fn: function() {
  12538. return (typeof WebKitCSSMatrix != 'undefined' && new WebKitCSSMatrix().hasOwnProperty('m41'));
  12539. }
  12540. },
  12541. /**
  12542. * @property CSS3LinearGradient True if the device supports CSS3 linear gradients
  12543. * @type {Boolean}
  12544. */
  12545. {
  12546. identity: 'CSS3LinearGradient',
  12547. fn: function(doc, div) {
  12548. var property = 'background-image:',
  12549. webkit = '-webkit-gradient(linear, left top, right bottom, from(black), to(white))',
  12550. w3c = 'linear-gradient(left top, black, white)',
  12551. moz = '-moz-' + w3c,
  12552. opera = '-o-' + w3c,
  12553. options = [property + webkit, property + w3c, property + moz, property + opera];
  12554. div.style.cssText = options.join(';');
  12555. return ("" + div.style.backgroundImage).indexOf('gradient') !== -1;
  12556. }
  12557. },
  12558. /**
  12559. * @property CSS3BorderRadius True if the device supports CSS3 border radius
  12560. * @type {Boolean}
  12561. */
  12562. {
  12563. identity: 'CSS3BorderRadius',
  12564. fn: function(doc, div) {
  12565. var domPrefixes = ['borderRadius', 'BorderRadius', 'MozBorderRadius', 'WebkitBorderRadius', 'OBorderRadius', 'KhtmlBorderRadius'],
  12566. pass = false,
  12567. i;
  12568. for (i = 0; i < domPrefixes.length; i++) {
  12569. if (document.body.style[domPrefixes[i]] !== undefined) {
  12570. return true;
  12571. }
  12572. }
  12573. return pass;
  12574. }
  12575. },
  12576. /**
  12577. * @property GeoLocation True if the device supports GeoLocation
  12578. * @type {Boolean}
  12579. */
  12580. {
  12581. identity: 'GeoLocation',
  12582. fn: function() {
  12583. return (typeof navigator != 'undefined' && typeof navigator.geolocation != 'undefined') || (typeof google != 'undefined' && typeof google.gears != 'undefined');
  12584. }
  12585. },
  12586. /**
  12587. * @property MouseEnterLeave True if the browser supports mouseenter and mouseleave events
  12588. * @type {Boolean}
  12589. */
  12590. {
  12591. identity: 'MouseEnterLeave',
  12592. fn: function(doc, div){
  12593. return ('onmouseenter' in div && 'onmouseleave' in div);
  12594. }
  12595. },
  12596. /**
  12597. * @property MouseWheel True if the browser supports the mousewheel event
  12598. * @type {Boolean}
  12599. */
  12600. {
  12601. identity: 'MouseWheel',
  12602. fn: function(doc, div) {
  12603. return ('onmousewheel' in div);
  12604. }
  12605. },
  12606. /**
  12607. * @property Opacity True if the browser supports normal css opacity
  12608. * @type {Boolean}
  12609. */
  12610. {
  12611. identity: 'Opacity',
  12612. fn: function(doc, div){
  12613. // Not a strict equal comparison in case opacity can be converted to a number.
  12614. if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
  12615. return false;
  12616. }
  12617. div.firstChild.style.cssText = 'opacity:0.73';
  12618. return div.firstChild.style.opacity == '0.73';
  12619. }
  12620. },
  12621. /**
  12622. * @property Placeholder True if the browser supports the HTML5 placeholder attribute on inputs
  12623. * @type {Boolean}
  12624. */
  12625. {
  12626. identity: 'Placeholder',
  12627. fn: function(doc) {
  12628. return 'placeholder' in doc.createElement('input');
  12629. }
  12630. },
  12631. /**
  12632. * @property Direct2DBug True if when asking for an element's dimension via offsetWidth or offsetHeight,
  12633. * getBoundingClientRect, etc. the browser returns the subpixel width rounded to the nearest pixel.
  12634. * @type {Boolean}
  12635. */
  12636. {
  12637. identity: 'Direct2DBug',
  12638. fn: function() {
  12639. return Ext.isString(document.body.style.msTransformOrigin);
  12640. }
  12641. },
  12642. /**
  12643. * @property BoundingClientRect True if the browser supports the getBoundingClientRect method on elements
  12644. * @type {Boolean}
  12645. */
  12646. {
  12647. identity: 'BoundingClientRect',
  12648. fn: function(doc, div) {
  12649. return Ext.isFunction(div.getBoundingClientRect);
  12650. }
  12651. },
  12652. {
  12653. identity: 'IncludePaddingInWidthCalculation',
  12654. fn: function(doc, div){
  12655. return div.childNodes[1].firstChild.offsetWidth == 210;
  12656. }
  12657. },
  12658. {
  12659. identity: 'IncludePaddingInHeightCalculation',
  12660. fn: function(doc, div){
  12661. return div.childNodes[1].firstChild.offsetHeight == 210;
  12662. }
  12663. },
  12664. /**
  12665. * @property ArraySort True if the Array sort native method isn't bugged.
  12666. * @type {Boolean}
  12667. */
  12668. {
  12669. identity: 'ArraySort',
  12670. fn: function() {
  12671. var a = [1,2,3,4,5].sort(function(){ return 0; });
  12672. return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
  12673. }
  12674. },
  12675. /**
  12676. * @property Range True if browser support document.createRange native method.
  12677. * @type {Boolean}
  12678. */
  12679. {
  12680. identity: 'Range',
  12681. fn: function() {
  12682. return !!document.createRange;
  12683. }
  12684. },
  12685. /**
  12686. * @property CreateContextualFragment True if browser support CreateContextualFragment range native methods.
  12687. * @type {Boolean}
  12688. */
  12689. {
  12690. identity: 'CreateContextualFragment',
  12691. fn: function() {
  12692. var range = Ext.supports.Range ? document.createRange() : false;
  12693. return range && !!range.createContextualFragment;
  12694. }
  12695. },
  12696. /**
  12697. * @property WindowOnError True if browser supports window.onerror.
  12698. * @type {Boolean}
  12699. */
  12700. {
  12701. identity: 'WindowOnError',
  12702. fn: function () {
  12703. // sadly, we cannot feature detect this...
  12704. return Ext.isIE || Ext.isGecko || Ext.webKitVersion >= 534.16; // Chrome 10+
  12705. }
  12706. },
  12707. /**
  12708. * @property TextAreaMaxLength True if the browser supports maxlength on textareas.
  12709. * @type {Boolean}
  12710. */
  12711. {
  12712. identity: 'TextAreaMaxLength',
  12713. fn: function(){
  12714. var el = document.createElement('textarea');
  12715. return ('maxlength' in el);
  12716. }
  12717. },
  12718. /**
  12719. * @property GetPositionPercentage True if the browser will return the left/top/right/bottom
  12720. * position as a percentage when explicitly set as a percentage value.
  12721. * @type {Boolean}
  12722. */
  12723. // Related bug: https://bugzilla.mozilla.org/show_bug.cgi?id=707691#c7
  12724. {
  12725. identity: 'GetPositionPercentage',
  12726. fn: function(doc, div){
  12727. return getStyle(div.childNodes[2], 'left') == '10%';
  12728. }
  12729. }
  12730. ]
  12731. };
  12732. }());
  12733. Ext.supports.init(); // run the "early" detections now
  12734. //@tag dom,core
  12735. //@require ../Support.js
  12736. //@define Ext.util.DelayedTask
  12737. /**
  12738. * @class Ext.util.DelayedTask
  12739. *
  12740. * The DelayedTask class provides a convenient way to "buffer" the execution of a method,
  12741. * performing setTimeout where a new timeout cancels the old timeout. When called, the
  12742. * task will wait the specified time period before executing. If durng that time period,
  12743. * the task is called again, the original call will be cancelled. This continues so that
  12744. * the function is only called a single time for each iteration.
  12745. *
  12746. * This method is especially useful for things like detecting whether a user has finished
  12747. * typing in a text field. An example would be performing validation on a keypress. You can
  12748. * use this class to buffer the keypress events for a certain number of milliseconds, and
  12749. * perform only if they stop for that amount of time.
  12750. *
  12751. * ## Usage
  12752. *
  12753. * var task = new Ext.util.DelayedTask(function(){
  12754. * alert(Ext.getDom('myInputField').value.length);
  12755. * });
  12756. *
  12757. * // Wait 500ms before calling our function. If the user presses another key
  12758. * // during that 500ms, it will be cancelled and we'll wait another 500ms.
  12759. * Ext.get('myInputField').on('keypress', function(){
  12760. * task.{@link #delay}(500);
  12761. * });
  12762. *
  12763. * Note that we are using a DelayedTask here to illustrate a point. The configuration
  12764. * option `buffer` for {@link Ext.util.Observable#addListener addListener/on} will
  12765. * also setup a delayed task for you to buffer events.
  12766. *
  12767. * @constructor The parameters to this constructor serve as defaults and are not required.
  12768. * @param {Function} fn (optional) The default function to call. If not specified here, it must be specified during the {@link #delay} call.
  12769. * @param {Object} scope (optional) The default scope (The <code><b>this</b></code> reference) in which the
  12770. * function is called. If not specified, <code>this</code> will refer to the browser window.
  12771. * @param {Array} args (optional) The default Array of arguments.
  12772. */
  12773. Ext.util.DelayedTask = function(fn, scope, args) {
  12774. var me = this,
  12775. id,
  12776. call = function() {
  12777. clearInterval(id);
  12778. id = null;
  12779. fn.apply(scope, args || []);
  12780. };
  12781. /**
  12782. * Cancels any pending timeout and queues a new one
  12783. * @param {Number} delay The milliseconds to delay
  12784. * @param {Function} newFn (optional) Overrides function passed to constructor
  12785. * @param {Object} newScope (optional) Overrides scope passed to constructor. Remember that if no scope
  12786. * is specified, <code>this</code> will refer to the browser window.
  12787. * @param {Array} newArgs (optional) Overrides args passed to constructor
  12788. */
  12789. this.delay = function(delay, newFn, newScope, newArgs) {
  12790. me.cancel();
  12791. fn = newFn || fn;
  12792. scope = newScope || scope;
  12793. args = newArgs || args;
  12794. id = setInterval(call, delay);
  12795. };
  12796. /**
  12797. * Cancel the last queued timeout
  12798. */
  12799. this.cancel = function(){
  12800. if (id) {
  12801. clearInterval(id);
  12802. id = null;
  12803. }
  12804. };
  12805. };
  12806. //@tag dom,core
  12807. //@define Ext.util.Event
  12808. //@require Ext.util.DelayedTask
  12809. Ext.require('Ext.util.DelayedTask', function() {
  12810. /**
  12811. * Represents single event type that an Observable object listens to.
  12812. * All actual listeners are tracked inside here. When the event fires,
  12813. * it calls all the registered listener functions.
  12814. *
  12815. * @private
  12816. */
  12817. Ext.util.Event = Ext.extend(Object, (function() {
  12818. var noOptions = {};
  12819. function createTargeted(handler, listener, o, scope){
  12820. return function(){
  12821. if (o.target === arguments[0]){
  12822. handler.apply(scope, arguments);
  12823. }
  12824. };
  12825. }
  12826. function createBuffered(handler, listener, o, scope) {
  12827. listener.task = new Ext.util.DelayedTask();
  12828. return function() {
  12829. listener.task.delay(o.buffer, handler, scope, Ext.Array.toArray(arguments));
  12830. };
  12831. }
  12832. function createDelayed(handler, listener, o, scope) {
  12833. return function() {
  12834. var task = new Ext.util.DelayedTask();
  12835. if (!listener.tasks) {
  12836. listener.tasks = [];
  12837. }
  12838. listener.tasks.push(task);
  12839. task.delay(o.delay || 10, handler, scope, Ext.Array.toArray(arguments));
  12840. };
  12841. }
  12842. function createSingle(handler, listener, o, scope) {
  12843. return function() {
  12844. var event = listener.ev;
  12845. if (event.removeListener(listener.fn, scope) && event.observable) {
  12846. // Removing from a regular Observable-owned, named event (not an anonymous
  12847. // event such as Ext's readyEvent): Decrement the listeners count
  12848. event.observable.hasListeners[event.name]--;
  12849. }
  12850. return handler.apply(scope, arguments);
  12851. };
  12852. }
  12853. return {
  12854. /**
  12855. * @property {Boolean} isEvent
  12856. * `true` in this class to identify an object as an instantiated Event, or subclass thereof.
  12857. */
  12858. isEvent: true,
  12859. constructor: function(observable, name) {
  12860. this.name = name;
  12861. this.observable = observable;
  12862. this.listeners = [];
  12863. },
  12864. addListener: function(fn, scope, options) {
  12865. var me = this,
  12866. listener;
  12867. scope = scope || me.observable;
  12868. if (!fn) {
  12869. Ext.Error.raise({
  12870. sourceClass: Ext.getClassName(this.observable),
  12871. sourceMethod: "addListener",
  12872. msg: "The specified callback function is undefined"
  12873. });
  12874. }
  12875. if (!me.isListening(fn, scope)) {
  12876. listener = me.createListener(fn, scope, options);
  12877. if (me.firing) {
  12878. // if we are currently firing this event, don't disturb the listener loop
  12879. me.listeners = me.listeners.slice(0);
  12880. }
  12881. me.listeners.push(listener);
  12882. }
  12883. },
  12884. createListener: function(fn, scope, options) {
  12885. options = options || noOptions;
  12886. scope = scope || this.observable;
  12887. var listener = {
  12888. fn: fn,
  12889. scope: scope,
  12890. o: options,
  12891. ev: this
  12892. },
  12893. handler = fn;
  12894. // The order is important. The 'single' wrapper must be wrapped by the 'buffer' and 'delayed' wrapper
  12895. // because the event removal that the single listener does destroys the listener's DelayedTask(s)
  12896. if (options.single) {
  12897. handler = createSingle(handler, listener, options, scope);
  12898. }
  12899. if (options.target) {
  12900. handler = createTargeted(handler, listener, options, scope);
  12901. }
  12902. if (options.delay) {
  12903. handler = createDelayed(handler, listener, options, scope);
  12904. }
  12905. if (options.buffer) {
  12906. handler = createBuffered(handler, listener, options, scope);
  12907. }
  12908. listener.fireFn = handler;
  12909. return listener;
  12910. },
  12911. findListener: function(fn, scope) {
  12912. var listeners = this.listeners,
  12913. i = listeners.length,
  12914. listener,
  12915. s;
  12916. while (i--) {
  12917. listener = listeners[i];
  12918. if (listener) {
  12919. s = listener.scope;
  12920. // Compare the listener's scope with *JUST THE PASSED SCOPE* if one is passed, and only fall back to the owning Observable if none is passed.
  12921. // We cannot use the test (s == scope || s == this.observable)
  12922. // Otherwise, if the Observable itself adds Ext.emptyFn as a listener, and then Ext.emptyFn is added under another scope, there will be a false match.
  12923. if (listener.fn == fn && (s == (scope || this.observable))) {
  12924. return i;
  12925. }
  12926. }
  12927. }
  12928. return - 1;
  12929. },
  12930. isListening: function(fn, scope) {
  12931. return this.findListener(fn, scope) !== -1;
  12932. },
  12933. removeListener: function(fn, scope) {
  12934. var me = this,
  12935. index,
  12936. listener,
  12937. k;
  12938. index = me.findListener(fn, scope);
  12939. if (index != -1) {
  12940. listener = me.listeners[index];
  12941. if (me.firing) {
  12942. me.listeners = me.listeners.slice(0);
  12943. }
  12944. // cancel and remove a buffered handler that hasn't fired yet
  12945. if (listener.task) {
  12946. listener.task.cancel();
  12947. delete listener.task;
  12948. }
  12949. // cancel and remove all delayed handlers that haven't fired yet
  12950. k = listener.tasks && listener.tasks.length;
  12951. if (k) {
  12952. while (k--) {
  12953. listener.tasks[k].cancel();
  12954. }
  12955. delete listener.tasks;
  12956. }
  12957. // remove this listener from the listeners array
  12958. Ext.Array.erase(me.listeners, index, 1);
  12959. return true;
  12960. }
  12961. return false;
  12962. },
  12963. // Iterate to stop any buffered/delayed events
  12964. clearListeners: function() {
  12965. var listeners = this.listeners,
  12966. i = listeners.length;
  12967. while (i--) {
  12968. this.removeListener(listeners[i].fn, listeners[i].scope);
  12969. }
  12970. },
  12971. fire: function() {
  12972. var me = this,
  12973. listeners = me.listeners,
  12974. count = listeners.length,
  12975. i,
  12976. args,
  12977. listener;
  12978. if (count > 0) {
  12979. me.firing = true;
  12980. for (i = 0; i < count; i++) {
  12981. listener = listeners[i];
  12982. args = arguments.length ? Array.prototype.slice.call(arguments, 0) : [];
  12983. if (listener.o) {
  12984. args.push(listener.o);
  12985. }
  12986. if (listener && listener.fireFn.apply(listener.scope || me.observable, args) === false) {
  12987. return (me.firing = false);
  12988. }
  12989. }
  12990. }
  12991. me.firing = false;
  12992. return true;
  12993. }
  12994. };
  12995. }()));
  12996. });
  12997. /**
  12998. * Base class that provides a common interface for publishing events. Subclasses are expected to to have a property
  12999. * "events" with all the events defined, and, optionally, a property "listeners" with configured listeners defined.
  13000. *
  13001. * For example:
  13002. *
  13003. * Ext.define('Employee', {
  13004. * mixins: {
  13005. * observable: 'Ext.util.Observable'
  13006. * },
  13007. *
  13008. * constructor: function (config) {
  13009. * // The Observable constructor copies all of the properties of `config` on
  13010. * // to `this` using {@link Ext#apply}. Further, the `listeners` property is
  13011. * // processed to add listeners.
  13012. * //
  13013. * this.mixins.observable.constructor.call(this, config);
  13014. *
  13015. * this.addEvents(
  13016. * 'fired',
  13017. * 'quit'
  13018. * );
  13019. * }
  13020. * });
  13021. *
  13022. * This could then be used like this:
  13023. *
  13024. * var newEmployee = new Employee({
  13025. * name: employeeName,
  13026. * listeners: {
  13027. * quit: function() {
  13028. * // By default, "this" will be the object that fired the event.
  13029. * alert(this.name + " has quit!");
  13030. * }
  13031. * }
  13032. * });
  13033. */
  13034. Ext.define('Ext.util.Observable', {
  13035. /* Begin Definitions */
  13036. requires: ['Ext.util.Event'],
  13037. statics: {
  13038. /**
  13039. * Removes **all** added captures from the Observable.
  13040. *
  13041. * @param {Ext.util.Observable} o The Observable to release
  13042. * @static
  13043. */
  13044. releaseCapture: function(o) {
  13045. o.fireEvent = this.prototype.fireEvent;
  13046. },
  13047. /**
  13048. * Starts capture on the specified Observable. All events will be passed to the supplied function with the event
  13049. * name + standard signature of the event **before** the event is fired. If the supplied function returns false,
  13050. * the event will not fire.
  13051. *
  13052. * @param {Ext.util.Observable} o The Observable to capture events from.
  13053. * @param {Function} fn The function to call when an event is fired.
  13054. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to
  13055. * the Observable firing the event.
  13056. * @static
  13057. */
  13058. capture: function(o, fn, scope) {
  13059. o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
  13060. },
  13061. /**
  13062. * Sets observability on the passed class constructor.
  13063. *
  13064. * This makes any event fired on any instance of the passed class also fire a single event through
  13065. * the **class** allowing for central handling of events on many instances at once.
  13066. *
  13067. * Usage:
  13068. *
  13069. * Ext.util.Observable.observe(Ext.data.Connection);
  13070. * Ext.data.Connection.on('beforerequest', function(con, options) {
  13071. * console.log('Ajax request made to ' + options.url);
  13072. * });
  13073. *
  13074. * @param {Function} c The class constructor to make observable.
  13075. * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
  13076. * @static
  13077. */
  13078. observe: function(cls, listeners) {
  13079. if (cls) {
  13080. if (!cls.isObservable) {
  13081. Ext.applyIf(cls, new this());
  13082. this.capture(cls.prototype, cls.fireEvent, cls);
  13083. }
  13084. if (Ext.isObject(listeners)) {
  13085. cls.on(listeners);
  13086. }
  13087. }
  13088. return cls;
  13089. },
  13090. /**
  13091. * Prepares a given class for observable instances. This method is called when a
  13092. * class derives from this class or uses this class as a mixin.
  13093. * @param {Function} T The class constructor to prepare.
  13094. * @private
  13095. */
  13096. prepareClass: function (T, mixin) {
  13097. // T.hasListeners is the object to track listeners on class T. This object's
  13098. // prototype (__proto__) is the "hasListeners" of T.superclass.
  13099. // Instances of T will create "hasListeners" that have T.hasListeners as their
  13100. // immediate prototype (__proto__).
  13101. if (!T.HasListeners) {
  13102. // We create a HasListeners "class" for this class. The "prototype" of the
  13103. // HasListeners class is an instance of the HasListeners class associated
  13104. // with this class's super class (or with Observable).
  13105. var Observable = Ext.util.Observable,
  13106. HasListeners = function () {},
  13107. SuperHL = T.superclass.HasListeners || (mixin && mixin.HasListeners) ||
  13108. Observable.HasListeners;
  13109. // Make the HasListener class available on the class and its prototype:
  13110. T.prototype.HasListeners = T.HasListeners = HasListeners;
  13111. // And connect its "prototype" to the new HasListeners of our super class
  13112. // (which is also the class-level "hasListeners" instance).
  13113. HasListeners.prototype = T.hasListeners = new SuperHL();
  13114. }
  13115. }
  13116. },
  13117. /* End Definitions */
  13118. /**
  13119. * @cfg {Object} listeners
  13120. *
  13121. * A config object containing one or more event handlers to be added to this object during initialization. This
  13122. * should be a valid listeners config object as specified in the {@link #addListener} example for attaching multiple
  13123. * handlers at once.
  13124. *
  13125. * **DOM events from Ext JS {@link Ext.Component Components}**
  13126. *
  13127. * While _some_ Ext JS Component classes export selected DOM events (e.g. "click", "mouseover" etc), this is usually
  13128. * only done when extra value can be added. For example the {@link Ext.view.View DataView}'s **`{@link
  13129. * Ext.view.View#itemclick itemclick}`** event passing the node clicked on. To access DOM events directly from a
  13130. * child element of a Component, we need to specify the `element` option to identify the Component property to add a
  13131. * DOM listener to:
  13132. *
  13133. * new Ext.panel.Panel({
  13134. * width: 400,
  13135. * height: 200,
  13136. * dockedItems: [{
  13137. * xtype: 'toolbar'
  13138. * }],
  13139. * listeners: {
  13140. * click: {
  13141. * element: 'el', //bind to the underlying el property on the panel
  13142. * fn: function(){ console.log('click el'); }
  13143. * },
  13144. * dblclick: {
  13145. * element: 'body', //bind to the underlying body property on the panel
  13146. * fn: function(){ console.log('dblclick body'); }
  13147. * }
  13148. * }
  13149. * });
  13150. */
  13151. /**
  13152. * @property {Boolean} isObservable
  13153. * `true` in this class to identify an object as an instantiated Observable, or subclass thereof.
  13154. */
  13155. isObservable: true,
  13156. /**
  13157. * @private
  13158. * Initial suspended call count. Incremented when {@link #suspendEvents} is called, decremented when {@link #resumeEvents} is called.
  13159. */
  13160. eventsSuspended: 0,
  13161. /**
  13162. * @property {Object} hasListeners
  13163. * @readonly
  13164. * This object holds a key for any event that has a listener. The listener may be set
  13165. * directly on the instance, or on its class or a super class (via {@link #observe}) or
  13166. * on the {@link Ext.app.EventBus MVC EventBus}. The values of this object are truthy
  13167. * (a non-zero number) and falsy (0 or undefined). They do not represent an exact count
  13168. * of listeners. The value for an event is truthy if the event must be fired and is
  13169. * falsy if there is no need to fire the event.
  13170. *
  13171. * The intended use of this property is to avoid the expense of fireEvent calls when
  13172. * there are no listeners. This can be particularly helpful when one would otherwise
  13173. * have to call fireEvent hundreds or thousands of times. It is used like this:
  13174. *
  13175. * if (this.hasListeners.foo) {
  13176. * this.fireEvent('foo', this, arg1);
  13177. * }
  13178. */
  13179. constructor: function(config) {
  13180. var me = this;
  13181. Ext.apply(me, config);
  13182. // The subclass may have already initialized it.
  13183. if (!me.hasListeners) {
  13184. me.hasListeners = new me.HasListeners();
  13185. }
  13186. me.events = me.events || {};
  13187. if (me.listeners) {
  13188. me.on(me.listeners);
  13189. me.listeners = null; //Set as an instance property to pre-empt the prototype in case any are set there.
  13190. }
  13191. if (me.bubbleEvents) {
  13192. me.enableBubble(me.bubbleEvents);
  13193. }
  13194. },
  13195. onClassExtended: function (T) {
  13196. if (!T.HasListeners) {
  13197. // Some classes derive from us and some others derive from those classes. All
  13198. // of these are passed to this method.
  13199. Ext.util.Observable.prepareClass(T);
  13200. }
  13201. },
  13202. // @private
  13203. eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal|freezeEvent)$/,
  13204. /**
  13205. * Adds listeners to any Observable object (or Ext.Element) which are automatically removed when this Component is
  13206. * destroyed.
  13207. *
  13208. * @param {Ext.util.Observable/Ext.Element} item The item to which to add a listener/listeners.
  13209. * @param {Object/String} ename The event name, or an object containing event name properties.
  13210. * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
  13211. * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
  13212. * in which the handler function is executed.
  13213. * @param {Object} opt (optional) If the `ename` parameter was an event name, this is the
  13214. * {@link Ext.util.Observable#addListener addListener} options.
  13215. */
  13216. addManagedListener : function(item, ename, fn, scope, options) {
  13217. var me = this,
  13218. managedListeners = me.managedListeners = me.managedListeners || [],
  13219. config;
  13220. if (typeof ename !== 'string') {
  13221. options = ename;
  13222. for (ename in options) {
  13223. if (options.hasOwnProperty(ename)) {
  13224. config = options[ename];
  13225. if (!me.eventOptionsRe.test(ename)) {
  13226. me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
  13227. }
  13228. }
  13229. }
  13230. }
  13231. else {
  13232. managedListeners.push({
  13233. item: item,
  13234. ename: ename,
  13235. fn: fn,
  13236. scope: scope,
  13237. options: options
  13238. });
  13239. item.on(ename, fn, scope, options);
  13240. }
  13241. },
  13242. /**
  13243. * Removes listeners that were added by the {@link #mon} method.
  13244. *
  13245. * @param {Ext.util.Observable/Ext.Element} item The item from which to remove a listener/listeners.
  13246. * @param {Object/String} ename The event name, or an object containing event name properties.
  13247. * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
  13248. * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
  13249. * in which the handler function is executed.
  13250. */
  13251. removeManagedListener : function(item, ename, fn, scope) {
  13252. var me = this,
  13253. options,
  13254. config,
  13255. managedListeners,
  13256. length,
  13257. i;
  13258. if (typeof ename !== 'string') {
  13259. options = ename;
  13260. for (ename in options) {
  13261. if (options.hasOwnProperty(ename)) {
  13262. config = options[ename];
  13263. if (!me.eventOptionsRe.test(ename)) {
  13264. me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
  13265. }
  13266. }
  13267. }
  13268. }
  13269. managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
  13270. for (i = 0, length = managedListeners.length; i < length; i++) {
  13271. me.removeManagedListenerItem(false, managedListeners[i], item, ename, fn, scope);
  13272. }
  13273. },
  13274. /**
  13275. * Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
  13276. * to {@link #addListener}).
  13277. *
  13278. * An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget}) by
  13279. * calling {@link #enableBubble}.
  13280. *
  13281. * @param {String} eventName The name of the event to fire.
  13282. * @param {Object...} args Variable number of parameters are passed to handlers.
  13283. * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
  13284. */
  13285. fireEvent: function(eventName) {
  13286. eventName = eventName.toLowerCase();
  13287. var me = this,
  13288. events = me.events,
  13289. event = events && events[eventName],
  13290. ret = true;
  13291. // Only continue firing the event if there are listeners to be informed.
  13292. // Bubbled events will always have a listener count, so will be fired.
  13293. if (event && me.hasListeners[eventName]) {
  13294. ret = me.continueFireEvent(eventName, Ext.Array.slice(arguments, 1), event.bubble);
  13295. }
  13296. return ret;
  13297. },
  13298. /**
  13299. * Continue to fire event.
  13300. * @private
  13301. *
  13302. * @param {String} eventName
  13303. * @param {Array} args
  13304. * @param {Boolean} bubbles
  13305. */
  13306. continueFireEvent: function(eventName, args, bubbles) {
  13307. var target = this,
  13308. queue, event,
  13309. ret = true;
  13310. do {
  13311. if (target.eventsSuspended) {
  13312. if ((queue = target.eventQueue)) {
  13313. queue.push([eventName, args, bubbles]);
  13314. }
  13315. return ret;
  13316. } else {
  13317. event = target.events[eventName];
  13318. // Continue bubbling if event exists and it is `true` or the handler didn't returns false and it
  13319. // configure to bubble.
  13320. if (event && event != true) {
  13321. if ((ret = event.fire.apply(event, args)) === false) {
  13322. break;
  13323. }
  13324. }
  13325. }
  13326. } while (bubbles && (target = target.getBubbleParent()));
  13327. return ret;
  13328. },
  13329. /**
  13330. * Gets the bubbling parent for an Observable
  13331. * @private
  13332. * @return {Ext.util.Observable} The bubble parent. null is returned if no bubble target exists
  13333. */
  13334. getBubbleParent: function(){
  13335. var me = this, parent = me.getBubbleTarget && me.getBubbleTarget();
  13336. if (parent && parent.isObservable) {
  13337. return parent;
  13338. }
  13339. return null;
  13340. },
  13341. /**
  13342. * Appends an event handler to this object. For example:
  13343. *
  13344. * myGridPanel.on("mouseover", this.onMouseOver, this);
  13345. *
  13346. * The method also allows for a single argument to be passed which is a config object
  13347. * containing properties which specify multiple events. For example:
  13348. *
  13349. * myGridPanel.on({
  13350. * cellClick: this.onCellClick,
  13351. * mouseover: this.onMouseOver,
  13352. * mouseout: this.onMouseOut,
  13353. * scope: this // Important. Ensure "this" is correct during handler execution
  13354. * });
  13355. *
  13356. * One can also specify options for each event handler separately:
  13357. *
  13358. * myGridPanel.on({
  13359. * cellClick: {fn: this.onCellClick, scope: this, single: true},
  13360. * mouseover: {fn: panel.onMouseOver, scope: panel}
  13361. * });
  13362. *
  13363. * *Names* of methods in a specified scope may also be used. Note that
  13364. * `scope` MUST be specified to use this option:
  13365. *
  13366. * myGridPanel.on({
  13367. * cellClick: {fn: 'onCellClick', scope: this, single: true},
  13368. * mouseover: {fn: 'onMouseOver', scope: panel}
  13369. * });
  13370. *
  13371. * @param {String/Object} eventName The name of the event to listen for.
  13372. * May also be an object who's property names are event names.
  13373. *
  13374. * @param {Function} [fn] The method the event invokes, or *if `scope` is specified, the *name* of the method within
  13375. * the specified `scope`. Will be called with arguments
  13376. * given to {@link #fireEvent} plus the `options` parameter described below.
  13377. *
  13378. * @param {Object} [scope] The scope (`this` reference) in which the handler function is
  13379. * executed. **If omitted, defaults to the object which fired the event.**
  13380. *
  13381. * @param {Object} [options] An object containing handler configuration.
  13382. *
  13383. * **Note:** Unlike in ExtJS 3.x, the options object will also be passed as the last
  13384. * argument to every event handler.
  13385. *
  13386. * This object may contain any of the following properties:
  13387. *
  13388. * @param {Object} options.scope
  13389. * The scope (`this` reference) in which the handler function is executed. **If omitted,
  13390. * defaults to the object which fired the event.**
  13391. *
  13392. * @param {Number} options.delay
  13393. * The number of milliseconds to delay the invocation of the handler after the event fires.
  13394. *
  13395. * @param {Boolean} options.single
  13396. * True to add a handler to handle just the next firing of the event, and then remove itself.
  13397. *
  13398. * @param {Number} options.buffer
  13399. * Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
  13400. * by the specified number of milliseconds. If the event fires again within that time,
  13401. * the original handler is _not_ invoked, but the new handler is scheduled in its place.
  13402. *
  13403. * @param {Ext.util.Observable} options.target
  13404. * Only call the handler if the event was fired on the target Observable, _not_ if the event
  13405. * was bubbled up from a child Observable.
  13406. *
  13407. * @param {String} options.element
  13408. * **This option is only valid for listeners bound to {@link Ext.Component Components}.**
  13409. * The name of a Component property which references an element to add a listener to.
  13410. *
  13411. * This option is useful during Component construction to add DOM event listeners to elements of
  13412. * {@link Ext.Component Components} which will exist only after the Component is rendered.
  13413. * For example, to add a click listener to a Panel's body:
  13414. *
  13415. * new Ext.panel.Panel({
  13416. * title: 'The title',
  13417. * listeners: {
  13418. * click: this.handlePanelClick,
  13419. * element: 'body'
  13420. * }
  13421. * });
  13422. *
  13423. * **Combining Options**
  13424. *
  13425. * Using the options argument, it is possible to combine different types of listeners:
  13426. *
  13427. * A delayed, one-time listener.
  13428. *
  13429. * myPanel.on('hide', this.handleClick, this, {
  13430. * single: true,
  13431. * delay: 100
  13432. * });
  13433. *
  13434. */
  13435. addListener: function(ename, fn, scope, options) {
  13436. var me = this,
  13437. config, event, hasListeners,
  13438. prevListenerCount = 0;
  13439. if (typeof ename !== 'string') {
  13440. options = ename;
  13441. for (ename in options) {
  13442. if (options.hasOwnProperty(ename)) {
  13443. config = options[ename];
  13444. if (!me.eventOptionsRe.test(ename)) {
  13445. me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
  13446. }
  13447. }
  13448. }
  13449. } else {
  13450. ename = ename.toLowerCase();
  13451. event = me.events[ename];
  13452. if (event && event.isEvent) {
  13453. prevListenerCount = event.listeners.length;
  13454. } else {
  13455. me.events[ename] = event = new Ext.util.Event(me, ename);
  13456. }
  13457. // Allow listeners: { click: 'onClick', scope: myObject }
  13458. if (typeof fn === 'string') {
  13459. if (!(scope[fn] || me[fn])) {
  13460. Ext.Error.raise('No method named "' + fn + '"');
  13461. }
  13462. fn = scope[fn] || me[fn];
  13463. }
  13464. event.addListener(fn, scope, options);
  13465. // If a new listener has been added (Event.addListener rejects duplicates of the same fn+scope)
  13466. // then increment the hasListeners counter
  13467. if (event.listeners.length !== prevListenerCount) {
  13468. hasListeners = me.hasListeners;
  13469. if (hasListeners.hasOwnProperty(ename)) {
  13470. // if we already have listeners at this level, just increment the count...
  13471. ++hasListeners[ename];
  13472. } else {
  13473. // otherwise, start the count at 1 (which hides whatever is in our prototype
  13474. // chain)...
  13475. hasListeners[ename] = 1;
  13476. }
  13477. }
  13478. }
  13479. },
  13480. /**
  13481. * Removes an event handler.
  13482. *
  13483. * @param {String} eventName The type of event the handler was associated with.
  13484. * @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
  13485. * {@link #addListener} call.**
  13486. * @param {Object} scope (optional) The scope originally specified for the handler. It must be the same as the
  13487. * scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
  13488. */
  13489. removeListener: function(ename, fn, scope) {
  13490. var me = this,
  13491. config,
  13492. event,
  13493. options;
  13494. if (typeof ename !== 'string') {
  13495. options = ename;
  13496. for (ename in options) {
  13497. if (options.hasOwnProperty(ename)) {
  13498. config = options[ename];
  13499. if (!me.eventOptionsRe.test(ename)) {
  13500. me.removeListener(ename, config.fn || config, config.scope || options.scope);
  13501. }
  13502. }
  13503. }
  13504. } else {
  13505. ename = ename.toLowerCase();
  13506. event = me.events[ename];
  13507. if (event && event.isEvent) {
  13508. if (event.removeListener(fn, scope) && !--me.hasListeners[ename]) {
  13509. // Delete this entry, since 0 does not mean no one is listening, just
  13510. // that no one is *directly& listening. This allows the eventBus or
  13511. // class observers to "poke" through and expose their presence.
  13512. delete me.hasListeners[ename];
  13513. }
  13514. }
  13515. }
  13516. },
  13517. /**
  13518. * Removes all listeners for this object including the managed listeners
  13519. */
  13520. clearListeners: function() {
  13521. var events = this.events,
  13522. event,
  13523. key;
  13524. for (key in events) {
  13525. if (events.hasOwnProperty(key)) {
  13526. event = events[key];
  13527. if (event.isEvent) {
  13528. event.clearListeners();
  13529. }
  13530. }
  13531. }
  13532. this.clearManagedListeners();
  13533. },
  13534. purgeListeners : function() {
  13535. if (Ext.global.console) {
  13536. Ext.global.console.warn('Observable: purgeListeners has been deprecated. Please use clearListeners.');
  13537. }
  13538. return this.clearListeners.apply(this, arguments);
  13539. },
  13540. /**
  13541. * Removes all managed listeners for this object.
  13542. */
  13543. clearManagedListeners : function() {
  13544. var managedListeners = this.managedListeners || [],
  13545. i = 0,
  13546. len = managedListeners.length;
  13547. for (; i < len; i++) {
  13548. this.removeManagedListenerItem(true, managedListeners[i]);
  13549. }
  13550. this.managedListeners = [];
  13551. },
  13552. /**
  13553. * Remove a single managed listener item
  13554. * @private
  13555. * @param {Boolean} isClear True if this is being called during a clear
  13556. * @param {Object} managedListener The managed listener item
  13557. * See removeManagedListener for other args
  13558. */
  13559. removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
  13560. if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
  13561. managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
  13562. if (!isClear) {
  13563. Ext.Array.remove(this.managedListeners, managedListener);
  13564. }
  13565. }
  13566. },
  13567. purgeManagedListeners : function() {
  13568. if (Ext.global.console) {
  13569. Ext.global.console.warn('Observable: purgeManagedListeners has been deprecated. Please use clearManagedListeners.');
  13570. }
  13571. return this.clearManagedListeners.apply(this, arguments);
  13572. },
  13573. /**
  13574. * Adds the specified events to the list of events which this Observable may fire.
  13575. *
  13576. * @param {Object/String...} eventNames Either an object with event names as properties with
  13577. * a value of `true`. For example:
  13578. *
  13579. * this.addEvents({
  13580. * storeloaded: true,
  13581. * storecleared: true
  13582. * });
  13583. *
  13584. * Or any number of event names as separate parameters. For example:
  13585. *
  13586. * this.addEvents('storeloaded', 'storecleared');
  13587. *
  13588. */
  13589. addEvents: function(o) {
  13590. var me = this,
  13591. events = me.events || (me.events = {}),
  13592. arg, args, i;
  13593. if (typeof o == 'string') {
  13594. for (args = arguments, i = args.length; i--; ) {
  13595. arg = args[i];
  13596. if (!events[arg]) {
  13597. events[arg] = true;
  13598. }
  13599. }
  13600. } else {
  13601. Ext.applyIf(me.events, o);
  13602. }
  13603. },
  13604. /**
  13605. * Checks to see if this object has any listeners for a specified event, or whether the event bubbles. The answer
  13606. * indicates whether the event needs firing or not.
  13607. *
  13608. * @param {String} eventName The name of the event to check for
  13609. * @return {Boolean} `true` if the event is being listened for or bubbles, else `false`
  13610. */
  13611. hasListener: function(ename) {
  13612. return !!this.hasListeners[ename.toLowerCase()];
  13613. },
  13614. /**
  13615. * Suspends the firing of all events. (see {@link #resumeEvents})
  13616. *
  13617. * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
  13618. * after the {@link #resumeEvents} call instead of discarding all suspended events.
  13619. */
  13620. suspendEvents: function(queueSuspended) {
  13621. this.eventsSuspended += 1;
  13622. if (queueSuspended && !this.eventQueue) {
  13623. this.eventQueue = [];
  13624. }
  13625. },
  13626. /**
  13627. * Resumes firing events (see {@link #suspendEvents}).
  13628. *
  13629. * If events were suspended using the `queueSuspended` parameter, then all events fired
  13630. * during event suspension will be sent to any listeners now.
  13631. */
  13632. resumeEvents: function() {
  13633. var me = this,
  13634. queued = me.eventQueue,
  13635. qLen, q;
  13636. if (me.eventsSuspended && ! --me.eventsSuspended) {
  13637. delete me.eventQueue;
  13638. if (queued) {
  13639. qLen = queued.length;
  13640. for (q = 0; q < qLen; q++) {
  13641. me.continueFireEvent.apply(me, queued[q]);
  13642. }
  13643. }
  13644. }
  13645. },
  13646. /**
  13647. * Relays selected events from the specified Observable as if the events were fired by `this`.
  13648. *
  13649. * For example if you are extending Grid, you might decide to forward some events from store.
  13650. * So you can do this inside your initComponent:
  13651. *
  13652. * this.relayEvents(this.getStore(), ['load']);
  13653. *
  13654. * The grid instance will then have an observable 'load' event which will be passed the
  13655. * parameters of the store's load event and any function fired with the grid's load event
  13656. * would have access to the grid using the `this` keyword.
  13657. *
  13658. * @param {Object} origin The Observable whose events this object is to relay.
  13659. * @param {String[]} events Array of event names to relay.
  13660. * @param {String} [prefix] A common prefix to prepend to the event names. For example:
  13661. *
  13662. * this.relayEvents(this.getStore(), ['load', 'clear'], 'store');
  13663. *
  13664. * Now the grid will forward 'load' and 'clear' events of store as 'storeload' and 'storeclear'.
  13665. */
  13666. relayEvents : function(origin, events, prefix) {
  13667. var me = this,
  13668. len = events.length,
  13669. i = 0,
  13670. oldName,
  13671. newName;
  13672. for (; i < len; i++) {
  13673. oldName = events[i];
  13674. newName = prefix ? prefix + oldName : oldName;
  13675. // Add the relaying function as a ManagedListener so that it is removed when this.clearListeners is called (usually when _this_ is destroyed)
  13676. me.mon(origin, oldName, me.createRelayer(newName));
  13677. }
  13678. },
  13679. /**
  13680. * @private
  13681. * Creates an event handling function which refires the event from this object as the passed event name.
  13682. * @param newName
  13683. * @param {Array} beginEnd (optional) The caller can specify on which indices to slice
  13684. * @returns {Function}
  13685. */
  13686. createRelayer: function(newName, beginEnd){
  13687. var me = this;
  13688. return function() {
  13689. return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.apply(arguments, beginEnd || [0, -1])));
  13690. };
  13691. },
  13692. /**
  13693. * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
  13694. * present. There is no implementation in the Observable base class.
  13695. *
  13696. * This is commonly used by Ext.Components to bubble events to owner Containers.
  13697. * See {@link Ext.Component#getBubbleTarget}. The default implementation in Ext.Component returns the
  13698. * Component's immediate owner. But if a known target is required, this can be overridden to access the
  13699. * required target more quickly.
  13700. *
  13701. * Example:
  13702. *
  13703. * Ext.override(Ext.form.field.Base, {
  13704. * // Add functionality to Field's initComponent to enable the change event to bubble
  13705. * initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
  13706. * this.enableBubble('change');
  13707. * }),
  13708. *
  13709. * // We know that we want Field's events to bubble directly to the FormPanel.
  13710. * getBubbleTarget : function() {
  13711. * if (!this.formPanel) {
  13712. * this.formPanel = this.findParentByType('form');
  13713. * }
  13714. * return this.formPanel;
  13715. * }
  13716. * });
  13717. *
  13718. * var myForm = new Ext.formPanel({
  13719. * title: 'User Details',
  13720. * items: [{
  13721. * ...
  13722. * }],
  13723. * listeners: {
  13724. * change: function() {
  13725. * // Title goes red if form has been modified.
  13726. * myForm.header.setStyle('color', 'red');
  13727. * }
  13728. * }
  13729. * });
  13730. *
  13731. * @param {String/String[]} eventNames The event name to bubble, or an Array of event names.
  13732. */
  13733. enableBubble: function(eventNames) {
  13734. if (eventNames) {
  13735. var me = this,
  13736. names = (typeof eventNames == 'string') ? arguments : eventNames,
  13737. length = names.length,
  13738. events = me.events,
  13739. ename, event, i;
  13740. for (i = 0; i < length; ++i) {
  13741. ename = names[i].toLowerCase();
  13742. event = events[ename];
  13743. if (!event || typeof event == 'boolean') {
  13744. events[ename] = event = new Ext.util.Event(me, ename);
  13745. }
  13746. // Event must fire if it bubbles (We don't know if anyone up the bubble hierarchy has listeners added)
  13747. me.hasListeners[ename] = (me.hasListeners[ename]||0) + 1;
  13748. event.bubble = true;
  13749. }
  13750. }
  13751. }
  13752. }, function() {
  13753. var Observable = this,
  13754. proto = Observable.prototype,
  13755. HasListeners = function () {},
  13756. prepareMixin = function (T) {
  13757. if (!T.HasListeners) {
  13758. var proto = T.prototype;
  13759. // Classes that use us as a mixin (best practice) need to be prepared.
  13760. Observable.prepareClass(T, this);
  13761. // Now that we are mixed in to class T, we need to watch T for derivations
  13762. // and prepare them also.
  13763. T.onExtended(function (U) {
  13764. Observable.prepareClass(U);
  13765. });
  13766. // Also, if a class uses us as a mixin and that class is then used as
  13767. // a mixin, we need to be notified of that as well.
  13768. if (proto.onClassMixedIn) {
  13769. // play nice with other potential overrides...
  13770. Ext.override(T, {
  13771. onClassMixedIn: function (U) {
  13772. prepareMixin.call(this, U);
  13773. this.callParent(arguments);
  13774. }
  13775. });
  13776. } else {
  13777. // just us chickens, so add the method...
  13778. proto.onClassMixedIn = function (U) {
  13779. prepareMixin.call(this, U);
  13780. };
  13781. }
  13782. }
  13783. };
  13784. HasListeners.prototype = {
  13785. //$$: 42 // to make sure we have a proper prototype
  13786. };
  13787. proto.HasListeners = Observable.HasListeners = HasListeners;
  13788. Observable.createAlias({
  13789. /**
  13790. * @method
  13791. * Shorthand for {@link #addListener}.
  13792. * @inheritdoc Ext.util.Observable#addListener
  13793. */
  13794. on: 'addListener',
  13795. /**
  13796. * @method
  13797. * Shorthand for {@link #removeListener}.
  13798. * @inheritdoc Ext.util.Observable#removeListener
  13799. */
  13800. un: 'removeListener',
  13801. /**
  13802. * @method
  13803. * Shorthand for {@link #addManagedListener}.
  13804. * @inheritdoc Ext.util.Observable#addManagedListener
  13805. */
  13806. mon: 'addManagedListener',
  13807. /**
  13808. * @method
  13809. * Shorthand for {@link #removeManagedListener}.
  13810. * @inheritdoc Ext.util.Observable#removeManagedListener
  13811. */
  13812. mun: 'removeManagedListener'
  13813. });
  13814. //deprecated, will be removed in 5.0
  13815. Observable.observeClass = Observable.observe;
  13816. // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
  13817. // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
  13818. // private
  13819. function getMethodEvent(method){
  13820. var e = (this.methodEvents = this.methodEvents || {})[method],
  13821. returnValue,
  13822. v,
  13823. cancel,
  13824. obj = this,
  13825. makeCall;
  13826. if (!e) {
  13827. this.methodEvents[method] = e = {};
  13828. e.originalFn = this[method];
  13829. e.methodName = method;
  13830. e.before = [];
  13831. e.after = [];
  13832. makeCall = function(fn, scope, args){
  13833. if((v = fn.apply(scope || obj, args)) !== undefined){
  13834. if (typeof v == 'object') {
  13835. if(v.returnValue !== undefined){
  13836. returnValue = v.returnValue;
  13837. }else{
  13838. returnValue = v;
  13839. }
  13840. cancel = !!v.cancel;
  13841. }
  13842. else
  13843. if (v === false) {
  13844. cancel = true;
  13845. }
  13846. else {
  13847. returnValue = v;
  13848. }
  13849. }
  13850. };
  13851. this[method] = function(){
  13852. var args = Array.prototype.slice.call(arguments, 0),
  13853. b, i, len;
  13854. returnValue = v = undefined;
  13855. cancel = false;
  13856. for(i = 0, len = e.before.length; i < len; i++){
  13857. b = e.before[i];
  13858. makeCall(b.fn, b.scope, args);
  13859. if (cancel) {
  13860. return returnValue;
  13861. }
  13862. }
  13863. if((v = e.originalFn.apply(obj, args)) !== undefined){
  13864. returnValue = v;
  13865. }
  13866. for(i = 0, len = e.after.length; i < len; i++){
  13867. b = e.after[i];
  13868. makeCall(b.fn, b.scope, args);
  13869. if (cancel) {
  13870. return returnValue;
  13871. }
  13872. }
  13873. return returnValue;
  13874. };
  13875. }
  13876. return e;
  13877. }
  13878. Ext.apply(proto, {
  13879. onClassMixedIn: prepareMixin,
  13880. // these are considered experimental
  13881. // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
  13882. // adds an 'interceptor' called before the original method
  13883. beforeMethod : function(method, fn, scope){
  13884. getMethodEvent.call(this, method).before.push({
  13885. fn: fn,
  13886. scope: scope
  13887. });
  13888. },
  13889. // adds a 'sequence' called after the original method
  13890. afterMethod : function(method, fn, scope){
  13891. getMethodEvent.call(this, method).after.push({
  13892. fn: fn,
  13893. scope: scope
  13894. });
  13895. },
  13896. removeMethodListener: function(method, fn, scope){
  13897. var e = this.getMethodEvent(method),
  13898. i, len;
  13899. for(i = 0, len = e.before.length; i < len; i++){
  13900. if(e.before[i].fn == fn && e.before[i].scope == scope){
  13901. Ext.Array.erase(e.before, i, 1);
  13902. return;
  13903. }
  13904. }
  13905. for(i = 0, len = e.after.length; i < len; i++){
  13906. if(e.after[i].fn == fn && e.after[i].scope == scope){
  13907. Ext.Array.erase(e.after, i, 1);
  13908. return;
  13909. }
  13910. }
  13911. },
  13912. toggleEventLogging: function(toggle) {
  13913. Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
  13914. if (Ext.isDefined(Ext.global.console)) {
  13915. Ext.global.console.log(en, arguments);
  13916. }
  13917. });
  13918. }
  13919. });
  13920. });
  13921. /**
  13922. * @class Ext.util.HashMap
  13923. * <p>
  13924. * Represents a collection of a set of key and value pairs. Each key in the HashMap
  13925. * must be unique, the same key cannot exist twice. Access to items is provided via
  13926. * the key only. Sample usage:
  13927. * <pre><code>
  13928. var map = new Ext.util.HashMap();
  13929. map.add('key1', 1);
  13930. map.add('key2', 2);
  13931. map.add('key3', 3);
  13932. map.each(function(key, value, length){
  13933. console.log(key, value, length);
  13934. });
  13935. * </code></pre>
  13936. * </p>
  13937. *
  13938. * <p>The HashMap is an unordered class,
  13939. * there is no guarantee when iterating over the items that they will be in any particular
  13940. * order. If this is required, then use a {@link Ext.util.MixedCollection}.
  13941. * </p>
  13942. */
  13943. Ext.define('Ext.util.HashMap', {
  13944. mixins: {
  13945. observable: 'Ext.util.Observable'
  13946. },
  13947. /**
  13948. * @cfg {Function} keyFn A function that is used to retrieve a default key for a passed object.
  13949. * A default is provided that returns the <b>id</b> property on the object. This function is only used
  13950. * if the add method is called with a single argument.
  13951. */
  13952. /**
  13953. * Creates new HashMap.
  13954. * @param {Object} config (optional) Config object.
  13955. */
  13956. constructor: function(config) {
  13957. config = config || {};
  13958. var me = this,
  13959. keyFn = config.keyFn;
  13960. me.addEvents(
  13961. /**
  13962. * @event add
  13963. * Fires when a new item is added to the hash
  13964. * @param {Ext.util.HashMap} this.
  13965. * @param {String} key The key of the added item.
  13966. * @param {Object} value The value of the added item.
  13967. */
  13968. 'add',
  13969. /**
  13970. * @event clear
  13971. * Fires when the hash is cleared.
  13972. * @param {Ext.util.HashMap} this.
  13973. */
  13974. 'clear',
  13975. /**
  13976. * @event remove
  13977. * Fires when an item is removed from the hash.
  13978. * @param {Ext.util.HashMap} this.
  13979. * @param {String} key The key of the removed item.
  13980. * @param {Object} value The value of the removed item.
  13981. */
  13982. 'remove',
  13983. /**
  13984. * @event replace
  13985. * Fires when an item is replaced in the hash.
  13986. * @param {Ext.util.HashMap} this.
  13987. * @param {String} key The key of the replaced item.
  13988. * @param {Object} value The new value for the item.
  13989. * @param {Object} old The old value for the item.
  13990. */
  13991. 'replace'
  13992. );
  13993. me.mixins.observable.constructor.call(me, config);
  13994. me.clear(true);
  13995. if (keyFn) {
  13996. me.getKey = keyFn;
  13997. }
  13998. },
  13999. /**
  14000. * Gets the number of items in the hash.
  14001. * @return {Number} The number of items in the hash.
  14002. */
  14003. getCount: function() {
  14004. return this.length;
  14005. },
  14006. /**
  14007. * Implementation for being able to extract the key from an object if only
  14008. * a single argument is passed.
  14009. * @private
  14010. * @param {String} key The key
  14011. * @param {Object} value The value
  14012. * @return {Array} [key, value]
  14013. */
  14014. getData: function(key, value) {
  14015. // if we have no value, it means we need to get the key from the object
  14016. if (value === undefined) {
  14017. value = key;
  14018. key = this.getKey(value);
  14019. }
  14020. return [key, value];
  14021. },
  14022. /**
  14023. * Extracts the key from an object. This is a default implementation, it may be overridden
  14024. * @param {Object} o The object to get the key from
  14025. * @return {String} The key to use.
  14026. */
  14027. getKey: function(o) {
  14028. return o.id;
  14029. },
  14030. /**
  14031. * Adds an item to the collection. Fires the {@link #event-add} event when complete.
  14032. *
  14033. * @param {String/Object} key The key to associate with the item, or the new item.
  14034. *
  14035. * If a {@link #getKey} implementation was specified for this HashMap,
  14036. * or if the key of the stored items is in a property called `id`,
  14037. * the HashMap will be able to *derive* the key for the new item.
  14038. * In this case just pass the new item in this parameter.
  14039. *
  14040. * @param {Object} [o] The item to add.
  14041. *
  14042. * @return {Object} The item added.
  14043. */
  14044. add: function(key, value) {
  14045. var me = this;
  14046. if (value === undefined) {
  14047. value = key;
  14048. key = me.getKey(value);
  14049. }
  14050. if (me.containsKey(key)) {
  14051. return me.replace(key, value);
  14052. }
  14053. me.map[key] = value;
  14054. ++me.length;
  14055. if (me.hasListeners.add) {
  14056. me.fireEvent('add', me, key, value);
  14057. }
  14058. return value;
  14059. },
  14060. /**
  14061. * Replaces an item in the hash. If the key doesn't exist, the
  14062. * {@link #method-add} method will be used.
  14063. * @param {String} key The key of the item.
  14064. * @param {Object} value The new value for the item.
  14065. * @return {Object} The new value of the item.
  14066. */
  14067. replace: function(key, value) {
  14068. var me = this,
  14069. map = me.map,
  14070. old;
  14071. if (value === undefined) {
  14072. value = key;
  14073. key = me.getKey(value);
  14074. }
  14075. if (!me.containsKey(key)) {
  14076. me.add(key, value);
  14077. }
  14078. old = map[key];
  14079. map[key] = value;
  14080. if (me.hasListeners.replace) {
  14081. me.fireEvent('replace', me, key, value, old);
  14082. }
  14083. return value;
  14084. },
  14085. /**
  14086. * Remove an item from the hash.
  14087. * @param {Object} o The value of the item to remove.
  14088. * @return {Boolean} True if the item was successfully removed.
  14089. */
  14090. remove: function(o) {
  14091. var key = this.findKey(o);
  14092. if (key !== undefined) {
  14093. return this.removeAtKey(key);
  14094. }
  14095. return false;
  14096. },
  14097. /**
  14098. * Remove an item from the hash.
  14099. * @param {String} key The key to remove.
  14100. * @return {Boolean} True if the item was successfully removed.
  14101. */
  14102. removeAtKey: function(key) {
  14103. var me = this,
  14104. value;
  14105. if (me.containsKey(key)) {
  14106. value = me.map[key];
  14107. delete me.map[key];
  14108. --me.length;
  14109. if (me.hasListeners.remove) {
  14110. me.fireEvent('remove', me, key, value);
  14111. }
  14112. return true;
  14113. }
  14114. return false;
  14115. },
  14116. /**
  14117. * Retrieves an item with a particular key.
  14118. * @param {String} key The key to lookup.
  14119. * @return {Object} The value at that key. If it doesn't exist, <tt>undefined</tt> is returned.
  14120. */
  14121. get: function(key) {
  14122. return this.map[key];
  14123. },
  14124. /**
  14125. * Removes all items from the hash.
  14126. * @return {Ext.util.HashMap} this
  14127. */
  14128. clear: function(/* private */ initial) {
  14129. var me = this;
  14130. me.map = {};
  14131. me.length = 0;
  14132. if (initial !== true && me.hasListeners.clear) {
  14133. me.fireEvent('clear', me);
  14134. }
  14135. return me;
  14136. },
  14137. /**
  14138. * Checks whether a key exists in the hash.
  14139. * @param {String} key The key to check for.
  14140. * @return {Boolean} True if they key exists in the hash.
  14141. */
  14142. containsKey: function(key) {
  14143. return this.map[key] !== undefined;
  14144. },
  14145. /**
  14146. * Checks whether a value exists in the hash.
  14147. * @param {Object} value The value to check for.
  14148. * @return {Boolean} True if the value exists in the dictionary.
  14149. */
  14150. contains: function(value) {
  14151. return this.containsKey(this.findKey(value));
  14152. },
  14153. /**
  14154. * Return all of the keys in the hash.
  14155. * @return {Array} An array of keys.
  14156. */
  14157. getKeys: function() {
  14158. return this.getArray(true);
  14159. },
  14160. /**
  14161. * Return all of the values in the hash.
  14162. * @return {Array} An array of values.
  14163. */
  14164. getValues: function() {
  14165. return this.getArray(false);
  14166. },
  14167. /**
  14168. * Gets either the keys/values in an array from the hash.
  14169. * @private
  14170. * @param {Boolean} isKey True to extract the keys, otherwise, the value
  14171. * @return {Array} An array of either keys/values from the hash.
  14172. */
  14173. getArray: function(isKey) {
  14174. var arr = [],
  14175. key,
  14176. map = this.map;
  14177. for (key in map) {
  14178. if (map.hasOwnProperty(key)) {
  14179. arr.push(isKey ? key: map[key]);
  14180. }
  14181. }
  14182. return arr;
  14183. },
  14184. /**
  14185. * Executes the specified function once for each item in the hash.
  14186. * Returning false from the function will cease iteration.
  14187. *
  14188. * The paramaters passed to the function are:
  14189. * <div class="mdetail-params"><ul>
  14190. * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
  14191. * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
  14192. * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
  14193. * </ul></div>
  14194. * @param {Function} fn The function to execute.
  14195. * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
  14196. * @return {Ext.util.HashMap} this
  14197. */
  14198. each: function(fn, scope) {
  14199. // copy items so they may be removed during iteration.
  14200. var items = Ext.apply({}, this.map),
  14201. key,
  14202. length = this.length;
  14203. scope = scope || this;
  14204. for (key in items) {
  14205. if (items.hasOwnProperty(key)) {
  14206. if (fn.call(scope, key, items[key], length) === false) {
  14207. break;
  14208. }
  14209. }
  14210. }
  14211. return this;
  14212. },
  14213. /**
  14214. * Performs a shallow copy on this hash.
  14215. * @return {Ext.util.HashMap} The new hash object.
  14216. */
  14217. clone: function() {
  14218. var hash = new this.self(),
  14219. map = this.map,
  14220. key;
  14221. hash.suspendEvents();
  14222. for (key in map) {
  14223. if (map.hasOwnProperty(key)) {
  14224. hash.add(key, map[key]);
  14225. }
  14226. }
  14227. hash.resumeEvents();
  14228. return hash;
  14229. },
  14230. /**
  14231. * @private
  14232. * Find the key for a value.
  14233. * @param {Object} value The value to find.
  14234. * @return {Object} The value of the item. Returns <tt>undefined</tt> if not found.
  14235. */
  14236. findKey: function(value) {
  14237. var key,
  14238. map = this.map;
  14239. for (key in map) {
  14240. if (map.hasOwnProperty(key) && map[key] === value) {
  14241. return key;
  14242. }
  14243. }
  14244. return undefined;
  14245. }
  14246. });
  14247. /**
  14248. * Base Manager class
  14249. */
  14250. Ext.define('Ext.AbstractManager', {
  14251. /* Begin Definitions */
  14252. requires: ['Ext.util.HashMap'],
  14253. /* End Definitions */
  14254. typeName: 'type',
  14255. constructor: function(config) {
  14256. Ext.apply(this, config || {});
  14257. /**
  14258. * @property {Ext.util.HashMap} all
  14259. * Contains all of the items currently managed
  14260. */
  14261. this.all = new Ext.util.HashMap();
  14262. this.types = {};
  14263. },
  14264. /**
  14265. * Returns an item by id.
  14266. * For additional details see {@link Ext.util.HashMap#get}.
  14267. * @param {String} id The id of the item
  14268. * @return {Object} The item, undefined if not found.
  14269. */
  14270. get : function(id) {
  14271. return this.all.get(id);
  14272. },
  14273. /**
  14274. * Registers an item to be managed
  14275. * @param {Object} item The item to register
  14276. */
  14277. register: function(item) {
  14278. var all = this.all,
  14279. key = all.getKey(item);
  14280. if (all.containsKey(key)) {
  14281. Ext.Error.raise('Registering duplicate id "' + key + '" with this manager');
  14282. }
  14283. this.all.add(item);
  14284. },
  14285. /**
  14286. * Unregisters an item by removing it from this manager
  14287. * @param {Object} item The item to unregister
  14288. */
  14289. unregister: function(item) {
  14290. this.all.remove(item);
  14291. },
  14292. /**
  14293. * Registers a new item constructor, keyed by a type key.
  14294. * @param {String} type The mnemonic string by which the class may be looked up.
  14295. * @param {Function} cls The new instance class.
  14296. */
  14297. registerType : function(type, cls) {
  14298. this.types[type] = cls;
  14299. cls[this.typeName] = type;
  14300. },
  14301. /**
  14302. * Checks if an item type is registered.
  14303. * @param {String} type The mnemonic string by which the class may be looked up
  14304. * @return {Boolean} Whether the type is registered.
  14305. */
  14306. isRegistered : function(type){
  14307. return this.types[type] !== undefined;
  14308. },
  14309. /**
  14310. * Creates and returns an instance of whatever this manager manages, based on the supplied type and
  14311. * config object.
  14312. * @param {Object} config The config object
  14313. * @param {String} defaultType If no type is discovered in the config object, we fall back to this type
  14314. * @return {Object} The instance of whatever this manager is managing
  14315. */
  14316. create: function(config, defaultType) {
  14317. var type = config[this.typeName] || config.type || defaultType,
  14318. Constructor = this.types[type];
  14319. if (Constructor === undefined) {
  14320. Ext.Error.raise("The '" + type + "' type has not been registered with this manager");
  14321. }
  14322. return new Constructor(config);
  14323. },
  14324. /**
  14325. * Registers a function that will be called when an item with the specified id is added to the manager.
  14326. * This will happen on instantiation.
  14327. * @param {String} id The item id
  14328. * @param {Function} fn The callback function. Called with a single parameter, the item.
  14329. * @param {Object} scope The scope (this reference) in which the callback is executed.
  14330. * Defaults to the item.
  14331. */
  14332. onAvailable : function(id, fn, scope){
  14333. var all = this.all,
  14334. item,
  14335. callback;
  14336. if (all.containsKey(id)) {
  14337. item = all.get(id);
  14338. fn.call(scope || item, item);
  14339. } else {
  14340. callback = function(map, key, item){
  14341. if (key == id) {
  14342. fn.call(scope || item, item);
  14343. all.un('add', callback);
  14344. }
  14345. };
  14346. all.on('add', callback);
  14347. }
  14348. },
  14349. /**
  14350. * Executes the specified function once for each item in the collection.
  14351. * @param {Function} fn The function to execute.
  14352. * @param {String} fn.key The key of the item
  14353. * @param {Number} fn.value The value of the item
  14354. * @param {Number} fn.length The total number of items in the collection
  14355. * @param {Boolean} fn.return False to cease iteration.
  14356. * @param {Object} scope The scope to execute in. Defaults to `this`.
  14357. */
  14358. each: function(fn, scope){
  14359. this.all.each(fn, scope || this);
  14360. },
  14361. /**
  14362. * Gets the number of items in the collection.
  14363. * @return {Number} The number of items in the collection.
  14364. */
  14365. getCount: function(){
  14366. return this.all.getCount();
  14367. }
  14368. });
  14369. /**
  14370. * @class Ext.ComponentManager
  14371. * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
  14372. * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
  14373. * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
  14374. * <p>This object also provides a registry of available Component <i>classes</i>
  14375. * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
  14376. * The <code>xtype</code> provides a way to avoid instantiating child Components
  14377. * when creating a full, nested config object for a complete Ext page.</p>
  14378. * <p>A child Component may be specified simply as a <i>config object</i>
  14379. * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
  14380. * needs rendering, the correct type can be looked up for lazy instantiation.</p>
  14381. * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
  14382. * @singleton
  14383. */
  14384. Ext.define('Ext.ComponentManager', {
  14385. extend: 'Ext.AbstractManager',
  14386. alternateClassName: 'Ext.ComponentMgr',
  14387. singleton: true,
  14388. typeName: 'xtype',
  14389. /**
  14390. * Creates a new Component from the specified config object using the
  14391. * config object's xtype to determine the class to instantiate.
  14392. * @param {Object} config A configuration object for the Component you wish to create.
  14393. * @param {String} defaultType (optional) The xtype to use if the config object does not
  14394. * contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
  14395. * @return {Ext.Component} The newly instantiated Component.
  14396. */
  14397. create: function(component, defaultType){
  14398. if (typeof component == 'string') {
  14399. return Ext.widget(component);
  14400. }
  14401. if (component.isComponent) {
  14402. return component;
  14403. }
  14404. return Ext.widget(component.xtype || defaultType, component);
  14405. },
  14406. registerType: function(type, cls) {
  14407. this.types[type] = cls;
  14408. cls[this.typeName] = type;
  14409. cls.prototype[this.typeName] = type;
  14410. }
  14411. });
  14412. /**
  14413. * Provides searching of Components within Ext.ComponentManager (globally) or a specific
  14414. * Ext.container.Container on the document with a similar syntax to a CSS selector.
  14415. *
  14416. * Components can be retrieved by using their {@link Ext.Component xtype}
  14417. *
  14418. * - `component`
  14419. * - `gridpanel`
  14420. *
  14421. * Matching by xtype matches inherited types, so in the following code, the previous field
  14422. * *of any type which inherits from `TextField`* will be found:
  14423. *
  14424. * prevField = myField.previousNode('textfield');
  14425. *
  14426. * To match only the exact type, pass the "shallow" flag (See {@link Ext.AbstractComponent#isXType AbstractComponent's isXType method})
  14427. *
  14428. * prevTextField = myField.previousNode('textfield(true)');
  14429. *
  14430. * An itemId or id must be prefixed with a #
  14431. *
  14432. * - `#myContainer`
  14433. *
  14434. * Attributes must be wrapped in brackets
  14435. *
  14436. * - `component[autoScroll]`
  14437. * - `panel[title="Test"]`
  14438. *
  14439. * Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
  14440. * the candidate Component will be included in the query:
  14441. *
  14442. * var disabledFields = myFormPanel.query("{isDisabled()}");
  14443. *
  14444. * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
  14445. *
  14446. * // Function receives array and returns a filtered array.
  14447. * Ext.ComponentQuery.pseudos.invalid = function(items) {
  14448. * var i = 0, l = items.length, c, result = [];
  14449. * for (; i < l; i++) {
  14450. * if (!(c = items[i]).isValid()) {
  14451. * result.push(c);
  14452. * }
  14453. * }
  14454. * return result;
  14455. * };
  14456. *
  14457. * var invalidFields = myFormPanel.query('field:invalid');
  14458. * if (invalidFields.length) {
  14459. * invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
  14460. * for (var i = 0, l = invalidFields.length; i < l; i++) {
  14461. * invalidFields[i].getEl().frame("red");
  14462. * }
  14463. * }
  14464. *
  14465. * Default pseudos include:
  14466. *
  14467. * - not
  14468. * - first
  14469. * - last
  14470. *
  14471. * Queries return an array of components.
  14472. * Here are some example queries.
  14473. *
  14474. * // retrieve all Ext.Panels in the document by xtype
  14475. * var panelsArray = Ext.ComponentQuery.query('panel');
  14476. *
  14477. * // retrieve all Ext.Panels within the container with an id myCt
  14478. * var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
  14479. *
  14480. * // retrieve all direct children which are Ext.Panels within myCt
  14481. * var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
  14482. *
  14483. * // retrieve all grids and trees
  14484. * var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
  14485. *
  14486. * For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
  14487. * {@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
  14488. * {@link Ext.Component#up}.
  14489. */
  14490. Ext.define('Ext.ComponentQuery', {
  14491. singleton: true,
  14492. requires: ['Ext.ComponentManager']
  14493. }, function() {
  14494. var cq = this,
  14495. // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
  14496. // as a member on each item in the passed array.
  14497. filterFnPattern = [
  14498. 'var r = [],',
  14499. 'i = 0,',
  14500. 'it = items,',
  14501. 'l = it.length,',
  14502. 'c;',
  14503. 'for (; i < l; i++) {',
  14504. 'c = it[i];',
  14505. 'if (c.{0}) {',
  14506. 'r.push(c);',
  14507. '}',
  14508. '}',
  14509. 'return r;'
  14510. ].join(''),
  14511. filterItems = function(items, operation) {
  14512. // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
  14513. // The operation's method loops over each item in the candidate array and
  14514. // returns an array of items which match its criteria
  14515. return operation.method.apply(this, [ items ].concat(operation.args));
  14516. },
  14517. getItems = function(items, mode) {
  14518. var result = [],
  14519. i = 0,
  14520. length = items.length,
  14521. candidate,
  14522. deep = mode !== '>';
  14523. for (; i < length; i++) {
  14524. candidate = items[i];
  14525. if (candidate.getRefItems) {
  14526. result = result.concat(candidate.getRefItems(deep));
  14527. }
  14528. }
  14529. return result;
  14530. },
  14531. getAncestors = function(items) {
  14532. var result = [],
  14533. i = 0,
  14534. length = items.length,
  14535. candidate;
  14536. for (; i < length; i++) {
  14537. candidate = items[i];
  14538. while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
  14539. result.push(candidate);
  14540. }
  14541. }
  14542. return result;
  14543. },
  14544. // Filters the passed candidate array and returns only items which match the passed xtype
  14545. filterByXType = function(items, xtype, shallow) {
  14546. if (xtype === '*') {
  14547. return items.slice();
  14548. }
  14549. else {
  14550. var result = [],
  14551. i = 0,
  14552. length = items.length,
  14553. candidate;
  14554. for (; i < length; i++) {
  14555. candidate = items[i];
  14556. if (candidate.isXType(xtype, shallow)) {
  14557. result.push(candidate);
  14558. }
  14559. }
  14560. return result;
  14561. }
  14562. },
  14563. // Filters the passed candidate array and returns only items which have the passed className
  14564. filterByClassName = function(items, className) {
  14565. var EA = Ext.Array,
  14566. result = [],
  14567. i = 0,
  14568. length = items.length,
  14569. candidate;
  14570. for (; i < length; i++) {
  14571. candidate = items[i];
  14572. if (candidate.hasCls(className)) {
  14573. result.push(candidate);
  14574. }
  14575. }
  14576. return result;
  14577. },
  14578. // Filters the passed candidate array and returns only items which have the specified property match
  14579. filterByAttribute = function(items, property, operator, value) {
  14580. var result = [],
  14581. i = 0,
  14582. length = items.length,
  14583. candidate;
  14584. for (; i < length; i++) {
  14585. candidate = items[i];
  14586. if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
  14587. result.push(candidate);
  14588. }
  14589. }
  14590. return result;
  14591. },
  14592. // Filters the passed candidate array and returns only items which have the specified itemId or id
  14593. filterById = function(items, id) {
  14594. var result = [],
  14595. i = 0,
  14596. length = items.length,
  14597. candidate;
  14598. for (; i < length; i++) {
  14599. candidate = items[i];
  14600. if (candidate.getItemId() === id) {
  14601. result.push(candidate);
  14602. }
  14603. }
  14604. return result;
  14605. },
  14606. // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
  14607. filterByPseudo = function(items, name, value) {
  14608. return cq.pseudos[name](items, value);
  14609. },
  14610. // Determines leading mode
  14611. // > for direct child, and ^ to switch to ownerCt axis
  14612. modeRe = /^(\s?([>\^])\s?|\s|$)/,
  14613. // Matches a token with possibly (true|false) appended for the "shallow" parameter
  14614. tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
  14615. matchers = [{
  14616. // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
  14617. re: /^\.([\w\-]+)(?:\((true|false)\))?/,
  14618. method: filterByXType
  14619. },{
  14620. // checks for [attribute=value]
  14621. re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
  14622. method: filterByAttribute
  14623. }, {
  14624. // checks for #cmpItemId
  14625. re: /^#([\w\-]+)/,
  14626. method: filterById
  14627. }, {
  14628. // checks for :<pseudo_class>(<selector>)
  14629. re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
  14630. method: filterByPseudo
  14631. }, {
  14632. // checks for {<member_expression>}
  14633. re: /^(?:\{([^\}]+)\})/,
  14634. method: filterFnPattern
  14635. }];
  14636. // Internal class Ext.ComponentQuery.Query
  14637. cq.Query = Ext.extend(Object, {
  14638. constructor: function(cfg) {
  14639. cfg = cfg || {};
  14640. Ext.apply(this, cfg);
  14641. },
  14642. // Executes this Query upon the selected root.
  14643. // The root provides the initial source of candidate Component matches which are progressively
  14644. // filtered by iterating through this Query's operations cache.
  14645. // If no root is provided, all registered Components are searched via the ComponentManager.
  14646. // root may be a Container who's descendant Components are filtered
  14647. // root may be a Component with an implementation of getRefItems which provides some nested Components such as the
  14648. // docked items within a Panel.
  14649. // root may be an array of candidate Components to filter using this Query.
  14650. execute : function(root) {
  14651. var operations = this.operations,
  14652. i = 0,
  14653. length = operations.length,
  14654. operation,
  14655. workingItems;
  14656. // no root, use all Components in the document
  14657. if (!root) {
  14658. workingItems = Ext.ComponentManager.all.getArray();
  14659. }
  14660. // Root is a candidate Array
  14661. else if (Ext.isArray(root)) {
  14662. workingItems = root;
  14663. }
  14664. // Root is a MixedCollection
  14665. else if (root.isMixedCollection) {
  14666. workingItems = root.items;
  14667. }
  14668. // We are going to loop over our operations and take care of them
  14669. // one by one.
  14670. for (; i < length; i++) {
  14671. operation = operations[i];
  14672. // The mode operation requires some custom handling.
  14673. // All other operations essentially filter down our current
  14674. // working items, while mode replaces our current working
  14675. // items by getting children from each one of our current
  14676. // working items. The type of mode determines the type of
  14677. // children we get. (e.g. > only gets direct children)
  14678. if (operation.mode === '^') {
  14679. workingItems = getAncestors(workingItems || [root]);
  14680. }
  14681. else if (operation.mode) {
  14682. workingItems = getItems(workingItems || [root], operation.mode);
  14683. }
  14684. else {
  14685. workingItems = filterItems(workingItems || getItems([root]), operation);
  14686. }
  14687. // If this is the last operation, it means our current working
  14688. // items are the final matched items. Thus return them!
  14689. if (i === length -1) {
  14690. return workingItems;
  14691. }
  14692. }
  14693. return [];
  14694. },
  14695. is: function(component) {
  14696. var operations = this.operations,
  14697. components = Ext.isArray(component) ? component : [component],
  14698. originalLength = components.length,
  14699. lastOperation = operations[operations.length-1],
  14700. ln, i;
  14701. components = filterItems(components, lastOperation);
  14702. if (components.length === originalLength) {
  14703. if (operations.length > 1) {
  14704. for (i = 0, ln = components.length; i < ln; i++) {
  14705. if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
  14706. return false;
  14707. }
  14708. }
  14709. }
  14710. return true;
  14711. }
  14712. return false;
  14713. }
  14714. });
  14715. Ext.apply(this, {
  14716. // private cache of selectors and matching ComponentQuery.Query objects
  14717. cache: {},
  14718. // private cache of pseudo class filter functions
  14719. pseudos: {
  14720. not: function(components, selector){
  14721. var CQ = Ext.ComponentQuery,
  14722. i = 0,
  14723. length = components.length,
  14724. results = [],
  14725. index = -1,
  14726. component;
  14727. for(; i < length; ++i) {
  14728. component = components[i];
  14729. if (!CQ.is(component, selector)) {
  14730. results[++index] = component;
  14731. }
  14732. }
  14733. return results;
  14734. },
  14735. first: function(components) {
  14736. var ret = [];
  14737. if (components.length > 0) {
  14738. ret.push(components[0]);
  14739. }
  14740. return ret;
  14741. },
  14742. last: function(components) {
  14743. var len = components.length,
  14744. ret = [];
  14745. if (len > 0) {
  14746. ret.push(components[len - 1]);
  14747. }
  14748. return ret;
  14749. }
  14750. },
  14751. /**
  14752. * Returns an array of matched Components from within the passed root object.
  14753. *
  14754. * This method filters returned Components in a similar way to how CSS selector based DOM
  14755. * queries work using a textual selector string.
  14756. *
  14757. * See class summary for details.
  14758. *
  14759. * @param {String} selector The selector string to filter returned Components
  14760. * @param {Ext.container.Container} root The Container within which to perform the query.
  14761. * If omitted, all Components within the document are included in the search.
  14762. *
  14763. * This parameter may also be an array of Components to filter according to the selector.</p>
  14764. * @returns {Ext.Component[]} The matched Components.
  14765. *
  14766. * @member Ext.ComponentQuery
  14767. */
  14768. query: function(selector, root) {
  14769. var selectors = selector.split(','),
  14770. length = selectors.length,
  14771. i = 0,
  14772. results = [],
  14773. noDupResults = [],
  14774. dupMatcher = {},
  14775. query, resultsLn, cmp;
  14776. for (; i < length; i++) {
  14777. selector = Ext.String.trim(selectors[i]);
  14778. query = this.cache[selector] || (this.cache[selector] = this.parse(selector));
  14779. results = results.concat(query.execute(root));
  14780. }
  14781. // multiple selectors, potential to find duplicates
  14782. // lets filter them out.
  14783. if (length > 1) {
  14784. resultsLn = results.length;
  14785. for (i = 0; i < resultsLn; i++) {
  14786. cmp = results[i];
  14787. if (!dupMatcher[cmp.id]) {
  14788. noDupResults.push(cmp);
  14789. dupMatcher[cmp.id] = true;
  14790. }
  14791. }
  14792. results = noDupResults;
  14793. }
  14794. return results;
  14795. },
  14796. /**
  14797. * Tests whether the passed Component matches the selector string.
  14798. * @param {Ext.Component} component The Component to test
  14799. * @param {String} selector The selector string to test against.
  14800. * @return {Boolean} True if the Component matches the selector.
  14801. * @member Ext.ComponentQuery
  14802. */
  14803. is: function(component, selector) {
  14804. if (!selector) {
  14805. return true;
  14806. }
  14807. var selectors = selector.split(','),
  14808. length = selectors.length,
  14809. i = 0,
  14810. query;
  14811. for (; i < length; i++) {
  14812. selector = Ext.String.trim(selectors[i]);
  14813. query = this.cache[selector] || (this.cache[selector] = this.parse(selector));
  14814. if (query.is(component)) {
  14815. return true;
  14816. }
  14817. }
  14818. return false;
  14819. },
  14820. parse: function(selector) {
  14821. var operations = [],
  14822. length = matchers.length,
  14823. lastSelector,
  14824. tokenMatch,
  14825. matchedChar,
  14826. modeMatch,
  14827. selectorMatch,
  14828. i, matcher, method;
  14829. // We are going to parse the beginning of the selector over and
  14830. // over again, slicing off the selector any portions we converted into an
  14831. // operation, until it is an empty string.
  14832. while (selector && lastSelector !== selector) {
  14833. lastSelector = selector;
  14834. // First we check if we are dealing with a token like #, * or an xtype
  14835. tokenMatch = selector.match(tokenRe);
  14836. if (tokenMatch) {
  14837. matchedChar = tokenMatch[1];
  14838. // If the token is prefixed with a # we push a filterById operation to our stack
  14839. if (matchedChar === '#') {
  14840. operations.push({
  14841. method: filterById,
  14842. args: [Ext.String.trim(tokenMatch[2])]
  14843. });
  14844. }
  14845. // If the token is prefixed with a . we push a filterByClassName operation to our stack
  14846. // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
  14847. else if (matchedChar === '.') {
  14848. operations.push({
  14849. method: filterByClassName,
  14850. args: [Ext.String.trim(tokenMatch[2])]
  14851. });
  14852. }
  14853. // If the token is a * or an xtype string, we push a filterByXType
  14854. // operation to the stack.
  14855. else {
  14856. operations.push({
  14857. method: filterByXType,
  14858. args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
  14859. });
  14860. }
  14861. // Now we slice of the part we just converted into an operation
  14862. selector = selector.replace(tokenMatch[0], '');
  14863. }
  14864. // If the next part of the query is not a space or > or ^, it means we
  14865. // are going to check for more things that our current selection
  14866. // has to comply to.
  14867. while (!(modeMatch = selector.match(modeRe))) {
  14868. // Lets loop over each type of matcher and execute it
  14869. // on our current selector.
  14870. for (i = 0; selector && i < length; i++) {
  14871. matcher = matchers[i];
  14872. selectorMatch = selector.match(matcher.re);
  14873. method = matcher.method;
  14874. // If we have a match, add an operation with the method
  14875. // associated with this matcher, and pass the regular
  14876. // expression matches are arguments to the operation.
  14877. if (selectorMatch) {
  14878. operations.push({
  14879. method: Ext.isString(matcher.method)
  14880. // Turn a string method into a function by formatting the string with our selector matche expression
  14881. // A new method is created for different match expressions, eg {id=='textfield-1024'}
  14882. // Every expression may be different in different selectors.
  14883. ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
  14884. : matcher.method,
  14885. args: selectorMatch.slice(1)
  14886. });
  14887. selector = selector.replace(selectorMatch[0], '');
  14888. break; // Break on match
  14889. }
  14890. // Exhausted all matches: It's an error
  14891. if (i === (length - 1)) {
  14892. Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
  14893. }
  14894. }
  14895. }
  14896. // Now we are going to check for a mode change. This means a space
  14897. // or a > to determine if we are going to select all the children
  14898. // of the currently matched items, or a ^ if we are going to use the
  14899. // ownerCt axis as the candidate source.
  14900. if (modeMatch[1]) { // Assignment, and test for truthiness!
  14901. operations.push({
  14902. mode: modeMatch[2]||modeMatch[1]
  14903. });
  14904. selector = selector.replace(modeMatch[0], '');
  14905. }
  14906. }
  14907. // Now that we have all our operations in an array, we are going
  14908. // to create a new Query using these operations.
  14909. return new cq.Query({
  14910. operations: operations
  14911. });
  14912. }
  14913. });
  14914. });
  14915. /*
  14916. * The dirty implementation in this class is quite naive. The reasoning for this is that the dirty state
  14917. * will only be used in very specific circumstances, specifically, after the render process has begun but
  14918. * the component is not yet rendered to the DOM. As such, we want it to perform as quickly as possible
  14919. * so it's not as fully featured as you may expect.
  14920. */
  14921. /**
  14922. * Manages certain element-like data prior to rendering. These values are passed
  14923. * on to the render process. This is currently used to manage the "class" and "style" attributes
  14924. * of a component's primary el as well as the bodyEl of panels. This allows things like
  14925. * addBodyCls in Panel to share logic with addCls in AbstractComponent.
  14926. * @private
  14927. */
  14928. Ext.define('Ext.util.ProtoElement', (function () {
  14929. var splitWords = Ext.String.splitWords,
  14930. toMap = Ext.Array.toMap;
  14931. return {
  14932. isProtoEl: true,
  14933. /**
  14934. * The property name for the className on the data object passed to {@link #writeTo}.
  14935. */
  14936. clsProp: 'cls',
  14937. /**
  14938. * The property name for the style on the data object passed to {@link #writeTo}.
  14939. */
  14940. styleProp: 'style',
  14941. /**
  14942. * The property name for the removed classes on the data object passed to {@link #writeTo}.
  14943. */
  14944. removedProp: 'removed',
  14945. /**
  14946. * True if the style must be converted to text during {@link #writeTo}. When used to
  14947. * populate tpl data, this will be true. When used to populate {@link Ext.DomHelper}
  14948. * specs, this will be false (the default).
  14949. */
  14950. styleIsText: false,
  14951. constructor: function (config) {
  14952. var me = this;
  14953. Ext.apply(me, config);
  14954. me.classList = splitWords(me.cls);
  14955. me.classMap = toMap(me.classList);
  14956. delete me.cls;
  14957. if (Ext.isFunction(me.style)) {
  14958. me.styleFn = me.style;
  14959. delete me.style;
  14960. } else if (typeof me.style == 'string') {
  14961. me.style = Ext.Element.parseStyles(me.style);
  14962. } else if (me.style) {
  14963. me.style = Ext.apply({}, me.style); // don't edit the given object
  14964. }
  14965. },
  14966. /**
  14967. * Indicates that the current state of the object has been flushed to the DOM, so we need
  14968. * to track any subsequent changes
  14969. */
  14970. flush: function(){
  14971. this.flushClassList = [];
  14972. this.removedClasses = {};
  14973. // clear the style, it will be recreated if we add anything new
  14974. delete this.style;
  14975. },
  14976. /**
  14977. * Adds class to the element.
  14978. * @param {String} cls One or more classnames separated with spaces.
  14979. * @return {Ext.util.ProtoElement} this
  14980. */
  14981. addCls: function (cls) {
  14982. var me = this,
  14983. add = splitWords(cls),
  14984. length = add.length,
  14985. list = me.classList,
  14986. map = me.classMap,
  14987. flushList = me.flushClassList,
  14988. i = 0,
  14989. c;
  14990. for (; i < length; ++i) {
  14991. c = add[i];
  14992. if (!map[c]) {
  14993. map[c] = true;
  14994. list.push(c);
  14995. if (flushList) {
  14996. flushList.push(c);
  14997. delete me.removedClasses[c];
  14998. }
  14999. }
  15000. }
  15001. return me;
  15002. },
  15003. /**
  15004. * True if the element has given class.
  15005. * @param {String} cls
  15006. * @return {Boolean}
  15007. */
  15008. hasCls: function (cls) {
  15009. return cls in this.classMap;
  15010. },
  15011. /**
  15012. * Removes class from the element.
  15013. * @param {String} cls One or more classnames separated with spaces.
  15014. * @return {Ext.util.ProtoElement} this
  15015. */
  15016. removeCls: function (cls) {
  15017. var me = this,
  15018. list = me.classList,
  15019. newList = (me.classList = []),
  15020. remove = toMap(splitWords(cls)),
  15021. length = list.length,
  15022. map = me.classMap,
  15023. removedClasses = me.removedClasses,
  15024. i, c;
  15025. for (i = 0; i < length; ++i) {
  15026. c = list[i];
  15027. if (remove[c]) {
  15028. if (removedClasses) {
  15029. if (map[c]) {
  15030. removedClasses[c] = true;
  15031. Ext.Array.remove(me.flushClassList, c);
  15032. }
  15033. }
  15034. delete map[c];
  15035. } else {
  15036. newList.push(c);
  15037. }
  15038. }
  15039. return me;
  15040. },
  15041. /**
  15042. * Adds styles to the element.
  15043. * @param {String/Object} prop The style property to be set, or an object of multiple styles.
  15044. * @param {String} [value] The value to apply to the given property.
  15045. * @return {Ext.util.ProtoElement} this
  15046. */
  15047. setStyle: function (prop, value) {
  15048. var me = this,
  15049. style = me.style || (me.style = {});
  15050. if (typeof prop == 'string') {
  15051. if (arguments.length === 1) {
  15052. me.setStyle(Ext.Element.parseStyles(prop));
  15053. } else {
  15054. style[prop] = value;
  15055. }
  15056. } else {
  15057. Ext.apply(style, prop);
  15058. }
  15059. return me;
  15060. },
  15061. /**
  15062. * Writes style and class properties to given object.
  15063. * Styles will be written to {@link #styleProp} and class names to {@link #clsProp}.
  15064. * @param {Object} to
  15065. * @return {Object} to
  15066. */
  15067. writeTo: function (to) {
  15068. var me = this,
  15069. classList = me.flushClassList || me.classList,
  15070. removedClasses = me.removedClasses,
  15071. style;
  15072. if (me.styleFn) {
  15073. style = Ext.apply({}, me.styleFn());
  15074. Ext.apply(style, me.style);
  15075. } else {
  15076. style = me.style;
  15077. }
  15078. to[me.clsProp] = classList.join(' ');
  15079. if (style) {
  15080. to[me.styleProp] = me.styleIsText ? Ext.DomHelper.generateStyles(style) : style;
  15081. }
  15082. if (removedClasses) {
  15083. removedClasses = Ext.Object.getKeys(removedClasses);
  15084. if (removedClasses.length) {
  15085. to[me.removedProp] = removedClasses.join(' ');
  15086. }
  15087. }
  15088. return to;
  15089. }
  15090. };
  15091. }()));
  15092. //@tag dom,core
  15093. //@require util/Event.js
  15094. //@define Ext.EventManager
  15095. /**
  15096. * @class Ext.EventManager
  15097. * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
  15098. * several useful events directly.
  15099. * See {@link Ext.EventObject} for more details on normalized event objects.
  15100. * @singleton
  15101. */
  15102. Ext.EventManager = new function() {
  15103. var EventManager = this,
  15104. doc = document,
  15105. win = window,
  15106. initExtCss = function() {
  15107. // find the body element
  15108. var bd = doc.body || doc.getElementsByTagName('body')[0],
  15109. baseCSSPrefix = Ext.baseCSSPrefix,
  15110. cls = [baseCSSPrefix + 'body'],
  15111. htmlCls = [],
  15112. supportsLG = Ext.supports.CSS3LinearGradient,
  15113. supportsBR = Ext.supports.CSS3BorderRadius,
  15114. resetCls = [],
  15115. html,
  15116. resetElementSpec;
  15117. if (!bd) {
  15118. return false;
  15119. }
  15120. html = bd.parentNode;
  15121. function add (c) {
  15122. cls.push(baseCSSPrefix + c);
  15123. }
  15124. //Let's keep this human readable!
  15125. if (Ext.isIE) {
  15126. add('ie');
  15127. // very often CSS needs to do checks like "IE7+" or "IE6 or 7". To help
  15128. // reduce the clutter (since CSS/SCSS cannot do these tests), we add some
  15129. // additional classes:
  15130. //
  15131. // x-ie7p : IE7+ : 7 <= ieVer
  15132. // x-ie7m : IE7- : ieVer <= 7
  15133. // x-ie8p : IE8+ : 8 <= ieVer
  15134. // x-ie8m : IE8- : ieVer <= 8
  15135. // x-ie9p : IE9+ : 9 <= ieVer
  15136. // x-ie78 : IE7 or 8 : 7 <= ieVer <= 8
  15137. //
  15138. if (Ext.isIE6) {
  15139. add('ie6');
  15140. } else { // ignore pre-IE6 :)
  15141. add('ie7p');
  15142. if (Ext.isIE7) {
  15143. add('ie7');
  15144. } else {
  15145. add('ie8p');
  15146. if (Ext.isIE8) {
  15147. add('ie8');
  15148. } else {
  15149. add('ie9p');
  15150. if (Ext.isIE9) {
  15151. add('ie9');
  15152. }
  15153. }
  15154. }
  15155. }
  15156. if (Ext.isIE6 || Ext.isIE7) {
  15157. add('ie7m');
  15158. }
  15159. if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
  15160. add('ie8m');
  15161. }
  15162. if (Ext.isIE7 || Ext.isIE8) {
  15163. add('ie78');
  15164. }
  15165. }
  15166. if (Ext.isGecko) {
  15167. add('gecko');
  15168. if (Ext.isGecko3) {
  15169. add('gecko3');
  15170. }
  15171. if (Ext.isGecko4) {
  15172. add('gecko4');
  15173. }
  15174. if (Ext.isGecko5) {
  15175. add('gecko5');
  15176. }
  15177. }
  15178. if (Ext.isOpera) {
  15179. add('opera');
  15180. }
  15181. if (Ext.isWebKit) {
  15182. add('webkit');
  15183. }
  15184. if (Ext.isSafari) {
  15185. add('safari');
  15186. if (Ext.isSafari2) {
  15187. add('safari2');
  15188. }
  15189. if (Ext.isSafari3) {
  15190. add('safari3');
  15191. }
  15192. if (Ext.isSafari4) {
  15193. add('safari4');
  15194. }
  15195. if (Ext.isSafari5) {
  15196. add('safari5');
  15197. }
  15198. if (Ext.isSafari5_0) {
  15199. add('safari5_0')
  15200. }
  15201. }
  15202. if (Ext.isChrome) {
  15203. add('chrome');
  15204. }
  15205. if (Ext.isMac) {
  15206. add('mac');
  15207. }
  15208. if (Ext.isLinux) {
  15209. add('linux');
  15210. }
  15211. if (!supportsBR) {
  15212. add('nbr');
  15213. }
  15214. if (!supportsLG) {
  15215. add('nlg');
  15216. }
  15217. // If we are not globally resetting scope, but just resetting it in a wrapper around
  15218. // serarately rendered widgets, then create a common reset element for use when creating
  15219. // measurable elements. Using a common DomHelper spec.
  15220. if (Ext.scopeResetCSS) {
  15221. // Create Ext.resetElementSpec for use in Renderable when wrapping top level Components.
  15222. resetElementSpec = Ext.resetElementSpec = {
  15223. cls: baseCSSPrefix + 'reset'
  15224. };
  15225. if (!supportsLG) {
  15226. resetCls.push(baseCSSPrefix + 'nlg');
  15227. }
  15228. if (!supportsBR) {
  15229. resetCls.push(baseCSSPrefix + 'nbr');
  15230. }
  15231. if (resetCls.length) {
  15232. resetElementSpec.cn = {
  15233. cls: resetCls.join(' ')
  15234. };
  15235. }
  15236. Ext.resetElement = Ext.getBody().createChild(resetElementSpec);
  15237. if (resetCls.length) {
  15238. Ext.resetElement = Ext.get(Ext.resetElement.dom.firstChild);
  15239. }
  15240. }
  15241. // Otherwise, the common reset element is the document body
  15242. else {
  15243. Ext.resetElement = Ext.getBody();
  15244. add('reset');
  15245. }
  15246. // add to the parent to allow for selectors x-strict x-border-box, also set the isBorderBox property correctly
  15247. if (html) {
  15248. if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
  15249. Ext.isBorderBox = false;
  15250. }
  15251. else {
  15252. Ext.isBorderBox = true;
  15253. }
  15254. if(Ext.isBorderBox) {
  15255. htmlCls.push(baseCSSPrefix + 'border-box');
  15256. }
  15257. if (Ext.isStrict) {
  15258. htmlCls.push(baseCSSPrefix + 'strict');
  15259. } else {
  15260. htmlCls.push(baseCSSPrefix + 'quirks');
  15261. }
  15262. Ext.fly(html, '_internal').addCls(htmlCls);
  15263. }
  15264. Ext.fly(bd, '_internal').addCls(cls);
  15265. return true;
  15266. };
  15267. Ext.apply(EventManager, {
  15268. /**
  15269. * Check if we have bound our global onReady listener
  15270. * @private
  15271. */
  15272. hasBoundOnReady: false,
  15273. /**
  15274. * Check if fireDocReady has been called
  15275. * @private
  15276. */
  15277. hasFiredReady: false,
  15278. /**
  15279. * Additionally, allow the 'DOM' listener thread to complete (usually desirable with mobWebkit, Gecko)
  15280. * before firing the entire onReady chain (high stack load on Loader) by specifying a delay value
  15281. * @default 1ms
  15282. * @private
  15283. */
  15284. deferReadyEvent : 1,
  15285. /*
  15286. * diags: a list of event names passed to onReadyEvent (in chron order)
  15287. * @private
  15288. */
  15289. onReadyChain : [],
  15290. /**
  15291. * Holds references to any onReady functions
  15292. * @private
  15293. */
  15294. readyEvent:
  15295. (function () {
  15296. var event = new Ext.util.Event();
  15297. event.fire = function () {
  15298. Ext._beforeReadyTime = Ext._beforeReadyTime || new Date().getTime();
  15299. event.self.prototype.fire.apply(event, arguments);
  15300. Ext._afterReadytime = new Date().getTime();
  15301. };
  15302. return event;
  15303. }()),
  15304. /**
  15305. * Fires when a DOM event handler finishes its run, just before returning to browser control.
  15306. * This can be useful for performing cleanup, or upfdate tasks which need to happen only
  15307. * after all code in an event handler has been run, but which should not be executed in a timer
  15308. * due to the intervening browser reflow/repaint which would take place.
  15309. *
  15310. */
  15311. idleEvent: new Ext.util.Event(),
  15312. /**
  15313. * detects whether the EventManager has been placed in a paused state for synchronization
  15314. * with external debugging / perf tools (PageAnalyzer)
  15315. * @private
  15316. */
  15317. isReadyPaused: function(){
  15318. return (/[?&]ext-pauseReadyFire\b/i.test(location.search) && !Ext._continueFireReady);
  15319. },
  15320. /**
  15321. * Binds the appropriate browser event for checking if the DOM has loaded.
  15322. * @private
  15323. */
  15324. bindReadyEvent: function() {
  15325. if (EventManager.hasBoundOnReady) {
  15326. return;
  15327. }
  15328. // Test scenario where Core is dynamically loaded AFTER window.load
  15329. if ( doc.readyState == 'complete' ) { // Firefox4+ got support for this state, others already do.
  15330. EventManager.onReadyEvent({
  15331. type: doc.readyState || 'body'
  15332. });
  15333. } else {
  15334. document.addEventListener('DOMContentLoaded', EventManager.onReadyEvent, false);
  15335. window.addEventListener('load', EventManager.onReadyEvent, false);
  15336. EventManager.hasBoundOnReady = true;
  15337. }
  15338. },
  15339. onReadyEvent : function(e) {
  15340. if (e && e.type) {
  15341. EventManager.onReadyChain.push(e.type);
  15342. }
  15343. if (EventManager.hasBoundOnReady) {
  15344. document.removeEventListener('DOMContentLoaded', EventManager.onReadyEvent, false);
  15345. window.removeEventListener('load', EventManager.onReadyEvent, false);
  15346. }
  15347. if (!Ext.isReady) {
  15348. EventManager.fireDocReady();
  15349. }
  15350. },
  15351. /**
  15352. * We know the document is loaded, so trigger any onReady events.
  15353. * @private
  15354. */
  15355. fireDocReady: function() {
  15356. if (!Ext.isReady) {
  15357. Ext._readyTime = new Date().getTime();
  15358. Ext.isReady = true;
  15359. Ext.supports.init();
  15360. EventManager.onWindowUnload();
  15361. EventManager.readyEvent.onReadyChain = EventManager.onReadyChain; //diags report
  15362. if (Ext.isNumber(EventManager.deferReadyEvent)) {
  15363. Ext.Function.defer(EventManager.fireReadyEvent, EventManager.deferReadyEvent);
  15364. EventManager.hasDocReadyTimer = true;
  15365. } else {
  15366. EventManager.fireReadyEvent();
  15367. }
  15368. }
  15369. },
  15370. /**
  15371. * Fires the ready event
  15372. * @private
  15373. */
  15374. fireReadyEvent: function(){
  15375. var readyEvent = EventManager.readyEvent;
  15376. // Unset the timer flag here since other onReady events may be
  15377. // added during the fire() call and we don't want to block them
  15378. EventManager.hasDocReadyTimer = false;
  15379. EventManager.isFiring = true;
  15380. // Ready events are all single: true, if we get to the end
  15381. // & there are more listeners, it means they were added
  15382. // inside some other ready event
  15383. while (readyEvent.listeners.length && !EventManager.isReadyPaused()) {
  15384. readyEvent.fire();
  15385. }
  15386. EventManager.isFiring = false;
  15387. EventManager.hasFiredReady = true;
  15388. },
  15389. /**
  15390. * Adds a listener to be notified when the document is ready (before onload and before images are loaded).
  15391. *
  15392. * @param {Function} fn The method the event invokes.
  15393. * @param {Object} [scope] The scope (`this` reference) in which the handler function executes.
  15394. * Defaults to the browser window.
  15395. * @param {Object} [options] Options object as passed to {@link Ext.Element#addListener}.
  15396. */
  15397. onDocumentReady: function(fn, scope, options) {
  15398. options = options || {};
  15399. // force single, only ever fire it once
  15400. options.single = true;
  15401. EventManager.readyEvent.addListener(fn, scope, options);
  15402. // If we're in the middle of firing, or we have a deferred timer
  15403. // pending, drop out since the event will be fired later
  15404. if (!(EventManager.isFiring || EventManager.hasDocReadyTimer)) {
  15405. if (Ext.isReady) {
  15406. EventManager.fireReadyEvent();
  15407. } else {
  15408. EventManager.bindReadyEvent();
  15409. }
  15410. }
  15411. },
  15412. // --------------------- event binding ---------------------
  15413. /**
  15414. * Contains a list of all document mouse downs, so we can ensure they fire even when stopEvent is called.
  15415. * @private
  15416. */
  15417. stoppedMouseDownEvent: new Ext.util.Event(),
  15418. /**
  15419. * Options to parse for the 4th argument to addListener.
  15420. * @private
  15421. */
  15422. propRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|freezeEvent)$/,
  15423. /**
  15424. * Get the id of the element. If one has not been assigned, automatically assign it.
  15425. * @param {HTMLElement/Ext.Element} element The element to get the id for.
  15426. * @return {String} id
  15427. */
  15428. getId : function(element) {
  15429. var id;
  15430. element = Ext.getDom(element);
  15431. if (element === doc || element === win) {
  15432. id = element === doc ? Ext.documentId : Ext.windowId;
  15433. }
  15434. else {
  15435. id = Ext.id(element);
  15436. }
  15437. if (!Ext.cache[id]) {
  15438. Ext.addCacheEntry(id, null, element);
  15439. }
  15440. return id;
  15441. },
  15442. /**
  15443. * Convert a "config style" listener into a set of flat arguments so they can be passed to addListener
  15444. * @private
  15445. * @param {Object} element The element the event is for
  15446. * @param {Object} event The event configuration
  15447. * @param {Object} isRemove True if a removal should be performed, otherwise an add will be done.
  15448. */
  15449. prepareListenerConfig: function(element, config, isRemove) {
  15450. var propRe = EventManager.propRe,
  15451. key, value, args;
  15452. // loop over all the keys in the object
  15453. for (key in config) {
  15454. if (config.hasOwnProperty(key)) {
  15455. // if the key is something else then an event option
  15456. if (!propRe.test(key)) {
  15457. value = config[key];
  15458. // if the value is a function it must be something like click: function() {}, scope: this
  15459. // which means that there might be multiple event listeners with shared options
  15460. if (typeof value == 'function') {
  15461. // shared options
  15462. args = [element, key, value, config.scope, config];
  15463. } else {
  15464. // if its not a function, it must be an object like click: {fn: function() {}, scope: this}
  15465. args = [element, key, value.fn, value.scope, value];
  15466. }
  15467. if (isRemove) {
  15468. EventManager.removeListener.apply(EventManager, args);
  15469. } else {
  15470. EventManager.addListener.apply(EventManager, args);
  15471. }
  15472. }
  15473. }
  15474. }
  15475. },
  15476. mouseEnterLeaveRe: /mouseenter|mouseleave/,
  15477. /**
  15478. * Normalize cross browser event differences
  15479. * @private
  15480. * @param {Object} eventName The event name
  15481. * @param {Object} fn The function to execute
  15482. * @return {Object} The new event name/function
  15483. */
  15484. normalizeEvent: function(eventName, fn) {
  15485. if (EventManager.mouseEnterLeaveRe.test(eventName) && !Ext.supports.MouseEnterLeave) {
  15486. if (fn) {
  15487. fn = Ext.Function.createInterceptor(fn, EventManager.contains);
  15488. }
  15489. eventName = eventName == 'mouseenter' ? 'mouseover' : 'mouseout';
  15490. } else if (eventName == 'mousewheel' && !Ext.supports.MouseWheel && !Ext.isOpera) {
  15491. eventName = 'DOMMouseScroll';
  15492. }
  15493. return {
  15494. eventName: eventName,
  15495. fn: fn
  15496. };
  15497. },
  15498. /**
  15499. * Checks whether the event's relatedTarget is contained inside (or <b>is</b>) the element.
  15500. * @private
  15501. * @param {Object} event
  15502. */
  15503. contains: function(event) {
  15504. var parent = event.browserEvent.currentTarget,
  15505. child = EventManager.getRelatedTarget(event);
  15506. if (parent && parent.firstChild) {
  15507. while (child) {
  15508. if (child === parent) {
  15509. return false;
  15510. }
  15511. child = child.parentNode;
  15512. if (child && (child.nodeType != 1)) {
  15513. child = null;
  15514. }
  15515. }
  15516. }
  15517. return true;
  15518. },
  15519. /**
  15520. * Appends an event handler to an element. The shorthand version {@link #on} is equivalent. Typically you will
  15521. * use {@link Ext.Element#addListener} directly on an Element in favor of calling this version.
  15522. * @param {String/HTMLElement} el The html element or id to assign the event handler to.
  15523. * @param {String} eventName The name of the event to listen for.
  15524. * @param {Function} handler The handler function the event invokes. This function is passed
  15525. * the following parameters:<ul>
  15526. * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
  15527. * <li>t : Element<div class="sub-desc">The {@link Ext.Element Element} which was the target of the event.
  15528. * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
  15529. * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
  15530. * </ul>
  15531. * @param {Object} scope (optional) The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.
  15532. * @param {Object} options (optional) An object containing handler configuration properties.
  15533. * This may contain any of the following properties:<ul>
  15534. * <li>scope : Object<div class="sub-desc">The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.</div></li>
  15535. * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
  15536. * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
  15537. * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
  15538. * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
  15539. * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
  15540. * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
  15541. * <li>single : Boolean<div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
  15542. * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
  15543. * by the specified number of milliseconds. If the event fires again within that time, the original
  15544. * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
  15545. * <li>target : Element<div class="sub-desc">Only call the handler if the event was fired on the target Element, <i>not</i> if the event was bubbled up from a child node.</div></li>
  15546. * </ul><br>
  15547. * <p>See {@link Ext.Element#addListener} for examples of how to use these options.</p>
  15548. */
  15549. addListener: function(element, eventName, fn, scope, options) {
  15550. // Check if we've been passed a "config style" event.
  15551. if (typeof eventName !== 'string') {
  15552. EventManager.prepareListenerConfig(element, eventName);
  15553. return;
  15554. }
  15555. var dom = element.dom || Ext.getDom(element),
  15556. bind, wrap;
  15557. if (!fn) {
  15558. Ext.Error.raise({
  15559. sourceClass: 'Ext.EventManager',
  15560. sourceMethod: 'addListener',
  15561. targetElement: element,
  15562. eventName: eventName,
  15563. msg: 'Error adding "' + eventName + '\" listener. The handler function is undefined.'
  15564. });
  15565. }
  15566. // create the wrapper function
  15567. options = options || {};
  15568. bind = EventManager.normalizeEvent(eventName, fn);
  15569. wrap = EventManager.createListenerWrap(dom, eventName, bind.fn, scope, options);
  15570. if (dom.attachEvent) {
  15571. dom.attachEvent('on' + bind.eventName, wrap);
  15572. } else {
  15573. dom.addEventListener(bind.eventName, wrap, options.capture || false);
  15574. }
  15575. if (dom == doc && eventName == 'mousedown') {
  15576. EventManager.stoppedMouseDownEvent.addListener(wrap);
  15577. }
  15578. // add all required data into the event cache
  15579. EventManager.getEventListenerCache(element.dom ? element : dom, eventName).push({
  15580. fn: fn,
  15581. wrap: wrap,
  15582. scope: scope
  15583. });
  15584. },
  15585. /**
  15586. * Removes an event handler from an element. The shorthand version {@link #un} is equivalent. Typically
  15587. * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version.
  15588. * @param {String/HTMLElement} el The id or html element from which to remove the listener.
  15589. * @param {String} eventName The name of the event.
  15590. * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
  15591. * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
  15592. * then this must refer to the same object.
  15593. */
  15594. removeListener : function(element, eventName, fn, scope) {
  15595. // handle our listener config object syntax
  15596. if (typeof eventName !== 'string') {
  15597. EventManager.prepareListenerConfig(element, eventName, true);
  15598. return;
  15599. }
  15600. var dom = Ext.getDom(element),
  15601. el = element.dom ? element : Ext.get(dom),
  15602. cache = EventManager.getEventListenerCache(el, eventName),
  15603. bindName = EventManager.normalizeEvent(eventName).eventName,
  15604. i = cache.length, j,
  15605. listener, wrap, tasks;
  15606. while (i--) {
  15607. listener = cache[i];
  15608. if (listener && (!fn || listener.fn == fn) && (!scope || listener.scope === scope)) {
  15609. wrap = listener.wrap;
  15610. // clear buffered calls
  15611. if (wrap.task) {
  15612. clearTimeout(wrap.task);
  15613. delete wrap.task;
  15614. }
  15615. // clear delayed calls
  15616. j = wrap.tasks && wrap.tasks.length;
  15617. if (j) {
  15618. while (j--) {
  15619. clearTimeout(wrap.tasks[j]);
  15620. }
  15621. delete wrap.tasks;
  15622. }
  15623. if (dom.detachEvent) {
  15624. dom.detachEvent('on' + bindName, wrap);
  15625. } else {
  15626. dom.removeEventListener(bindName, wrap, false);
  15627. }
  15628. if (wrap && dom == doc && eventName == 'mousedown') {
  15629. EventManager.stoppedMouseDownEvent.removeListener(wrap);
  15630. }
  15631. // remove listener from cache
  15632. Ext.Array.erase(cache, i, 1);
  15633. }
  15634. }
  15635. },
  15636. /**
  15637. * Removes all event handers from an element. Typically you will use {@link Ext.Element#removeAllListeners}
  15638. * directly on an Element in favor of calling this version.
  15639. * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
  15640. */
  15641. removeAll : function(element) {
  15642. var el = element.dom ? element : Ext.get(element),
  15643. cache, events, eventName;
  15644. if (!el) {
  15645. return;
  15646. }
  15647. cache = (el.$cache || el.getCache());
  15648. events = cache.events;
  15649. for (eventName in events) {
  15650. if (events.hasOwnProperty(eventName)) {
  15651. EventManager.removeListener(el, eventName);
  15652. }
  15653. }
  15654. cache.events = {};
  15655. },
  15656. /**
  15657. * Recursively removes all previous added listeners from an element and its children. Typically you will use {@link Ext.Element#purgeAllListeners}
  15658. * directly on an Element in favor of calling this version.
  15659. * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
  15660. * @param {String} eventName (optional) The name of the event.
  15661. */
  15662. purgeElement : function(element, eventName) {
  15663. var dom = Ext.getDom(element),
  15664. i = 0, len;
  15665. if (eventName) {
  15666. EventManager.removeListener(element, eventName);
  15667. }
  15668. else {
  15669. EventManager.removeAll(element);
  15670. }
  15671. if (dom && dom.childNodes) {
  15672. for (len = element.childNodes.length; i < len; i++) {
  15673. EventManager.purgeElement(element.childNodes[i], eventName);
  15674. }
  15675. }
  15676. },
  15677. /**
  15678. * Create the wrapper function for the event
  15679. * @private
  15680. * @param {HTMLElement} dom The dom element
  15681. * @param {String} ename The event name
  15682. * @param {Function} fn The function to execute
  15683. * @param {Object} scope The scope to execute callback in
  15684. * @param {Object} options The options
  15685. * @return {Function} the wrapper function
  15686. */
  15687. createListenerWrap : function(dom, ename, fn, scope, options) {
  15688. options = options || {};
  15689. var f, gen, escapeRx = /\\/g, wrap = function(e, args) {
  15690. // Compile the implementation upon first firing
  15691. if (!gen) {
  15692. f = ['if(!' + Ext.name + ') {return;}'];
  15693. if(options.buffer || options.delay || options.freezeEvent) {
  15694. f.push('e = new X.EventObjectImpl(e, ' + (options.freezeEvent ? 'true' : 'false' ) + ');');
  15695. } else {
  15696. f.push('e = X.EventObject.setEvent(e);');
  15697. }
  15698. if (options.delegate) {
  15699. // double up '\' characters so escape sequences survive the
  15700. // string-literal translation
  15701. f.push('var result, t = e.getTarget("' + (options.delegate + '').replace(escapeRx, '\\\\') + '", this);');
  15702. f.push('if(!t) {return;}');
  15703. } else {
  15704. f.push('var t = e.target, result;');
  15705. }
  15706. if (options.target) {
  15707. f.push('if(e.target !== options.target) {return;}');
  15708. }
  15709. if(options.stopEvent) {
  15710. f.push('e.stopEvent();');
  15711. } else {
  15712. if(options.preventDefault) {
  15713. f.push('e.preventDefault();');
  15714. }
  15715. if(options.stopPropagation) {
  15716. f.push('e.stopPropagation();');
  15717. }
  15718. }
  15719. if(options.normalized === false) {
  15720. f.push('e = e.browserEvent;');
  15721. }
  15722. if(options.buffer) {
  15723. f.push('(wrap.task && clearTimeout(wrap.task));');
  15724. f.push('wrap.task = setTimeout(function() {');
  15725. }
  15726. if(options.delay) {
  15727. f.push('wrap.tasks = wrap.tasks || [];');
  15728. f.push('wrap.tasks.push(setTimeout(function() {');
  15729. }
  15730. // finally call the actual handler fn
  15731. f.push('result = fn.call(scope || dom, e, t, options);');
  15732. if(options.single) {
  15733. f.push('evtMgr.removeListener(dom, ename, fn, scope);');
  15734. }
  15735. // Fire the global idle event for all events except mousemove which is too common, and
  15736. // fires too frequently and fast to be use in tiggering onIdle processing.
  15737. if (ename !== 'mousemove') {
  15738. f.push('if (evtMgr.idleEvent.listeners.length) {');
  15739. f.push('evtMgr.idleEvent.fire();');
  15740. f.push('}');
  15741. }
  15742. if(options.delay) {
  15743. f.push('}, ' + options.delay + '));');
  15744. }
  15745. if(options.buffer) {
  15746. f.push('}, ' + options.buffer + ');');
  15747. }
  15748. f.push('return result;')
  15749. gen = Ext.cacheableFunctionFactory('e', 'options', 'fn', 'scope', 'ename', 'dom', 'wrap', 'args', 'X', 'evtMgr', f.join('\n'));
  15750. }
  15751. return gen.call(dom, e, options, fn, scope, ename, dom, wrap, args, Ext, EventManager);
  15752. };
  15753. return wrap;
  15754. },
  15755. /**
  15756. * Get the event cache for a particular element for a particular event
  15757. * @private
  15758. * @param {HTMLElement} element The element
  15759. * @param {Object} eventName The event name
  15760. * @return {Array} The events for the element
  15761. */
  15762. getEventListenerCache : function(element, eventName) {
  15763. var elementCache, eventCache;
  15764. if (!element) {
  15765. return [];
  15766. }
  15767. if (element.$cache) {
  15768. elementCache = element.$cache;
  15769. } else {
  15770. // getId will populate the cache for this element if it isn't already present
  15771. elementCache = Ext.cache[EventManager.getId(element)];
  15772. }
  15773. eventCache = elementCache.events || (elementCache.events = {});
  15774. return eventCache[eventName] || (eventCache[eventName] = []);
  15775. },
  15776. // --------------------- utility methods ---------------------
  15777. mouseLeaveRe: /(mouseout|mouseleave)/,
  15778. mouseEnterRe: /(mouseover|mouseenter)/,
  15779. /**
  15780. * Stop the event (preventDefault and stopPropagation)
  15781. * @param {Event} The event to stop
  15782. */
  15783. stopEvent: function(event) {
  15784. EventManager.stopPropagation(event);
  15785. EventManager.preventDefault(event);
  15786. },
  15787. /**
  15788. * Cancels bubbling of the event.
  15789. * @param {Event} The event to stop bubbling.
  15790. */
  15791. stopPropagation: function(event) {
  15792. event = event.browserEvent || event;
  15793. if (event.stopPropagation) {
  15794. event.stopPropagation();
  15795. } else {
  15796. event.cancelBubble = true;
  15797. }
  15798. },
  15799. /**
  15800. * Prevents the browsers default handling of the event.
  15801. * @param {Event} The event to prevent the default
  15802. */
  15803. preventDefault: function(event) {
  15804. event = event.browserEvent || event;
  15805. if (event.preventDefault) {
  15806. event.preventDefault();
  15807. } else {
  15808. event.returnValue = false;
  15809. // Some keys events require setting the keyCode to -1 to be prevented
  15810. try {
  15811. // all ctrl + X and F1 -> F12
  15812. if (event.ctrlKey || event.keyCode > 111 && event.keyCode < 124) {
  15813. event.keyCode = -1;
  15814. }
  15815. } catch (e) {
  15816. // see this outdated document http://support.microsoft.com/kb/934364/en-us for more info
  15817. }
  15818. }
  15819. },
  15820. /**
  15821. * Gets the related target from the event.
  15822. * @param {Object} event The event
  15823. * @return {HTMLElement} The related target.
  15824. */
  15825. getRelatedTarget: function(event) {
  15826. event = event.browserEvent || event;
  15827. var target = event.relatedTarget;
  15828. if (!target) {
  15829. if (EventManager.mouseLeaveRe.test(event.type)) {
  15830. target = event.toElement;
  15831. } else if (EventManager.mouseEnterRe.test(event.type)) {
  15832. target = event.fromElement;
  15833. }
  15834. }
  15835. return EventManager.resolveTextNode(target);
  15836. },
  15837. /**
  15838. * Gets the x coordinate from the event
  15839. * @param {Object} event The event
  15840. * @return {Number} The x coordinate
  15841. */
  15842. getPageX: function(event) {
  15843. return EventManager.getPageXY(event)[0];
  15844. },
  15845. /**
  15846. * Gets the y coordinate from the event
  15847. * @param {Object} event The event
  15848. * @return {Number} The y coordinate
  15849. */
  15850. getPageY: function(event) {
  15851. return EventManager.getPageXY(event)[1];
  15852. },
  15853. /**
  15854. * Gets the x & y coordinate from the event
  15855. * @param {Object} event The event
  15856. * @return {Number[]} The x/y coordinate
  15857. */
  15858. getPageXY: function(event) {
  15859. event = event.browserEvent || event;
  15860. var x = event.pageX,
  15861. y = event.pageY,
  15862. docEl = doc.documentElement,
  15863. body = doc.body;
  15864. // pageX/pageY not available (undefined, not null), use clientX/clientY instead
  15865. if (!x && x !== 0) {
  15866. x = event.clientX + (docEl && docEl.scrollLeft || body && body.scrollLeft || 0) - (docEl && docEl.clientLeft || body && body.clientLeft || 0);
  15867. y = event.clientY + (docEl && docEl.scrollTop || body && body.scrollTop || 0) - (docEl && docEl.clientTop || body && body.clientTop || 0);
  15868. }
  15869. return [x, y];
  15870. },
  15871. /**
  15872. * Gets the target of the event.
  15873. * @param {Object} event The event
  15874. * @return {HTMLElement} target
  15875. */
  15876. getTarget: function(event) {
  15877. event = event.browserEvent || event;
  15878. return EventManager.resolveTextNode(event.target || event.srcElement);
  15879. },
  15880. // technically no need to browser sniff this, however it makes
  15881. // no sense to check this every time, for every event, whether
  15882. // the string is equal.
  15883. /**
  15884. * Resolve any text nodes accounting for browser differences.
  15885. * @private
  15886. * @param {HTMLElement} node The node
  15887. * @return {HTMLElement} The resolved node
  15888. */
  15889. resolveTextNode: Ext.isGecko ?
  15890. function(node) {
  15891. if (!node) {
  15892. return;
  15893. }
  15894. // work around firefox bug, https://bugzilla.mozilla.org/show_bug.cgi?id=101197
  15895. var s = HTMLElement.prototype.toString.call(node);
  15896. if (s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]') {
  15897. return;
  15898. }
  15899. return node.nodeType == 3 ? node.parentNode: node;
  15900. }: function(node) {
  15901. return node && node.nodeType == 3 ? node.parentNode: node;
  15902. },
  15903. // --------------------- custom event binding ---------------------
  15904. // Keep track of the current width/height
  15905. curWidth: 0,
  15906. curHeight: 0,
  15907. /**
  15908. * Adds a listener to be notified when the browser window is resized and provides resize event buffering (100 milliseconds),
  15909. * passes new viewport width and height to handlers.
  15910. * @param {Function} fn The handler function the window resize event invokes.
  15911. * @param {Object} scope The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
  15912. * @param {Boolean} options Options object as passed to {@link Ext.Element#addListener}
  15913. */
  15914. onWindowResize: function(fn, scope, options) {
  15915. var resize = EventManager.resizeEvent;
  15916. if (!resize) {
  15917. EventManager.resizeEvent = resize = new Ext.util.Event();
  15918. EventManager.on(win, 'resize', EventManager.fireResize, null, {buffer: 100});
  15919. }
  15920. resize.addListener(fn, scope, options);
  15921. },
  15922. /**
  15923. * Fire the resize event.
  15924. * @private
  15925. */
  15926. fireResize: function() {
  15927. var w = Ext.Element.getViewWidth(),
  15928. h = Ext.Element.getViewHeight();
  15929. //whacky problem in IE where the resize event will sometimes fire even though the w/h are the same.
  15930. if (EventManager.curHeight != h || EventManager.curWidth != w) {
  15931. EventManager.curHeight = h;
  15932. EventManager.curWidth = w;
  15933. EventManager.resizeEvent.fire(w, h);
  15934. }
  15935. },
  15936. /**
  15937. * Removes the passed window resize listener.
  15938. * @param {Function} fn The method the event invokes
  15939. * @param {Object} scope The scope of handler
  15940. */
  15941. removeResizeListener: function(fn, scope) {
  15942. var resize = EventManager.resizeEvent;
  15943. if (resize) {
  15944. resize.removeListener(fn, scope);
  15945. }
  15946. },
  15947. /**
  15948. * Adds a listener to be notified when the browser window is unloaded.
  15949. * @param {Function} fn The handler function the window unload event invokes.
  15950. * @param {Object} scope The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
  15951. * @param {Boolean} options Options object as passed to {@link Ext.Element#addListener}
  15952. */
  15953. onWindowUnload: function(fn, scope, options) {
  15954. var unload = EventManager.unloadEvent;
  15955. if (!unload) {
  15956. EventManager.unloadEvent = unload = new Ext.util.Event();
  15957. EventManager.addListener(win, 'unload', EventManager.fireUnload);
  15958. }
  15959. if (fn) {
  15960. unload.addListener(fn, scope, options);
  15961. }
  15962. },
  15963. /**
  15964. * Fires the unload event for items bound with onWindowUnload
  15965. * @private
  15966. */
  15967. fireUnload: function() {
  15968. // wrap in a try catch, could have some problems during unload
  15969. try {
  15970. // relinquish references.
  15971. doc = win = undefined;
  15972. var gridviews, i, ln,
  15973. el, cache;
  15974. EventManager.unloadEvent.fire();
  15975. // Work around FF3 remembering the last scroll position when refreshing the grid and then losing grid view
  15976. if (Ext.isGecko3) {
  15977. gridviews = Ext.ComponentQuery.query('gridview');
  15978. i = 0;
  15979. ln = gridviews.length;
  15980. for (; i < ln; i++) {
  15981. gridviews[i].scrollToTop();
  15982. }
  15983. }
  15984. // Purge all elements in the cache
  15985. cache = Ext.cache;
  15986. for (el in cache) {
  15987. if (cache.hasOwnProperty(el)) {
  15988. EventManager.removeAll(el);
  15989. }
  15990. }
  15991. } catch(e) {
  15992. }
  15993. },
  15994. /**
  15995. * Removes the passed window unload listener.
  15996. * @param {Function} fn The method the event invokes
  15997. * @param {Object} scope The scope of handler
  15998. */
  15999. removeUnloadListener: function(fn, scope) {
  16000. var unload = EventManager.unloadEvent;
  16001. if (unload) {
  16002. unload.removeListener(fn, scope);
  16003. }
  16004. },
  16005. /**
  16006. * note 1: IE fires ONLY the keydown event on specialkey autorepeat
  16007. * note 2: Safari < 3.1, Gecko (Mac/Linux) & Opera fire only the keypress event on specialkey autorepeat
  16008. * (research done by Jan Wolter at http://unixpapa.com/js/key.html)
  16009. * @private
  16010. */
  16011. useKeyDown: Ext.isWebKit ?
  16012. parseInt(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1], 10) >= 525 :
  16013. !((Ext.isGecko && !Ext.isWindows) || Ext.isOpera),
  16014. /**
  16015. * Indicates which event to use for getting key presses.
  16016. * @return {String} The appropriate event name.
  16017. */
  16018. getKeyEvent: function() {
  16019. return EventManager.useKeyDown ? 'keydown' : 'keypress';
  16020. }
  16021. });
  16022. // route "< ie9-Standards" to a legacy IE onReady implementation
  16023. if(!('addEventListener' in document) && document.attachEvent) {
  16024. Ext.apply( EventManager, {
  16025. /* Customized implementation for Legacy IE. The default implementation is configured for use
  16026. * with all other 'standards compliant' agents.
  16027. * References: http://javascript.nwbox.com/IEContentLoaded/
  16028. * licensed courtesy of http://developer.yahoo.com/yui/license.html
  16029. */
  16030. /**
  16031. * This strategy has minimal benefits for Sencha solutions that build themselves (ie. minimal initial page markup).
  16032. * However, progressively-enhanced pages (with image content and/or embedded frames) will benefit the most from it.
  16033. * Browser timer resolution is too poor to ensure a doScroll check more than once on a page loaded with minimal
  16034. * assets (the readystatechange event 'complete' usually beats the doScroll timer on a 'lightly-loaded' initial document).
  16035. */
  16036. pollScroll : function() {
  16037. var scrollable = true;
  16038. try {
  16039. document.documentElement.doScroll('left');
  16040. } catch(e) {
  16041. scrollable = false;
  16042. }
  16043. // on IE8, when running within an iFrame, document.body is not immediately available
  16044. if (scrollable && document.body) {
  16045. EventManager.onReadyEvent({
  16046. type:'doScroll'
  16047. });
  16048. } else {
  16049. /*
  16050. * minimize thrashing --
  16051. * adjusted for setTimeout's close-to-minimums (not too low),
  16052. * as this method SHOULD always be called once initially
  16053. */
  16054. EventManager.scrollTimeout = setTimeout(EventManager.pollScroll, 20);
  16055. }
  16056. return scrollable;
  16057. },
  16058. /**
  16059. * Timer for doScroll polling
  16060. * @private
  16061. */
  16062. scrollTimeout: null,
  16063. /* @private
  16064. */
  16065. readyStatesRe : /complete/i,
  16066. /* @private
  16067. */
  16068. checkReadyState: function() {
  16069. var state = document.readyState;
  16070. if (EventManager.readyStatesRe.test(state)) {
  16071. EventManager.onReadyEvent({
  16072. type: state
  16073. });
  16074. }
  16075. },
  16076. bindReadyEvent: function() {
  16077. var topContext = true;
  16078. if (EventManager.hasBoundOnReady) {
  16079. return;
  16080. }
  16081. //are we in an IFRAME? (doScroll ineffective here)
  16082. try {
  16083. topContext = window.frameElement === undefined;
  16084. } catch(e) {
  16085. // If we throw an exception, it means we're probably getting access denied,
  16086. // which means we're in an iframe cross domain.
  16087. topContext = false;
  16088. }
  16089. if (!topContext || !doc.documentElement.doScroll) {
  16090. EventManager.pollScroll = Ext.emptyFn; //then noop this test altogether
  16091. }
  16092. // starts doScroll polling if necessary
  16093. if (EventManager.pollScroll() === true) {
  16094. return;
  16095. }
  16096. // Core is loaded AFTER initial document write/load ?
  16097. if (doc.readyState == 'complete' ) {
  16098. EventManager.onReadyEvent({type: 'already ' + (doc.readyState || 'body') });
  16099. } else {
  16100. doc.attachEvent('onreadystatechange', EventManager.checkReadyState);
  16101. window.attachEvent('onload', EventManager.onReadyEvent);
  16102. EventManager.hasBoundOnReady = true;
  16103. }
  16104. },
  16105. onReadyEvent : function(e) {
  16106. if (e && e.type) {
  16107. EventManager.onReadyChain.push(e.type);
  16108. }
  16109. if (EventManager.hasBoundOnReady) {
  16110. document.detachEvent('onreadystatechange', EventManager.checkReadyState);
  16111. window.detachEvent('onload', EventManager.onReadyEvent);
  16112. }
  16113. if (Ext.isNumber(EventManager.scrollTimeout)) {
  16114. clearTimeout(EventManager.scrollTimeout);
  16115. delete EventManager.scrollTimeout;
  16116. }
  16117. if (!Ext.isReady) {
  16118. EventManager.fireDocReady();
  16119. }
  16120. },
  16121. //diags: a list of event types passed to onReadyEvent (in chron order)
  16122. onReadyChain : []
  16123. });
  16124. }
  16125. /**
  16126. * Alias for {@link Ext.Loader#onReady Ext.Loader.onReady} with withDomReady set to true
  16127. * @member Ext
  16128. * @method onReady
  16129. */
  16130. Ext.onReady = function(fn, scope, options) {
  16131. Ext.Loader.onReady(fn, scope, true, options);
  16132. };
  16133. /**
  16134. * Alias for {@link Ext.EventManager#onDocumentReady Ext.EventManager.onDocumentReady}
  16135. * @member Ext
  16136. * @method onDocumentReady
  16137. */
  16138. Ext.onDocumentReady = EventManager.onDocumentReady;
  16139. /**
  16140. * Alias for {@link Ext.EventManager#addListener Ext.EventManager.addListener}
  16141. * @member Ext.EventManager
  16142. * @method on
  16143. */
  16144. EventManager.on = EventManager.addListener;
  16145. /**
  16146. * Alias for {@link Ext.EventManager#removeListener Ext.EventManager.removeListener}
  16147. * @member Ext.EventManager
  16148. * @method un
  16149. */
  16150. EventManager.un = EventManager.removeListener;
  16151. Ext.onReady(initExtCss);
  16152. };
  16153. //@tag dom,core
  16154. //@require EventManager.js
  16155. //@define Ext.EventObject
  16156. /**
  16157. * @class Ext.EventObject
  16158. Just as {@link Ext.Element} wraps around a native DOM node, Ext.EventObject
  16159. wraps the browser's native event-object normalizing cross-browser differences,
  16160. such as which mouse button is clicked, keys pressed, mechanisms to stop
  16161. event-propagation along with a method to prevent default actions from taking place.
  16162. For example:
  16163. function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
  16164. e.preventDefault();
  16165. var target = e.getTarget(); // same as t (the target HTMLElement)
  16166. ...
  16167. }
  16168. var myDiv = {@link Ext#get Ext.get}("myDiv"); // get reference to an {@link Ext.Element}
  16169. myDiv.on( // 'on' is shorthand for addListener
  16170. "click", // perform an action on click of myDiv
  16171. handleClick // reference to the action handler
  16172. );
  16173. // other methods to do the same:
  16174. Ext.EventManager.on("myDiv", 'click', handleClick);
  16175. Ext.EventManager.addListener("myDiv", 'click', handleClick);
  16176. * @singleton
  16177. * @markdown
  16178. */
  16179. Ext.define('Ext.EventObjectImpl', {
  16180. uses: ['Ext.util.Point'],
  16181. /** Key constant @type Number */
  16182. BACKSPACE: 8,
  16183. /** Key constant @type Number */
  16184. TAB: 9,
  16185. /** Key constant @type Number */
  16186. NUM_CENTER: 12,
  16187. /** Key constant @type Number */
  16188. ENTER: 13,
  16189. /** Key constant @type Number */
  16190. RETURN: 13,
  16191. /** Key constant @type Number */
  16192. SHIFT: 16,
  16193. /** Key constant @type Number */
  16194. CTRL: 17,
  16195. /** Key constant @type Number */
  16196. ALT: 18,
  16197. /** Key constant @type Number */
  16198. PAUSE: 19,
  16199. /** Key constant @type Number */
  16200. CAPS_LOCK: 20,
  16201. /** Key constant @type Number */
  16202. ESC: 27,
  16203. /** Key constant @type Number */
  16204. SPACE: 32,
  16205. /** Key constant @type Number */
  16206. PAGE_UP: 33,
  16207. /** Key constant @type Number */
  16208. PAGE_DOWN: 34,
  16209. /** Key constant @type Number */
  16210. END: 35,
  16211. /** Key constant @type Number */
  16212. HOME: 36,
  16213. /** Key constant @type Number */
  16214. LEFT: 37,
  16215. /** Key constant @type Number */
  16216. UP: 38,
  16217. /** Key constant @type Number */
  16218. RIGHT: 39,
  16219. /** Key constant @type Number */
  16220. DOWN: 40,
  16221. /** Key constant @type Number */
  16222. PRINT_SCREEN: 44,
  16223. /** Key constant @type Number */
  16224. INSERT: 45,
  16225. /** Key constant @type Number */
  16226. DELETE: 46,
  16227. /** Key constant @type Number */
  16228. ZERO: 48,
  16229. /** Key constant @type Number */
  16230. ONE: 49,
  16231. /** Key constant @type Number */
  16232. TWO: 50,
  16233. /** Key constant @type Number */
  16234. THREE: 51,
  16235. /** Key constant @type Number */
  16236. FOUR: 52,
  16237. /** Key constant @type Number */
  16238. FIVE: 53,
  16239. /** Key constant @type Number */
  16240. SIX: 54,
  16241. /** Key constant @type Number */
  16242. SEVEN: 55,
  16243. /** Key constant @type Number */
  16244. EIGHT: 56,
  16245. /** Key constant @type Number */
  16246. NINE: 57,
  16247. /** Key constant @type Number */
  16248. A: 65,
  16249. /** Key constant @type Number */
  16250. B: 66,
  16251. /** Key constant @type Number */
  16252. C: 67,
  16253. /** Key constant @type Number */
  16254. D: 68,
  16255. /** Key constant @type Number */
  16256. E: 69,
  16257. /** Key constant @type Number */
  16258. F: 70,
  16259. /** Key constant @type Number */
  16260. G: 71,
  16261. /** Key constant @type Number */
  16262. H: 72,
  16263. /** Key constant @type Number */
  16264. I: 73,
  16265. /** Key constant @type Number */
  16266. J: 74,
  16267. /** Key constant @type Number */
  16268. K: 75,
  16269. /** Key constant @type Number */
  16270. L: 76,
  16271. /** Key constant @type Number */
  16272. M: 77,
  16273. /** Key constant @type Number */
  16274. N: 78,
  16275. /** Key constant @type Number */
  16276. O: 79,
  16277. /** Key constant @type Number */
  16278. P: 80,
  16279. /** Key constant @type Number */
  16280. Q: 81,
  16281. /** Key constant @type Number */
  16282. R: 82,
  16283. /** Key constant @type Number */
  16284. S: 83,
  16285. /** Key constant @type Number */
  16286. T: 84,
  16287. /** Key constant @type Number */
  16288. U: 85,
  16289. /** Key constant @type Number */
  16290. V: 86,
  16291. /** Key constant @type Number */
  16292. W: 87,
  16293. /** Key constant @type Number */
  16294. X: 88,
  16295. /** Key constant @type Number */
  16296. Y: 89,
  16297. /** Key constant @type Number */
  16298. Z: 90,
  16299. /** Key constant @type Number */
  16300. CONTEXT_MENU: 93,
  16301. /** Key constant @type Number */
  16302. NUM_ZERO: 96,
  16303. /** Key constant @type Number */
  16304. NUM_ONE: 97,
  16305. /** Key constant @type Number */
  16306. NUM_TWO: 98,
  16307. /** Key constant @type Number */
  16308. NUM_THREE: 99,
  16309. /** Key constant @type Number */
  16310. NUM_FOUR: 100,
  16311. /** Key constant @type Number */
  16312. NUM_FIVE: 101,
  16313. /** Key constant @type Number */
  16314. NUM_SIX: 102,
  16315. /** Key constant @type Number */
  16316. NUM_SEVEN: 103,
  16317. /** Key constant @type Number */
  16318. NUM_EIGHT: 104,
  16319. /** Key constant @type Number */
  16320. NUM_NINE: 105,
  16321. /** Key constant @type Number */
  16322. NUM_MULTIPLY: 106,
  16323. /** Key constant @type Number */
  16324. NUM_PLUS: 107,
  16325. /** Key constant @type Number */
  16326. NUM_MINUS: 109,
  16327. /** Key constant @type Number */
  16328. NUM_PERIOD: 110,
  16329. /** Key constant @type Number */
  16330. NUM_DIVISION: 111,
  16331. /** Key constant @type Number */
  16332. F1: 112,
  16333. /** Key constant @type Number */
  16334. F2: 113,
  16335. /** Key constant @type Number */
  16336. F3: 114,
  16337. /** Key constant @type Number */
  16338. F4: 115,
  16339. /** Key constant @type Number */
  16340. F5: 116,
  16341. /** Key constant @type Number */
  16342. F6: 117,
  16343. /** Key constant @type Number */
  16344. F7: 118,
  16345. /** Key constant @type Number */
  16346. F8: 119,
  16347. /** Key constant @type Number */
  16348. F9: 120,
  16349. /** Key constant @type Number */
  16350. F10: 121,
  16351. /** Key constant @type Number */
  16352. F11: 122,
  16353. /** Key constant @type Number */
  16354. F12: 123,
  16355. /**
  16356. * The mouse wheel delta scaling factor. This value depends on browser version and OS and
  16357. * attempts to produce a similar scrolling experience across all platforms and browsers.
  16358. *
  16359. * To change this value:
  16360. *
  16361. * Ext.EventObjectImpl.prototype.WHEEL_SCALE = 72;
  16362. *
  16363. * @type Number
  16364. * @markdown
  16365. */
  16366. WHEEL_SCALE: (function () {
  16367. var scale;
  16368. if (Ext.isGecko) {
  16369. // Firefox uses 3 on all platforms
  16370. scale = 3;
  16371. } else if (Ext.isMac) {
  16372. // Continuous scrolling devices have momentum and produce much more scroll than
  16373. // discrete devices on the same OS and browser. To make things exciting, Safari
  16374. // (and not Chrome) changed from small values to 120 (like IE).
  16375. if (Ext.isSafari && Ext.webKitVersion >= 532.0) {
  16376. // Safari changed the scrolling factor to match IE (for details see
  16377. // https://bugs.webkit.org/show_bug.cgi?id=24368). The WebKit version where this
  16378. // change was introduced was 532.0
  16379. // Detailed discussion:
  16380. // https://bugs.webkit.org/show_bug.cgi?id=29601
  16381. // http://trac.webkit.org/browser/trunk/WebKit/chromium/src/mac/WebInputEventFactory.mm#L1063
  16382. scale = 120;
  16383. } else {
  16384. // MS optical wheel mouse produces multiples of 12 which is close enough
  16385. // to help tame the speed of the continuous mice...
  16386. scale = 12;
  16387. }
  16388. // Momentum scrolling produces very fast scrolling, so increase the scale factor
  16389. // to help produce similar results cross platform. This could be even larger and
  16390. // it would help those mice, but other mice would become almost unusable as a
  16391. // result (since we cannot tell which device type is in use).
  16392. scale *= 3;
  16393. } else {
  16394. // IE, Opera and other Windows browsers use 120.
  16395. scale = 120;
  16396. }
  16397. return scale;
  16398. }()),
  16399. /**
  16400. * Simple click regex
  16401. * @private
  16402. */
  16403. clickRe: /(dbl)?click/,
  16404. // safari keypress events for special keys return bad keycodes
  16405. safariKeys: {
  16406. 3: 13, // enter
  16407. 63234: 37, // left
  16408. 63235: 39, // right
  16409. 63232: 38, // up
  16410. 63233: 40, // down
  16411. 63276: 33, // page up
  16412. 63277: 34, // page down
  16413. 63272: 46, // delete
  16414. 63273: 36, // home
  16415. 63275: 35 // end
  16416. },
  16417. // normalize button clicks, don't see any way to feature detect this.
  16418. btnMap: Ext.isIE ? {
  16419. 1: 0,
  16420. 4: 1,
  16421. 2: 2
  16422. } : {
  16423. 0: 0,
  16424. 1: 1,
  16425. 2: 2
  16426. },
  16427. /**
  16428. * @property {Boolean} ctrlKey
  16429. * True if the control key was down during the event.
  16430. * In Mac this will also be true when meta key was down.
  16431. */
  16432. /**
  16433. * @property {Boolean} altKey
  16434. * True if the alt key was down during the event.
  16435. */
  16436. /**
  16437. * @property {Boolean} shiftKey
  16438. * True if the shift key was down during the event.
  16439. */
  16440. constructor: function(event, freezeEvent){
  16441. if (event) {
  16442. this.setEvent(event.browserEvent || event, freezeEvent);
  16443. }
  16444. },
  16445. setEvent: function(event, freezeEvent){
  16446. var me = this, button, options;
  16447. if (event == me || (event && event.browserEvent)) { // already wrapped
  16448. return event;
  16449. }
  16450. me.browserEvent = event;
  16451. if (event) {
  16452. // normalize buttons
  16453. button = event.button ? me.btnMap[event.button] : (event.which ? event.which - 1 : -1);
  16454. if (me.clickRe.test(event.type) && button == -1) {
  16455. button = 0;
  16456. }
  16457. options = {
  16458. type: event.type,
  16459. button: button,
  16460. shiftKey: event.shiftKey,
  16461. // mac metaKey behaves like ctrlKey
  16462. ctrlKey: event.ctrlKey || event.metaKey || false,
  16463. altKey: event.altKey,
  16464. // in getKey these will be normalized for the mac
  16465. keyCode: event.keyCode,
  16466. charCode: event.charCode,
  16467. // cache the targets for the delayed and or buffered events
  16468. target: Ext.EventManager.getTarget(event),
  16469. relatedTarget: Ext.EventManager.getRelatedTarget(event),
  16470. currentTarget: event.currentTarget,
  16471. xy: (freezeEvent ? me.getXY() : null)
  16472. };
  16473. } else {
  16474. options = {
  16475. button: -1,
  16476. shiftKey: false,
  16477. ctrlKey: false,
  16478. altKey: false,
  16479. keyCode: 0,
  16480. charCode: 0,
  16481. target: null,
  16482. xy: [0, 0]
  16483. };
  16484. }
  16485. Ext.apply(me, options);
  16486. return me;
  16487. },
  16488. /**
  16489. * Stop the event (preventDefault and stopPropagation)
  16490. */
  16491. stopEvent: function(){
  16492. this.stopPropagation();
  16493. this.preventDefault();
  16494. },
  16495. /**
  16496. * Prevents the browsers default handling of the event.
  16497. */
  16498. preventDefault: function(){
  16499. if (this.browserEvent) {
  16500. Ext.EventManager.preventDefault(this.browserEvent);
  16501. }
  16502. },
  16503. /**
  16504. * Cancels bubbling of the event.
  16505. */
  16506. stopPropagation: function(){
  16507. var browserEvent = this.browserEvent;
  16508. if (browserEvent) {
  16509. if (browserEvent.type == 'mousedown') {
  16510. Ext.EventManager.stoppedMouseDownEvent.fire(this);
  16511. }
  16512. Ext.EventManager.stopPropagation(browserEvent);
  16513. }
  16514. },
  16515. /**
  16516. * Gets the character code for the event.
  16517. * @return {Number}
  16518. */
  16519. getCharCode: function(){
  16520. return this.charCode || this.keyCode;
  16521. },
  16522. /**
  16523. * Returns a normalized keyCode for the event.
  16524. * @return {Number} The key code
  16525. */
  16526. getKey: function(){
  16527. return this.normalizeKey(this.keyCode || this.charCode);
  16528. },
  16529. /**
  16530. * Normalize key codes across browsers
  16531. * @private
  16532. * @param {Number} key The key code
  16533. * @return {Number} The normalized code
  16534. */
  16535. normalizeKey: function(key){
  16536. // can't feature detect this
  16537. return Ext.isWebKit ? (this.safariKeys[key] || key) : key;
  16538. },
  16539. /**
  16540. * Gets the x coordinate of the event.
  16541. * @return {Number}
  16542. * @deprecated 4.0 Replaced by {@link #getX}
  16543. */
  16544. getPageX: function(){
  16545. return this.getX();
  16546. },
  16547. /**
  16548. * Gets the y coordinate of the event.
  16549. * @return {Number}
  16550. * @deprecated 4.0 Replaced by {@link #getY}
  16551. */
  16552. getPageY: function(){
  16553. return this.getY();
  16554. },
  16555. /**
  16556. * Gets the x coordinate of the event.
  16557. * @return {Number}
  16558. */
  16559. getX: function() {
  16560. return this.getXY()[0];
  16561. },
  16562. /**
  16563. * Gets the y coordinate of the event.
  16564. * @return {Number}
  16565. */
  16566. getY: function() {
  16567. return this.getXY()[1];
  16568. },
  16569. /**
  16570. * Gets the page coordinates of the event.
  16571. * @return {Number[]} The xy values like [x, y]
  16572. */
  16573. getXY: function() {
  16574. if (!this.xy) {
  16575. // same for XY
  16576. this.xy = Ext.EventManager.getPageXY(this.browserEvent);
  16577. }
  16578. return this.xy;
  16579. },
  16580. /**
  16581. * Gets the target for the event.
  16582. * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
  16583. * @param {Number/HTMLElement} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
  16584. * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
  16585. * @return {HTMLElement}
  16586. */
  16587. getTarget : function(selector, maxDepth, returnEl){
  16588. if (selector) {
  16589. return Ext.fly(this.target).findParent(selector, maxDepth, returnEl);
  16590. }
  16591. return returnEl ? Ext.get(this.target) : this.target;
  16592. },
  16593. /**
  16594. * Gets the related target.
  16595. * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
  16596. * @param {Number/HTMLElement} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
  16597. * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
  16598. * @return {HTMLElement}
  16599. */
  16600. getRelatedTarget : function(selector, maxDepth, returnEl){
  16601. if (selector) {
  16602. return Ext.fly(this.relatedTarget).findParent(selector, maxDepth, returnEl);
  16603. }
  16604. return returnEl ? Ext.get(this.relatedTarget) : this.relatedTarget;
  16605. },
  16606. /**
  16607. * Correctly scales a given wheel delta.
  16608. * @param {Number} delta The delta value.
  16609. */
  16610. correctWheelDelta : function (delta) {
  16611. var scale = this.WHEEL_SCALE,
  16612. ret = Math.round(delta / scale);
  16613. if (!ret && delta) {
  16614. ret = (delta < 0) ? -1 : 1; // don't allow non-zero deltas to go to zero!
  16615. }
  16616. return ret;
  16617. },
  16618. /**
  16619. * Returns the mouse wheel deltas for this event.
  16620. * @return {Object} An object with "x" and "y" properties holding the mouse wheel deltas.
  16621. */
  16622. getWheelDeltas : function () {
  16623. var me = this,
  16624. event = me.browserEvent,
  16625. dx = 0, dy = 0; // the deltas
  16626. if (Ext.isDefined(event.wheelDeltaX)) { // WebKit has both dimensions
  16627. dx = event.wheelDeltaX;
  16628. dy = event.wheelDeltaY;
  16629. } else if (event.wheelDelta) { // old WebKit and IE
  16630. dy = event.wheelDelta;
  16631. } else if (event.detail) { // Gecko
  16632. dy = -event.detail; // gecko is backwards
  16633. // Gecko sometimes returns really big values if the user changes settings to
  16634. // scroll a whole page per scroll
  16635. if (dy > 100) {
  16636. dy = 3;
  16637. } else if (dy < -100) {
  16638. dy = -3;
  16639. }
  16640. // Firefox 3.1 adds an axis field to the event to indicate direction of
  16641. // scroll. See https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
  16642. if (Ext.isDefined(event.axis) && event.axis === event.HORIZONTAL_AXIS) {
  16643. dx = dy;
  16644. dy = 0;
  16645. }
  16646. }
  16647. return {
  16648. x: me.correctWheelDelta(dx),
  16649. y: me.correctWheelDelta(dy)
  16650. };
  16651. },
  16652. /**
  16653. * Normalizes mouse wheel y-delta across browsers. To get x-delta information, use
  16654. * {@link #getWheelDeltas} instead.
  16655. * @return {Number} The mouse wheel y-delta
  16656. */
  16657. getWheelDelta : function(){
  16658. var deltas = this.getWheelDeltas();
  16659. return deltas.y;
  16660. },
  16661. /**
  16662. * Returns true if the target of this event is a child of el. Unless the allowEl parameter is set, it will return false if if the target is el.
  16663. * Example usage:<pre><code>
  16664. // Handle click on any child of an element
  16665. Ext.getBody().on('click', function(e){
  16666. if(e.within('some-el')){
  16667. alert('Clicked on a child of some-el!');
  16668. }
  16669. });
  16670. // Handle click directly on an element, ignoring clicks on child nodes
  16671. Ext.getBody().on('click', function(e,t){
  16672. if((t.id == 'some-el') && !e.within(t, true)){
  16673. alert('Clicked directly on some-el!');
  16674. }
  16675. });
  16676. </code></pre>
  16677. * @param {String/HTMLElement/Ext.Element} el The id, DOM element or Ext.Element to check
  16678. * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
  16679. * @param {Boolean} allowEl (optional) true to also check if the passed element is the target or related target
  16680. * @return {Boolean}
  16681. */
  16682. within : function(el, related, allowEl){
  16683. if(el){
  16684. var t = related ? this.getRelatedTarget() : this.getTarget(),
  16685. result;
  16686. if (t) {
  16687. result = Ext.fly(el).contains(t);
  16688. if (!result && allowEl) {
  16689. result = t == Ext.getDom(el);
  16690. }
  16691. return result;
  16692. }
  16693. }
  16694. return false;
  16695. },
  16696. /**
  16697. * Checks if the key pressed was a "navigation" key
  16698. * @return {Boolean} True if the press is a navigation keypress
  16699. */
  16700. isNavKeyPress : function(){
  16701. var me = this,
  16702. k = this.normalizeKey(me.keyCode);
  16703. return (k >= 33 && k <= 40) || // Page Up/Down, End, Home, Left, Up, Right, Down
  16704. k == me.RETURN ||
  16705. k == me.TAB ||
  16706. k == me.ESC;
  16707. },
  16708. /**
  16709. * Checks if the key pressed was a "special" key
  16710. * @return {Boolean} True if the press is a special keypress
  16711. */
  16712. isSpecialKey : function(){
  16713. var k = this.normalizeKey(this.keyCode);
  16714. return (this.type == 'keypress' && this.ctrlKey) ||
  16715. this.isNavKeyPress() ||
  16716. (k == this.BACKSPACE) || // Backspace
  16717. (k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock
  16718. (k >= 44 && k <= 46); // Print Screen, Insert, Delete
  16719. },
  16720. /**
  16721. * Returns a point object that consists of the object coordinates.
  16722. * @return {Ext.util.Point} point
  16723. */
  16724. getPoint : function(){
  16725. var xy = this.getXY();
  16726. return new Ext.util.Point(xy[0], xy[1]);
  16727. },
  16728. /**
  16729. * Returns true if the control, meta, shift or alt key was pressed during this event.
  16730. * @return {Boolean}
  16731. */
  16732. hasModifier : function(){
  16733. return this.ctrlKey || this.altKey || this.shiftKey || this.metaKey;
  16734. },
  16735. /**
  16736. * Injects a DOM event using the data in this object and (optionally) a new target.
  16737. * This is a low-level technique and not likely to be used by application code. The
  16738. * currently supported event types are:
  16739. * <p><b>HTMLEvents</b></p>
  16740. * <ul>
  16741. * <li>load</li>
  16742. * <li>unload</li>
  16743. * <li>select</li>
  16744. * <li>change</li>
  16745. * <li>submit</li>
  16746. * <li>reset</li>
  16747. * <li>resize</li>
  16748. * <li>scroll</li>
  16749. * </ul>
  16750. * <p><b>MouseEvents</b></p>
  16751. * <ul>
  16752. * <li>click</li>
  16753. * <li>dblclick</li>
  16754. * <li>mousedown</li>
  16755. * <li>mouseup</li>
  16756. * <li>mouseover</li>
  16757. * <li>mousemove</li>
  16758. * <li>mouseout</li>
  16759. * </ul>
  16760. * <p><b>UIEvents</b></p>
  16761. * <ul>
  16762. * <li>focusin</li>
  16763. * <li>focusout</li>
  16764. * <li>activate</li>
  16765. * <li>focus</li>
  16766. * <li>blur</li>
  16767. * </ul>
  16768. * @param {Ext.Element/HTMLElement} target (optional) If specified, the target for the event. This
  16769. * is likely to be used when relaying a DOM event. If not specified, {@link #getTarget}
  16770. * is used to determine the target.
  16771. */
  16772. injectEvent: (function () {
  16773. var API,
  16774. dispatchers = {}, // keyed by event type (e.g., 'mousedown')
  16775. crazyIEButtons;
  16776. // Good reference: http://developer.yahoo.com/yui/docs/UserAction.js.html
  16777. // IE9 has createEvent, but this code causes major problems with htmleditor (it
  16778. // blocks all mouse events and maybe more). TODO
  16779. if (!Ext.isIE && document.createEvent) { // if (DOM compliant)
  16780. API = {
  16781. createHtmlEvent: function (doc, type, bubbles, cancelable) {
  16782. var event = doc.createEvent('HTMLEvents');
  16783. event.initEvent(type, bubbles, cancelable);
  16784. return event;
  16785. },
  16786. createMouseEvent: function (doc, type, bubbles, cancelable, detail,
  16787. clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
  16788. button, relatedTarget) {
  16789. var event = doc.createEvent('MouseEvents'),
  16790. view = doc.defaultView || window;
  16791. if (event.initMouseEvent) {
  16792. event.initMouseEvent(type, bubbles, cancelable, view, detail,
  16793. clientX, clientY, clientX, clientY, ctrlKey, altKey,
  16794. shiftKey, metaKey, button, relatedTarget);
  16795. } else { // old Safari
  16796. event = doc.createEvent('UIEvents');
  16797. event.initEvent(type, bubbles, cancelable);
  16798. event.view = view;
  16799. event.detail = detail;
  16800. event.screenX = clientX;
  16801. event.screenY = clientY;
  16802. event.clientX = clientX;
  16803. event.clientY = clientY;
  16804. event.ctrlKey = ctrlKey;
  16805. event.altKey = altKey;
  16806. event.metaKey = metaKey;
  16807. event.shiftKey = shiftKey;
  16808. event.button = button;
  16809. event.relatedTarget = relatedTarget;
  16810. }
  16811. return event;
  16812. },
  16813. createUIEvent: function (doc, type, bubbles, cancelable, detail) {
  16814. var event = doc.createEvent('UIEvents'),
  16815. view = doc.defaultView || window;
  16816. event.initUIEvent(type, bubbles, cancelable, view, detail);
  16817. return event;
  16818. },
  16819. fireEvent: function (target, type, event) {
  16820. target.dispatchEvent(event);
  16821. },
  16822. fixTarget: function (target) {
  16823. // Safari3 doesn't have window.dispatchEvent()
  16824. if (target == window && !target.dispatchEvent) {
  16825. return document;
  16826. }
  16827. return target;
  16828. }
  16829. };
  16830. } else if (document.createEventObject) { // else if (IE)
  16831. crazyIEButtons = { 0: 1, 1: 4, 2: 2 };
  16832. API = {
  16833. createHtmlEvent: function (doc, type, bubbles, cancelable) {
  16834. var event = doc.createEventObject();
  16835. event.bubbles = bubbles;
  16836. event.cancelable = cancelable;
  16837. return event;
  16838. },
  16839. createMouseEvent: function (doc, type, bubbles, cancelable, detail,
  16840. clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
  16841. button, relatedTarget) {
  16842. var event = doc.createEventObject();
  16843. event.bubbles = bubbles;
  16844. event.cancelable = cancelable;
  16845. event.detail = detail;
  16846. event.screenX = clientX;
  16847. event.screenY = clientY;
  16848. event.clientX = clientX;
  16849. event.clientY = clientY;
  16850. event.ctrlKey = ctrlKey;
  16851. event.altKey = altKey;
  16852. event.shiftKey = shiftKey;
  16853. event.metaKey = metaKey;
  16854. event.button = crazyIEButtons[button] || button;
  16855. event.relatedTarget = relatedTarget; // cannot assign to/fromElement
  16856. return event;
  16857. },
  16858. createUIEvent: function (doc, type, bubbles, cancelable, detail) {
  16859. var event = doc.createEventObject();
  16860. event.bubbles = bubbles;
  16861. event.cancelable = cancelable;
  16862. return event;
  16863. },
  16864. fireEvent: function (target, type, event) {
  16865. target.fireEvent('on' + type, event);
  16866. },
  16867. fixTarget: function (target) {
  16868. if (target == document) {
  16869. // IE6,IE7 thinks window==document and doesn't have window.fireEvent()
  16870. // IE6,IE7 cannot properly call document.fireEvent()
  16871. return document.documentElement;
  16872. }
  16873. return target;
  16874. }
  16875. };
  16876. }
  16877. //----------------
  16878. // HTMLEvents
  16879. Ext.Object.each({
  16880. load: [false, false],
  16881. unload: [false, false],
  16882. select: [true, false],
  16883. change: [true, false],
  16884. submit: [true, true],
  16885. reset: [true, false],
  16886. resize: [true, false],
  16887. scroll: [true, false]
  16888. },
  16889. function (name, value) {
  16890. var bubbles = value[0], cancelable = value[1];
  16891. dispatchers[name] = function (targetEl, srcEvent) {
  16892. var e = API.createHtmlEvent(name, bubbles, cancelable);
  16893. API.fireEvent(targetEl, name, e);
  16894. };
  16895. });
  16896. //----------------
  16897. // MouseEvents
  16898. function createMouseEventDispatcher (type, detail) {
  16899. var cancelable = (type != 'mousemove');
  16900. return function (targetEl, srcEvent) {
  16901. var xy = srcEvent.getXY(),
  16902. e = API.createMouseEvent(targetEl.ownerDocument, type, true, cancelable,
  16903. detail, xy[0], xy[1], srcEvent.ctrlKey, srcEvent.altKey,
  16904. srcEvent.shiftKey, srcEvent.metaKey, srcEvent.button,
  16905. srcEvent.relatedTarget);
  16906. API.fireEvent(targetEl, type, e);
  16907. };
  16908. }
  16909. Ext.each(['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout'],
  16910. function (eventName) {
  16911. dispatchers[eventName] = createMouseEventDispatcher(eventName, 1);
  16912. });
  16913. //----------------
  16914. // UIEvents
  16915. Ext.Object.each({
  16916. focusin: [true, false],
  16917. focusout: [true, false],
  16918. activate: [true, true],
  16919. focus: [false, false],
  16920. blur: [false, false]
  16921. },
  16922. function (name, value) {
  16923. var bubbles = value[0], cancelable = value[1];
  16924. dispatchers[name] = function (targetEl, srcEvent) {
  16925. var e = API.createUIEvent(targetEl.ownerDocument, name, bubbles, cancelable, 1);
  16926. API.fireEvent(targetEl, name, e);
  16927. };
  16928. });
  16929. //---------
  16930. if (!API) {
  16931. // not even sure what ancient browsers fall into this category...
  16932. dispatchers = {}; // never mind all those we just built :P
  16933. API = {
  16934. fixTarget: function (t) {
  16935. return t;
  16936. }
  16937. };
  16938. }
  16939. function cannotInject (target, srcEvent) {
  16940. // TODO log something
  16941. }
  16942. return function (target) {
  16943. var me = this,
  16944. dispatcher = dispatchers[me.type] || cannotInject,
  16945. t = target ? (target.dom || target) : me.getTarget();
  16946. t = API.fixTarget(t);
  16947. dispatcher(t, me);
  16948. };
  16949. }()) // call to produce method
  16950. }, function() {
  16951. Ext.EventObject = new Ext.EventObjectImpl();
  16952. });
  16953. //@tag dom,core
  16954. //@require ../EventObject.js
  16955. /**
  16956. * @class Ext.dom.AbstractQuery
  16957. * @private
  16958. */
  16959. Ext.define('Ext.dom.AbstractQuery', {
  16960. /**
  16961. * Selects a group of elements.
  16962. * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
  16963. * @param {HTMLElement/String} [root] The start of the query (defaults to document).
  16964. * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are
  16965. * no matches, and empty Array is returned.
  16966. */
  16967. select: function(q, root) {
  16968. var results = [],
  16969. nodes,
  16970. i,
  16971. j,
  16972. qlen,
  16973. nlen;
  16974. root = root || document;
  16975. if (typeof root == 'string') {
  16976. root = document.getElementById(root);
  16977. }
  16978. q = q.split(",");
  16979. for (i = 0,qlen = q.length; i < qlen; i++) {
  16980. if (typeof q[i] == 'string') {
  16981. //support for node attribute selection
  16982. if (typeof q[i][0] == '@') {
  16983. nodes = root.getAttributeNode(q[i].substring(1));
  16984. results.push(nodes);
  16985. } else {
  16986. nodes = root.querySelectorAll(q[i]);
  16987. for (j = 0,nlen = nodes.length; j < nlen; j++) {
  16988. results.push(nodes[j]);
  16989. }
  16990. }
  16991. }
  16992. }
  16993. return results;
  16994. },
  16995. /**
  16996. * Selects a single element.
  16997. * @param {String} selector The selector/xpath query
  16998. * @param {HTMLElement/String} [root] The start of the query (defaults to document).
  16999. * @return {HTMLElement} The DOM element which matched the selector.
  17000. */
  17001. selectNode: function(q, root) {
  17002. return this.select(q, root)[0];
  17003. },
  17004. /**
  17005. * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
  17006. * @param {String/HTMLElement/Array} el An element id, element or array of elements
  17007. * @param {String} selector The simple selector to test
  17008. * @return {Boolean}
  17009. */
  17010. is: function(el, q) {
  17011. if (typeof el == "string") {
  17012. el = document.getElementById(el);
  17013. }
  17014. return this.select(q).indexOf(el) !== -1;
  17015. }
  17016. });
  17017. //@tag dom,core
  17018. //@require AbstractQuery.js
  17019. /**
  17020. * Abstract base class for {@link Ext.dom.Helper}.
  17021. * @private
  17022. */
  17023. Ext.define('Ext.dom.AbstractHelper', {
  17024. emptyTags : /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
  17025. confRe : /(?:tag|children|cn|html|tpl|tplData)$/i,
  17026. endRe : /end/i,
  17027. // Since cls & for are reserved words, we need to transform them
  17028. attributeTransform: { cls : 'class', htmlFor : 'for' },
  17029. closeTags: {},
  17030. decamelizeName : (function () {
  17031. var camelCaseRe = /([a-z])([A-Z])/g,
  17032. cache = {};
  17033. function decamel (match, p1, p2) {
  17034. return p1 + '-' + p2.toLowerCase();
  17035. }
  17036. return function (s) {
  17037. return cache[s] || (cache[s] = s.replace(camelCaseRe, decamel));
  17038. };
  17039. }()),
  17040. generateMarkup: function(spec, buffer) {
  17041. var me = this,
  17042. attr, val, tag, i, closeTags;
  17043. if (typeof spec == "string") {
  17044. buffer.push(spec);
  17045. } else if (Ext.isArray(spec)) {
  17046. for (i = 0; i < spec.length; i++) {
  17047. if (spec[i]) {
  17048. me.generateMarkup(spec[i], buffer);
  17049. }
  17050. }
  17051. } else {
  17052. tag = spec.tag || 'div';
  17053. buffer.push('<', tag);
  17054. for (attr in spec) {
  17055. if (spec.hasOwnProperty(attr)) {
  17056. val = spec[attr];
  17057. if (!me.confRe.test(attr)) {
  17058. if (typeof val == "object") {
  17059. buffer.push(' ', attr, '="');
  17060. me.generateStyles(val, buffer).push('"');
  17061. } else {
  17062. buffer.push(' ', me.attributeTransform[attr] || attr, '="', val, '"');
  17063. }
  17064. }
  17065. }
  17066. }
  17067. // Now either just close the tag or try to add children and close the tag.
  17068. if (me.emptyTags.test(tag)) {
  17069. buffer.push('/>');
  17070. } else {
  17071. buffer.push('>');
  17072. // Apply the tpl html, and cn specifications
  17073. if ((val = spec.tpl)) {
  17074. val.applyOut(spec.tplData, buffer);
  17075. }
  17076. if ((val = spec.html)) {
  17077. buffer.push(val);
  17078. }
  17079. if ((val = spec.cn || spec.children)) {
  17080. me.generateMarkup(val, buffer);
  17081. }
  17082. // we generate a lot of close tags, so cache them rather than push 3 parts
  17083. closeTags = me.closeTags;
  17084. buffer.push(closeTags[tag] || (closeTags[tag] = '</' + tag + '>'));
  17085. }
  17086. }
  17087. return buffer;
  17088. },
  17089. /**
  17090. * Converts the styles from the given object to text. The styles are CSS style names
  17091. * with their associated value.
  17092. *
  17093. * The basic form of this method returns a string:
  17094. *
  17095. * var s = Ext.DomHelper.generateStyles({
  17096. * backgroundColor: 'red'
  17097. * });
  17098. *
  17099. * // s = 'background-color:red;'
  17100. *
  17101. * Alternatively, this method can append to an output array.
  17102. *
  17103. * var buf = [];
  17104. *
  17105. * ...
  17106. *
  17107. * Ext.DomHelper.generateStyles({
  17108. * backgroundColor: 'red'
  17109. * }, buf);
  17110. *
  17111. * In this case, the style text is pushed on to the array and the array is returned.
  17112. *
  17113. * @param {Object} styles The object describing the styles.
  17114. * @param {String[]} [buffer] The output buffer.
  17115. * @return {String/String[]} If buffer is passed, it is returned. Otherwise the style
  17116. * string is returned.
  17117. */
  17118. generateStyles: function (styles, buffer) {
  17119. var a = buffer || [],
  17120. name;
  17121. for (name in styles) {
  17122. if (styles.hasOwnProperty(name)) {
  17123. a.push(this.decamelizeName(name), ':', styles[name], ';');
  17124. }
  17125. }
  17126. return buffer || a.join('');
  17127. },
  17128. /**
  17129. * Returns the markup for the passed Element(s) config.
  17130. * @param {Object} spec The DOM object spec (and children)
  17131. * @return {String}
  17132. */
  17133. markup: function(spec) {
  17134. if (typeof spec == "string") {
  17135. return spec;
  17136. }
  17137. var buf = this.generateMarkup(spec, []);
  17138. return buf.join('');
  17139. },
  17140. /**
  17141. * Applies a style specification to an element.
  17142. * @param {String/HTMLElement} el The element to apply styles to
  17143. * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
  17144. * a function which returns such a specification.
  17145. */
  17146. applyStyles: function(el, styles) {
  17147. if (styles) {
  17148. var i = 0,
  17149. len,
  17150. style;
  17151. el = Ext.fly(el);
  17152. if (typeof styles == 'function') {
  17153. styles = styles.call();
  17154. }
  17155. if (typeof styles == 'string'){
  17156. styles = Ext.util.Format.trim(styles).split(/\s*(?::|;)\s*/);
  17157. for(len = styles.length; i < len;){
  17158. el.setStyle(styles[i++], styles[i++]);
  17159. }
  17160. } else if (Ext.isObject(styles)) {
  17161. el.setStyle(styles);
  17162. }
  17163. }
  17164. },
  17165. /**
  17166. * Inserts an HTML fragment into the DOM.
  17167. * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
  17168. *
  17169. * For example take the following HTML: `<div>Contents</div>`
  17170. *
  17171. * Using different `where` values inserts element to the following places:
  17172. *
  17173. * - beforeBegin: `<HERE><div>Contents</div>`
  17174. * - afterBegin: `<div><HERE>Contents</div>`
  17175. * - beforeEnd: `<div>Contents<HERE></div>`
  17176. * - afterEnd: `<div>Contents</div><HERE>`
  17177. *
  17178. * @param {HTMLElement/TextNode} el The context element
  17179. * @param {String} html The HTML fragment
  17180. * @return {HTMLElement} The new node
  17181. */
  17182. insertHtml: function(where, el, html) {
  17183. var hash = {},
  17184. hashVal,
  17185. setStart,
  17186. range,
  17187. frag,
  17188. rangeEl,
  17189. rs;
  17190. where = where.toLowerCase();
  17191. // add these here because they are used in both branches of the condition.
  17192. hash['beforebegin'] = ['BeforeBegin', 'previousSibling'];
  17193. hash['afterend'] = ['AfterEnd', 'nextSibling'];
  17194. range = el.ownerDocument.createRange();
  17195. setStart = 'setStart' + (this.endRe.test(where) ? 'After' : 'Before');
  17196. if (hash[where]) {
  17197. range[setStart](el);
  17198. frag = range.createContextualFragment(html);
  17199. el.parentNode.insertBefore(frag, where == 'beforebegin' ? el : el.nextSibling);
  17200. return el[(where == 'beforebegin' ? 'previous' : 'next') + 'Sibling'];
  17201. }
  17202. else {
  17203. rangeEl = (where == 'afterbegin' ? 'first' : 'last') + 'Child';
  17204. if (el.firstChild) {
  17205. range[setStart](el[rangeEl]);
  17206. frag = range.createContextualFragment(html);
  17207. if (where == 'afterbegin') {
  17208. el.insertBefore(frag, el.firstChild);
  17209. }
  17210. else {
  17211. el.appendChild(frag);
  17212. }
  17213. }
  17214. else {
  17215. el.innerHTML = html;
  17216. }
  17217. return el[rangeEl];
  17218. }
  17219. throw 'Illegal insertion point -> "' + where + '"';
  17220. },
  17221. /**
  17222. * Creates new DOM element(s) and inserts them before el.
  17223. * @param {String/HTMLElement/Ext.Element} el The context element
  17224. * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
  17225. * @param {Boolean} [returnElement] true to return a Ext.Element
  17226. * @return {HTMLElement/Ext.Element} The new node
  17227. */
  17228. insertBefore: function(el, o, returnElement) {
  17229. return this.doInsert(el, o, returnElement, 'beforebegin');
  17230. },
  17231. /**
  17232. * Creates new DOM element(s) and inserts them after el.
  17233. * @param {String/HTMLElement/Ext.Element} el The context element
  17234. * @param {Object} o The DOM object spec (and children)
  17235. * @param {Boolean} [returnElement] true to return a Ext.Element
  17236. * @return {HTMLElement/Ext.Element} The new node
  17237. */
  17238. insertAfter: function(el, o, returnElement) {
  17239. return this.doInsert(el, o, returnElement, 'afterend', 'nextSibling');
  17240. },
  17241. /**
  17242. * Creates new DOM element(s) and inserts them as the first child of el.
  17243. * @param {String/HTMLElement/Ext.Element} el The context element
  17244. * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
  17245. * @param {Boolean} [returnElement] true to return a Ext.Element
  17246. * @return {HTMLElement/Ext.Element} The new node
  17247. */
  17248. insertFirst: function(el, o, returnElement) {
  17249. return this.doInsert(el, o, returnElement, 'afterbegin', 'firstChild');
  17250. },
  17251. /**
  17252. * Creates new DOM element(s) and appends them to el.
  17253. * @param {String/HTMLElement/Ext.Element} el The context element
  17254. * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
  17255. * @param {Boolean} [returnElement] true to return a Ext.Element
  17256. * @return {HTMLElement/Ext.Element} The new node
  17257. */
  17258. append: function(el, o, returnElement) {
  17259. return this.doInsert(el, o, returnElement, 'beforeend', '', true);
  17260. },
  17261. /**
  17262. * Creates new DOM element(s) and overwrites the contents of el with them.
  17263. * @param {String/HTMLElement/Ext.Element} el The context element
  17264. * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
  17265. * @param {Boolean} [returnElement] true to return a Ext.Element
  17266. * @return {HTMLElement/Ext.Element} The new node
  17267. */
  17268. overwrite: function(el, o, returnElement) {
  17269. el = Ext.getDom(el);
  17270. el.innerHTML = this.markup(o);
  17271. return returnElement ? Ext.get(el.firstChild) : el.firstChild;
  17272. },
  17273. doInsert: function(el, o, returnElement, pos, sibling, append) {
  17274. var newNode = this.insertHtml(pos, Ext.getDom(el), this.markup(o));
  17275. return returnElement ? Ext.get(newNode, true) : newNode;
  17276. }
  17277. });
  17278. //@tag dom,core
  17279. //@require AbstractHelper.js
  17280. //@require Ext.Supports
  17281. //@require Ext.EventManager
  17282. //@define Ext.dom.AbstractElement
  17283. /**
  17284. * @class Ext.dom.AbstractElement
  17285. * @extend Ext.Base
  17286. * @private
  17287. */
  17288. (function() {
  17289. var document = window.document,
  17290. trimRe = /^\s+|\s+$/g,
  17291. whitespaceRe = /\s/;
  17292. if (!Ext.cache){
  17293. Ext.cache = {};
  17294. }
  17295. Ext.define('Ext.dom.AbstractElement', {
  17296. inheritableStatics: {
  17297. /**
  17298. * Retrieves Ext.dom.Element objects. {@link Ext#get} is alias for {@link Ext.dom.Element#get}.
  17299. *
  17300. * **This method does not retrieve {@link Ext.Component Component}s.** This method retrieves Ext.dom.Element
  17301. * objects which encapsulate DOM elements. To retrieve a Component by its ID, use {@link Ext.ComponentManager#get}.
  17302. *
  17303. * Uses simple caching to consistently return the same object. Automatically fixes if an object was recreated with
  17304. * the same id via AJAX or DOM.
  17305. *
  17306. * @param {String/HTMLElement/Ext.Element} el The id of the node, a DOM Node or an existing Element.
  17307. * @return {Ext.dom.Element} The Element object (or null if no matching element was found)
  17308. * @static
  17309. * @inheritable
  17310. */
  17311. get: function(el) {
  17312. var me = this,
  17313. El = Ext.dom.Element,
  17314. cacheItem,
  17315. extEl,
  17316. dom,
  17317. id;
  17318. if (!el) {
  17319. return null;
  17320. }
  17321. if (typeof el == "string") { // element id
  17322. if (el == Ext.windowId) {
  17323. return El.get(window);
  17324. } else if (el == Ext.documentId) {
  17325. return El.get(document);
  17326. }
  17327. cacheItem = Ext.cache[el];
  17328. // This code is here to catch the case where we've got a reference to a document of an iframe
  17329. // It getElementById will fail because it's not part of the document, so if we're skipping
  17330. // GC it means it's a window/document object that isn't the default window/document, which we have
  17331. // already handled above
  17332. if (cacheItem && cacheItem.skipGarbageCollection) {
  17333. extEl = cacheItem.el;
  17334. return extEl;
  17335. }
  17336. if (!(dom = document.getElementById(el))) {
  17337. return null;
  17338. }
  17339. if (cacheItem && cacheItem.el) {
  17340. extEl = Ext.updateCacheEntry(cacheItem, dom).el;
  17341. } else {
  17342. // Force new element if there's a cache but no el attached
  17343. extEl = new El(dom, !!cacheItem);
  17344. }
  17345. return extEl;
  17346. } else if (el.tagName) { // dom element
  17347. if (!(id = el.id)) {
  17348. id = Ext.id(el);
  17349. }
  17350. cacheItem = Ext.cache[id];
  17351. if (cacheItem && cacheItem.el) {
  17352. extEl = Ext.updateCacheEntry(cacheItem, el).el;
  17353. } else {
  17354. // Force new element if there's a cache but no el attached
  17355. extEl = new El(el, !!cacheItem);
  17356. }
  17357. return extEl;
  17358. } else if (el instanceof me) {
  17359. if (el != me.docEl && el != me.winEl) {
  17360. id = el.id;
  17361. // refresh dom element in case no longer valid,
  17362. // catch case where it hasn't been appended
  17363. cacheItem = Ext.cache[id];
  17364. if (cacheItem) {
  17365. Ext.updateCacheEntry(cacheItem, document.getElementById(id) || el.dom);
  17366. }
  17367. }
  17368. return el;
  17369. } else if (el.isComposite) {
  17370. return el;
  17371. } else if (Ext.isArray(el)) {
  17372. return me.select(el);
  17373. } else if (el === document) {
  17374. // create a bogus element object representing the document object
  17375. if (!me.docEl) {
  17376. me.docEl = Ext.Object.chain(El.prototype);
  17377. me.docEl.dom = document;
  17378. me.docEl.id = Ext.id(document);
  17379. me.addToCache(me.docEl);
  17380. }
  17381. return me.docEl;
  17382. } else if (el === window) {
  17383. if (!me.winEl) {
  17384. me.winEl = Ext.Object.chain(El.prototype);
  17385. me.winEl.dom = window;
  17386. me.winEl.id = Ext.id(window);
  17387. me.addToCache(me.winEl);
  17388. }
  17389. return me.winEl;
  17390. }
  17391. return null;
  17392. },
  17393. addToCache: function(el, id) {
  17394. if (el) {
  17395. Ext.addCacheEntry(id, el);
  17396. }
  17397. return el;
  17398. },
  17399. addMethods: function() {
  17400. this.override.apply(this, arguments);
  17401. },
  17402. /**
  17403. * <p>Returns an array of unique class names based upon the input strings, or string arrays.</p>
  17404. * <p>The number of parameters is unlimited.</p>
  17405. * <p>Example</p><code><pre>
  17406. // Add x-invalid and x-mandatory classes, do not duplicate
  17407. myElement.dom.className = Ext.core.Element.mergeClsList(this.initialClasses, 'x-invalid x-mandatory');
  17408. </pre></code>
  17409. * @param {Mixed} clsList1 A string of class names, or an array of class names.
  17410. * @param {Mixed} clsList2 A string of class names, or an array of class names.
  17411. * @return {Array} An array of strings representing remaining unique, merged class names. If class names were added to the first list, the <code>changed</code> property will be <code>true</code>.
  17412. * @static
  17413. * @inheritable
  17414. */
  17415. mergeClsList: function() {
  17416. var clsList, clsHash = {},
  17417. i, length, j, listLength, clsName, result = [],
  17418. changed = false;
  17419. for (i = 0, length = arguments.length; i < length; i++) {
  17420. clsList = arguments[i];
  17421. if (Ext.isString(clsList)) {
  17422. clsList = clsList.replace(trimRe, '').split(whitespaceRe);
  17423. }
  17424. if (clsList) {
  17425. for (j = 0, listLength = clsList.length; j < listLength; j++) {
  17426. clsName = clsList[j];
  17427. if (!clsHash[clsName]) {
  17428. if (i) {
  17429. changed = true;
  17430. }
  17431. clsHash[clsName] = true;
  17432. }
  17433. }
  17434. }
  17435. }
  17436. for (clsName in clsHash) {
  17437. result.push(clsName);
  17438. }
  17439. result.changed = changed;
  17440. return result;
  17441. },
  17442. /**
  17443. * <p>Returns an array of unique class names deom the first parameter with all class names
  17444. * from the second parameter removed.</p>
  17445. * <p>Example</p><code><pre>
  17446. // Remove x-invalid and x-mandatory classes if present.
  17447. myElement.dom.className = Ext.core.Element.removeCls(this.initialClasses, 'x-invalid x-mandatory');
  17448. </pre></code>
  17449. * @param {Mixed} existingClsList A string of class names, or an array of class names.
  17450. * @param {Mixed} removeClsList A string of class names, or an array of class names to remove from <code>existingClsList</code>.
  17451. * @return {Array} An array of strings representing remaining class names. If class names were removed, the <code>changed</code> property will be <code>true</code>.
  17452. * @static
  17453. * @inheritable
  17454. */
  17455. removeCls: function(existingClsList, removeClsList) {
  17456. var clsHash = {},
  17457. i, length, clsName, result = [],
  17458. changed = false;
  17459. if (existingClsList) {
  17460. if (Ext.isString(existingClsList)) {
  17461. existingClsList = existingClsList.replace(trimRe, '').split(whitespaceRe);
  17462. }
  17463. for (i = 0, length = existingClsList.length; i < length; i++) {
  17464. clsHash[existingClsList[i]] = true;
  17465. }
  17466. }
  17467. if (removeClsList) {
  17468. if (Ext.isString(removeClsList)) {
  17469. removeClsList = removeClsList.split(whitespaceRe);
  17470. }
  17471. for (i = 0, length = removeClsList.length; i < length; i++) {
  17472. clsName = removeClsList[i];
  17473. if (clsHash[clsName]) {
  17474. changed = true;
  17475. delete clsHash[clsName];
  17476. }
  17477. }
  17478. }
  17479. for (clsName in clsHash) {
  17480. result.push(clsName);
  17481. }
  17482. result.changed = changed;
  17483. return result;
  17484. },
  17485. /**
  17486. * @property
  17487. * Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}.
  17488. * Use the CSS 'visibility' property to hide the element.
  17489. *
  17490. * Note that in this mode, {@link Ext.dom.Element#isVisible isVisible} may return true
  17491. * for an element even though it actually has a parent element that is hidden. For this
  17492. * reason, and in most cases, using the {@link #OFFSETS} mode is a better choice.
  17493. * @static
  17494. * @inheritable
  17495. */
  17496. VISIBILITY: 1,
  17497. /**
  17498. * @property
  17499. * Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}.
  17500. * Use the CSS 'display' property to hide the element.
  17501. * @static
  17502. * @inheritable
  17503. */
  17504. DISPLAY: 2,
  17505. /**
  17506. * @property
  17507. * Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}.
  17508. * Use CSS absolute positioning and top/left offsets to hide the element.
  17509. * @static
  17510. * @inheritable
  17511. */
  17512. OFFSETS: 3,
  17513. /**
  17514. * @property
  17515. * Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}.
  17516. * Add or remove the {@link Ext.Layer#visibilityCls} class to hide the element.
  17517. * @static
  17518. * @inheritable
  17519. */
  17520. ASCLASS: 4
  17521. },
  17522. constructor: function(element, forceNew) {
  17523. var me = this,
  17524. dom = typeof element == 'string'
  17525. ? document.getElementById(element)
  17526. : element,
  17527. id;
  17528. if (!dom) {
  17529. return null;
  17530. }
  17531. id = dom.id;
  17532. if (!forceNew && id && Ext.cache[id]) {
  17533. // element object already exists
  17534. return Ext.cache[id].el;
  17535. }
  17536. /**
  17537. * @property {HTMLElement} dom
  17538. * The DOM element
  17539. */
  17540. me.dom = dom;
  17541. /**
  17542. * @property {String} id
  17543. * The DOM element ID
  17544. */
  17545. me.id = id || Ext.id(dom);
  17546. me.self.addToCache(me);
  17547. },
  17548. /**
  17549. * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
  17550. * @param {Object} o The object with the attributes
  17551. * @param {Boolean} [useSet=true] false to override the default setAttribute to use expandos.
  17552. * @return {Ext.dom.Element} this
  17553. */
  17554. set: function(o, useSet) {
  17555. var el = this.dom,
  17556. attr,
  17557. value;
  17558. for (attr in o) {
  17559. if (o.hasOwnProperty(attr)) {
  17560. value = o[attr];
  17561. if (attr == 'style') {
  17562. this.applyStyles(value);
  17563. }
  17564. else if (attr == 'cls') {
  17565. el.className = value;
  17566. }
  17567. else if (useSet !== false) {
  17568. if (value === undefined) {
  17569. el.removeAttribute(attr);
  17570. } else {
  17571. el.setAttribute(attr, value);
  17572. }
  17573. }
  17574. else {
  17575. el[attr] = value;
  17576. }
  17577. }
  17578. }
  17579. return this;
  17580. },
  17581. /**
  17582. * @property {String} defaultUnit
  17583. * The default unit to append to CSS values where a unit isn't provided.
  17584. */
  17585. defaultUnit: "px",
  17586. /**
  17587. * Returns true if this element matches the passed simple selector (e.g. div.some-class or span:first-child)
  17588. * @param {String} selector The simple selector to test
  17589. * @return {Boolean} True if this element matches the selector, else false
  17590. */
  17591. is: function(simpleSelector) {
  17592. return Ext.DomQuery.is(this.dom, simpleSelector);
  17593. },
  17594. /**
  17595. * Returns the value of the "value" attribute
  17596. * @param {Boolean} asNumber true to parse the value as a number
  17597. * @return {String/Number}
  17598. */
  17599. getValue: function(asNumber) {
  17600. var val = this.dom.value;
  17601. return asNumber ? parseInt(val, 10) : val;
  17602. },
  17603. /**
  17604. * Removes this element's dom reference. Note that event and cache removal is handled at {@link Ext#removeNode
  17605. * Ext.removeNode}
  17606. */
  17607. remove: function() {
  17608. var me = this,
  17609. dom = me.dom;
  17610. if (dom) {
  17611. Ext.removeNode(dom);
  17612. delete me.dom;
  17613. }
  17614. },
  17615. /**
  17616. * Returns true if this element is an ancestor of the passed element
  17617. * @param {HTMLElement/String} el The element to check
  17618. * @return {Boolean} True if this element is an ancestor of el, else false
  17619. */
  17620. contains: function(el) {
  17621. if (!el) {
  17622. return false;
  17623. }
  17624. var me = this,
  17625. dom = el.dom || el;
  17626. // we need el-contains-itself logic here because isAncestor does not do that:
  17627. return (dom === me.dom) || Ext.dom.AbstractElement.isAncestor(me.dom, dom);
  17628. },
  17629. /**
  17630. * Returns the value of an attribute from the element's underlying DOM node.
  17631. * @param {String} name The attribute name
  17632. * @param {String} [namespace] The namespace in which to look for the attribute
  17633. * @return {String} The attribute value
  17634. */
  17635. getAttribute: function(name, ns) {
  17636. var dom = this.dom;
  17637. return dom.getAttributeNS(ns, name) || dom.getAttribute(ns + ":" + name) || dom.getAttribute(name) || dom[name];
  17638. },
  17639. /**
  17640. * Update the innerHTML of this element
  17641. * @param {String} html The new HTML
  17642. * @return {Ext.dom.Element} this
  17643. */
  17644. update: function(html) {
  17645. if (this.dom) {
  17646. this.dom.innerHTML = html;
  17647. }
  17648. return this;
  17649. },
  17650. /**
  17651. * Set the innerHTML of this element
  17652. * @param {String} html The new HTML
  17653. * @return {Ext.Element} this
  17654. */
  17655. setHTML: function(html) {
  17656. if(this.dom) {
  17657. this.dom.innerHTML = html;
  17658. }
  17659. return this;
  17660. },
  17661. /**
  17662. * Returns the innerHTML of an Element or an empty string if the element's
  17663. * dom no longer exists.
  17664. */
  17665. getHTML: function() {
  17666. return this.dom ? this.dom.innerHTML : '';
  17667. },
  17668. /**
  17669. * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
  17670. * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
  17671. * @return {Ext.Element} this
  17672. */
  17673. hide: function() {
  17674. this.setVisible(false);
  17675. return this;
  17676. },
  17677. /**
  17678. * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
  17679. * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
  17680. * @return {Ext.Element} this
  17681. */
  17682. show: function() {
  17683. this.setVisible(true);
  17684. return this;
  17685. },
  17686. /**
  17687. * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
  17688. * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
  17689. * @param {Boolean} visible Whether the element is visible
  17690. * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
  17691. * @return {Ext.Element} this
  17692. */
  17693. setVisible: function(visible, animate) {
  17694. var me = this,
  17695. statics = me.self,
  17696. mode = me.getVisibilityMode(),
  17697. prefix = Ext.baseCSSPrefix;
  17698. switch (mode) {
  17699. case statics.VISIBILITY:
  17700. me.removeCls([prefix + 'hidden-display', prefix + 'hidden-offsets']);
  17701. me[visible ? 'removeCls' : 'addCls'](prefix + 'hidden-visibility');
  17702. break;
  17703. case statics.DISPLAY:
  17704. me.removeCls([prefix + 'hidden-visibility', prefix + 'hidden-offsets']);
  17705. me[visible ? 'removeCls' : 'addCls'](prefix + 'hidden-display');
  17706. break;
  17707. case statics.OFFSETS:
  17708. me.removeCls([prefix + 'hidden-visibility', prefix + 'hidden-display']);
  17709. me[visible ? 'removeCls' : 'addCls'](prefix + 'hidden-offsets');
  17710. break;
  17711. }
  17712. return me;
  17713. },
  17714. getVisibilityMode: function() {
  17715. // Only flyweights won't have a $cache object, by calling getCache the cache
  17716. // will be created for future accesses. As such, we're eliminating the method
  17717. // call since it's mostly redundant
  17718. var data = (this.$cache || this.getCache()).data,
  17719. visMode = data.visibilityMode;
  17720. if (visMode === undefined) {
  17721. data.visibilityMode = visMode = this.self.DISPLAY;
  17722. }
  17723. return visMode;
  17724. },
  17725. /**
  17726. * Use this to change the visibility mode between {@link #VISIBILITY}, {@link #DISPLAY}, {@link #OFFSETS} or {@link #ASCLASS}.
  17727. */
  17728. setVisibilityMode: function(mode) {
  17729. (this.$cache || this.getCache()).data.visibilityMode = mode;
  17730. return this;
  17731. },
  17732. getCache: function() {
  17733. var me = this,
  17734. id = me.dom.id || Ext.id(me.dom);
  17735. // Note that we do not assign an ID to the calling object here.
  17736. // An Ext.dom.Element will have one assigned at construction, and an Ext.dom.AbstractElement.Fly must not have one.
  17737. // We assign an ID to the DOM element if it does not have one.
  17738. me.$cache = Ext.cache[id] || Ext.addCacheEntry(id, null, me.dom);
  17739. return me.$cache;
  17740. }
  17741. }, function() {
  17742. var AbstractElement = this;
  17743. /**
  17744. * @private
  17745. * @member Ext
  17746. */
  17747. Ext.getDetachedBody = function () {
  17748. var detachedEl = AbstractElement.detachedBodyEl;
  17749. if (!detachedEl) {
  17750. detachedEl = document.createElement('div');
  17751. AbstractElement.detachedBodyEl = detachedEl = new AbstractElement.Fly(detachedEl);
  17752. detachedEl.isDetachedBody = true;
  17753. }
  17754. return detachedEl;
  17755. };
  17756. /**
  17757. * @private
  17758. * @member Ext
  17759. */
  17760. Ext.getElementById = function (id) {
  17761. var el = document.getElementById(id),
  17762. detachedBodyEl;
  17763. if (!el && (detachedBodyEl = AbstractElement.detachedBodyEl)) {
  17764. el = detachedBodyEl.dom.querySelector('#' + Ext.escapeId(id));
  17765. }
  17766. return el;
  17767. };
  17768. /**
  17769. * @member Ext
  17770. * @method get
  17771. * @inheritdoc Ext.dom.Element#get
  17772. */
  17773. Ext.get = function(el) {
  17774. return Ext.dom.Element.get(el);
  17775. };
  17776. this.addStatics({
  17777. /**
  17778. * @class Ext.dom.AbstractElement.Fly
  17779. * @extends Ext.dom.AbstractElement
  17780. *
  17781. * A non-persistent wrapper for a DOM element which may be used to execute methods of {@link Ext.dom.Element}
  17782. * upon a DOM element without creating an instance of {@link Ext.dom.Element}.
  17783. *
  17784. * A **singleton** instance of this class is returned when you use {@link Ext#fly}
  17785. *
  17786. * Because it is a singleton, this Flyweight does not have an ID, and must be used and discarded in a single line.
  17787. * You should not keep and use the reference to this singleton over multiple lines because methods that you call
  17788. * may themselves make use of {@link Ext#fly} and may change the DOM element to which the instance refers.
  17789. */
  17790. Fly: new Ext.Class({
  17791. extend: AbstractElement,
  17792. /**
  17793. * @property {Boolean} isFly
  17794. * This is `true` to identify Element flyweights
  17795. */
  17796. isFly: true,
  17797. constructor: function(dom) {
  17798. this.dom = dom;
  17799. },
  17800. /**
  17801. * @private
  17802. * Attach this fliyweight instance to the passed DOM element.
  17803. *
  17804. * Note that a flightweight does **not** have an ID, and does not acquire the ID of the DOM element.
  17805. */
  17806. attach: function (dom) {
  17807. // Attach to the passed DOM element. The same code as in Ext.Fly
  17808. this.dom = dom;
  17809. // Use cached data if there is existing cached data for the referenced DOM element,
  17810. // otherwise it will be created when needed by getCache.
  17811. this.$cache = dom.id ? Ext.cache[dom.id] : null;
  17812. return this;
  17813. }
  17814. }),
  17815. _flyweights: {},
  17816. /**
  17817. * Gets the singleton {@link Ext.dom.AbstractElement.Fly flyweight} element, with the passed node as the active element.
  17818. *
  17819. * Because it is a singleton, this Flyweight does not have an ID, and must be used and discarded in a single line.
  17820. * You may not keep and use the reference to this singleton over multiple lines because methods that you call
  17821. * may themselves make use of {@link Ext#fly} and may change the DOM element to which the instance refers.
  17822. *
  17823. * {@link Ext#fly} is alias for {@link Ext.dom.AbstractElement#fly}.
  17824. *
  17825. * Use this to make one-time references to DOM elements which are not going to be accessed again either by
  17826. * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link
  17827. * Ext#get Ext.get} will be more appropriate to take advantage of the caching provided by the Ext.dom.Element
  17828. * class.
  17829. *
  17830. * @param {String/HTMLElement} dom The dom node or id
  17831. * @param {String} [named] Allows for creation of named reusable flyweights to prevent conflicts (e.g.
  17832. * internally Ext uses "_global")
  17833. * @return {Ext.dom.AbstractElement.Fly} The singleton flyweight object (or null if no matching element was found)
  17834. * @static
  17835. * @member Ext.dom.AbstractElement
  17836. */
  17837. fly: function(dom, named) {
  17838. var fly = null,
  17839. _flyweights = AbstractElement._flyweights;
  17840. named = named || '_global';
  17841. dom = Ext.getDom(dom);
  17842. if (dom) {
  17843. fly = _flyweights[named] || (_flyweights[named] = new AbstractElement.Fly());
  17844. // Attach to the passed DOM element.
  17845. // This code performs the same function as Fly.attach, but inline it for efficiency
  17846. fly.dom = dom;
  17847. // Use cached data if there is existing cached data for the referenced DOM element,
  17848. // otherwise it will be created when needed by getCache.
  17849. fly.$cache = dom.id ? Ext.cache[dom.id] : null;
  17850. }
  17851. return fly;
  17852. }
  17853. });
  17854. /**
  17855. * @member Ext
  17856. * @method fly
  17857. * @inheritdoc Ext.dom.AbstractElement#fly
  17858. */
  17859. Ext.fly = function() {
  17860. return AbstractElement.fly.apply(AbstractElement, arguments);
  17861. };
  17862. (function (proto) {
  17863. /**
  17864. * @method destroy
  17865. * @member Ext.dom.AbstractElement
  17866. * @inheritdoc Ext.dom.AbstractElement#remove
  17867. * Alias to {@link #remove}.
  17868. */
  17869. proto.destroy = proto.remove;
  17870. /**
  17871. * Returns a child element of this element given its `id`.
  17872. * @method getById
  17873. * @member Ext.dom.AbstractElement
  17874. * @param {String} id The id of the desired child element.
  17875. * @param {Boolean} [asDom=false] True to return the DOM element, false to return a
  17876. * wrapped Element object.
  17877. */
  17878. if (document.querySelector) {
  17879. proto.getById = function (id, asDom) {
  17880. // for normal elements getElementById is the best solution, but if the el is
  17881. // not part of the document.body, we have to resort to querySelector
  17882. var dom = document.getElementById(id) ||
  17883. this.dom.querySelector('#'+Ext.escapeId(id));
  17884. return asDom ? dom : (dom ? Ext.get(dom) : null);
  17885. };
  17886. } else {
  17887. proto.getById = function (id, asDom) {
  17888. var dom = document.getElementById(id);
  17889. return asDom ? dom : (dom ? Ext.get(dom) : null);
  17890. };
  17891. }
  17892. }(this.prototype));
  17893. });
  17894. }());
  17895. //@tag dom,core
  17896. //@require AbstractElement.js
  17897. //@define Ext.dom.AbstractElement-static
  17898. //@define Ext.dom.AbstractElement
  17899. /**
  17900. * @class Ext.dom.AbstractElement
  17901. */
  17902. Ext.dom.AbstractElement.addInheritableStatics({
  17903. unitRe: /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
  17904. camelRe: /(-[a-z])/gi,
  17905. cssRe: /([a-z0-9\-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
  17906. opacityRe: /alpha\(opacity=(.*)\)/i,
  17907. propertyCache: {},
  17908. defaultUnit : "px",
  17909. borders: {l: 'border-left-width', r: 'border-right-width', t: 'border-top-width', b: 'border-bottom-width'},
  17910. paddings: {l: 'padding-left', r: 'padding-right', t: 'padding-top', b: 'padding-bottom'},
  17911. margins: {l: 'margin-left', r: 'margin-right', t: 'margin-top', b: 'margin-bottom'},
  17912. /**
  17913. * Test if size has a unit, otherwise appends the passed unit string, or the default for this Element.
  17914. * @param size {Object} The size to set
  17915. * @param units {String} The units to append to a numeric size value
  17916. * @private
  17917. * @static
  17918. */
  17919. addUnits: function(size, units) {
  17920. // Most common case first: Size is set to a number
  17921. if (typeof size == 'number') {
  17922. return size + (units || this.defaultUnit || 'px');
  17923. }
  17924. // Size set to a value which means "auto"
  17925. if (size === "" || size == "auto" || size === undefined || size === null) {
  17926. return size || '';
  17927. }
  17928. // Otherwise, warn if it's not a valid CSS measurement
  17929. if (!this.unitRe.test(size)) {
  17930. if (Ext.isDefined(Ext.global.console)) {
  17931. Ext.global.console.warn("Warning, size detected as NaN on Element.addUnits.");
  17932. }
  17933. return size || '';
  17934. }
  17935. return size;
  17936. },
  17937. /**
  17938. * @static
  17939. * @private
  17940. */
  17941. isAncestor: function(p, c) {
  17942. var ret = false;
  17943. p = Ext.getDom(p);
  17944. c = Ext.getDom(c);
  17945. if (p && c) {
  17946. if (p.contains) {
  17947. return p.contains(c);
  17948. } else if (p.compareDocumentPosition) {
  17949. return !!(p.compareDocumentPosition(c) & 16);
  17950. } else {
  17951. while ((c = c.parentNode)) {
  17952. ret = c == p || ret;
  17953. }
  17954. }
  17955. }
  17956. return ret;
  17957. },
  17958. /**
  17959. * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
  17960. * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
  17961. * @static
  17962. * @param {Number/String} box The encoded margins
  17963. * @return {Object} An object with margin sizes for top, right, bottom and left
  17964. */
  17965. parseBox: function(box) {
  17966. if (typeof box != 'string') {
  17967. box = box.toString();
  17968. }
  17969. var parts = box.split(' '),
  17970. ln = parts.length;
  17971. if (ln == 1) {
  17972. parts[1] = parts[2] = parts[3] = parts[0];
  17973. }
  17974. else if (ln == 2) {
  17975. parts[2] = parts[0];
  17976. parts[3] = parts[1];
  17977. }
  17978. else if (ln == 3) {
  17979. parts[3] = parts[1];
  17980. }
  17981. return {
  17982. top :parseFloat(parts[0]) || 0,
  17983. right :parseFloat(parts[1]) || 0,
  17984. bottom:parseFloat(parts[2]) || 0,
  17985. left :parseFloat(parts[3]) || 0
  17986. };
  17987. },
  17988. /**
  17989. * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
  17990. * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
  17991. * @static
  17992. * @param {Number/String} box The encoded margins
  17993. * @param {String} units The type of units to add
  17994. * @return {String} An string with unitized (px if units is not specified) metrics for top, right, bottom and left
  17995. */
  17996. unitizeBox: function(box, units) {
  17997. var a = this.addUnits,
  17998. b = this.parseBox(box);
  17999. return a(b.top, units) + ' ' +
  18000. a(b.right, units) + ' ' +
  18001. a(b.bottom, units) + ' ' +
  18002. a(b.left, units);
  18003. },
  18004. // private
  18005. camelReplaceFn: function(m, a) {
  18006. return a.charAt(1).toUpperCase();
  18007. },
  18008. /**
  18009. * Normalizes CSS property keys from dash delimited to camel case JavaScript Syntax.
  18010. * For example:
  18011. *
  18012. * - border-width -> borderWidth
  18013. * - padding-top -> paddingTop
  18014. *
  18015. * @static
  18016. * @param {String} prop The property to normalize
  18017. * @return {String} The normalized string
  18018. */
  18019. normalize: function(prop) {
  18020. // TODO: Mobile optimization?
  18021. if (prop == 'float') {
  18022. prop = Ext.supports.Float ? 'cssFloat' : 'styleFloat';
  18023. }
  18024. return this.propertyCache[prop] || (this.propertyCache[prop] = prop.replace(this.camelRe, this.camelReplaceFn));
  18025. },
  18026. /**
  18027. * Retrieves the document height
  18028. * @static
  18029. * @return {Number} documentHeight
  18030. */
  18031. getDocumentHeight: function() {
  18032. return Math.max(!Ext.isStrict ? document.body.scrollHeight : document.documentElement.scrollHeight, this.getViewportHeight());
  18033. },
  18034. /**
  18035. * Retrieves the document width
  18036. * @static
  18037. * @return {Number} documentWidth
  18038. */
  18039. getDocumentWidth: function() {
  18040. return Math.max(!Ext.isStrict ? document.body.scrollWidth : document.documentElement.scrollWidth, this.getViewportWidth());
  18041. },
  18042. /**
  18043. * Retrieves the viewport height of the window.
  18044. * @static
  18045. * @return {Number} viewportHeight
  18046. */
  18047. getViewportHeight: function(){
  18048. return window.innerHeight;
  18049. },
  18050. /**
  18051. * Retrieves the viewport width of the window.
  18052. * @static
  18053. * @return {Number} viewportWidth
  18054. */
  18055. getViewportWidth: function() {
  18056. return window.innerWidth;
  18057. },
  18058. /**
  18059. * Retrieves the viewport size of the window.
  18060. * @static
  18061. * @return {Object} object containing width and height properties
  18062. */
  18063. getViewSize: function() {
  18064. return {
  18065. width: window.innerWidth,
  18066. height: window.innerHeight
  18067. };
  18068. },
  18069. /**
  18070. * Retrieves the current orientation of the window. This is calculated by
  18071. * determing if the height is greater than the width.
  18072. * @static
  18073. * @return {String} Orientation of window: 'portrait' or 'landscape'
  18074. */
  18075. getOrientation: function() {
  18076. if (Ext.supports.OrientationChange) {
  18077. return (window.orientation == 0) ? 'portrait' : 'landscape';
  18078. }
  18079. return (window.innerHeight > window.innerWidth) ? 'portrait' : 'landscape';
  18080. },
  18081. /**
  18082. * Returns the top Element that is located at the passed coordinates
  18083. * @static
  18084. * @param {Number} x The x coordinate
  18085. * @param {Number} y The y coordinate
  18086. * @return {String} The found Element
  18087. */
  18088. fromPoint: function(x, y) {
  18089. return Ext.get(document.elementFromPoint(x, y));
  18090. },
  18091. /**
  18092. * Converts a CSS string into an object with a property for each style.
  18093. *
  18094. * The sample code below would return an object with 2 properties, one
  18095. * for background-color and one for color.
  18096. *
  18097. * var css = 'background-color: red;color: blue; ';
  18098. * console.log(Ext.dom.Element.parseStyles(css));
  18099. *
  18100. * @static
  18101. * @param {String} styles A CSS string
  18102. * @return {Object} styles
  18103. */
  18104. parseStyles: function(styles){
  18105. var out = {},
  18106. cssRe = this.cssRe,
  18107. matches;
  18108. if (styles) {
  18109. // Since we're using the g flag on the regex, we need to set the lastIndex.
  18110. // This automatically happens on some implementations, but not others, see:
  18111. // http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls
  18112. // http://blog.stevenlevithan.com/archives/fixing-javascript-regexp
  18113. cssRe.lastIndex = 0;
  18114. while ((matches = cssRe.exec(styles))) {
  18115. out[matches[1]] = matches[2];
  18116. }
  18117. }
  18118. return out;
  18119. }
  18120. });
  18121. //TODO Need serious cleanups
  18122. (function(){
  18123. var doc = document,
  18124. AbstractElement = Ext.dom.AbstractElement,
  18125. activeElement = null,
  18126. isCSS1 = doc.compatMode == "CSS1Compat",
  18127. flyInstance,
  18128. fly = function (el) {
  18129. if (!flyInstance) {
  18130. flyInstance = new AbstractElement.Fly();
  18131. }
  18132. flyInstance.attach(el);
  18133. return flyInstance;
  18134. };
  18135. // If the browser does not support document.activeElement we need some assistance.
  18136. // This covers old Safari 3.2 (4.0 added activeElement along with just about all
  18137. // other browsers). We need this support to handle issues with old Safari.
  18138. if (!('activeElement' in doc) && doc.addEventListener) {
  18139. doc.addEventListener('focus',
  18140. function (ev) {
  18141. if (ev && ev.target) {
  18142. activeElement = (ev.target == doc) ? null : ev.target;
  18143. }
  18144. }, true);
  18145. }
  18146. /*
  18147. * Helper function to create the function that will restore the selection.
  18148. */
  18149. function makeSelectionRestoreFn (activeEl, start, end) {
  18150. return function () {
  18151. activeEl.selectionStart = start;
  18152. activeEl.selectionEnd = end;
  18153. };
  18154. }
  18155. AbstractElement.addInheritableStatics({
  18156. /**
  18157. * Returns the active element in the DOM. If the browser supports activeElement
  18158. * on the document, this is returned. If not, the focus is tracked and the active
  18159. * element is maintained internally.
  18160. * @return {HTMLElement} The active (focused) element in the document.
  18161. */
  18162. getActiveElement: function () {
  18163. return doc.activeElement || activeElement;
  18164. },
  18165. /**
  18166. * Creates a function to call to clean up problems with the work-around for the
  18167. * WebKit RightMargin bug. The work-around is to add "display: 'inline-block'" to
  18168. * the element before calling getComputedStyle and then to restore its original
  18169. * display value. The problem with this is that it corrupts the selection of an
  18170. * INPUT or TEXTAREA element (as in the "I-beam" goes away but ths focus remains).
  18171. * To cleanup after this, we need to capture the selection of any such element and
  18172. * then restore it after we have restored the display style.
  18173. *
  18174. * @param {Ext.dom.Element} target The top-most element being adjusted.
  18175. * @private
  18176. */
  18177. getRightMarginFixCleaner: function (target) {
  18178. var supports = Ext.supports,
  18179. hasInputBug = supports.DisplayChangeInputSelectionBug,
  18180. hasTextAreaBug = supports.DisplayChangeTextAreaSelectionBug,
  18181. activeEl,
  18182. tag,
  18183. start,
  18184. end;
  18185. if (hasInputBug || hasTextAreaBug) {
  18186. activeEl = doc.activeElement || activeElement; // save a call
  18187. tag = activeEl && activeEl.tagName;
  18188. if ((hasTextAreaBug && tag == 'TEXTAREA') ||
  18189. (hasInputBug && tag == 'INPUT' && activeEl.type == 'text')) {
  18190. if (Ext.dom.Element.isAncestor(target, activeEl)) {
  18191. start = activeEl.selectionStart;
  18192. end = activeEl.selectionEnd;
  18193. if (Ext.isNumber(start) && Ext.isNumber(end)) { // to be safe...
  18194. // We don't create the raw closure here inline because that
  18195. // will be costly even if we don't want to return it (nested
  18196. // function decls and exprs are often instantiated on entry
  18197. // regardless of whether execution ever reaches them):
  18198. return makeSelectionRestoreFn(activeEl, start, end);
  18199. }
  18200. }
  18201. }
  18202. }
  18203. return Ext.emptyFn; // avoid special cases, just return a nop
  18204. },
  18205. getViewWidth: function(full) {
  18206. return full ? Ext.dom.Element.getDocumentWidth() : Ext.dom.Element.getViewportWidth();
  18207. },
  18208. getViewHeight: function(full) {
  18209. return full ? Ext.dom.Element.getDocumentHeight() : Ext.dom.Element.getViewportHeight();
  18210. },
  18211. getDocumentHeight: function() {
  18212. return Math.max(!isCSS1 ? doc.body.scrollHeight : doc.documentElement.scrollHeight, Ext.dom.Element.getViewportHeight());
  18213. },
  18214. getDocumentWidth: function() {
  18215. return Math.max(!isCSS1 ? doc.body.scrollWidth : doc.documentElement.scrollWidth, Ext.dom.Element.getViewportWidth());
  18216. },
  18217. getViewportHeight: function(){
  18218. return Ext.isIE ?
  18219. (Ext.isStrict ? doc.documentElement.clientHeight : doc.body.clientHeight) :
  18220. self.innerHeight;
  18221. },
  18222. getViewportWidth: function() {
  18223. return (!Ext.isStrict && !Ext.isOpera) ? doc.body.clientWidth :
  18224. Ext.isIE ? doc.documentElement.clientWidth : self.innerWidth;
  18225. },
  18226. getY: function(el) {
  18227. return Ext.dom.Element.getXY(el)[1];
  18228. },
  18229. getX: function(el) {
  18230. return Ext.dom.Element.getXY(el)[0];
  18231. },
  18232. getXY: function(el) {
  18233. var bd = doc.body,
  18234. docEl = doc.documentElement,
  18235. leftBorder = 0,
  18236. topBorder = 0,
  18237. ret = [0,0],
  18238. round = Math.round,
  18239. box,
  18240. scroll;
  18241. el = Ext.getDom(el);
  18242. if(el != doc && el != bd){
  18243. // IE has the potential to throw when getBoundingClientRect called
  18244. // on element not attached to dom
  18245. if (Ext.isIE) {
  18246. try {
  18247. box = el.getBoundingClientRect();
  18248. // In some versions of IE, the documentElement (HTML element) will have a 2px border that gets included, so subtract it off
  18249. topBorder = docEl.clientTop || bd.clientTop;
  18250. leftBorder = docEl.clientLeft || bd.clientLeft;
  18251. } catch (ex) {
  18252. box = { left: 0, top: 0 };
  18253. }
  18254. } else {
  18255. box = el.getBoundingClientRect();
  18256. }
  18257. scroll = fly(document).getScroll();
  18258. ret = [round(box.left + scroll.left - leftBorder), round(box.top + scroll.top - topBorder)];
  18259. }
  18260. return ret;
  18261. },
  18262. setXY: function(el, xy) {
  18263. (el = Ext.fly(el, '_setXY')).position();
  18264. var pts = el.translatePoints(xy),
  18265. style = el.dom.style,
  18266. pos;
  18267. for (pos in pts) {
  18268. if (!isNaN(pts[pos])) {
  18269. style[pos] = pts[pos] + "px";
  18270. }
  18271. }
  18272. },
  18273. setX: function(el, x) {
  18274. Ext.dom.Element.setXY(el, [x, false]);
  18275. },
  18276. setY: function(el, y) {
  18277. Ext.dom.Element.setXY(el, [false, y]);
  18278. },
  18279. /**
  18280. * Serializes a DOM form into a url encoded string
  18281. * @param {Object} form The form
  18282. * @return {String} The url encoded form
  18283. */
  18284. serializeForm: function(form) {
  18285. var fElements = form.elements || (document.forms[form] || Ext.getDom(form)).elements,
  18286. hasSubmit = false,
  18287. encoder = encodeURIComponent,
  18288. data = '',
  18289. eLen = fElements.length,
  18290. element, name, type, options, hasValue, e,
  18291. o, oLen, opt;
  18292. for (e = 0; e < eLen; e++) {
  18293. element = fElements[e];
  18294. name = element.name;
  18295. type = element.type;
  18296. options = element.options;
  18297. if (!element.disabled && name) {
  18298. if (/select-(one|multiple)/i.test(type)) {
  18299. oLen = options.length;
  18300. for (o = 0; o < oLen; o++) {
  18301. opt = options[o];
  18302. if (opt.selected) {
  18303. hasValue = opt.hasAttribute ? opt.hasAttribute('value') : opt.getAttributeNode('value').specified;
  18304. data += Ext.String.format("{0}={1}&", encoder(name), encoder(hasValue ? opt.value : opt.text));
  18305. }
  18306. }
  18307. } else if (!(/file|undefined|reset|button/i.test(type))) {
  18308. if (!(/radio|checkbox/i.test(type) && !element.checked) && !(type == 'submit' && hasSubmit)) {
  18309. data += encoder(name) + '=' + encoder(element.value) + '&';
  18310. hasSubmit = /submit/i.test(type);
  18311. }
  18312. }
  18313. }
  18314. }
  18315. return data.substr(0, data.length - 1);
  18316. }
  18317. });
  18318. }());
  18319. //@tag dom,core
  18320. //@require Ext.dom.AbstractElement-static
  18321. //@define Ext.dom.AbstractElement-alignment
  18322. /**
  18323. * @class Ext.dom.AbstractElement
  18324. */
  18325. Ext.dom.AbstractElement.override({
  18326. /**
  18327. * Gets the x,y coordinates specified by the anchor position on the element.
  18328. * @param {String} [anchor] The specified anchor position (defaults to "c"). See {@link Ext.dom.Element#alignTo}
  18329. * for details on supported anchor positions.
  18330. * @param {Boolean} [local] True to get the local (element top/left-relative) anchor position instead
  18331. * of page coordinates
  18332. * @param {Object} [size] An object containing the size to use for calculating anchor position
  18333. * {width: (target width), height: (target height)} (defaults to the element's current size)
  18334. * @return {Array} [x, y] An array containing the element's x and y coordinates
  18335. */
  18336. getAnchorXY: function(anchor, local, size) {
  18337. //Passing a different size is useful for pre-calculating anchors,
  18338. //especially for anchored animations that change the el size.
  18339. anchor = (anchor || "tl").toLowerCase();
  18340. size = size || {};
  18341. var me = this,
  18342. vp = me.dom == document.body || me.dom == document,
  18343. width = size.width || vp ? window.innerWidth: me.getWidth(),
  18344. height = size.height || vp ? window.innerHeight: me.getHeight(),
  18345. xy,
  18346. rnd = Math.round,
  18347. myXY = me.getXY(),
  18348. extraX = vp ? 0: !local ? myXY[0] : 0,
  18349. extraY = vp ? 0: !local ? myXY[1] : 0,
  18350. hash = {
  18351. c: [rnd(width * 0.5), rnd(height * 0.5)],
  18352. t: [rnd(width * 0.5), 0],
  18353. l: [0, rnd(height * 0.5)],
  18354. r: [width, rnd(height * 0.5)],
  18355. b: [rnd(width * 0.5), height],
  18356. tl: [0, 0],
  18357. bl: [0, height],
  18358. br: [width, height],
  18359. tr: [width, 0]
  18360. };
  18361. xy = hash[anchor];
  18362. return [xy[0] + extraX, xy[1] + extraY];
  18363. },
  18364. alignToRe: /^([a-z]+)-([a-z]+)(\?)?$/,
  18365. /**
  18366. * Gets the x,y coordinates to align this element with another element. See {@link Ext.dom.Element#alignTo} for more info on the
  18367. * supported position values.
  18368. * @param {Ext.Element/HTMLElement/String} element The element to align to.
  18369. * @param {String} [position="tl-bl?"] The position to align to.
  18370. * @param {Array} [offsets=[0,0]] Offset the positioning by [x, y]
  18371. * @return {Array} [x, y]
  18372. */
  18373. getAlignToXY: function(el, position, offsets, local) {
  18374. local = !!local;
  18375. el = Ext.get(el);
  18376. if (!el || !el.dom) {
  18377. throw new Error("Element.alignToXY with an element that doesn't exist");
  18378. }
  18379. offsets = offsets || [0, 0];
  18380. if (!position || position == '?') {
  18381. position = 'tl-bl?';
  18382. }
  18383. else if (! (/-/).test(position) && position !== "") {
  18384. position = 'tl-' + position;
  18385. }
  18386. position = position.toLowerCase();
  18387. var me = this,
  18388. matches = position.match(this.alignToRe),
  18389. dw = window.innerWidth,
  18390. dh = window.innerHeight,
  18391. p1 = "",
  18392. p2 = "",
  18393. a1,
  18394. a2,
  18395. x,
  18396. y,
  18397. swapX,
  18398. swapY,
  18399. p1x,
  18400. p1y,
  18401. p2x,
  18402. p2y,
  18403. width,
  18404. height,
  18405. region,
  18406. constrain;
  18407. if (!matches) {
  18408. throw "Element.alignTo with an invalid alignment " + position;
  18409. }
  18410. p1 = matches[1];
  18411. p2 = matches[2];
  18412. constrain = !!matches[3];
  18413. //Subtract the aligned el's internal xy from the target's offset xy
  18414. //plus custom offset to get the aligned el's new offset xy
  18415. a1 = me.getAnchorXY(p1, true);
  18416. a2 = el.getAnchorXY(p2, local);
  18417. x = a2[0] - a1[0] + offsets[0];
  18418. y = a2[1] - a1[1] + offsets[1];
  18419. if (constrain) {
  18420. width = me.getWidth();
  18421. height = me.getHeight();
  18422. region = el.getPageBox();
  18423. //If we are at a viewport boundary and the aligned el is anchored on a target border that is
  18424. //perpendicular to the vp border, allow the aligned el to slide on that border,
  18425. //otherwise swap the aligned el to the opposite border of the target.
  18426. p1y = p1.charAt(0);
  18427. p1x = p1.charAt(p1.length - 1);
  18428. p2y = p2.charAt(0);
  18429. p2x = p2.charAt(p2.length - 1);
  18430. swapY = ((p1y == "t" && p2y == "b") || (p1y == "b" && p2y == "t"));
  18431. swapX = ((p1x == "r" && p2x == "l") || (p1x == "l" && p2x == "r"));
  18432. if (x + width > dw) {
  18433. x = swapX ? region.left - width: dw - width;
  18434. }
  18435. if (x < 0) {
  18436. x = swapX ? region.right: 0;
  18437. }
  18438. if (y + height > dh) {
  18439. y = swapY ? region.top - height: dh - height;
  18440. }
  18441. if (y < 0) {
  18442. y = swapY ? region.bottom: 0;
  18443. }
  18444. }
  18445. return [x, y];
  18446. },
  18447. // private
  18448. getAnchor: function(){
  18449. var data = (this.$cache || this.getCache()).data,
  18450. anchor;
  18451. if (!this.dom) {
  18452. return;
  18453. }
  18454. anchor = data._anchor;
  18455. if(!anchor){
  18456. anchor = data._anchor = {};
  18457. }
  18458. return anchor;
  18459. },
  18460. // private ==> used outside of core
  18461. adjustForConstraints: function(xy, parent) {
  18462. var vector = this.getConstrainVector(parent, xy);
  18463. if (vector) {
  18464. xy[0] += vector[0];
  18465. xy[1] += vector[1];
  18466. }
  18467. return xy;
  18468. }
  18469. });
  18470. //@tag dom,core
  18471. //@require Ext.dom.AbstractElement-alignment
  18472. //@define Ext.dom.AbstractElement-insertion
  18473. //@define Ext.dom.AbstractElement
  18474. /**
  18475. * @class Ext.dom.AbstractElement
  18476. */
  18477. Ext.dom.AbstractElement.addMethods({
  18478. /**
  18479. * Appends the passed element(s) to this element
  18480. * @param {String/HTMLElement/Ext.dom.AbstractElement} el
  18481. * The id of the node, a DOM Node or an existing Element.
  18482. * @return {Ext.dom.AbstractElement} This element
  18483. */
  18484. appendChild: function(el) {
  18485. return Ext.get(el).appendTo(this);
  18486. },
  18487. /**
  18488. * Appends this element to the passed element
  18489. * @param {String/HTMLElement/Ext.dom.AbstractElement} el The new parent element.
  18490. * The id of the node, a DOM Node or an existing Element.
  18491. * @return {Ext.dom.AbstractElement} This element
  18492. */
  18493. appendTo: function(el) {
  18494. Ext.getDom(el).appendChild(this.dom);
  18495. return this;
  18496. },
  18497. /**
  18498. * Inserts this element before the passed element in the DOM
  18499. * @param {String/HTMLElement/Ext.dom.AbstractElement} el The element before which this element will be inserted.
  18500. * The id of the node, a DOM Node or an existing Element.
  18501. * @return {Ext.dom.AbstractElement} This element
  18502. */
  18503. insertBefore: function(el) {
  18504. el = Ext.getDom(el);
  18505. el.parentNode.insertBefore(this.dom, el);
  18506. return this;
  18507. },
  18508. /**
  18509. * Inserts this element after the passed element in the DOM
  18510. * @param {String/HTMLElement/Ext.dom.AbstractElement} el The element to insert after.
  18511. * The id of the node, a DOM Node or an existing Element.
  18512. * @return {Ext.dom.AbstractElement} This element
  18513. */
  18514. insertAfter: function(el) {
  18515. el = Ext.getDom(el);
  18516. el.parentNode.insertBefore(this.dom, el.nextSibling);
  18517. return this;
  18518. },
  18519. /**
  18520. * Inserts (or creates) an element (or DomHelper config) as the first child of this element
  18521. * @param {String/HTMLElement/Ext.dom.AbstractElement/Object} el The id or element to insert or a DomHelper config
  18522. * to create and insert
  18523. * @return {Ext.dom.AbstractElement} The new child
  18524. */
  18525. insertFirst: function(el, returnDom) {
  18526. el = el || {};
  18527. if (el.nodeType || el.dom || typeof el == 'string') { // element
  18528. el = Ext.getDom(el);
  18529. this.dom.insertBefore(el, this.dom.firstChild);
  18530. return !returnDom ? Ext.get(el) : el;
  18531. }
  18532. else { // dh config
  18533. return this.createChild(el, this.dom.firstChild, returnDom);
  18534. }
  18535. },
  18536. /**
  18537. * Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
  18538. * @param {String/HTMLElement/Ext.dom.AbstractElement/Object/Array} el The id, element to insert or a DomHelper config
  18539. * to create and insert *or* an array of any of those.
  18540. * @param {String} [where='before'] 'before' or 'after'
  18541. * @param {Boolean} [returnDom=false] True to return the .;ll;l,raw DOM element instead of Ext.dom.AbstractElement
  18542. * @return {Ext.dom.AbstractElement} The inserted Element. If an array is passed, the last inserted element is returned.
  18543. */
  18544. insertSibling: function(el, where, returnDom){
  18545. var me = this,
  18546. isAfter = (where || 'before').toLowerCase() == 'after',
  18547. rt, insertEl, eLen, e;
  18548. if (Ext.isArray(el)) {
  18549. insertEl = me;
  18550. eLen = el.length;
  18551. for (e = 0; e < eLen; e++) {
  18552. rt = Ext.fly(insertEl, '_internal').insertSibling(el[e], where, returnDom);
  18553. if (isAfter) {
  18554. insertEl = rt;
  18555. }
  18556. }
  18557. return rt;
  18558. }
  18559. el = el || {};
  18560. if(el.nodeType || el.dom){
  18561. rt = me.dom.parentNode.insertBefore(Ext.getDom(el), isAfter ? me.dom.nextSibling : me.dom);
  18562. if (!returnDom) {
  18563. rt = Ext.get(rt);
  18564. }
  18565. }else{
  18566. if (isAfter && !me.dom.nextSibling) {
  18567. rt = Ext.core.DomHelper.append(me.dom.parentNode, el, !returnDom);
  18568. } else {
  18569. rt = Ext.core.DomHelper[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
  18570. }
  18571. }
  18572. return rt;
  18573. },
  18574. /**
  18575. * Replaces the passed element with this element
  18576. * @param {String/HTMLElement/Ext.dom.AbstractElement} el The element to replace.
  18577. * The id of the node, a DOM Node or an existing Element.
  18578. * @return {Ext.dom.AbstractElement} This element
  18579. */
  18580. replace: function(el) {
  18581. el = Ext.get(el);
  18582. this.insertBefore(el);
  18583. el.remove();
  18584. return this;
  18585. },
  18586. /**
  18587. * Replaces this element with the passed element
  18588. * @param {String/HTMLElement/Ext.dom.AbstractElement/Object} el The new element (id of the node, a DOM Node
  18589. * or an existing Element) or a DomHelper config of an element to create
  18590. * @return {Ext.dom.AbstractElement} This element
  18591. */
  18592. replaceWith: function(el){
  18593. var me = this;
  18594. if(el.nodeType || el.dom || typeof el == 'string'){
  18595. el = Ext.get(el);
  18596. me.dom.parentNode.insertBefore(el, me.dom);
  18597. }else{
  18598. el = Ext.core.DomHelper.insertBefore(me.dom, el);
  18599. }
  18600. delete Ext.cache[me.id];
  18601. Ext.removeNode(me.dom);
  18602. me.id = Ext.id(me.dom = el);
  18603. Ext.dom.AbstractElement.addToCache(me.isFlyweight ? new Ext.dom.AbstractElement(me.dom) : me);
  18604. return me;
  18605. },
  18606. /**
  18607. * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
  18608. * @param {Object} config DomHelper element config object. If no tag is specified (e.g., {tag:'input'}) then a div will be
  18609. * automatically generated with the specified attributes.
  18610. * @param {HTMLElement} [insertBefore] a child element of this element
  18611. * @param {Boolean} [returnDom=false] true to return the dom node instead of creating an Element
  18612. * @return {Ext.dom.AbstractElement} The new child element
  18613. */
  18614. createChild: function(config, insertBefore, returnDom) {
  18615. config = config || {tag:'div'};
  18616. if (insertBefore) {
  18617. return Ext.core.DomHelper.insertBefore(insertBefore, config, returnDom !== true);
  18618. }
  18619. else {
  18620. return Ext.core.DomHelper[!this.dom.firstChild ? 'insertFirst' : 'append'](this.dom, config, returnDom !== true);
  18621. }
  18622. },
  18623. /**
  18624. * Creates and wraps this element with another element
  18625. * @param {Object} [config] DomHelper element config object for the wrapper element or null for an empty div
  18626. * @param {Boolean} [returnDom=false] True to return the raw DOM element instead of Ext.dom.AbstractElement
  18627. * @param {String} [selector] A {@link Ext.dom.Query DomQuery} selector to select a descendant node within the created element to use as the wrapping element.
  18628. * @return {HTMLElement/Ext.dom.AbstractElement} The newly created wrapper element
  18629. */
  18630. wrap: function(config, returnDom, selector) {
  18631. var newEl = Ext.core.DomHelper.insertBefore(this.dom, config || {tag: "div"}, true),
  18632. target = newEl;
  18633. if (selector) {
  18634. target = Ext.DomQuery.selectNode(selector, newEl.dom);
  18635. }
  18636. target.appendChild(this.dom);
  18637. return returnDom ? newEl.dom : newEl;
  18638. },
  18639. /**
  18640. * Inserts an html fragment into this element
  18641. * @param {String} where Where to insert the html in relation to this element - beforeBegin, afterBegin, beforeEnd, afterEnd.
  18642. * See {@link Ext.dom.Helper#insertHtml} for details.
  18643. * @param {String} html The HTML fragment
  18644. * @param {Boolean} [returnEl=false] True to return an Ext.dom.AbstractElement
  18645. * @return {HTMLElement/Ext.dom.AbstractElement} The inserted node (or nearest related if more than 1 inserted)
  18646. */
  18647. insertHtml: function(where, html, returnEl) {
  18648. var el = Ext.core.DomHelper.insertHtml(where, this.dom, html);
  18649. return returnEl ? Ext.get(el) : el;
  18650. }
  18651. });
  18652. //@tag dom,core
  18653. //@require Ext.dom.AbstractElement-insertion
  18654. //@define Ext.dom.AbstractElement-position
  18655. //@define Ext.dom.AbstractElement
  18656. /**
  18657. * @class Ext.dom.AbstractElement
  18658. */
  18659. (function(){
  18660. var Element = Ext.dom.AbstractElement;
  18661. Element.override({
  18662. /**
  18663. * Gets the current X position of the element based on page coordinates. Element must be part of the DOM
  18664. * tree to have page coordinates (display:none or elements not appended return false).
  18665. * @return {Number} The X position of the element
  18666. */
  18667. getX: function(el) {
  18668. return this.getXY(el)[0];
  18669. },
  18670. /**
  18671. * Gets the current Y position of the element based on page coordinates. Element must be part of the DOM
  18672. * tree to have page coordinates (display:none or elements not appended return false).
  18673. * @return {Number} The Y position of the element
  18674. */
  18675. getY: function(el) {
  18676. return this.getXY(el)[1];
  18677. },
  18678. /**
  18679. * Gets the current position of the element based on page coordinates. Element must be part of the DOM
  18680. * tree to have page coordinates (display:none or elements not appended return false).
  18681. * @return {Array} The XY position of the element
  18682. */
  18683. getXY: function() {
  18684. // @FEATUREDETECT
  18685. var point = window.webkitConvertPointFromNodeToPage(this.dom, new WebKitPoint(0, 0));
  18686. return [point.x, point.y];
  18687. },
  18688. /**
  18689. * Returns the offsets of this element from the passed element. Both element must be part of the DOM
  18690. * tree and not have display:none to have page coordinates.
  18691. * @param {Ext.Element/HTMLElement/String} element The element to get the offsets from.
  18692. * @return {Array} The XY page offsets (e.g. [100, -200])
  18693. */
  18694. getOffsetsTo: function(el){
  18695. var o = this.getXY(),
  18696. e = Ext.fly(el, '_internal').getXY();
  18697. return [o[0]-e[0],o[1]-e[1]];
  18698. },
  18699. /**
  18700. * Sets the X position of the element based on page coordinates. Element must be part of the DOM tree
  18701. * to have page coordinates (display:none or elements not appended return false).
  18702. * @param {Number} The X position of the element
  18703. * @param {Boolean/Object} [animate] True for the default animation, or a standard Element
  18704. * animation config object
  18705. * @return {Ext.dom.AbstractElement} this
  18706. */
  18707. setX: function(x){
  18708. return this.setXY([x, this.getY()]);
  18709. },
  18710. /**
  18711. * Sets the Y position of the element based on page coordinates. Element must be part of the DOM tree
  18712. * to have page coordinates (display:none or elements not appended return false).
  18713. * @param {Number} The Y position of the element
  18714. * @param {Boolean/Object} [animate] True for the default animation, or a standard Element
  18715. * animation config object
  18716. * @return {Ext.dom.AbstractElement} this
  18717. */
  18718. setY: function(y) {
  18719. return this.setXY([this.getX(), y]);
  18720. },
  18721. /**
  18722. * Sets the element's left position directly using CSS style (instead of {@link #setX}).
  18723. * @param {String} left The left CSS property value
  18724. * @return {Ext.dom.AbstractElement} this
  18725. */
  18726. setLeft: function(left) {
  18727. this.setStyle('left', Element.addUnits(left));
  18728. return this;
  18729. },
  18730. /**
  18731. * Sets the element's top position directly using CSS style (instead of {@link #setY}).
  18732. * @param {String} top The top CSS property value
  18733. * @return {Ext.dom.AbstractElement} this
  18734. */
  18735. setTop: function(top) {
  18736. this.setStyle('top', Element.addUnits(top));
  18737. return this;
  18738. },
  18739. /**
  18740. * Sets the element's CSS right style.
  18741. * @param {String} right The right CSS property value
  18742. * @return {Ext.dom.AbstractElement} this
  18743. */
  18744. setRight: function(right) {
  18745. this.setStyle('right', Element.addUnits(right));
  18746. return this;
  18747. },
  18748. /**
  18749. * Sets the element's CSS bottom style.
  18750. * @param {String} bottom The bottom CSS property value
  18751. * @return {Ext.dom.AbstractElement} this
  18752. */
  18753. setBottom: function(bottom) {
  18754. this.setStyle('bottom', Element.addUnits(bottom));
  18755. return this;
  18756. },
  18757. /**
  18758. * Sets the position of the element in page coordinates, regardless of how the element is positioned.
  18759. * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
  18760. * @param {Array} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
  18761. * @param {Boolean/Object} [animate] True for the default animation, or a standard Element animation config object
  18762. * @return {Ext.dom.AbstractElement} this
  18763. */
  18764. setXY: function(pos) {
  18765. var me = this,
  18766. pts,
  18767. style,
  18768. pt;
  18769. if (arguments.length > 1) {
  18770. pos = [pos, arguments[1]];
  18771. }
  18772. // me.position();
  18773. pts = me.translatePoints(pos);
  18774. style = me.dom.style;
  18775. for (pt in pts) {
  18776. if (!pts.hasOwnProperty(pt)) {
  18777. continue;
  18778. }
  18779. if (!isNaN(pts[pt])) {
  18780. style[pt] = pts[pt] + "px";
  18781. }
  18782. }
  18783. return me;
  18784. },
  18785. /**
  18786. * Gets the left X coordinate
  18787. * @param {Boolean} local True to get the local css position instead of page coordinate
  18788. * @return {Number}
  18789. */
  18790. getLeft: function(local) {
  18791. return parseInt(this.getStyle('left'), 10) || 0;
  18792. },
  18793. /**
  18794. * Gets the right X coordinate of the element (element X position + element width)
  18795. * @param {Boolean} local True to get the local css position instead of page coordinate
  18796. * @return {Number}
  18797. */
  18798. getRight: function(local) {
  18799. return parseInt(this.getStyle('right'), 10) || 0;
  18800. },
  18801. /**
  18802. * Gets the top Y coordinate
  18803. * @param {Boolean} local True to get the local css position instead of page coordinate
  18804. * @return {Number}
  18805. */
  18806. getTop: function(local) {
  18807. return parseInt(this.getStyle('top'), 10) || 0;
  18808. },
  18809. /**
  18810. * Gets the bottom Y coordinate of the element (element Y position + element height)
  18811. * @param {Boolean} local True to get the local css position instead of page coordinate
  18812. * @return {Number}
  18813. */
  18814. getBottom: function(local) {
  18815. return parseInt(this.getStyle('bottom'), 10) || 0;
  18816. },
  18817. /**
  18818. * Translates the passed page coordinates into left/top css values for this element
  18819. * @param {Number/Array} x The page x or an array containing [x, y]
  18820. * @param {Number} [y] The page y, required if x is not an array
  18821. * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
  18822. */
  18823. translatePoints: function(x, y) {
  18824. y = isNaN(x[1]) ? y : x[1];
  18825. x = isNaN(x[0]) ? x : x[0];
  18826. var me = this,
  18827. relative = me.isStyle('position', 'relative'),
  18828. o = me.getXY(),
  18829. l = parseInt(me.getStyle('left'), 10),
  18830. t = parseInt(me.getStyle('top'), 10);
  18831. l = !isNaN(l) ? l : (relative ? 0 : me.dom.offsetLeft);
  18832. t = !isNaN(t) ? t : (relative ? 0 : me.dom.offsetTop);
  18833. return {left: (x - o[0] + l), top: (y - o[1] + t)};
  18834. },
  18835. /**
  18836. * Sets the element's box. Use getBox() on another element to get a box obj.
  18837. * If animate is true then width, height, x and y will be animated concurrently.
  18838. * @param {Object} box The box to fill {x, y, width, height}
  18839. * @param {Boolean} [adjust] Whether to adjust for box-model issues automatically
  18840. * @param {Boolean/Object} [animate] true for the default animation or a standard
  18841. * Element animation config object
  18842. * @return {Ext.dom.AbstractElement} this
  18843. */
  18844. setBox: function(box) {
  18845. var me = this,
  18846. width = box.width,
  18847. height = box.height,
  18848. top = box.top,
  18849. left = box.left;
  18850. if (left !== undefined) {
  18851. me.setLeft(left);
  18852. }
  18853. if (top !== undefined) {
  18854. me.setTop(top);
  18855. }
  18856. if (width !== undefined) {
  18857. me.setWidth(width);
  18858. }
  18859. if (height !== undefined) {
  18860. me.setHeight(height);
  18861. }
  18862. return this;
  18863. },
  18864. /**
  18865. * Return an object defining the area of this Element which can be passed to {@link #setBox} to
  18866. * set another Element's size/location to match this element.
  18867. *
  18868. * @param {Boolean} [contentBox] If true a box for the content of the element is returned.
  18869. * @param {Boolean} [local] If true the element's left and top are returned instead of page x/y.
  18870. * @return {Object} box An object in the format:
  18871. *
  18872. * {
  18873. * x: <Element's X position>,
  18874. * y: <Element's Y position>,
  18875. * width: <Element's width>,
  18876. * height: <Element's height>,
  18877. * bottom: <Element's lower bound>,
  18878. * right: <Element's rightmost bound>
  18879. * }
  18880. *
  18881. * The returned object may also be addressed as an Array where index 0 contains the X position
  18882. * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
  18883. */
  18884. getBox: function(contentBox, local) {
  18885. var me = this,
  18886. dom = me.dom,
  18887. width = dom.offsetWidth,
  18888. height = dom.offsetHeight,
  18889. xy, box, l, r, t, b;
  18890. if (!local) {
  18891. xy = me.getXY();
  18892. }
  18893. else if (contentBox) {
  18894. xy = [0,0];
  18895. }
  18896. else {
  18897. xy = [parseInt(me.getStyle("left"), 10) || 0, parseInt(me.getStyle("top"), 10) || 0];
  18898. }
  18899. if (!contentBox) {
  18900. box = {
  18901. x: xy[0],
  18902. y: xy[1],
  18903. 0: xy[0],
  18904. 1: xy[1],
  18905. width: width,
  18906. height: height
  18907. };
  18908. }
  18909. else {
  18910. l = me.getBorderWidth.call(me, "l") + me.getPadding.call(me, "l");
  18911. r = me.getBorderWidth.call(me, "r") + me.getPadding.call(me, "r");
  18912. t = me.getBorderWidth.call(me, "t") + me.getPadding.call(me, "t");
  18913. b = me.getBorderWidth.call(me, "b") + me.getPadding.call(me, "b");
  18914. box = {
  18915. x: xy[0] + l,
  18916. y: xy[1] + t,
  18917. 0: xy[0] + l,
  18918. 1: xy[1] + t,
  18919. width: width - (l + r),
  18920. height: height - (t + b)
  18921. };
  18922. }
  18923. box.left = box.x;
  18924. box.top = box.y;
  18925. box.right = box.x + box.width;
  18926. box.bottom = box.y + box.height;
  18927. return box;
  18928. },
  18929. /**
  18930. * Return an object defining the area of this Element which can be passed to {@link #setBox} to
  18931. * set another Element's size/location to match this element.
  18932. *
  18933. * @param {Boolean} [asRegion] If true an Ext.util.Region will be returned
  18934. * @return {Object} box An object in the format
  18935. *
  18936. * {
  18937. * left: <Element's X position>,
  18938. * top: <Element's Y position>,
  18939. * width: <Element's width>,
  18940. * height: <Element's height>,
  18941. * bottom: <Element's lower bound>,
  18942. * right: <Element's rightmost bound>
  18943. * }
  18944. *
  18945. * The returned object may also be addressed as an Array where index 0 contains the X position
  18946. * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
  18947. */
  18948. getPageBox: function(getRegion) {
  18949. var me = this,
  18950. el = me.dom,
  18951. w = el.offsetWidth,
  18952. h = el.offsetHeight,
  18953. xy = me.getXY(),
  18954. t = xy[1],
  18955. r = xy[0] + w,
  18956. b = xy[1] + h,
  18957. l = xy[0];
  18958. if (!el) {
  18959. return new Ext.util.Region();
  18960. }
  18961. if (getRegion) {
  18962. return new Ext.util.Region(t, r, b, l);
  18963. }
  18964. else {
  18965. return {
  18966. left: l,
  18967. top: t,
  18968. width: w,
  18969. height: h,
  18970. right: r,
  18971. bottom: b
  18972. };
  18973. }
  18974. }
  18975. });
  18976. }());
  18977. //@tag dom,core
  18978. //@require Ext.dom.AbstractElement-position
  18979. //@define Ext.dom.AbstractElement-style
  18980. //@define Ext.dom.AbstractElement
  18981. /**
  18982. * @class Ext.dom.AbstractElement
  18983. */
  18984. (function(){
  18985. // local style camelizing for speed
  18986. var Element = Ext.dom.AbstractElement,
  18987. view = document.defaultView,
  18988. array = Ext.Array,
  18989. trimRe = /^\s+|\s+$/g,
  18990. wordsRe = /\w/g,
  18991. spacesRe = /\s+/,
  18992. transparentRe = /^(?:transparent|(?:rgba[(](?:\s*\d+\s*[,]){3}\s*0\s*[)]))$/i,
  18993. hasClassList = Ext.supports.ClassList,
  18994. PADDING = 'padding',
  18995. MARGIN = 'margin',
  18996. BORDER = 'border',
  18997. LEFT_SUFFIX = '-left',
  18998. RIGHT_SUFFIX = '-right',
  18999. TOP_SUFFIX = '-top',
  19000. BOTTOM_SUFFIX = '-bottom',
  19001. WIDTH = '-width',
  19002. // special markup used throughout Ext when box wrapping elements
  19003. borders = {l: BORDER + LEFT_SUFFIX + WIDTH, r: BORDER + RIGHT_SUFFIX + WIDTH, t: BORDER + TOP_SUFFIX + WIDTH, b: BORDER + BOTTOM_SUFFIX + WIDTH},
  19004. paddings = {l: PADDING + LEFT_SUFFIX, r: PADDING + RIGHT_SUFFIX, t: PADDING + TOP_SUFFIX, b: PADDING + BOTTOM_SUFFIX},
  19005. margins = {l: MARGIN + LEFT_SUFFIX, r: MARGIN + RIGHT_SUFFIX, t: MARGIN + TOP_SUFFIX, b: MARGIN + BOTTOM_SUFFIX};
  19006. Element.override({
  19007. /**
  19008. * This shared object is keyed by style name (e.g., 'margin-left' or 'marginLeft'). The
  19009. * values are objects with the following properties:
  19010. *
  19011. * * `name` (String) : The actual name to be presented to the DOM. This is typically the value
  19012. * returned by {@link #normalize}.
  19013. * * `get` (Function) : A hook function that will perform the get on this style. These
  19014. * functions receive "(dom, el)" arguments. The `dom` parameter is the DOM Element
  19015. * from which to get ths tyle. The `el` argument (may be null) is the Ext.Element.
  19016. * * `set` (Function) : A hook function that will perform the set on this style. These
  19017. * functions receive "(dom, value, el)" arguments. The `dom` parameter is the DOM Element
  19018. * from which to get ths tyle. The `value` parameter is the new value for the style. The
  19019. * `el` argument (may be null) is the Ext.Element.
  19020. *
  19021. * The `this` pointer is the object that contains `get` or `set`, which means that
  19022. * `this.name` can be accessed if needed. The hook functions are both optional.
  19023. * @private
  19024. */
  19025. styleHooks: {},
  19026. // private
  19027. addStyles : function(sides, styles){
  19028. var totalSize = 0,
  19029. sidesArr = (sides || '').match(wordsRe),
  19030. i,
  19031. len = sidesArr.length,
  19032. side,
  19033. styleSides = [];
  19034. if (len == 1) {
  19035. totalSize = Math.abs(parseFloat(this.getStyle(styles[sidesArr[0]])) || 0);
  19036. } else if (len) {
  19037. for (i = 0; i < len; i++) {
  19038. side = sidesArr[i];
  19039. styleSides.push(styles[side]);
  19040. }
  19041. //Gather all at once, returning a hash
  19042. styleSides = this.getStyle(styleSides);
  19043. for (i=0; i < len; i++) {
  19044. side = sidesArr[i];
  19045. totalSize += Math.abs(parseFloat(styleSides[styles[side]]) || 0);
  19046. }
  19047. }
  19048. return totalSize;
  19049. },
  19050. /**
  19051. * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
  19052. * @param {String/String[]} className The CSS classes to add separated by space, or an array of classes
  19053. * @return {Ext.dom.Element} this
  19054. * @method
  19055. */
  19056. addCls: hasClassList ?
  19057. function (className) {
  19058. if (String(className).indexOf('undefined') > -1) {
  19059. Ext.Logger.warn("called with an undefined className: " + className);
  19060. }
  19061. var me = this,
  19062. dom = me.dom,
  19063. classList,
  19064. newCls,
  19065. i,
  19066. len,
  19067. cls;
  19068. if (typeof(className) == 'string') {
  19069. // split string on spaces to make an array of className
  19070. className = className.replace(trimRe, '').split(spacesRe);
  19071. }
  19072. // the gain we have here is that we can skip parsing className and use the
  19073. // classList.contains method, so now O(M) not O(M+N)
  19074. if (dom && className && !!(len = className.length)) {
  19075. if (!dom.className) {
  19076. dom.className = className.join(' ');
  19077. } else {
  19078. classList = dom.classList;
  19079. for (i = 0; i < len; ++i) {
  19080. cls = className[i];
  19081. if (cls) {
  19082. if (!classList.contains(cls)) {
  19083. if (newCls) {
  19084. newCls.push(cls);
  19085. } else {
  19086. newCls = dom.className.replace(trimRe, '');
  19087. newCls = newCls ? [newCls, cls] : [cls];
  19088. }
  19089. }
  19090. }
  19091. }
  19092. if (newCls) {
  19093. dom.className = newCls.join(' '); // write to DOM once
  19094. }
  19095. }
  19096. }
  19097. return me;
  19098. } :
  19099. function(className) {
  19100. if (String(className).indexOf('undefined') > -1) {
  19101. Ext.Logger.warn("called with an undefined className: '" + className + "'");
  19102. }
  19103. var me = this,
  19104. dom = me.dom,
  19105. changed,
  19106. elClasses;
  19107. if (dom && className && className.length) {
  19108. elClasses = Ext.Element.mergeClsList(dom.className, className);
  19109. if (elClasses.changed) {
  19110. dom.className = elClasses.join(' '); // write to DOM once
  19111. }
  19112. }
  19113. return me;
  19114. },
  19115. /**
  19116. * Removes one or more CSS classes from the element.
  19117. * @param {String/String[]} className The CSS classes to remove separated by space, or an array of classes
  19118. * @return {Ext.dom.Element} this
  19119. */
  19120. removeCls: function(className) {
  19121. var me = this,
  19122. dom = me.dom,
  19123. len,
  19124. elClasses;
  19125. if (typeof(className) == 'string') {
  19126. // split string on spaces to make an array of className
  19127. className = className.replace(trimRe, '').split(spacesRe);
  19128. }
  19129. if (dom && dom.className && className && !!(len = className.length)) {
  19130. if (len == 1 && hasClassList) {
  19131. if (className[0]) {
  19132. dom.classList.remove(className[0]); // one DOM write
  19133. }
  19134. } else {
  19135. elClasses = Ext.Element.removeCls(dom.className, className);
  19136. if (elClasses.changed) {
  19137. dom.className = elClasses.join(' ');
  19138. }
  19139. }
  19140. }
  19141. return me;
  19142. },
  19143. /**
  19144. * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
  19145. * @param {String/String[]} className The CSS class to add, or an array of classes
  19146. * @return {Ext.dom.Element} this
  19147. */
  19148. radioCls: function(className) {
  19149. var cn = this.dom.parentNode.childNodes,
  19150. v,
  19151. i, len;
  19152. className = Ext.isArray(className) ? className: [className];
  19153. for (i = 0, len = cn.length; i < len; i++) {
  19154. v = cn[i];
  19155. if (v && v.nodeType == 1) {
  19156. Ext.fly(v, '_internal').removeCls(className);
  19157. }
  19158. }
  19159. return this.addCls(className);
  19160. },
  19161. /**
  19162. * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
  19163. * @param {String} className The CSS class to toggle
  19164. * @return {Ext.dom.Element} this
  19165. * @method
  19166. */
  19167. toggleCls: hasClassList ?
  19168. function (className) {
  19169. var me = this,
  19170. dom = me.dom;
  19171. if (dom) {
  19172. className = className.replace(trimRe, '');
  19173. if (className) {
  19174. dom.classList.toggle(className);
  19175. }
  19176. }
  19177. return me;
  19178. } :
  19179. function(className) {
  19180. var me = this;
  19181. return me.hasCls(className) ? me.removeCls(className) : me.addCls(className);
  19182. },
  19183. /**
  19184. * Checks if the specified CSS class exists on this element's DOM node.
  19185. * @param {String} className The CSS class to check for
  19186. * @return {Boolean} True if the class exists, else false
  19187. * @method
  19188. */
  19189. hasCls: hasClassList ?
  19190. function (className) {
  19191. var dom = this.dom;
  19192. return (dom && className) ? dom.classList.contains(className) : false;
  19193. } :
  19194. function(className) {
  19195. var dom = this.dom;
  19196. return dom ? className && (' '+dom.className+' ').indexOf(' '+className+' ') != -1 : false;
  19197. },
  19198. /**
  19199. * Replaces a CSS class on the element with another. If the old name does not exist, the new name will simply be added.
  19200. * @param {String} oldClassName The CSS class to replace
  19201. * @param {String} newClassName The replacement CSS class
  19202. * @return {Ext.dom.Element} this
  19203. */
  19204. replaceCls: function(oldClassName, newClassName){
  19205. return this.removeCls(oldClassName).addCls(newClassName);
  19206. },
  19207. /**
  19208. * Checks if the current value of a style is equal to a given value.
  19209. * @param {String} style property whose value is returned.
  19210. * @param {String} value to check against.
  19211. * @return {Boolean} true for when the current value equals the given value.
  19212. */
  19213. isStyle: function(style, val) {
  19214. return this.getStyle(style) == val;
  19215. },
  19216. /**
  19217. * Returns a named style property based on computed/currentStyle (primary) and
  19218. * inline-style if primary is not available.
  19219. *
  19220. * @param {String/String[]} property The style property (or multiple property names
  19221. * in an array) whose value is returned.
  19222. * @param {Boolean} [inline=false] if `true` only inline styles will be returned.
  19223. * @return {String/Object} The current value of the style property for this element
  19224. * (or a hash of named style values if multiple property arguments are requested).
  19225. * @method
  19226. */
  19227. getStyle: function (property, inline) {
  19228. var me = this,
  19229. dom = me.dom,
  19230. multiple = typeof property != 'string',
  19231. hooks = me.styleHooks,
  19232. prop = property,
  19233. props = prop,
  19234. len = 1,
  19235. domStyle, camel, values, hook, out, style, i;
  19236. if (multiple) {
  19237. values = {};
  19238. prop = props[0];
  19239. i = 0;
  19240. if (!(len = props.length)) {
  19241. return values;
  19242. }
  19243. }
  19244. if (!dom || dom.documentElement) {
  19245. return values || '';
  19246. }
  19247. domStyle = dom.style;
  19248. if (inline) {
  19249. style = domStyle;
  19250. } else {
  19251. // Caution: Firefox will not render "presentation" (ie. computed styles) in
  19252. // iframes that are display:none or those inheriting display:none. Similar
  19253. // issues with legacy Safari.
  19254. //
  19255. style = dom.ownerDocument.defaultView.getComputedStyle(dom, null);
  19256. // fallback to inline style if rendering context not available
  19257. if (!style) {
  19258. inline = true;
  19259. style = domStyle;
  19260. }
  19261. }
  19262. do {
  19263. hook = hooks[prop];
  19264. if (!hook) {
  19265. hooks[prop] = hook = { name: Element.normalize(prop) };
  19266. }
  19267. if (hook.get) {
  19268. out = hook.get(dom, me, inline, style);
  19269. } else {
  19270. camel = hook.name;
  19271. out = style[camel];
  19272. }
  19273. if (!multiple) {
  19274. return out;
  19275. }
  19276. values[prop] = out;
  19277. prop = props[++i];
  19278. } while (i < len);
  19279. return values;
  19280. },
  19281. getStyles: function () {
  19282. var props = Ext.Array.slice(arguments),
  19283. len = props.length,
  19284. inline;
  19285. if (len && typeof props[len-1] == 'boolean') {
  19286. inline = props.pop();
  19287. }
  19288. return this.getStyle(props, inline);
  19289. },
  19290. /**
  19291. * Returns true if the value of the given property is visually transparent. This
  19292. * may be due to a 'transparent' style value or an rgba value with 0 in the alpha
  19293. * component.
  19294. * @param {String} prop The style property whose value is to be tested.
  19295. * @return {Boolean} True if the style property is visually transparent.
  19296. */
  19297. isTransparent: function (prop) {
  19298. var value = this.getStyle(prop);
  19299. return value ? transparentRe.test(value) : false;
  19300. },
  19301. /**
  19302. * Wrapper for setting style properties, also takes single object parameter of multiple styles.
  19303. * @param {String/Object} property The style property to be set, or an object of multiple styles.
  19304. * @param {String} [value] The value to apply to the given property, or null if an object was passed.
  19305. * @return {Ext.dom.Element} this
  19306. */
  19307. setStyle: function(prop, value) {
  19308. var me = this,
  19309. dom = me.dom,
  19310. hooks = me.styleHooks,
  19311. style = dom.style,
  19312. name = prop,
  19313. hook;
  19314. // we don't promote the 2-arg form to object-form to avoid the overhead...
  19315. if (typeof name == 'string') {
  19316. hook = hooks[name];
  19317. if (!hook) {
  19318. hooks[name] = hook = { name: Element.normalize(name) };
  19319. }
  19320. value = (value == null) ? '' : value;
  19321. if (hook.set) {
  19322. hook.set(dom, value, me);
  19323. } else {
  19324. style[hook.name] = value;
  19325. }
  19326. if (hook.afterSet) {
  19327. hook.afterSet(dom, value, me);
  19328. }
  19329. } else {
  19330. for (name in prop) {
  19331. if (prop.hasOwnProperty(name)) {
  19332. hook = hooks[name];
  19333. if (!hook) {
  19334. hooks[name] = hook = { name: Element.normalize(name) };
  19335. }
  19336. value = prop[name];
  19337. value = (value == null) ? '' : value;
  19338. if (hook.set) {
  19339. hook.set(dom, value, me);
  19340. } else {
  19341. style[hook.name] = value;
  19342. }
  19343. if (hook.afterSet) {
  19344. hook.afterSet(dom, value, me);
  19345. }
  19346. }
  19347. }
  19348. }
  19349. return me;
  19350. },
  19351. /**
  19352. * Returns the offset height of the element
  19353. * @param {Boolean} [contentHeight] true to get the height minus borders and padding
  19354. * @return {Number} The element's height
  19355. */
  19356. getHeight: function(contentHeight) {
  19357. var dom = this.dom,
  19358. height = contentHeight ? (dom.clientHeight - this.getPadding("tb")) : dom.offsetHeight;
  19359. return height > 0 ? height: 0;
  19360. },
  19361. /**
  19362. * Returns the offset width of the element
  19363. * @param {Boolean} [contentWidth] true to get the width minus borders and padding
  19364. * @return {Number} The element's width
  19365. */
  19366. getWidth: function(contentWidth) {
  19367. var dom = this.dom,
  19368. width = contentWidth ? (dom.clientWidth - this.getPadding("lr")) : dom.offsetWidth;
  19369. return width > 0 ? width: 0;
  19370. },
  19371. /**
  19372. * Set the width of this Element.
  19373. * @param {Number/String} width The new width. This may be one of:
  19374. *
  19375. * - A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).
  19376. * - A String used to set the CSS width style. Animation may **not** be used.
  19377. *
  19378. * @param {Boolean/Object} [animate] true for the default animation or a standard Element animation config object
  19379. * @return {Ext.dom.Element} this
  19380. */
  19381. setWidth: function(width) {
  19382. var me = this;
  19383. me.dom.style.width = Element.addUnits(width);
  19384. return me;
  19385. },
  19386. /**
  19387. * Set the height of this Element.
  19388. *
  19389. * // change the height to 200px and animate with default configuration
  19390. * Ext.fly('elementId').setHeight(200, true);
  19391. *
  19392. * // change the height to 150px and animate with a custom configuration
  19393. * Ext.fly('elId').setHeight(150, {
  19394. * duration : 500, // animation will have a duration of .5 seconds
  19395. * // will change the content to "finished"
  19396. * callback: function(){ this.{@link #update}("finished"); }
  19397. * });
  19398. *
  19399. * @param {Number/String} height The new height. This may be one of:
  19400. *
  19401. * - A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels.)
  19402. * - A String used to set the CSS height style. Animation may **not** be used.
  19403. *
  19404. * @param {Boolean/Object} [animate] true for the default animation or a standard Element animation config object
  19405. * @return {Ext.dom.Element} this
  19406. */
  19407. setHeight: function(height) {
  19408. var me = this;
  19409. me.dom.style.height = Element.addUnits(height);
  19410. return me;
  19411. },
  19412. /**
  19413. * Gets the width of the border(s) for the specified side(s)
  19414. * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
  19415. * passing `'lr'` would get the border **l**eft width + the border **r**ight width.
  19416. * @return {Number} The width of the sides passed added together
  19417. */
  19418. getBorderWidth: function(side){
  19419. return this.addStyles(side, borders);
  19420. },
  19421. /**
  19422. * Gets the width of the padding(s) for the specified side(s)
  19423. * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
  19424. * passing `'lr'` would get the padding **l**eft + the padding **r**ight.
  19425. * @return {Number} The padding of the sides passed added together
  19426. */
  19427. getPadding: function(side){
  19428. return this.addStyles(side, paddings);
  19429. },
  19430. margins : margins,
  19431. /**
  19432. * More flexible version of {@link #setStyle} for setting style properties.
  19433. * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
  19434. * a function which returns such a specification.
  19435. * @return {Ext.dom.Element} this
  19436. */
  19437. applyStyles: function(styles) {
  19438. if (styles) {
  19439. var i,
  19440. len,
  19441. dom = this.dom;
  19442. if (typeof styles == 'function') {
  19443. styles = styles.call();
  19444. }
  19445. if (typeof styles == 'string') {
  19446. styles = Ext.util.Format.trim(styles).split(/\s*(?::|;)\s*/);
  19447. for (i = 0, len = styles.length; i < len;) {
  19448. dom.style[Element.normalize(styles[i++])] = styles[i++];
  19449. }
  19450. }
  19451. else if (typeof styles == 'object') {
  19452. this.setStyle(styles);
  19453. }
  19454. }
  19455. },
  19456. /**
  19457. * Set the size of this Element. If animation is true, both width and height will be animated concurrently.
  19458. * @param {Number/String} width The new width. This may be one of:
  19459. *
  19460. * - A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).
  19461. * - A String used to set the CSS width style. Animation may **not** be used.
  19462. * - A size object in the format `{width: widthValue, height: heightValue}`.
  19463. *
  19464. * @param {Number/String} height The new height. This may be one of:
  19465. *
  19466. * - A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).
  19467. * - A String used to set the CSS height style. Animation may **not** be used.
  19468. *
  19469. * @param {Boolean/Object} [animate] true for the default animation or a standard Element animation config object
  19470. * @return {Ext.dom.Element} this
  19471. */
  19472. setSize: function(width, height) {
  19473. var me = this,
  19474. style = me.dom.style;
  19475. if (Ext.isObject(width)) {
  19476. // in case of object from getSize()
  19477. height = width.height;
  19478. width = width.width;
  19479. }
  19480. style.width = Element.addUnits(width);
  19481. style.height = Element.addUnits(height);
  19482. return me;
  19483. },
  19484. /**
  19485. * Returns the dimensions of the element available to lay content out in.
  19486. *
  19487. * If the element (or any ancestor element) has CSS style `display: none`, the dimensions will be zero.
  19488. *
  19489. * Example:
  19490. *
  19491. * var vpSize = Ext.getBody().getViewSize();
  19492. *
  19493. * // all Windows created afterwards will have a default value of 90% height and 95% width
  19494. * Ext.Window.override({
  19495. * width: vpSize.width * 0.9,
  19496. * height: vpSize.height * 0.95
  19497. * });
  19498. * // To handle window resizing you would have to hook onto onWindowResize.
  19499. *
  19500. * getViewSize utilizes clientHeight/clientWidth which excludes sizing of scrollbars.
  19501. * To obtain the size including scrollbars, use getStyleSize
  19502. *
  19503. * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
  19504. *
  19505. * @return {Object} Object describing width and height.
  19506. * @return {Number} return.width
  19507. * @return {Number} return.height
  19508. */
  19509. getViewSize: function() {
  19510. var doc = document,
  19511. dom = this.dom;
  19512. if (dom == doc || dom == doc.body) {
  19513. return {
  19514. width: Element.getViewportWidth(),
  19515. height: Element.getViewportHeight()
  19516. };
  19517. }
  19518. else {
  19519. return {
  19520. width: dom.clientWidth,
  19521. height: dom.clientHeight
  19522. };
  19523. }
  19524. },
  19525. /**
  19526. * Returns the size of the element.
  19527. * @param {Boolean} [contentSize] true to get the width/size minus borders and padding
  19528. * @return {Object} An object containing the element's size:
  19529. * @return {Number} return.width
  19530. * @return {Number} return.height
  19531. */
  19532. getSize: function(contentSize) {
  19533. var dom = this.dom;
  19534. return {
  19535. width: Math.max(0, contentSize ? (dom.clientWidth - this.getPadding("lr")) : dom.offsetWidth),
  19536. height: Math.max(0, contentSize ? (dom.clientHeight - this.getPadding("tb")) : dom.offsetHeight)
  19537. };
  19538. },
  19539. /**
  19540. * Forces the browser to repaint this element
  19541. * @return {Ext.dom.Element} this
  19542. */
  19543. repaint: function(){
  19544. var dom = this.dom;
  19545. this.addCls(Ext.baseCSSPrefix + 'repaint');
  19546. setTimeout(function(){
  19547. Ext.fly(dom).removeCls(Ext.baseCSSPrefix + 'repaint');
  19548. }, 1);
  19549. return this;
  19550. },
  19551. /**
  19552. * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
  19553. * then it returns the calculated width of the sides (see getPadding)
  19554. * @param {String} [sides] Any combination of l, r, t, b to get the sum of those sides
  19555. * @return {Object/Number}
  19556. */
  19557. getMargin: function(side){
  19558. var me = this,
  19559. hash = {t:"top", l:"left", r:"right", b: "bottom"},
  19560. key,
  19561. o,
  19562. margins;
  19563. if (!side) {
  19564. margins = [];
  19565. for (key in me.margins) {
  19566. if(me.margins.hasOwnProperty(key)) {
  19567. margins.push(me.margins[key]);
  19568. }
  19569. }
  19570. o = me.getStyle(margins);
  19571. if(o && typeof o == 'object') {
  19572. //now mixin nomalized values (from hash table)
  19573. for (key in me.margins) {
  19574. if(me.margins.hasOwnProperty(key)) {
  19575. o[hash[key]] = parseFloat(o[me.margins[key]]) || 0;
  19576. }
  19577. }
  19578. }
  19579. return o;
  19580. } else {
  19581. return me.addStyles.call(me, side, me.margins);
  19582. }
  19583. },
  19584. /**
  19585. * Puts a mask over this element to disable user interaction. Requires core.css.
  19586. * This method can only be applied to elements which accept child nodes.
  19587. * @param {String} [msg] A message to display in the mask
  19588. * @param {String} [msgCls] A css class to apply to the msg element
  19589. */
  19590. mask: function(msg, msgCls, transparent) {
  19591. var me = this,
  19592. dom = me.dom,
  19593. data = (me.$cache || me.getCache()).data,
  19594. el = data.mask,
  19595. mask,
  19596. size,
  19597. cls = '',
  19598. prefix = Ext.baseCSSPrefix;
  19599. me.addCls(prefix + 'masked');
  19600. if (me.getStyle("position") == "static") {
  19601. me.addCls(prefix + 'masked-relative');
  19602. }
  19603. if (el) {
  19604. el.remove();
  19605. }
  19606. if (msgCls && typeof msgCls == 'string' ) {
  19607. cls = ' ' + msgCls;
  19608. }
  19609. else {
  19610. cls = ' ' + prefix + 'mask-gray';
  19611. }
  19612. mask = me.createChild({
  19613. cls: prefix + 'mask' + ((transparent !== false) ? '' : (' ' + prefix + 'mask-gray')),
  19614. html: msg ? ('<div class="' + (msgCls || (prefix + 'mask-message')) + '">' + msg + '</div>') : ''
  19615. });
  19616. size = me.getSize();
  19617. data.mask = mask;
  19618. if (dom === document.body) {
  19619. size.height = window.innerHeight;
  19620. if (me.orientationHandler) {
  19621. Ext.EventManager.unOrientationChange(me.orientationHandler, me);
  19622. }
  19623. me.orientationHandler = function() {
  19624. size = me.getSize();
  19625. size.height = window.innerHeight;
  19626. mask.setSize(size);
  19627. };
  19628. Ext.EventManager.onOrientationChange(me.orientationHandler, me);
  19629. }
  19630. mask.setSize(size);
  19631. if (Ext.is.iPad) {
  19632. Ext.repaint();
  19633. }
  19634. },
  19635. /**
  19636. * Removes a previously applied mask.
  19637. */
  19638. unmask: function() {
  19639. var me = this,
  19640. data = (me.$cache || me.getCache()).data,
  19641. mask = data.mask,
  19642. prefix = Ext.baseCSSPrefix;
  19643. if (mask) {
  19644. mask.remove();
  19645. delete data.mask;
  19646. }
  19647. me.removeCls([prefix + 'masked', prefix + 'masked-relative']);
  19648. if (me.dom === document.body) {
  19649. Ext.EventManager.unOrientationChange(me.orientationHandler, me);
  19650. delete me.orientationHandler;
  19651. }
  19652. }
  19653. });
  19654. /**
  19655. * Creates mappings for 'margin-before' to 'marginLeft' (etc.) given the output
  19656. * map and an ordering pair (e.g., ['left', 'right']). The ordering pair is in
  19657. * before/after order.
  19658. */
  19659. Element.populateStyleMap = function (map, order) {
  19660. var baseStyles = ['margin-', 'padding-', 'border-width-'],
  19661. beforeAfter = ['before', 'after'],
  19662. index, style, name, i;
  19663. for (index = baseStyles.length; index--; ) {
  19664. for (i = 2; i--; ) {
  19665. style = baseStyles[index] + beforeAfter[i]; // margin-before
  19666. // ex: maps margin-before and marginBefore to marginLeft
  19667. map[Element.normalize(style)] = map[style] = {
  19668. name: Element.normalize(baseStyles[index] + order[i])
  19669. };
  19670. }
  19671. }
  19672. };
  19673. Ext.onReady(function () {
  19674. var supports = Ext.supports,
  19675. styleHooks,
  19676. colorStyles, i, name, camel;
  19677. function fixTransparent (dom, el, inline, style) {
  19678. var value = style[this.name] || '';
  19679. return transparentRe.test(value) ? 'transparent' : value;
  19680. }
  19681. function fixRightMargin (dom, el, inline, style) {
  19682. var result = style.marginRight,
  19683. domStyle, display;
  19684. // Ignore cases when the margin is correctly reported as 0, the bug only shows
  19685. // numbers larger.
  19686. if (result != '0px') {
  19687. domStyle = dom.style;
  19688. display = domStyle.display;
  19689. domStyle.display = 'inline-block';
  19690. result = (inline ? style : dom.ownerDocument.defaultView.getComputedStyle(dom, null)).marginRight;
  19691. domStyle.display = display;
  19692. }
  19693. return result;
  19694. }
  19695. function fixRightMarginAndInputFocus (dom, el, inline, style) {
  19696. var result = style.marginRight,
  19697. domStyle, cleaner, display;
  19698. if (result != '0px') {
  19699. domStyle = dom.style;
  19700. cleaner = Element.getRightMarginFixCleaner(dom);
  19701. display = domStyle.display;
  19702. domStyle.display = 'inline-block';
  19703. result = (inline ? style : dom.ownerDocument.defaultView.getComputedStyle(dom, '')).marginRight;
  19704. domStyle.display = display;
  19705. cleaner();
  19706. }
  19707. return result;
  19708. }
  19709. styleHooks = Element.prototype.styleHooks;
  19710. // Populate the LTR flavors of margin-before et.al. (see Ext.rtl.AbstractElement):
  19711. Element.populateStyleMap(styleHooks, ['left', 'right']);
  19712. // Ext.supports needs to be initialized (we run very early in the onready sequence),
  19713. // but it is OK to call Ext.supports.init() more times than necessary...
  19714. if (supports.init) {
  19715. supports.init();
  19716. }
  19717. // Fix bug caused by this: https://bugs.webkit.org/show_bug.cgi?id=13343
  19718. if (!supports.RightMargin) {
  19719. styleHooks.marginRight = styleHooks['margin-right'] = {
  19720. name: 'marginRight',
  19721. // TODO - Touch should use conditional compilation here or ensure that the
  19722. // underlying Ext.supports flags are set correctly...
  19723. get: (supports.DisplayChangeInputSelectionBug || supports.DisplayChangeTextAreaSelectionBug) ?
  19724. fixRightMarginAndInputFocus : fixRightMargin
  19725. };
  19726. }
  19727. if (!supports.TransparentColor) {
  19728. colorStyles = ['background-color', 'border-color', 'color', 'outline-color'];
  19729. for (i = colorStyles.length; i--; ) {
  19730. name = colorStyles[i];
  19731. camel = Element.normalize(name);
  19732. styleHooks[name] = styleHooks[camel] = {
  19733. name: camel,
  19734. get: fixTransparent
  19735. };
  19736. }
  19737. }
  19738. });
  19739. }());
  19740. //@tag dom,core
  19741. //@require Ext.dom.AbstractElement-style
  19742. //@define Ext.dom.AbstractElement-traversal
  19743. //@define Ext.dom.AbstractElement
  19744. /**
  19745. * @class Ext.dom.AbstractElement
  19746. */
  19747. Ext.dom.AbstractElement.override({
  19748. /**
  19749. * Looks at this node and then at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
  19750. * @param {String} selector The simple selector to test
  19751. * @param {Number/String/HTMLElement/Ext.Element} [limit]
  19752. * The max depth to search as a number or an element which causes the upward traversal to stop
  19753. * and is <b>not</b> considered for inclusion as the result. (defaults to 50 || document.documentElement)
  19754. * @param {Boolean} [returnEl=false] True to return a Ext.Element object instead of DOM node
  19755. * @return {HTMLElement} The matching DOM node (or null if no match was found)
  19756. */
  19757. findParent: function(simpleSelector, limit, returnEl) {
  19758. var target = this.dom,
  19759. topmost = document.documentElement,
  19760. depth = 0,
  19761. stopEl;
  19762. limit = limit || 50;
  19763. if (isNaN(limit)) {
  19764. stopEl = Ext.getDom(limit);
  19765. limit = Number.MAX_VALUE;
  19766. }
  19767. while (target && target.nodeType == 1 && depth < limit && target != topmost && target != stopEl) {
  19768. if (Ext.DomQuery.is(target, simpleSelector)) {
  19769. return returnEl ? Ext.get(target) : target;
  19770. }
  19771. depth++;
  19772. target = target.parentNode;
  19773. }
  19774. return null;
  19775. },
  19776. /**
  19777. * Looks at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
  19778. * @param {String} selector The simple selector to test
  19779. * @param {Number/String/HTMLElement/Ext.Element} [limit]
  19780. * The max depth to search as a number or an element which causes the upward traversal to stop
  19781. * and is <b>not</b> considered for inclusion as the result. (defaults to 50 || document.documentElement)
  19782. * @param {Boolean} [returnEl=false] True to return a Ext.Element object instead of DOM node
  19783. * @return {HTMLElement} The matching DOM node (or null if no match was found)
  19784. */
  19785. findParentNode: function(simpleSelector, limit, returnEl) {
  19786. var p = Ext.fly(this.dom.parentNode, '_internal');
  19787. return p ? p.findParent(simpleSelector, limit, returnEl) : null;
  19788. },
  19789. /**
  19790. * Walks up the dom looking for a parent node that matches the passed simple selector (e.g. div.some-class or span:first-child).
  19791. * This is a shortcut for findParentNode() that always returns an Ext.dom.Element.
  19792. * @param {String} selector The simple selector to test
  19793. * @param {Number/String/HTMLElement/Ext.Element} [limit]
  19794. * The max depth to search as a number or an element which causes the upward traversal to stop
  19795. * and is <b>not</b> considered for inclusion as the result. (defaults to 50 || document.documentElement)
  19796. * @return {Ext.Element} The matching DOM node (or null if no match was found)
  19797. */
  19798. up: function(simpleSelector, limit) {
  19799. return this.findParentNode(simpleSelector, limit, true);
  19800. },
  19801. /**
  19802. * Creates a {@link Ext.CompositeElement} for child nodes based on the passed CSS selector (the selector should not contain an id).
  19803. * @param {String} selector The CSS selector
  19804. * @param {Boolean} [unique] True to create a unique Ext.Element for each element. Defaults to a shared flyweight object.
  19805. * @return {Ext.CompositeElement} The composite element
  19806. */
  19807. select: function(selector, composite) {
  19808. return Ext.dom.Element.select(selector, this.dom, composite);
  19809. },
  19810. /**
  19811. * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
  19812. * @param {String} selector The CSS selector
  19813. * @return {HTMLElement[]} An array of the matched nodes
  19814. */
  19815. query: function(selector) {
  19816. return Ext.DomQuery.select(selector, this.dom);
  19817. },
  19818. /**
  19819. * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
  19820. * @param {String} selector The CSS selector
  19821. * @param {Boolean} [returnDom=false] True to return the DOM node instead of Ext.dom.Element
  19822. * @return {HTMLElement/Ext.dom.Element} The child Ext.dom.Element (or DOM node if returnDom = true)
  19823. */
  19824. down: function(selector, returnDom) {
  19825. var n = Ext.DomQuery.selectNode(selector, this.dom);
  19826. return returnDom ? n : Ext.get(n);
  19827. },
  19828. /**
  19829. * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
  19830. * @param {String} selector The CSS selector
  19831. * @param {Boolean} [returnDom=false] True to return the DOM node instead of Ext.dom.Element.
  19832. * @return {HTMLElement/Ext.dom.Element} The child Ext.dom.Element (or DOM node if returnDom = true)
  19833. */
  19834. child: function(selector, returnDom) {
  19835. var node,
  19836. me = this,
  19837. id;
  19838. // Pull the ID from the DOM (Ext.id also ensures that there *is* an ID).
  19839. // If this object is a Flyweight, it will not have an ID
  19840. id = Ext.id(me.dom);
  19841. // Escape "invalid" chars
  19842. id = Ext.escapeId(id);
  19843. node = Ext.DomQuery.selectNode('#' + id + " > " + selector, me.dom);
  19844. return returnDom ? node : Ext.get(node);
  19845. },
  19846. /**
  19847. * Gets the parent node for this element, optionally chaining up trying to match a selector
  19848. * @param {String} [selector] Find a parent node that matches the passed simple selector
  19849. * @param {Boolean} [returnDom=false] True to return a raw dom node instead of an Ext.dom.Element
  19850. * @return {Ext.dom.Element/HTMLElement} The parent node or null
  19851. */
  19852. parent: function(selector, returnDom) {
  19853. return this.matchNode('parentNode', 'parentNode', selector, returnDom);
  19854. },
  19855. /**
  19856. * Gets the next sibling, skipping text nodes
  19857. * @param {String} [selector] Find the next sibling that matches the passed simple selector
  19858. * @param {Boolean} [returnDom=false] True to return a raw dom node instead of an Ext.dom.Element
  19859. * @return {Ext.dom.Element/HTMLElement} The next sibling or null
  19860. */
  19861. next: function(selector, returnDom) {
  19862. return this.matchNode('nextSibling', 'nextSibling', selector, returnDom);
  19863. },
  19864. /**
  19865. * Gets the previous sibling, skipping text nodes
  19866. * @param {String} [selector] Find the previous sibling that matches the passed simple selector
  19867. * @param {Boolean} [returnDom=false] True to return a raw dom node instead of an Ext.dom.Element
  19868. * @return {Ext.dom.Element/HTMLElement} The previous sibling or null
  19869. */
  19870. prev: function(selector, returnDom) {
  19871. return this.matchNode('previousSibling', 'previousSibling', selector, returnDom);
  19872. },
  19873. /**
  19874. * Gets the first child, skipping text nodes
  19875. * @param {String} [selector] Find the next sibling that matches the passed simple selector
  19876. * @param {Boolean} [returnDom=false] True to return a raw dom node instead of an Ext.dom.Element
  19877. * @return {Ext.dom.Element/HTMLElement} The first child or null
  19878. */
  19879. first: function(selector, returnDom) {
  19880. return this.matchNode('nextSibling', 'firstChild', selector, returnDom);
  19881. },
  19882. /**
  19883. * Gets the last child, skipping text nodes
  19884. * @param {String} [selector] Find the previous sibling that matches the passed simple selector
  19885. * @param {Boolean} [returnDom=false] True to return a raw dom node instead of an Ext.dom.Element
  19886. * @return {Ext.dom.Element/HTMLElement} The last child or null
  19887. */
  19888. last: function(selector, returnDom) {
  19889. return this.matchNode('previousSibling', 'lastChild', selector, returnDom);
  19890. },
  19891. matchNode: function(dir, start, selector, returnDom) {
  19892. if (!this.dom) {
  19893. return null;
  19894. }
  19895. var n = this.dom[start];
  19896. while (n) {
  19897. if (n.nodeType == 1 && (!selector || Ext.DomQuery.is(n, selector))) {
  19898. return !returnDom ? Ext.get(n) : n;
  19899. }
  19900. n = n[dir];
  19901. }
  19902. return null;
  19903. },
  19904. isAncestor: function(element) {
  19905. return this.self.isAncestor.call(this.self, this.dom, element);
  19906. }
  19907. });
  19908. //@tag dom,core
  19909. //@define Ext.DomHelper
  19910. //@define Ext.core.DomHelper
  19911. //@require Ext.dom.AbstractElement-traversal
  19912. /**
  19913. * @class Ext.DomHelper
  19914. * @extends Ext.dom.Helper
  19915. * @alternateClassName Ext.core.DomHelper
  19916. * @singleton
  19917. *
  19918. * The DomHelper class provides a layer of abstraction from DOM and transparently supports creating elements via DOM or
  19919. * using HTML fragments. It also has the ability to create HTML fragment templates from your DOM building code.
  19920. *
  19921. * # DomHelper element specification object
  19922. *
  19923. * A specification object is used when creating elements. Attributes of this object are assumed to be element
  19924. * attributes, except for 4 special attributes:
  19925. *
  19926. * - **tag** - The tag name of the element.
  19927. * - **children** or **cn** - An array of the same kind of element definition objects to be created and appended.
  19928. * These can be nested as deep as you want.
  19929. * - **cls** - The class attribute of the element. This will end up being either the "class" attribute on a HTML
  19930. * fragment or className for a DOM node, depending on whether DomHelper is using fragments or DOM.
  19931. * - **html** - The innerHTML for the element.
  19932. *
  19933. * **NOTE:** For other arbitrary attributes, the value will currently **not** be automatically HTML-escaped prior to
  19934. * building the element's HTML string. This means that if your attribute value contains special characters that would
  19935. * not normally be allowed in a double-quoted attribute value, you **must** manually HTML-encode it beforehand (see
  19936. * {@link Ext.String#htmlEncode}) or risk malformed HTML being created. This behavior may change in a future release.
  19937. *
  19938. * # Insertion methods
  19939. *
  19940. * Commonly used insertion methods:
  19941. *
  19942. * - **{@link #append}**
  19943. * - **{@link #insertBefore}**
  19944. * - **{@link #insertAfter}**
  19945. * - **{@link #overwrite}**
  19946. * - **{@link #createTemplate}**
  19947. * - **{@link #insertHtml}**
  19948. *
  19949. * # Example
  19950. *
  19951. * This is an example, where an unordered list with 3 children items is appended to an existing element with
  19952. * id 'my-div':
  19953. *
  19954. * var dh = Ext.DomHelper; // create shorthand alias
  19955. * // specification object
  19956. * var spec = {
  19957. * id: 'my-ul',
  19958. * tag: 'ul',
  19959. * cls: 'my-list',
  19960. * // append children after creating
  19961. * children: [ // may also specify 'cn' instead of 'children'
  19962. * {tag: 'li', id: 'item0', html: 'List Item 0'},
  19963. * {tag: 'li', id: 'item1', html: 'List Item 1'},
  19964. * {tag: 'li', id: 'item2', html: 'List Item 2'}
  19965. * ]
  19966. * };
  19967. * var list = dh.append(
  19968. * 'my-div', // the context element 'my-div' can either be the id or the actual node
  19969. * spec // the specification object
  19970. * );
  19971. *
  19972. * Element creation specification parameters in this class may also be passed as an Array of specification objects. This
  19973. * can be used to insert multiple sibling nodes into an existing container very efficiently. For example, to add more
  19974. * list items to the example above:
  19975. *
  19976. * dh.append('my-ul', [
  19977. * {tag: 'li', id: 'item3', html: 'List Item 3'},
  19978. * {tag: 'li', id: 'item4', html: 'List Item 4'}
  19979. * ]);
  19980. *
  19981. * # Templating
  19982. *
  19983. * The real power is in the built-in templating. Instead of creating or appending any elements, {@link #createTemplate}
  19984. * returns a Template object which can be used over and over to insert new elements. Revisiting the example above, we
  19985. * could utilize templating this time:
  19986. *
  19987. * // create the node
  19988. * var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
  19989. * // get template
  19990. * var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
  19991. *
  19992. * for(var i = 0; i < 5, i++){
  19993. * tpl.append(list, [i]); // use template to append to the actual node
  19994. * }
  19995. *
  19996. * An example using a template:
  19997. *
  19998. * var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
  19999. *
  20000. * var tpl = new Ext.DomHelper.createTemplate(html);
  20001. * tpl.append('blog-roll', ['link1', 'http://www.edspencer.net/', "Ed's Site"]);
  20002. * tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin's Site"]);
  20003. *
  20004. * The same example using named parameters:
  20005. *
  20006. * var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
  20007. *
  20008. * var tpl = new Ext.DomHelper.createTemplate(html);
  20009. * tpl.append('blog-roll', {
  20010. * id: 'link1',
  20011. * url: 'http://www.edspencer.net/',
  20012. * text: "Ed's Site"
  20013. * });
  20014. * tpl.append('blog-roll', {
  20015. * id: 'link2',
  20016. * url: 'http://www.dustindiaz.com/',
  20017. * text: "Dustin's Site"
  20018. * });
  20019. *
  20020. * # Compiling Templates
  20021. *
  20022. * Templates are applied using regular expressions. The performance is great, but if you are adding a bunch of DOM
  20023. * elements using the same template, you can increase performance even further by {@link Ext.Template#compile
  20024. * "compiling"} the template. The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
  20025. * broken up at the different variable points and a dynamic function is created and eval'ed. The generated function
  20026. * performs string concatenation of these parts and the passed variables instead of using regular expressions.
  20027. *
  20028. * var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
  20029. *
  20030. * var tpl = new Ext.DomHelper.createTemplate(html);
  20031. * tpl.compile();
  20032. *
  20033. * //... use template like normal
  20034. *
  20035. * # Performance Boost
  20036. *
  20037. * DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead of DOM can significantly
  20038. * boost performance.
  20039. *
  20040. * Element creation specification parameters may also be strings. If {@link #useDom} is false, then the string is used
  20041. * as innerHTML. If {@link #useDom} is true, a string specification results in the creation of a text node. Usage:
  20042. *
  20043. * Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
  20044. *
  20045. */
  20046. (function() {
  20047. // kill repeat to save bytes
  20048. var afterbegin = 'afterbegin',
  20049. afterend = 'afterend',
  20050. beforebegin = 'beforebegin',
  20051. beforeend = 'beforeend',
  20052. ts = '<table>',
  20053. te = '</table>',
  20054. tbs = ts+'<tbody>',
  20055. tbe = '</tbody>'+te,
  20056. trs = tbs + '<tr>',
  20057. tre = '</tr>'+tbe,
  20058. detachedDiv = document.createElement('div'),
  20059. bbValues = ['BeforeBegin', 'previousSibling'],
  20060. aeValues = ['AfterEnd', 'nextSibling'],
  20061. bb_ae_PositionHash = {
  20062. beforebegin: bbValues,
  20063. afterend: aeValues
  20064. },
  20065. fullPositionHash = {
  20066. beforebegin: bbValues,
  20067. afterend: aeValues,
  20068. afterbegin: ['AfterBegin', 'firstChild'],
  20069. beforeend: ['BeforeEnd', 'lastChild']
  20070. };
  20071. /**
  20072. * The actual class of which {@link Ext.DomHelper} is instance of.
  20073. *
  20074. * Use singleton {@link Ext.DomHelper} instead.
  20075. *
  20076. * @private
  20077. */
  20078. Ext.define('Ext.dom.Helper', {
  20079. extend: 'Ext.dom.AbstractHelper',
  20080. requires:['Ext.dom.AbstractElement'],
  20081. tableRe: /^table|tbody|tr|td$/i,
  20082. tableElRe: /td|tr|tbody/i,
  20083. /**
  20084. * @property {Boolean} useDom
  20085. * True to force the use of DOM instead of html fragments.
  20086. */
  20087. useDom : false,
  20088. /**
  20089. * Creates new DOM element(s) without inserting them to the document.
  20090. * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
  20091. * @return {HTMLElement} The new uninserted node
  20092. */
  20093. createDom: function(o, parentNode){
  20094. var el,
  20095. doc = document,
  20096. useSet,
  20097. attr,
  20098. val,
  20099. cn,
  20100. i, l;
  20101. if (Ext.isArray(o)) { // Allow Arrays of siblings to be inserted
  20102. el = doc.createDocumentFragment(); // in one shot using a DocumentFragment
  20103. for (i = 0, l = o.length; i < l; i++) {
  20104. this.createDom(o[i], el);
  20105. }
  20106. } else if (typeof o == 'string') { // Allow a string as a child spec.
  20107. el = doc.createTextNode(o);
  20108. } else {
  20109. el = doc.createElement(o.tag || 'div');
  20110. useSet = !!el.setAttribute; // In IE some elements don't have setAttribute
  20111. for (attr in o) {
  20112. if (!this.confRe.test(attr)) {
  20113. val = o[attr];
  20114. if (attr == 'cls') {
  20115. el.className = val;
  20116. } else {
  20117. if (useSet) {
  20118. el.setAttribute(attr, val);
  20119. } else {
  20120. el[attr] = val;
  20121. }
  20122. }
  20123. }
  20124. }
  20125. Ext.DomHelper.applyStyles(el, o.style);
  20126. if ((cn = o.children || o.cn)) {
  20127. this.createDom(cn, el);
  20128. } else if (o.html) {
  20129. el.innerHTML = o.html;
  20130. }
  20131. }
  20132. if (parentNode) {
  20133. parentNode.appendChild(el);
  20134. }
  20135. return el;
  20136. },
  20137. ieTable: function(depth, openingTags, htmlContent, closingTags){
  20138. detachedDiv.innerHTML = [openingTags, htmlContent, closingTags].join('');
  20139. var i = -1,
  20140. el = detachedDiv,
  20141. ns;
  20142. while (++i < depth) {
  20143. el = el.firstChild;
  20144. }
  20145. // If the result is multiple siblings, then encapsulate them into one fragment.
  20146. ns = el.nextSibling;
  20147. if (ns) {
  20148. el = document.createDocumentFragment();
  20149. while (ns) {
  20150. el.appendChild(ns);
  20151. ns = ns.nextSibling;
  20152. }
  20153. }
  20154. return el;
  20155. },
  20156. /**
  20157. * @private
  20158. * Nasty code for IE's broken table implementation
  20159. */
  20160. insertIntoTable: function(tag, where, destinationEl, html) {
  20161. var node,
  20162. before,
  20163. bb = where == beforebegin,
  20164. ab = where == afterbegin,
  20165. be = where == beforeend,
  20166. ae = where == afterend;
  20167. if (tag == 'td' && (ab || be) || !this.tableElRe.test(tag) && (bb || ae)) {
  20168. return null;
  20169. }
  20170. before = bb ? destinationEl :
  20171. ae ? destinationEl.nextSibling :
  20172. ab ? destinationEl.firstChild : null;
  20173. if (bb || ae) {
  20174. destinationEl = destinationEl.parentNode;
  20175. }
  20176. if (tag == 'td' || (tag == 'tr' && (be || ab))) {
  20177. node = this.ieTable(4, trs, html, tre);
  20178. } else if ((tag == 'tbody' && (be || ab)) ||
  20179. (tag == 'tr' && (bb || ae))) {
  20180. node = this.ieTable(3, tbs, html, tbe);
  20181. } else {
  20182. node = this.ieTable(2, ts, html, te);
  20183. }
  20184. destinationEl.insertBefore(node, before);
  20185. return node;
  20186. },
  20187. /**
  20188. * @private
  20189. * Fix for IE9 createContextualFragment missing method
  20190. */
  20191. createContextualFragment: function(html) {
  20192. var fragment = document.createDocumentFragment(),
  20193. length, childNodes;
  20194. detachedDiv.innerHTML = html;
  20195. childNodes = detachedDiv.childNodes;
  20196. length = childNodes.length;
  20197. // Move nodes into fragment, don't clone: http://jsperf.com/create-fragment
  20198. while (length--) {
  20199. fragment.appendChild(childNodes[0]);
  20200. }
  20201. return fragment;
  20202. },
  20203. applyStyles: function(el, styles) {
  20204. if (styles) {
  20205. el = Ext.fly(el);
  20206. if (typeof styles == "function") {
  20207. styles = styles.call();
  20208. }
  20209. if (typeof styles == "string") {
  20210. styles = Ext.dom.Element.parseStyles(styles);
  20211. }
  20212. if (typeof styles == "object") {
  20213. el.setStyle(styles);
  20214. }
  20215. }
  20216. },
  20217. /**
  20218. * Alias for {@link #markup}.
  20219. * @inheritdoc Ext.dom.AbstractHelper#markup
  20220. */
  20221. createHtml: function(spec) {
  20222. return this.markup(spec);
  20223. },
  20224. doInsert: function(el, o, returnElement, pos, sibling, append) {
  20225. el = el.dom || Ext.getDom(el);
  20226. var newNode;
  20227. if (this.useDom) {
  20228. newNode = this.createDom(o, null);
  20229. if (append) {
  20230. el.appendChild(newNode);
  20231. }
  20232. else {
  20233. (sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
  20234. }
  20235. } else {
  20236. newNode = this.insertHtml(pos, el, this.markup(o));
  20237. }
  20238. return returnElement ? Ext.get(newNode, true) : newNode;
  20239. },
  20240. /**
  20241. * Creates new DOM element(s) and overwrites the contents of el with them.
  20242. * @param {String/HTMLElement/Ext.Element} el The context element
  20243. * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
  20244. * @param {Boolean} [returnElement] true to return an Ext.Element
  20245. * @return {HTMLElement/Ext.Element} The new node
  20246. */
  20247. overwrite: function(el, html, returnElement) {
  20248. var newNode;
  20249. el = Ext.getDom(el);
  20250. html = this.markup(html);
  20251. // IE Inserting HTML into a table/tbody/tr requires extra processing: http://www.ericvasilik.com/2006/07/code-karma.html
  20252. if (Ext.isIE && this.tableRe.test(el.tagName)) {
  20253. // Clearing table elements requires removal of all elements.
  20254. while (el.firstChild) {
  20255. el.removeChild(el.firstChild);
  20256. }
  20257. if (html) {
  20258. newNode = this.insertHtml('afterbegin', el, html);
  20259. return returnElement ? Ext.get(newNode) : newNode;
  20260. }
  20261. return null;
  20262. }
  20263. el.innerHTML = html;
  20264. return returnElement ? Ext.get(el.firstChild) : el.firstChild;
  20265. },
  20266. insertHtml: function(where, el, html) {
  20267. var hashVal,
  20268. range,
  20269. rangeEl,
  20270. setStart,
  20271. frag;
  20272. where = where.toLowerCase();
  20273. // Has fast HTML insertion into existing DOM: http://www.w3.org/TR/html5/apis-in-html-documents.html#insertadjacenthtml
  20274. if (el.insertAdjacentHTML) {
  20275. // IE's incomplete table implementation: http://www.ericvasilik.com/2006/07/code-karma.html
  20276. if (Ext.isIE && this.tableRe.test(el.tagName) && (frag = this.insertIntoTable(el.tagName.toLowerCase(), where, el, html))) {
  20277. return frag;
  20278. }
  20279. if ((hashVal = fullPositionHash[where])) {
  20280. el.insertAdjacentHTML(hashVal[0], html);
  20281. return el[hashVal[1]];
  20282. }
  20283. // if (not IE and context element is an HTMLElement) or TextNode
  20284. } else {
  20285. // we cannot insert anything inside a textnode so...
  20286. if (el.nodeType === 3) {
  20287. where = where === 'afterbegin' ? 'beforebegin' : where;
  20288. where = where === 'beforeend' ? 'afterend' : where;
  20289. }
  20290. range = Ext.supports.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
  20291. setStart = 'setStart' + (this.endRe.test(where) ? 'After' : 'Before');
  20292. if (bb_ae_PositionHash[where]) {
  20293. if (range) {
  20294. range[setStart](el);
  20295. frag = range.createContextualFragment(html);
  20296. } else {
  20297. frag = this.createContextualFragment(html);
  20298. }
  20299. el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
  20300. return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
  20301. } else {
  20302. rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
  20303. if (el.firstChild) {
  20304. if (range) {
  20305. range[setStart](el[rangeEl]);
  20306. frag = range.createContextualFragment(html);
  20307. } else {
  20308. frag = this.createContextualFragment(html);
  20309. }
  20310. if (where == afterbegin) {
  20311. el.insertBefore(frag, el.firstChild);
  20312. } else {
  20313. el.appendChild(frag);
  20314. }
  20315. } else {
  20316. el.innerHTML = html;
  20317. }
  20318. return el[rangeEl];
  20319. }
  20320. }
  20321. Ext.Error.raise({
  20322. sourceClass: 'Ext.DomHelper',
  20323. sourceMethod: 'insertHtml',
  20324. htmlToInsert: html,
  20325. targetElement: el,
  20326. msg: 'Illegal insertion point reached: "' + where + '"'
  20327. });
  20328. },
  20329. /**
  20330. * Creates a new Ext.Template from the DOM object spec.
  20331. * @param {Object} o The DOM object spec (and children)
  20332. * @return {Ext.Template} The new template
  20333. */
  20334. createTemplate: function(o) {
  20335. var html = this.markup(o);
  20336. return new Ext.Template(html);
  20337. }
  20338. }, function() {
  20339. Ext.ns('Ext.core');
  20340. Ext.DomHelper = Ext.core.DomHelper = new this;
  20341. });
  20342. }());
  20343. //@tag dom,core
  20344. //@require Helper.js
  20345. //@define Ext.dom.Query
  20346. //@define Ext.core.Query
  20347. //@define Ext.DomQuery
  20348. /*
  20349. * This is code is also distributed under MIT license for use
  20350. * with jQuery and prototype JavaScript libraries.
  20351. */
  20352. /**
  20353. * @class Ext.dom.Query
  20354. * @alternateClassName Ext.DomQuery
  20355. * @alternateClassName Ext.core.DomQuery
  20356. * @singleton
  20357. *
  20358. * Provides high performance selector/xpath processing by compiling queries into reusable functions. New pseudo classes
  20359. * and matchers can be plugged. It works on HTML and XML documents (if a content node is passed in).
  20360. *
  20361. * DomQuery supports most of the [CSS3 selectors spec][1], along with some custom selectors and basic XPath.
  20362. *
  20363. * All selectors, attribute filters and pseudos below can be combined infinitely in any order. For example
  20364. * `div.foo:nth-child(odd)[@foo=bar].bar:first` would be a perfectly valid selector. Node filters are processed
  20365. * in the order in which they appear, which allows you to optimize your queries for your document structure.
  20366. *
  20367. * ## Element Selectors:
  20368. *
  20369. * - **`*`** any element
  20370. * - **`E`** an element with the tag E
  20371. * - **`E F`** All descendent elements of E that have the tag F
  20372. * - **`E > F`** or **E/F** all direct children elements of E that have the tag F
  20373. * - **`E + F`** all elements with the tag F that are immediately preceded by an element with the tag E
  20374. * - **`E ~ F`** all elements with the tag F that are preceded by a sibling element with the tag E
  20375. *
  20376. * ## Attribute Selectors:
  20377. *
  20378. * The use of `@` and quotes are optional. For example, `div[@foo='bar']` is also a valid attribute selector.
  20379. *
  20380. * - **`E[foo]`** has an attribute "foo"
  20381. * - **`E[foo=bar]`** has an attribute "foo" that equals "bar"
  20382. * - **`E[foo^=bar]`** has an attribute "foo" that starts with "bar"
  20383. * - **`E[foo$=bar]`** has an attribute "foo" that ends with "bar"
  20384. * - **`E[foo*=bar]`** has an attribute "foo" that contains the substring "bar"
  20385. * - **`E[foo%=2]`** has an attribute "foo" that is evenly divisible by 2
  20386. * - **`E[foo!=bar]`** attribute "foo" does not equal "bar"
  20387. *
  20388. * ## Pseudo Classes:
  20389. *
  20390. * - **`E:first-child`** E is the first child of its parent
  20391. * - **`E:last-child`** E is the last child of its parent
  20392. * - **`E:nth-child(_n_)`** E is the _n_th child of its parent (1 based as per the spec)
  20393. * - **`E:nth-child(odd)`** E is an odd child of its parent
  20394. * - **`E:nth-child(even)`** E is an even child of its parent
  20395. * - **`E:only-child`** E is the only child of its parent
  20396. * - **`E:checked`** E is an element that is has a checked attribute that is true (e.g. a radio or checkbox)
  20397. * - **`E:first`** the first E in the resultset
  20398. * - **`E:last`** the last E in the resultset
  20399. * - **`E:nth(_n_)`** the _n_th E in the resultset (1 based)
  20400. * - **`E:odd`** shortcut for :nth-child(odd)
  20401. * - **`E:even`** shortcut for :nth-child(even)
  20402. * - **`E:contains(foo)`** E's innerHTML contains the substring "foo"
  20403. * - **`E:nodeValue(foo)`** E contains a textNode with a nodeValue that equals "foo"
  20404. * - **`E:not(S)`** an E element that does not match simple selector S
  20405. * - **`E:has(S)`** an E element that has a descendent that matches simple selector S
  20406. * - **`E:next(S)`** an E element whose next sibling matches simple selector S
  20407. * - **`E:prev(S)`** an E element whose previous sibling matches simple selector S
  20408. * - **`E:any(S1|S2|S2)`** an E element which matches any of the simple selectors S1, S2 or S3
  20409. *
  20410. * ## CSS Value Selectors:
  20411. *
  20412. * - **`E{display=none}`** css value "display" that equals "none"
  20413. * - **`E{display^=none}`** css value "display" that starts with "none"
  20414. * - **`E{display$=none}`** css value "display" that ends with "none"
  20415. * - **`E{display*=none}`** css value "display" that contains the substring "none"
  20416. * - **`E{display%=2}`** css value "display" that is evenly divisible by 2
  20417. * - **`E{display!=none}`** css value "display" that does not equal "none"
  20418. *
  20419. * [1]: http://www.w3.org/TR/2005/WD-css3-selectors-20051215/#selectors
  20420. */
  20421. Ext.ns('Ext.core');
  20422. Ext.dom.Query = Ext.core.DomQuery = Ext.DomQuery = (function(){
  20423. var cache = {},
  20424. simpleCache = {},
  20425. valueCache = {},
  20426. nonSpace = /\S/,
  20427. trimRe = /^\s+|\s+$/g,
  20428. tplRe = /\{(\d+)\}/g,
  20429. modeRe = /^(\s?[\/>+~]\s?|\s|$)/,
  20430. tagTokenRe = /^(#)?([\w\-\*\\]+)/,
  20431. nthRe = /(\d*)n\+?(\d*)/,
  20432. nthRe2 = /\D/,
  20433. startIdRe = /^\s*\#/,
  20434. // This is for IE MSXML which does not support expandos.
  20435. // IE runs the same speed using setAttribute, however FF slows way down
  20436. // and Safari completely fails so they need to continue to use expandos.
  20437. isIE = window.ActiveXObject ? true : false,
  20438. key = 30803,
  20439. longHex = /\\([0-9a-fA-F]{6})/g,
  20440. shortHex = /\\([0-9a-fA-F]{1,6})\s{0,1}/g,
  20441. nonHex = /\\([^0-9a-fA-F]{1})/g,
  20442. escapes = /\\/g,
  20443. num, hasEscapes,
  20444. // replaces a long hex regex match group with the appropriate ascii value
  20445. // $args indicate regex match pos
  20446. longHexToChar = function($0, $1) {
  20447. return String.fromCharCode(parseInt($1, 16));
  20448. },
  20449. // converts a shortHex regex match to the long form
  20450. shortToLongHex = function($0, $1) {
  20451. while ($1.length < 6) {
  20452. $1 = '0' + $1;
  20453. }
  20454. return '\\' + $1;
  20455. },
  20456. // converts a single char escape to long escape form
  20457. charToLongHex = function($0, $1) {
  20458. num = $1.charCodeAt(0).toString(16);
  20459. if (num.length === 1) {
  20460. num = '0' + num;
  20461. }
  20462. return '\\0000' + num;
  20463. },
  20464. // Un-escapes an input selector string. Assumes all escape sequences have been
  20465. // normalized to the css '\\0000##' 6-hex-digit style escape sequence :
  20466. // will not handle any other escape formats
  20467. unescapeCssSelector = function (selector) {
  20468. return (hasEscapes)
  20469. ? selector.replace(longHex, longHexToChar)
  20470. : selector;
  20471. },
  20472. // checks if the path has escaping & does any appropriate replacements
  20473. setupEscapes = function(path){
  20474. hasEscapes = (path.indexOf('\\') > -1);
  20475. if (hasEscapes) {
  20476. path = path
  20477. .replace(shortHex, shortToLongHex)
  20478. .replace(nonHex, charToLongHex)
  20479. .replace(escapes, '\\\\'); // double the '\' for js compilation
  20480. }
  20481. return path;
  20482. };
  20483. // this eval is stop the compressor from
  20484. // renaming the variable to something shorter
  20485. eval("var batch = 30803;");
  20486. // Retrieve the child node from a particular
  20487. // parent at the specified index.
  20488. function child(parent, index){
  20489. var i = 0,
  20490. n = parent.firstChild;
  20491. while(n){
  20492. if(n.nodeType == 1){
  20493. if(++i == index){
  20494. return n;
  20495. }
  20496. }
  20497. n = n.nextSibling;
  20498. }
  20499. return null;
  20500. }
  20501. // retrieve the next element node
  20502. function next(n){
  20503. while((n = n.nextSibling) && n.nodeType != 1);
  20504. return n;
  20505. }
  20506. // retrieve the previous element node
  20507. function prev(n){
  20508. while((n = n.previousSibling) && n.nodeType != 1);
  20509. return n;
  20510. }
  20511. // Mark each child node with a nodeIndex skipping and
  20512. // removing empty text nodes.
  20513. function children(parent){
  20514. var n = parent.firstChild,
  20515. nodeIndex = -1,
  20516. nextNode;
  20517. while(n){
  20518. nextNode = n.nextSibling;
  20519. // clean worthless empty nodes.
  20520. if(n.nodeType == 3 && !nonSpace.test(n.nodeValue)){
  20521. parent.removeChild(n);
  20522. }else{
  20523. // add an expando nodeIndex
  20524. n.nodeIndex = ++nodeIndex;
  20525. }
  20526. n = nextNode;
  20527. }
  20528. return this;
  20529. }
  20530. // nodeSet - array of nodes
  20531. // cls - CSS Class
  20532. function byClassName(nodeSet, cls){
  20533. cls = unescapeCssSelector(cls);
  20534. if(!cls){
  20535. return nodeSet;
  20536. }
  20537. var result = [], ri = -1,
  20538. i, ci;
  20539. for(i = 0, ci; ci = nodeSet[i]; i++){
  20540. if((' '+ci.className+' ').indexOf(cls) != -1){
  20541. result[++ri] = ci;
  20542. }
  20543. }
  20544. return result;
  20545. }
  20546. function attrValue(n, attr){
  20547. // if its an array, use the first node.
  20548. if(!n.tagName && typeof n.length != "undefined"){
  20549. n = n[0];
  20550. }
  20551. if(!n){
  20552. return null;
  20553. }
  20554. if(attr == "for"){
  20555. return n.htmlFor;
  20556. }
  20557. if(attr == "class" || attr == "className"){
  20558. return n.className;
  20559. }
  20560. return n.getAttribute(attr) || n[attr];
  20561. }
  20562. // ns - nodes
  20563. // mode - false, /, >, +, ~
  20564. // tagName - defaults to "*"
  20565. function getNodes(ns, mode, tagName){
  20566. var result = [], ri = -1, cs,
  20567. i, ni, j, ci, cn, utag, n, cj;
  20568. if(!ns){
  20569. return result;
  20570. }
  20571. tagName = tagName || "*";
  20572. // convert to array
  20573. if(typeof ns.getElementsByTagName != "undefined"){
  20574. ns = [ns];
  20575. }
  20576. // no mode specified, grab all elements by tagName
  20577. // at any depth
  20578. if(!mode){
  20579. for(i = 0, ni; ni = ns[i]; i++){
  20580. cs = ni.getElementsByTagName(tagName);
  20581. for(j = 0, ci; ci = cs[j]; j++){
  20582. result[++ri] = ci;
  20583. }
  20584. }
  20585. // Direct Child mode (/ or >)
  20586. // E > F or E/F all direct children elements of E that have the tag
  20587. } else if(mode == "/" || mode == ">"){
  20588. utag = tagName.toUpperCase();
  20589. for(i = 0, ni, cn; ni = ns[i]; i++){
  20590. cn = ni.childNodes;
  20591. for(j = 0, cj; cj = cn[j]; j++){
  20592. if(cj.nodeName == utag || cj.nodeName == tagName || tagName == '*'){
  20593. result[++ri] = cj;
  20594. }
  20595. }
  20596. }
  20597. // Immediately Preceding mode (+)
  20598. // E + F all elements with the tag F that are immediately preceded by an element with the tag E
  20599. }else if(mode == "+"){
  20600. utag = tagName.toUpperCase();
  20601. for(i = 0, n; n = ns[i]; i++){
  20602. while((n = n.nextSibling) && n.nodeType != 1);
  20603. if(n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')){
  20604. result[++ri] = n;
  20605. }
  20606. }
  20607. // Sibling mode (~)
  20608. // E ~ F all elements with the tag F that are preceded by a sibling element with the tag E
  20609. }else if(mode == "~"){
  20610. utag = tagName.toUpperCase();
  20611. for(i = 0, n; n = ns[i]; i++){
  20612. while((n = n.nextSibling)){
  20613. if (n.nodeName == utag || n.nodeName == tagName || tagName == '*'){
  20614. result[++ri] = n;
  20615. }
  20616. }
  20617. }
  20618. }
  20619. return result;
  20620. }
  20621. function concat(a, b){
  20622. if(b.slice){
  20623. return a.concat(b);
  20624. }
  20625. for(var i = 0, l = b.length; i < l; i++){
  20626. a[a.length] = b[i];
  20627. }
  20628. return a;
  20629. }
  20630. function byTag(cs, tagName){
  20631. if(cs.tagName || cs == document){
  20632. cs = [cs];
  20633. }
  20634. if(!tagName){
  20635. return cs;
  20636. }
  20637. var result = [], ri = -1,
  20638. i, ci;
  20639. tagName = tagName.toLowerCase();
  20640. for(i = 0, ci; ci = cs[i]; i++){
  20641. if(ci.nodeType == 1 && ci.tagName.toLowerCase() == tagName){
  20642. result[++ri] = ci;
  20643. }
  20644. }
  20645. return result;
  20646. }
  20647. function byId(cs, id){
  20648. id = unescapeCssSelector(id);
  20649. if(cs.tagName || cs == document){
  20650. cs = [cs];
  20651. }
  20652. if(!id){
  20653. return cs;
  20654. }
  20655. var result = [], ri = -1,
  20656. i, ci;
  20657. for(i = 0, ci; ci = cs[i]; i++){
  20658. if(ci && ci.id == id){
  20659. result[++ri] = ci;
  20660. return result;
  20661. }
  20662. }
  20663. return result;
  20664. }
  20665. // operators are =, !=, ^=, $=, *=, %=, |= and ~=
  20666. // custom can be "{"
  20667. function byAttribute(cs, attr, value, op, custom){
  20668. var result = [],
  20669. ri = -1,
  20670. useGetStyle = custom == "{",
  20671. fn = Ext.DomQuery.operators[op],
  20672. a,
  20673. xml,
  20674. hasXml,
  20675. i, ci;
  20676. value = unescapeCssSelector(value);
  20677. for(i = 0, ci; ci = cs[i]; i++){
  20678. // skip non-element nodes.
  20679. if(ci.nodeType != 1){
  20680. continue;
  20681. }
  20682. // only need to do this for the first node
  20683. if(!hasXml){
  20684. xml = Ext.DomQuery.isXml(ci);
  20685. hasXml = true;
  20686. }
  20687. // we only need to change the property names if we're dealing with html nodes, not XML
  20688. if(!xml){
  20689. if(useGetStyle){
  20690. a = Ext.DomQuery.getStyle(ci, attr);
  20691. } else if (attr == "class" || attr == "className"){
  20692. a = ci.className;
  20693. } else if (attr == "for"){
  20694. a = ci.htmlFor;
  20695. } else if (attr == "href"){
  20696. // getAttribute href bug
  20697. // http://www.glennjones.net/Post/809/getAttributehrefbug.htm
  20698. a = ci.getAttribute("href", 2);
  20699. } else{
  20700. a = ci.getAttribute(attr);
  20701. }
  20702. }else{
  20703. a = ci.getAttribute(attr);
  20704. }
  20705. if((fn && fn(a, value)) || (!fn && a)){
  20706. result[++ri] = ci;
  20707. }
  20708. }
  20709. return result;
  20710. }
  20711. function byPseudo(cs, name, value){
  20712. value = unescapeCssSelector(value);
  20713. return Ext.DomQuery.pseudos[name](cs, value);
  20714. }
  20715. function nodupIEXml(cs){
  20716. var d = ++key,
  20717. r,
  20718. i, len, c;
  20719. cs[0].setAttribute("_nodup", d);
  20720. r = [cs[0]];
  20721. for(i = 1, len = cs.length; i < len; i++){
  20722. c = cs[i];
  20723. if(!c.getAttribute("_nodup") != d){
  20724. c.setAttribute("_nodup", d);
  20725. r[r.length] = c;
  20726. }
  20727. }
  20728. for(i = 0, len = cs.length; i < len; i++){
  20729. cs[i].removeAttribute("_nodup");
  20730. }
  20731. return r;
  20732. }
  20733. function nodup(cs){
  20734. if(!cs){
  20735. return [];
  20736. }
  20737. var len = cs.length, c, i, r = cs, cj, ri = -1, d, j;
  20738. if(!len || typeof cs.nodeType != "undefined" || len == 1){
  20739. return cs;
  20740. }
  20741. if(isIE && typeof cs[0].selectSingleNode != "undefined"){
  20742. return nodupIEXml(cs);
  20743. }
  20744. d = ++key;
  20745. cs[0]._nodup = d;
  20746. for(i = 1; c = cs[i]; i++){
  20747. if(c._nodup != d){
  20748. c._nodup = d;
  20749. }else{
  20750. r = [];
  20751. for(j = 0; j < i; j++){
  20752. r[++ri] = cs[j];
  20753. }
  20754. for(j = i+1; cj = cs[j]; j++){
  20755. if(cj._nodup != d){
  20756. cj._nodup = d;
  20757. r[++ri] = cj;
  20758. }
  20759. }
  20760. return r;
  20761. }
  20762. }
  20763. return r;
  20764. }
  20765. function quickDiffIEXml(c1, c2){
  20766. var d = ++key,
  20767. r = [],
  20768. i, len;
  20769. for(i = 0, len = c1.length; i < len; i++){
  20770. c1[i].setAttribute("_qdiff", d);
  20771. }
  20772. for(i = 0, len = c2.length; i < len; i++){
  20773. if(c2[i].getAttribute("_qdiff") != d){
  20774. r[r.length] = c2[i];
  20775. }
  20776. }
  20777. for(i = 0, len = c1.length; i < len; i++){
  20778. c1[i].removeAttribute("_qdiff");
  20779. }
  20780. return r;
  20781. }
  20782. function quickDiff(c1, c2){
  20783. var len1 = c1.length,
  20784. d = ++key,
  20785. r = [],
  20786. i, len;
  20787. if(!len1){
  20788. return c2;
  20789. }
  20790. if(isIE && typeof c1[0].selectSingleNode != "undefined"){
  20791. return quickDiffIEXml(c1, c2);
  20792. }
  20793. for(i = 0; i < len1; i++){
  20794. c1[i]._qdiff = d;
  20795. }
  20796. for(i = 0, len = c2.length; i < len; i++){
  20797. if(c2[i]._qdiff != d){
  20798. r[r.length] = c2[i];
  20799. }
  20800. }
  20801. return r;
  20802. }
  20803. function quickId(ns, mode, root, id){
  20804. if(ns == root){
  20805. id = unescapeCssSelector(id);
  20806. var d = root.ownerDocument || root;
  20807. return d.getElementById(id);
  20808. }
  20809. ns = getNodes(ns, mode, "*");
  20810. return byId(ns, id);
  20811. }
  20812. return {
  20813. getStyle : function(el, name){
  20814. return Ext.fly(el).getStyle(name);
  20815. },
  20816. /**
  20817. * Compiles a selector/xpath query into a reusable function. The returned function
  20818. * takes one parameter "root" (optional), which is the context node from where the query should start.
  20819. * @param {String} selector The selector/xpath query
  20820. * @param {String} [type="select"] Either "select" or "simple" for a simple selector match
  20821. * @return {Function}
  20822. */
  20823. compile : function(path, type){
  20824. type = type || "select";
  20825. // setup fn preamble
  20826. var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],
  20827. mode,
  20828. lastPath,
  20829. matchers = Ext.DomQuery.matchers,
  20830. matchersLn = matchers.length,
  20831. modeMatch,
  20832. // accept leading mode switch
  20833. lmode = path.match(modeRe),
  20834. tokenMatch, matched, j, t, m;
  20835. path = setupEscapes(path);
  20836. if(lmode && lmode[1]){
  20837. fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
  20838. path = path.replace(lmode[1], "");
  20839. }
  20840. // strip leading slashes
  20841. while(path.substr(0, 1)=="/"){
  20842. path = path.substr(1);
  20843. }
  20844. while(path && lastPath != path){
  20845. lastPath = path;
  20846. tokenMatch = path.match(tagTokenRe);
  20847. if(type == "select"){
  20848. if(tokenMatch){
  20849. // ID Selector
  20850. if(tokenMatch[1] == "#"){
  20851. fn[fn.length] = 'n = quickId(n, mode, root, "'+tokenMatch[2]+'");';
  20852. }else{
  20853. fn[fn.length] = 'n = getNodes(n, mode, "'+tokenMatch[2]+'");';
  20854. }
  20855. path = path.replace(tokenMatch[0], "");
  20856. }else if(path.substr(0, 1) != '@'){
  20857. fn[fn.length] = 'n = getNodes(n, mode, "*");';
  20858. }
  20859. // type of "simple"
  20860. }else{
  20861. if(tokenMatch){
  20862. if(tokenMatch[1] == "#"){
  20863. fn[fn.length] = 'n = byId(n, "'+tokenMatch[2]+'");';
  20864. }else{
  20865. fn[fn.length] = 'n = byTag(n, "'+tokenMatch[2]+'");';
  20866. }
  20867. path = path.replace(tokenMatch[0], "");
  20868. }
  20869. }
  20870. while(!(modeMatch = path.match(modeRe))){
  20871. matched = false;
  20872. for(j = 0; j < matchersLn; j++){
  20873. t = matchers[j];
  20874. m = path.match(t.re);
  20875. if(m){
  20876. fn[fn.length] = t.select.replace(tplRe, function(x, i){
  20877. return m[i];
  20878. });
  20879. path = path.replace(m[0], "");
  20880. matched = true;
  20881. break;
  20882. }
  20883. }
  20884. // prevent infinite loop on bad selector
  20885. if(!matched){
  20886. Ext.Error.raise({
  20887. sourceClass: 'Ext.DomQuery',
  20888. sourceMethod: 'compile',
  20889. msg: 'Error parsing selector. Parsing failed at "' + path + '"'
  20890. });
  20891. }
  20892. }
  20893. if(modeMatch[1]){
  20894. fn[fn.length] = 'mode="'+modeMatch[1].replace(trimRe, "")+'";';
  20895. path = path.replace(modeMatch[1], "");
  20896. }
  20897. }
  20898. // close fn out
  20899. fn[fn.length] = "return nodup(n);\n}";
  20900. // eval fn and return it
  20901. eval(fn.join(""));
  20902. return f;
  20903. },
  20904. /**
  20905. * Selects an array of DOM nodes using JavaScript-only implementation.
  20906. *
  20907. * Use {@link #select} to take advantage of browsers built-in support for CSS selectors.
  20908. * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
  20909. * @param {HTMLElement/String} [root=document] The start of the query.
  20910. * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are
  20911. * no matches, and empty Array is returned.
  20912. */
  20913. jsSelect: function(path, root, type){
  20914. // set root to doc if not specified.
  20915. root = root || document;
  20916. if(typeof root == "string"){
  20917. root = document.getElementById(root);
  20918. }
  20919. var paths = path.split(","),
  20920. results = [],
  20921. i, len, subPath, result;
  20922. // loop over each selector
  20923. for(i = 0, len = paths.length; i < len; i++){
  20924. subPath = paths[i].replace(trimRe, "");
  20925. // compile and place in cache
  20926. if(!cache[subPath]){
  20927. // When we compile, escaping is handled inside the compile method
  20928. cache[subPath] = Ext.DomQuery.compile(subPath, type);
  20929. if(!cache[subPath]){
  20930. Ext.Error.raise({
  20931. sourceClass: 'Ext.DomQuery',
  20932. sourceMethod: 'jsSelect',
  20933. msg: subPath + ' is not a valid selector'
  20934. });
  20935. }
  20936. } else {
  20937. // If we've already compiled, we still need to check if the
  20938. // selector has escaping and setup the appropriate flags
  20939. setupEscapes(subPath);
  20940. }
  20941. result = cache[subPath](root);
  20942. if(result && result != document){
  20943. results = results.concat(result);
  20944. }
  20945. }
  20946. // if there were multiple selectors, make sure dups
  20947. // are eliminated
  20948. if(paths.length > 1){
  20949. return nodup(results);
  20950. }
  20951. return results;
  20952. },
  20953. isXml: function(el) {
  20954. var docEl = (el ? el.ownerDocument || el : 0).documentElement;
  20955. return docEl ? docEl.nodeName !== "HTML" : false;
  20956. },
  20957. /**
  20958. * Selects an array of DOM nodes by CSS/XPath selector.
  20959. *
  20960. * Uses [document.querySelectorAll][0] if browser supports that, otherwise falls back to
  20961. * {@link Ext.dom.Query#jsSelect} to do the work.
  20962. *
  20963. * Aliased as {@link Ext#query}.
  20964. *
  20965. * [0]: https://developer.mozilla.org/en/DOM/document.querySelectorAll
  20966. *
  20967. * @param {String} path The selector/xpath query
  20968. * @param {HTMLElement} [root=document] The start of the query.
  20969. * @return {HTMLElement[]} An array of DOM elements (not a NodeList as returned by `querySelectorAll`).
  20970. * @param {String} [type="select"] Either "select" or "simple" for a simple selector match (only valid when
  20971. * used when the call is deferred to the jsSelect method)
  20972. * @method
  20973. */
  20974. select : document.querySelectorAll ? function(path, root, type) {
  20975. root = root || document;
  20976. if (!Ext.DomQuery.isXml(root)) {
  20977. try {
  20978. /*
  20979. * This checking here is to "fix" the behaviour of querySelectorAll
  20980. * for non root document queries. The way qsa works is intentional,
  20981. * however it's definitely not the expected way it should work.
  20982. * When descendant selectors are used, only the lowest selector must be inside the root!
  20983. * More info: http://ejohn.org/blog/thoughts-on-queryselectorall/
  20984. * So we create a descendant selector by prepending the root's ID, and query the parent node.
  20985. * UNLESS the root has no parent in which qsa will work perfectly.
  20986. *
  20987. * We only modify the path for single selectors (ie, no multiples),
  20988. * without a full parser it makes it difficult to do this correctly.
  20989. */
  20990. if (root.parentNode && (root.nodeType !== 9) && path.indexOf(',') === -1 && !startIdRe.test(path)) {
  20991. path = '#' + Ext.escapeId(Ext.id(root)) + ' ' + path;
  20992. root = root.parentNode;
  20993. }
  20994. return Ext.Array.toArray(root.querySelectorAll(path));
  20995. }
  20996. catch (e) {
  20997. }
  20998. }
  20999. return Ext.DomQuery.jsSelect.call(this, path, root, type);
  21000. } : function(path, root, type) {
  21001. return Ext.DomQuery.jsSelect.call(this, path, root, type);
  21002. },
  21003. /**
  21004. * Selects a single element.
  21005. * @param {String} selector The selector/xpath query
  21006. * @param {HTMLElement} [root=document] The start of the query.
  21007. * @return {HTMLElement} The DOM element which matched the selector.
  21008. */
  21009. selectNode : function(path, root){
  21010. return Ext.DomQuery.select(path, root)[0];
  21011. },
  21012. /**
  21013. * Selects the value of a node, optionally replacing null with the defaultValue.
  21014. * @param {String} selector The selector/xpath query
  21015. * @param {HTMLElement} [root=document] The start of the query.
  21016. * @param {String} [defaultValue] When specified, this is return as empty value.
  21017. * @return {String}
  21018. */
  21019. selectValue : function(path, root, defaultValue){
  21020. path = path.replace(trimRe, "");
  21021. if (!valueCache[path]) {
  21022. valueCache[path] = Ext.DomQuery.compile(path, "select");
  21023. } else {
  21024. setupEscapes(path);
  21025. }
  21026. var n = valueCache[path](root),
  21027. v;
  21028. n = n[0] ? n[0] : n;
  21029. // overcome a limitation of maximum textnode size
  21030. // Rumored to potentially crash IE6 but has not been confirmed.
  21031. // http://reference.sitepoint.com/javascript/Node/normalize
  21032. // https://developer.mozilla.org/En/DOM/Node.normalize
  21033. if (typeof n.normalize == 'function') {
  21034. n.normalize();
  21035. }
  21036. v = (n && n.firstChild ? n.firstChild.nodeValue : null);
  21037. return ((v === null||v === undefined||v==='') ? defaultValue : v);
  21038. },
  21039. /**
  21040. * Selects the value of a node, parsing integers and floats.
  21041. * Returns the defaultValue, or 0 if none is specified.
  21042. * @param {String} selector The selector/xpath query
  21043. * @param {HTMLElement} [root=document] The start of the query.
  21044. * @param {Number} [defaultValue] When specified, this is return as empty value.
  21045. * @return {Number}
  21046. */
  21047. selectNumber : function(path, root, defaultValue){
  21048. var v = Ext.DomQuery.selectValue(path, root, defaultValue || 0);
  21049. return parseFloat(v);
  21050. },
  21051. /**
  21052. * Returns true if the passed element(s) match the passed simple selector
  21053. * (e.g. `div.some-class` or `span:first-child`)
  21054. * @param {String/HTMLElement/HTMLElement[]} el An element id, element or array of elements
  21055. * @param {String} selector The simple selector to test
  21056. * @return {Boolean}
  21057. */
  21058. is : function(el, ss){
  21059. if(typeof el == "string"){
  21060. el = document.getElementById(el);
  21061. }
  21062. var isArray = Ext.isArray(el),
  21063. result = Ext.DomQuery.filter(isArray ? el : [el], ss);
  21064. return isArray ? (result.length == el.length) : (result.length > 0);
  21065. },
  21066. /**
  21067. * Filters an array of elements to only include matches of a simple selector
  21068. * (e.g. `div.some-class` or `span:first-child`)
  21069. * @param {HTMLElement[]} el An array of elements to filter
  21070. * @param {String} selector The simple selector to test
  21071. * @param {Boolean} nonMatches If true, it returns the elements that DON'T match the selector instead of the
  21072. * ones that match
  21073. * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are no matches, and empty
  21074. * Array is returned.
  21075. */
  21076. filter : function(els, ss, nonMatches){
  21077. ss = ss.replace(trimRe, "");
  21078. if (!simpleCache[ss]) {
  21079. simpleCache[ss] = Ext.DomQuery.compile(ss, "simple");
  21080. } else {
  21081. setupEscapes(ss);
  21082. }
  21083. var result = simpleCache[ss](els);
  21084. return nonMatches ? quickDiff(result, els) : result;
  21085. },
  21086. /**
  21087. * Collection of matching regular expressions and code snippets.
  21088. * Each capture group within `()` will be replace the `{}` in the select
  21089. * statement as specified by their index.
  21090. */
  21091. matchers : [{
  21092. re: /^\.([\w\-\\]+)/,
  21093. select: 'n = byClassName(n, " {1} ");'
  21094. }, {
  21095. re: /^\:([\w\-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
  21096. select: 'n = byPseudo(n, "{1}", "{2}");'
  21097. },{
  21098. re: /^(?:([\[\{])(?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,
  21099. select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'
  21100. }, {
  21101. re: /^#([\w\-\\]+)/,
  21102. select: 'n = byId(n, "{1}");'
  21103. },{
  21104. re: /^@([\w\-]+)/,
  21105. select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
  21106. }
  21107. ],
  21108. /**
  21109. * Collection of operator comparison functions.
  21110. * The default operators are `=`, `!=`, `^=`, `$=`, `*=`, `%=`, `|=` and `~=`.
  21111. * New operators can be added as long as the match the format *c*`=` where *c*
  21112. * is any character other than space, `>`, or `<`.
  21113. */
  21114. operators : {
  21115. "=" : function(a, v){
  21116. return a == v;
  21117. },
  21118. "!=" : function(a, v){
  21119. return a != v;
  21120. },
  21121. "^=" : function(a, v){
  21122. return a && a.substr(0, v.length) == v;
  21123. },
  21124. "$=" : function(a, v){
  21125. return a && a.substr(a.length-v.length) == v;
  21126. },
  21127. "*=" : function(a, v){
  21128. return a && a.indexOf(v) !== -1;
  21129. },
  21130. "%=" : function(a, v){
  21131. return (a % v) == 0;
  21132. },
  21133. "|=" : function(a, v){
  21134. return a && (a == v || a.substr(0, v.length+1) == v+'-');
  21135. },
  21136. "~=" : function(a, v){
  21137. return a && (' '+a+' ').indexOf(' '+v+' ') != -1;
  21138. }
  21139. },
  21140. /**
  21141. * Object hash of "pseudo class" filter functions which are used when filtering selections.
  21142. * Each function is passed two parameters:
  21143. *
  21144. * - **c** : Array
  21145. * An Array of DOM elements to filter.
  21146. *
  21147. * - **v** : String
  21148. * The argument (if any) supplied in the selector.
  21149. *
  21150. * A filter function returns an Array of DOM elements which conform to the pseudo class.
  21151. * In addition to the provided pseudo classes listed above such as `first-child` and `nth-child`,
  21152. * developers may add additional, custom psuedo class filters to select elements according to application-specific requirements.
  21153. *
  21154. * For example, to filter `a` elements to only return links to __external__ resources:
  21155. *
  21156. * Ext.DomQuery.pseudos.external = function(c, v){
  21157. * var r = [], ri = -1;
  21158. * for(var i = 0, ci; ci = c[i]; i++){
  21159. * // Include in result set only if it's a link to an external resource
  21160. * if(ci.hostname != location.hostname){
  21161. * r[++ri] = ci;
  21162. * }
  21163. * }
  21164. * return r;
  21165. * };
  21166. *
  21167. * Then external links could be gathered with the following statement:
  21168. *
  21169. * var externalLinks = Ext.select("a:external");
  21170. */
  21171. pseudos : {
  21172. "first-child" : function(c){
  21173. var r = [], ri = -1, n,
  21174. i, ci;
  21175. for(i = 0; (ci = n = c[i]); i++){
  21176. while((n = n.previousSibling) && n.nodeType != 1);
  21177. if(!n){
  21178. r[++ri] = ci;
  21179. }
  21180. }
  21181. return r;
  21182. },
  21183. "last-child" : function(c){
  21184. var r = [], ri = -1, n,
  21185. i, ci;
  21186. for(i = 0; (ci = n = c[i]); i++){
  21187. while((n = n.nextSibling) && n.nodeType != 1);
  21188. if(!n){
  21189. r[++ri] = ci;
  21190. }
  21191. }
  21192. return r;
  21193. },
  21194. "nth-child" : function(c, a) {
  21195. var r = [], ri = -1,
  21196. m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),
  21197. f = (m[1] || 1) - 0, l = m[2] - 0,
  21198. i, n, j, cn, pn;
  21199. for(i = 0; n = c[i]; i++){
  21200. pn = n.parentNode;
  21201. if (batch != pn._batch) {
  21202. j = 0;
  21203. for(cn = pn.firstChild; cn; cn = cn.nextSibling){
  21204. if(cn.nodeType == 1){
  21205. cn.nodeIndex = ++j;
  21206. }
  21207. }
  21208. pn._batch = batch;
  21209. }
  21210. if (f == 1) {
  21211. if (l == 0 || n.nodeIndex == l){
  21212. r[++ri] = n;
  21213. }
  21214. } else if ((n.nodeIndex + l) % f == 0){
  21215. r[++ri] = n;
  21216. }
  21217. }
  21218. return r;
  21219. },
  21220. "only-child" : function(c){
  21221. var r = [], ri = -1,
  21222. i, ci;
  21223. for(i = 0; ci = c[i]; i++){
  21224. if(!prev(ci) && !next(ci)){
  21225. r[++ri] = ci;
  21226. }
  21227. }
  21228. return r;
  21229. },
  21230. "empty" : function(c){
  21231. var r = [], ri = -1,
  21232. i, ci, cns, j, cn, empty;
  21233. for(i = 0, ci; ci = c[i]; i++){
  21234. cns = ci.childNodes;
  21235. j = 0;
  21236. empty = true;
  21237. while(cn = cns[j]){
  21238. ++j;
  21239. if(cn.nodeType == 1 || cn.nodeType == 3){
  21240. empty = false;
  21241. break;
  21242. }
  21243. }
  21244. if(empty){
  21245. r[++ri] = ci;
  21246. }
  21247. }
  21248. return r;
  21249. },
  21250. "contains" : function(c, v){
  21251. var r = [], ri = -1,
  21252. i, ci;
  21253. for(i = 0; ci = c[i]; i++){
  21254. if((ci.textContent||ci.innerText||ci.text||'').indexOf(v) != -1){
  21255. r[++ri] = ci;
  21256. }
  21257. }
  21258. return r;
  21259. },
  21260. "nodeValue" : function(c, v){
  21261. var r = [], ri = -1,
  21262. i, ci;
  21263. for(i = 0; ci = c[i]; i++){
  21264. if(ci.firstChild && ci.firstChild.nodeValue == v){
  21265. r[++ri] = ci;
  21266. }
  21267. }
  21268. return r;
  21269. },
  21270. "checked" : function(c){
  21271. var r = [], ri = -1,
  21272. i, ci;
  21273. for(i = 0; ci = c[i]; i++){
  21274. if(ci.checked == true){
  21275. r[++ri] = ci;
  21276. }
  21277. }
  21278. return r;
  21279. },
  21280. "not" : function(c, ss){
  21281. return Ext.DomQuery.filter(c, ss, true);
  21282. },
  21283. "any" : function(c, selectors){
  21284. var ss = selectors.split('|'),
  21285. r = [], ri = -1, s,
  21286. i, ci, j;
  21287. for(i = 0; ci = c[i]; i++){
  21288. for(j = 0; s = ss[j]; j++){
  21289. if(Ext.DomQuery.is(ci, s)){
  21290. r[++ri] = ci;
  21291. break;
  21292. }
  21293. }
  21294. }
  21295. return r;
  21296. },
  21297. "odd" : function(c){
  21298. return this["nth-child"](c, "odd");
  21299. },
  21300. "even" : function(c){
  21301. return this["nth-child"](c, "even");
  21302. },
  21303. "nth" : function(c, a){
  21304. return c[a-1] || [];
  21305. },
  21306. "first" : function(c){
  21307. return c[0] || [];
  21308. },
  21309. "last" : function(c){
  21310. return c[c.length-1] || [];
  21311. },
  21312. "has" : function(c, ss){
  21313. var s = Ext.DomQuery.select,
  21314. r = [], ri = -1,
  21315. i, ci;
  21316. for(i = 0; ci = c[i]; i++){
  21317. if(s(ss, ci).length > 0){
  21318. r[++ri] = ci;
  21319. }
  21320. }
  21321. return r;
  21322. },
  21323. "next" : function(c, ss){
  21324. var is = Ext.DomQuery.is,
  21325. r = [], ri = -1,
  21326. i, ci, n;
  21327. for(i = 0; ci = c[i]; i++){
  21328. n = next(ci);
  21329. if(n && is(n, ss)){
  21330. r[++ri] = ci;
  21331. }
  21332. }
  21333. return r;
  21334. },
  21335. "prev" : function(c, ss){
  21336. var is = Ext.DomQuery.is,
  21337. r = [], ri = -1,
  21338. i, ci, n;
  21339. for(i = 0; ci = c[i]; i++){
  21340. n = prev(ci);
  21341. if(n && is(n, ss)){
  21342. r[++ri] = ci;
  21343. }
  21344. }
  21345. return r;
  21346. }
  21347. }
  21348. };
  21349. }());
  21350. /**
  21351. * Shorthand of {@link Ext.dom.Query#select}
  21352. * @member Ext
  21353. * @method query
  21354. * @inheritdoc Ext.dom.Query#select
  21355. */
  21356. Ext.query = Ext.DomQuery.select;
  21357. //@tag dom,core
  21358. //@require Query.js
  21359. //@define Ext.dom.Element
  21360. //@require Ext.dom.AbstractElement
  21361. /**
  21362. * @class Ext.dom.Element
  21363. * @alternateClassName Ext.Element
  21364. * @alternateClassName Ext.core.Element
  21365. * @extend Ext.dom.AbstractElement
  21366. *
  21367. * Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.
  21368. *
  21369. * All instances of this class inherit the methods of {@link Ext.fx.Anim} making visual effects easily available to all
  21370. * DOM elements.
  21371. *
  21372. * Note that the events documented in this class are not Ext events, they encapsulate browser events. Some older browsers
  21373. * may not support the full range of events. Which events are supported is beyond the control of Ext JS.
  21374. *
  21375. * Usage:
  21376. *
  21377. * // by id
  21378. * var el = Ext.get("my-div");
  21379. *
  21380. * // by DOM element reference
  21381. * var el = Ext.get(myDivElement);
  21382. *
  21383. * # Animations
  21384. *
  21385. * When an element is manipulated, by default there is no animation.
  21386. *
  21387. * var el = Ext.get("my-div");
  21388. *
  21389. * // no animation
  21390. * el.setWidth(100);
  21391. *
  21392. * Many of the functions for manipulating an element have an optional "animate" parameter. This parameter can be
  21393. * specified as boolean (true) for default animation effects.
  21394. *
  21395. * // default animation
  21396. * el.setWidth(100, true);
  21397. *
  21398. * To configure the effects, an object literal with animation options to use as the Element animation configuration
  21399. * object can also be specified. Note that the supported Element animation configuration options are a subset of the
  21400. * {@link Ext.fx.Anim} animation options specific to Fx effects. The supported Element animation configuration options
  21401. * are:
  21402. *
  21403. * Option Default Description
  21404. * --------- -------- ---------------------------------------------
  21405. * {@link Ext.fx.Anim#duration duration} 350 The duration of the animation in milliseconds
  21406. * {@link Ext.fx.Anim#easing easing} easeOut The easing method
  21407. * {@link Ext.fx.Anim#callback callback} none A function to execute when the anim completes
  21408. * {@link Ext.fx.Anim#scope scope} this The scope (this) of the callback function
  21409. *
  21410. * Usage:
  21411. *
  21412. * // Element animation options object
  21413. * var opt = {
  21414. * {@link Ext.fx.Anim#duration duration}: 1000,
  21415. * {@link Ext.fx.Anim#easing easing}: 'elasticIn',
  21416. * {@link Ext.fx.Anim#callback callback}: this.foo,
  21417. * {@link Ext.fx.Anim#scope scope}: this
  21418. * };
  21419. * // animation with some options set
  21420. * el.setWidth(100, opt);
  21421. *
  21422. * The Element animation object being used for the animation will be set on the options object as "anim", which allows
  21423. * you to stop or manipulate the animation. Here is an example:
  21424. *
  21425. * // using the "anim" property to get the Anim object
  21426. * if(opt.anim.isAnimated()){
  21427. * opt.anim.stop();
  21428. * }
  21429. *
  21430. * # Composite (Collections of) Elements
  21431. *
  21432. * For working with collections of Elements, see {@link Ext.CompositeElement}
  21433. *
  21434. * @constructor
  21435. * Creates new Element directly.
  21436. * @param {String/HTMLElement} element
  21437. * @param {Boolean} [forceNew] By default the constructor checks to see if there is already an instance of this
  21438. * element in the cache and if there is it returns the same instance. This will skip that check (useful for extending
  21439. * this class).
  21440. * @return {Object}
  21441. */
  21442. (function() {
  21443. var HIDDEN = 'hidden',
  21444. DOC = document,
  21445. VISIBILITY = "visibility",
  21446. DISPLAY = "display",
  21447. NONE = "none",
  21448. XMASKED = Ext.baseCSSPrefix + "masked",
  21449. XMASKEDRELATIVE = Ext.baseCSSPrefix + "masked-relative",
  21450. EXTELMASKMSG = Ext.baseCSSPrefix + "mask-msg",
  21451. bodyRe = /^body/i,
  21452. visFly,
  21453. // speedy lookup for elements never to box adjust
  21454. noBoxAdjust = Ext.isStrict ? {
  21455. select: 1
  21456. }: {
  21457. input: 1,
  21458. select: 1,
  21459. textarea: 1
  21460. },
  21461. // Pseudo for use by cacheScrollValues
  21462. isScrolled = function(c) {
  21463. var r = [], ri = -1,
  21464. i, ci;
  21465. for (i = 0; ci = c[i]; i++) {
  21466. if (ci.scrollTop > 0 || ci.scrollLeft > 0) {
  21467. r[++ri] = ci;
  21468. }
  21469. }
  21470. return r;
  21471. },
  21472. Element = Ext.define('Ext.dom.Element', {
  21473. extend: 'Ext.dom.AbstractElement',
  21474. alternateClassName: ['Ext.Element', 'Ext.core.Element'],
  21475. addUnits: function() {
  21476. return this.self.addUnits.apply(this.self, arguments);
  21477. },
  21478. /**
  21479. * Tries to focus the element. Any exceptions are caught and ignored.
  21480. * @param {Number} [defer] Milliseconds to defer the focus
  21481. * @return {Ext.dom.Element} this
  21482. */
  21483. focus: function(defer, /* private */ dom) {
  21484. var me = this,
  21485. scrollTop,
  21486. body;
  21487. dom = dom || me.dom;
  21488. body = (dom.ownerDocument || DOC).body || DOC.body;
  21489. try {
  21490. if (Number(defer)) {
  21491. Ext.defer(me.focus, defer, me, [null, dom]);
  21492. } else {
  21493. // Focusing a large element, the browser attempts to scroll as much of it into view
  21494. // as possible. We need to override this behaviour.
  21495. if (dom.offsetHeight > Element.getViewHeight()) {
  21496. scrollTop = body.scrollTop;
  21497. }
  21498. dom.focus();
  21499. if (scrollTop !== undefined) {
  21500. body.scrollTop = scrollTop;
  21501. }
  21502. }
  21503. } catch(e) {
  21504. }
  21505. return me;
  21506. },
  21507. /**
  21508. * Tries to blur the element. Any exceptions are caught and ignored.
  21509. * @return {Ext.dom.Element} this
  21510. */
  21511. blur: function() {
  21512. try {
  21513. this.dom.blur();
  21514. } catch(e) {
  21515. }
  21516. return this;
  21517. },
  21518. /**
  21519. * Tests various css rules/browsers to determine if this element uses a border box
  21520. * @return {Boolean}
  21521. */
  21522. isBorderBox: function() {
  21523. var box = Ext.isBorderBox;
  21524. if (box) {
  21525. box = !((this.dom.tagName || "").toLowerCase() in noBoxAdjust);
  21526. }
  21527. return box;
  21528. },
  21529. /**
  21530. * Sets up event handlers to call the passed functions when the mouse is moved into and out of the Element.
  21531. * @param {Function} overFn The function to call when the mouse enters the Element.
  21532. * @param {Function} outFn The function to call when the mouse leaves the Element.
  21533. * @param {Object} [scope] The scope (`this` reference) in which the functions are executed. Defaults
  21534. * to the Element's DOM element.
  21535. * @param {Object} [options] Options for the listener. See {@link Ext.util.Observable#addListener the
  21536. * options parameter}.
  21537. * @return {Ext.dom.Element} this
  21538. */
  21539. hover: function(overFn, outFn, scope, options) {
  21540. var me = this;
  21541. me.on('mouseenter', overFn, scope || me.dom, options);
  21542. me.on('mouseleave', outFn, scope || me.dom, options);
  21543. return me;
  21544. },
  21545. /**
  21546. * Returns the value of a namespaced attribute from the element's underlying DOM node.
  21547. * @param {String} namespace The namespace in which to look for the attribute
  21548. * @param {String} name The attribute name
  21549. * @return {String} The attribute value
  21550. */
  21551. getAttributeNS: function(ns, name) {
  21552. return this.getAttribute(name, ns);
  21553. },
  21554. getAttribute: (Ext.isIE && !(Ext.isIE9 && DOC.documentMode === 9)) ?
  21555. function(name, ns) {
  21556. var d = this.dom,
  21557. type;
  21558. if (ns) {
  21559. type = typeof d[ns + ":" + name];
  21560. if (type != 'undefined' && type != 'unknown') {
  21561. return d[ns + ":" + name] || null;
  21562. }
  21563. return null;
  21564. }
  21565. if (name === "for") {
  21566. name = "htmlFor";
  21567. }
  21568. return d[name] || null;
  21569. } : function(name, ns) {
  21570. var d = this.dom;
  21571. if (ns) {
  21572. return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name);
  21573. }
  21574. return d.getAttribute(name) || d[name] || null;
  21575. },
  21576. /**
  21577. * When an element is moved around in the DOM, or is hidden using `display:none`, it loses layout, and therefore
  21578. * all scroll positions of all descendant elements are lost.
  21579. *
  21580. * This function caches them, and returns a function, which when run will restore the cached positions.
  21581. * In the following example, the Panel is moved from one Container to another which will cause it to lose all scroll positions:
  21582. *
  21583. * var restoreScroll = myPanel.el.cacheScrollValues();
  21584. * myOtherContainer.add(myPanel);
  21585. * restoreScroll();
  21586. *
  21587. * @return {Function} A function which will restore all descentant elements of this Element to their scroll
  21588. * positions recorded when this function was executed. Be aware that the returned function is a closure which has
  21589. * captured the scope of `cacheScrollValues`, so take care to derefence it as soon as not needed - if is it is a `var`
  21590. * it will drop out of scope, and the reference will be freed.
  21591. */
  21592. cacheScrollValues: function() {
  21593. var me = this,
  21594. scrolledDescendants,
  21595. el, i,
  21596. scrollValues = [],
  21597. result = function() {
  21598. for (i = 0; i < scrolledDescendants.length; i++) {
  21599. el = scrolledDescendants[i];
  21600. el.scrollLeft = scrollValues[i][0];
  21601. el.scrollTop = scrollValues[i][1];
  21602. }
  21603. };
  21604. if (!Ext.DomQuery.pseudos.isScrolled) {
  21605. Ext.DomQuery.pseudos.isScrolled = isScrolled;
  21606. }
  21607. scrolledDescendants = me.query(':isScrolled');
  21608. for (i = 0; i < scrolledDescendants.length; i++) {
  21609. el = scrolledDescendants[i];
  21610. scrollValues[i] = [el.scrollLeft, el.scrollTop];
  21611. }
  21612. return result;
  21613. },
  21614. /**
  21615. * @property {Boolean} autoBoxAdjust
  21616. * True to automatically adjust width and height settings for box-model issues.
  21617. */
  21618. autoBoxAdjust: true,
  21619. /**
  21620. * Checks whether the element is currently visible using both visibility and display properties.
  21621. * @param {Boolean} [deep=false] True to walk the dom and see if parent elements are hidden.
  21622. * If false, the function only checks the visibility of the element itself and it may return
  21623. * `true` even though a parent is not visible.
  21624. * @return {Boolean} `true` if the element is currently visible, else `false`
  21625. */
  21626. isVisible : function(deep) {
  21627. var me = this,
  21628. dom = me.dom,
  21629. stopNode = dom.ownerDocument.documentElement;
  21630. if (!visFly) {
  21631. visFly = new Element.Fly();
  21632. }
  21633. while (dom !== stopNode) {
  21634. // We're invisible if we hit a nonexistent parentNode or a document
  21635. // fragment or computed style visibility:hidden or display:none
  21636. if (!dom || dom.nodeType === 11 || (visFly.attach(dom)).isStyle(VISIBILITY, HIDDEN) || visFly.isStyle(DISPLAY, NONE)) {
  21637. return false;
  21638. }
  21639. // Quit now unless we are being asked to check parent nodes.
  21640. if (!deep) {
  21641. break;
  21642. }
  21643. dom = dom.parentNode;
  21644. }
  21645. return true;
  21646. },
  21647. /**
  21648. * Returns true if display is not "none"
  21649. * @return {Boolean}
  21650. */
  21651. isDisplayed : function() {
  21652. return !this.isStyle(DISPLAY, NONE);
  21653. },
  21654. /**
  21655. * Convenience method for setVisibilityMode(Element.DISPLAY)
  21656. * @param {String} [display] What to set display to when visible
  21657. * @return {Ext.dom.Element} this
  21658. */
  21659. enableDisplayMode : function(display) {
  21660. var me = this;
  21661. me.setVisibilityMode(Element.DISPLAY);
  21662. if (!Ext.isEmpty(display)) {
  21663. (me.$cache || me.getCache()).data.originalDisplay = display;
  21664. }
  21665. return me;
  21666. },
  21667. /**
  21668. * Puts a mask over this element to disable user interaction. Requires core.css.
  21669. * This method can only be applied to elements which accept child nodes.
  21670. * @param {String} [msg] A message to display in the mask
  21671. * @param {String} [msgCls] A css class to apply to the msg element
  21672. * @return {Ext.dom.Element} The mask element
  21673. */
  21674. mask : function(msg, msgCls /* private - passed by AbstractComponent.mask to avoid the need to interrogate the DOM to get the height*/, elHeight) {
  21675. var me = this,
  21676. dom = me.dom,
  21677. // In some cases, setExpression will exist but not be of a function type,
  21678. // so we check it explicitly here to stop IE throwing errors
  21679. setExpression = dom.style.setExpression,
  21680. data = (me.$cache || me.getCache()).data,
  21681. maskEl = data.maskEl,
  21682. maskMsg = data.maskMsg;
  21683. if (!(bodyRe.test(dom.tagName) && me.getStyle('position') == 'static')) {
  21684. me.addCls(XMASKEDRELATIVE);
  21685. }
  21686. // We always needs to recreate the mask since the DOM element may have been re-created
  21687. if (maskEl) {
  21688. maskEl.remove();
  21689. }
  21690. if (maskMsg) {
  21691. maskMsg.remove();
  21692. }
  21693. Ext.DomHelper.append(dom, [{
  21694. cls : Ext.baseCSSPrefix + "mask"
  21695. }, {
  21696. cls : msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG,
  21697. cn : {
  21698. tag: 'div',
  21699. html: msg || ''
  21700. }
  21701. }]);
  21702. maskMsg = Ext.get(dom.lastChild);
  21703. maskEl = Ext.get(maskMsg.dom.previousSibling);
  21704. data.maskMsg = maskMsg;
  21705. data.maskEl = maskEl;
  21706. me.addCls(XMASKED);
  21707. maskEl.setDisplayed(true);
  21708. if (typeof msg == 'string') {
  21709. maskMsg.setDisplayed(true);
  21710. maskMsg.center(me);
  21711. } else {
  21712. maskMsg.setDisplayed(false);
  21713. }
  21714. // NOTE: CSS expressions are resource intensive and to be used only as a last resort
  21715. // These expressions are removed as soon as they are no longer necessary - in the unmask method.
  21716. // In normal use cases an element will be masked for a limited period of time.
  21717. // Fix for https://sencha.jira.com/browse/EXTJSIV-19.
  21718. // IE6 strict mode and IE6-9 quirks mode takes off left+right padding when calculating width!
  21719. if (!Ext.supports.IncludePaddingInWidthCalculation && setExpression) {
  21720. // In an occasional case setExpression will throw an exception
  21721. try {
  21722. maskEl.dom.style.setExpression('width', 'this.parentNode.clientWidth + "px"');
  21723. } catch (e) {}
  21724. }
  21725. // Some versions and modes of IE subtract top+bottom padding when calculating height.
  21726. // Different versions from those which make the same error for width!
  21727. if (!Ext.supports.IncludePaddingInHeightCalculation && setExpression) {
  21728. // In an occasional case setExpression will throw an exception
  21729. try {
  21730. maskEl.dom.style.setExpression('height', 'this.parentNode.' + (dom == DOC.body ? 'scrollHeight' : 'offsetHeight') + ' + "px"');
  21731. } catch (e) {}
  21732. }
  21733. // ie will not expand full height automatically
  21734. else if (Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto') {
  21735. maskEl.setSize(undefined, elHeight || me.getHeight());
  21736. }
  21737. return maskEl;
  21738. },
  21739. /**
  21740. * Hides a previously applied mask.
  21741. */
  21742. unmask : function() {
  21743. var me = this,
  21744. data = (me.$cache || me.getCache()).data,
  21745. maskEl = data.maskEl,
  21746. maskMsg = data.maskMsg,
  21747. style;
  21748. if (maskEl) {
  21749. style = maskEl.dom.style;
  21750. // Remove resource-intensive CSS expressions as soon as they are not required.
  21751. if (style.clearExpression) {
  21752. style.clearExpression('width');
  21753. style.clearExpression('height');
  21754. }
  21755. if (maskEl) {
  21756. maskEl.remove();
  21757. delete data.maskEl;
  21758. }
  21759. if (maskMsg) {
  21760. maskMsg.remove();
  21761. delete data.maskMsg;
  21762. }
  21763. me.removeCls([XMASKED, XMASKEDRELATIVE]);
  21764. }
  21765. },
  21766. /**
  21767. * Returns true if this element is masked. Also re-centers any displayed message within the mask.
  21768. * @return {Boolean}
  21769. */
  21770. isMasked : function() {
  21771. var me = this,
  21772. data = (me.$cache || me.getCache()).data,
  21773. maskEl = data.maskEl,
  21774. maskMsg = data.maskMsg,
  21775. hasMask = false;
  21776. if (maskEl && maskEl.isVisible()) {
  21777. if (maskMsg) {
  21778. maskMsg.center(me);
  21779. }
  21780. hasMask = true;
  21781. }
  21782. return hasMask;
  21783. },
  21784. /**
  21785. * Creates an iframe shim for this element to keep selects and other windowed objects from
  21786. * showing through.
  21787. * @return {Ext.dom.Element} The new shim element
  21788. */
  21789. createShim : function() {
  21790. var el = DOC.createElement('iframe'),
  21791. shim;
  21792. el.frameBorder = '0';
  21793. el.className = Ext.baseCSSPrefix + 'shim';
  21794. el.src = Ext.SSL_SECURE_URL;
  21795. shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom));
  21796. shim.autoBoxAdjust = false;
  21797. return shim;
  21798. },
  21799. /**
  21800. * Convenience method for constructing a KeyMap
  21801. * @param {String/Number/Number[]/Object} key Either a string with the keys to listen for, the numeric key code,
  21802. * array of key codes or an object with the following options:
  21803. * @param {Number/Array} key.key
  21804. * @param {Boolean} key.shift
  21805. * @param {Boolean} key.ctrl
  21806. * @param {Boolean} key.alt
  21807. * @param {Function} fn The function to call
  21808. * @param {Object} [scope] The scope (`this` reference) in which the specified function is executed. Defaults to this Element.
  21809. * @return {Ext.util.KeyMap} The KeyMap created
  21810. */
  21811. addKeyListener : function(key, fn, scope){
  21812. var config;
  21813. if(typeof key != 'object' || Ext.isArray(key)){
  21814. config = {
  21815. target: this,
  21816. key: key,
  21817. fn: fn,
  21818. scope: scope
  21819. };
  21820. }else{
  21821. config = {
  21822. target: this,
  21823. key : key.key,
  21824. shift : key.shift,
  21825. ctrl : key.ctrl,
  21826. alt : key.alt,
  21827. fn: fn,
  21828. scope: scope
  21829. };
  21830. }
  21831. return new Ext.util.KeyMap(config);
  21832. },
  21833. /**
  21834. * Creates a KeyMap for this element
  21835. * @param {Object} config The KeyMap config. See {@link Ext.util.KeyMap} for more details
  21836. * @return {Ext.util.KeyMap} The KeyMap created
  21837. */
  21838. addKeyMap : function(config) {
  21839. return new Ext.util.KeyMap(Ext.apply({
  21840. target: this
  21841. }, config));
  21842. },
  21843. // Mouse events
  21844. /**
  21845. * @event click
  21846. * Fires when a mouse click is detected within the element.
  21847. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21848. * @param {HTMLElement} t The target of the event.
  21849. */
  21850. /**
  21851. * @event contextmenu
  21852. * Fires when a right click is detected within the element.
  21853. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21854. * @param {HTMLElement} t The target of the event.
  21855. */
  21856. /**
  21857. * @event dblclick
  21858. * Fires when a mouse double click is detected within the element.
  21859. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21860. * @param {HTMLElement} t The target of the event.
  21861. */
  21862. /**
  21863. * @event mousedown
  21864. * Fires when a mousedown is detected within the element.
  21865. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21866. * @param {HTMLElement} t The target of the event.
  21867. */
  21868. /**
  21869. * @event mouseup
  21870. * Fires when a mouseup is detected within the element.
  21871. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21872. * @param {HTMLElement} t The target of the event.
  21873. */
  21874. /**
  21875. * @event mouseover
  21876. * Fires when a mouseover is detected within the element.
  21877. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21878. * @param {HTMLElement} t The target of the event.
  21879. */
  21880. /**
  21881. * @event mousemove
  21882. * Fires when a mousemove is detected with the element.
  21883. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21884. * @param {HTMLElement} t The target of the event.
  21885. */
  21886. /**
  21887. * @event mouseout
  21888. * Fires when a mouseout is detected with the element.
  21889. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21890. * @param {HTMLElement} t The target of the event.
  21891. */
  21892. /**
  21893. * @event mouseenter
  21894. * Fires when the mouse enters the element.
  21895. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21896. * @param {HTMLElement} t The target of the event.
  21897. */
  21898. /**
  21899. * @event mouseleave
  21900. * Fires when the mouse leaves the element.
  21901. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21902. * @param {HTMLElement} t The target of the event.
  21903. */
  21904. // Keyboard events
  21905. /**
  21906. * @event keypress
  21907. * Fires when a keypress is detected within the element.
  21908. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21909. * @param {HTMLElement} t The target of the event.
  21910. */
  21911. /**
  21912. * @event keydown
  21913. * Fires when a keydown is detected within the element.
  21914. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21915. * @param {HTMLElement} t The target of the event.
  21916. */
  21917. /**
  21918. * @event keyup
  21919. * Fires when a keyup is detected within the element.
  21920. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21921. * @param {HTMLElement} t The target of the event.
  21922. */
  21923. // HTML frame/object events
  21924. /**
  21925. * @event load
  21926. * Fires when the user agent finishes loading all content within the element. Only supported by window, frames,
  21927. * objects and images.
  21928. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21929. * @param {HTMLElement} t The target of the event.
  21930. */
  21931. /**
  21932. * @event unload
  21933. * Fires when the user agent removes all content from a window or frame. For elements, it fires when the target
  21934. * element or any of its content has been removed.
  21935. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21936. * @param {HTMLElement} t The target of the event.
  21937. */
  21938. /**
  21939. * @event abort
  21940. * Fires when an object/image is stopped from loading before completely loaded.
  21941. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21942. * @param {HTMLElement} t The target of the event.
  21943. */
  21944. /**
  21945. * @event error
  21946. * Fires when an object/image/frame cannot be loaded properly.
  21947. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21948. * @param {HTMLElement} t The target of the event.
  21949. */
  21950. /**
  21951. * @event resize
  21952. * Fires when a document view is resized.
  21953. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21954. * @param {HTMLElement} t The target of the event.
  21955. */
  21956. /**
  21957. * @event scroll
  21958. * Fires when a document view is scrolled.
  21959. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21960. * @param {HTMLElement} t The target of the event.
  21961. */
  21962. // Form events
  21963. /**
  21964. * @event select
  21965. * Fires when a user selects some text in a text field, including input and textarea.
  21966. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21967. * @param {HTMLElement} t The target of the event.
  21968. */
  21969. /**
  21970. * @event change
  21971. * Fires when a control loses the input focus and its value has been modified since gaining focus.
  21972. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21973. * @param {HTMLElement} t The target of the event.
  21974. */
  21975. /**
  21976. * @event submit
  21977. * Fires when a form is submitted.
  21978. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21979. * @param {HTMLElement} t The target of the event.
  21980. */
  21981. /**
  21982. * @event reset
  21983. * Fires when a form is reset.
  21984. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21985. * @param {HTMLElement} t The target of the event.
  21986. */
  21987. /**
  21988. * @event focus
  21989. * Fires when an element receives focus either via the pointing device or by tab navigation.
  21990. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21991. * @param {HTMLElement} t The target of the event.
  21992. */
  21993. /**
  21994. * @event blur
  21995. * Fires when an element loses focus either via the pointing device or by tabbing navigation.
  21996. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  21997. * @param {HTMLElement} t The target of the event.
  21998. */
  21999. // User Interface events
  22000. /**
  22001. * @event DOMFocusIn
  22002. * Where supported. Similar to HTML focus event, but can be applied to any focusable element.
  22003. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  22004. * @param {HTMLElement} t The target of the event.
  22005. */
  22006. /**
  22007. * @event DOMFocusOut
  22008. * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
  22009. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  22010. * @param {HTMLElement} t The target of the event.
  22011. */
  22012. /**
  22013. * @event DOMActivate
  22014. * Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
  22015. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  22016. * @param {HTMLElement} t The target of the event.
  22017. */
  22018. // DOM Mutation events
  22019. /**
  22020. * @event DOMSubtreeModified
  22021. * Where supported. Fires when the subtree is modified.
  22022. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  22023. * @param {HTMLElement} t The target of the event.
  22024. */
  22025. /**
  22026. * @event DOMNodeInserted
  22027. * Where supported. Fires when a node has been added as a child of another node.
  22028. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  22029. * @param {HTMLElement} t The target of the event.
  22030. */
  22031. /**
  22032. * @event DOMNodeRemoved
  22033. * Where supported. Fires when a descendant node of the element is removed.
  22034. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  22035. * @param {HTMLElement} t The target of the event.
  22036. */
  22037. /**
  22038. * @event DOMNodeRemovedFromDocument
  22039. * Where supported. Fires when a node is being removed from a document.
  22040. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  22041. * @param {HTMLElement} t The target of the event.
  22042. */
  22043. /**
  22044. * @event DOMNodeInsertedIntoDocument
  22045. * Where supported. Fires when a node is being inserted into a document.
  22046. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  22047. * @param {HTMLElement} t The target of the event.
  22048. */
  22049. /**
  22050. * @event DOMAttrModified
  22051. * Where supported. Fires when an attribute has been modified.
  22052. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  22053. * @param {HTMLElement} t The target of the event.
  22054. */
  22055. /**
  22056. * @event DOMCharacterDataModified
  22057. * Where supported. Fires when the character data has been modified.
  22058. * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
  22059. * @param {HTMLElement} t The target of the event.
  22060. */
  22061. /**
  22062. * Appends an event handler to this element.
  22063. *
  22064. * @param {String} eventName The name of event to handle.
  22065. *
  22066. * @param {Function} fn The handler function the event invokes. This function is passed the following parameters:
  22067. *
  22068. * - **evt** : EventObject
  22069. *
  22070. * The {@link Ext.EventObject EventObject} describing the event.
  22071. *
  22072. * - **el** : HtmlElement
  22073. *
  22074. * The DOM element which was the target of the event. Note that this may be filtered by using the delegate option.
  22075. *
  22076. * - **o** : Object
  22077. *
  22078. * The options object from the call that setup the listener.
  22079. *
  22080. * @param {Object} scope (optional) The scope (**this** reference) in which the handler function is executed. **If
  22081. * omitted, defaults to this Element.**
  22082. *
  22083. * @param {Object} options (optional) An object containing handler configuration properties. This may contain any of
  22084. * the following properties:
  22085. *
  22086. * - **scope** Object :
  22087. *
  22088. * The scope (**this** reference) in which the handler function is executed. **If omitted, defaults to this
  22089. * Element.**
  22090. *
  22091. * - **delegate** String:
  22092. *
  22093. * A simple selector to filter the target or look for a descendant of the target. See below for additional details.
  22094. *
  22095. * - **stopEvent** Boolean:
  22096. *
  22097. * True to stop the event. That is stop propagation, and prevent the default action.
  22098. *
  22099. * - **preventDefault** Boolean:
  22100. *
  22101. * True to prevent the default action
  22102. *
  22103. * - **stopPropagation** Boolean:
  22104. *
  22105. * True to prevent event propagation
  22106. *
  22107. * - **normalized** Boolean:
  22108. *
  22109. * False to pass a browser event to the handler function instead of an Ext.EventObject
  22110. *
  22111. * - **target** Ext.dom.Element:
  22112. *
  22113. * Only call the handler if the event was fired on the target Element, _not_ if the event was bubbled up from a
  22114. * child node.
  22115. *
  22116. * - **delay** Number:
  22117. *
  22118. * The number of milliseconds to delay the invocation of the handler after the event fires.
  22119. *
  22120. * - **single** Boolean:
  22121. *
  22122. * True to add a handler to handle just the next firing of the event, and then remove itself.
  22123. *
  22124. * - **buffer** Number:
  22125. *
  22126. * Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
  22127. * milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
  22128. * handler is scheduled in its place.
  22129. *
  22130. * **Combining Options**
  22131. *
  22132. * Using the options argument, it is possible to combine different types of listeners:
  22133. *
  22134. * A delayed, one-time listener that auto stops the event and adds a custom argument (forumId) to the options
  22135. * object. The options object is available as the third parameter in the handler function.
  22136. *
  22137. * Code:
  22138. *
  22139. * el.on('click', this.onClick, this, {
  22140. * single: true,
  22141. * delay: 100,
  22142. * stopEvent : true,
  22143. * forumId: 4
  22144. * });
  22145. *
  22146. * **Attaching multiple handlers in 1 call**
  22147. *
  22148. * The method also allows for a single argument to be passed which is a config object containing properties which
  22149. * specify multiple handlers.
  22150. *
  22151. * Code:
  22152. *
  22153. * el.on({
  22154. * 'click' : {
  22155. * fn: this.onClick,
  22156. * scope: this,
  22157. * delay: 100
  22158. * },
  22159. * 'mouseover' : {
  22160. * fn: this.onMouseOver,
  22161. * scope: this
  22162. * },
  22163. * 'mouseout' : {
  22164. * fn: this.onMouseOut,
  22165. * scope: this
  22166. * }
  22167. * });
  22168. *
  22169. * Or a shorthand syntax:
  22170. *
  22171. * Code:
  22172. *
  22173. * el.on({
  22174. * 'click' : this.onClick,
  22175. * 'mouseover' : this.onMouseOver,
  22176. * 'mouseout' : this.onMouseOut,
  22177. * scope: this
  22178. * });
  22179. *
  22180. * **delegate**
  22181. *
  22182. * This is a configuration option that you can pass along when registering a handler for an event to assist with
  22183. * event delegation. Event delegation is a technique that is used to reduce memory consumption and prevent exposure
  22184. * to memory-leaks. By registering an event for a container element as opposed to each element within a container.
  22185. * By setting this configuration option to a simple selector, the target element will be filtered to look for a
  22186. * descendant of the target. For example:
  22187. *
  22188. * // using this markup:
  22189. * <div id='elId'>
  22190. * <p id='p1'>paragraph one</p>
  22191. * <p id='p2' class='clickable'>paragraph two</p>
  22192. * <p id='p3'>paragraph three</p>
  22193. * </div>
  22194. *
  22195. * // utilize event delegation to registering just one handler on the container element:
  22196. * el = Ext.get('elId');
  22197. * el.on(
  22198. * 'click',
  22199. * function(e,t) {
  22200. * // handle click
  22201. * console.info(t.id); // 'p2'
  22202. * },
  22203. * this,
  22204. * {
  22205. * // filter the target element to be a descendant with the class 'clickable'
  22206. * delegate: '.clickable'
  22207. * }
  22208. * );
  22209. *
  22210. * @return {Ext.dom.Element} this
  22211. */
  22212. on: function(eventName, fn, scope, options) {
  22213. Ext.EventManager.on(this, eventName, fn, scope || this, options);
  22214. return this;
  22215. },
  22216. /**
  22217. * Removes an event handler from this element.
  22218. *
  22219. * **Note**: if a *scope* was explicitly specified when {@link #on adding} the listener,
  22220. * the same scope must be specified here.
  22221. *
  22222. * Example:
  22223. *
  22224. * el.un('click', this.handlerFn);
  22225. * // or
  22226. * el.removeListener('click', this.handlerFn);
  22227. *
  22228. * @param {String} eventName The name of the event from which to remove the handler.
  22229. * @param {Function} fn The handler function to remove. **This must be a reference to the function passed into the
  22230. * {@link #on} call.**
  22231. * @param {Object} scope If a scope (**this** reference) was specified when the listener was added, then this must
  22232. * refer to the same object.
  22233. * @return {Ext.dom.Element} this
  22234. */
  22235. un: function(eventName, fn, scope) {
  22236. Ext.EventManager.un(this, eventName, fn, scope || this);
  22237. return this;
  22238. },
  22239. /**
  22240. * Removes all previous added listeners from this element
  22241. * @return {Ext.dom.Element} this
  22242. */
  22243. removeAllListeners: function() {
  22244. Ext.EventManager.removeAll(this);
  22245. return this;
  22246. },
  22247. /**
  22248. * Recursively removes all previous added listeners from this element and its children
  22249. * @return {Ext.dom.Element} this
  22250. */
  22251. purgeAllListeners: function() {
  22252. Ext.EventManager.purgeElement(this);
  22253. return this;
  22254. }
  22255. }, function() {
  22256. var EC = Ext.cache,
  22257. El = this,
  22258. AbstractElement = Ext.dom.AbstractElement,
  22259. focusRe = /a|button|embed|iframe|img|input|object|select|textarea/i,
  22260. nonSpaceRe = /\S/,
  22261. scriptTagRe = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
  22262. replaceScriptTagRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
  22263. srcRe = /\ssrc=([\'\"])(.*?)\1/i,
  22264. typeRe = /\stype=([\'\"])(.*?)\1/i,
  22265. useDocForId = !(Ext.isIE6 || Ext.isIE7 || Ext.isIE8);
  22266. El.boxMarkup = '<div class="{0}-tl"><div class="{0}-tr"><div class="{0}-tc"></div></div></div><div class="{0}-ml"><div class="{0}-mr"><div class="{0}-mc"></div></div></div><div class="{0}-bl"><div class="{0}-br"><div class="{0}-bc"></div></div></div>';
  22267. //</!if>
  22268. // private
  22269. // Garbage collection - uncache elements/purge listeners on orphaned elements
  22270. // so we don't hold a reference and cause the browser to retain them
  22271. function garbageCollect() {
  22272. if (!Ext.enableGarbageCollector) {
  22273. clearInterval(El.collectorThreadId);
  22274. } else {
  22275. var eid,
  22276. d,
  22277. o,
  22278. t;
  22279. for (eid in EC) {
  22280. if (!EC.hasOwnProperty(eid)) {
  22281. continue;
  22282. }
  22283. o = EC[eid];
  22284. // Skip document and window elements
  22285. if (o.skipGarbageCollection) {
  22286. continue;
  22287. }
  22288. d = o.dom;
  22289. // Should always have a DOM node
  22290. if (!d) {
  22291. Ext.Error.raise('Missing DOM node in element garbage collection: ' + eid);
  22292. }
  22293. // Check that document and window elements haven't got through
  22294. if (d && (d.getElementById || d.navigator)) {
  22295. Ext.Error.raise('Unexpected document or window element in element garbage collection');
  22296. }
  22297. // -------------------------------------------------------
  22298. // Determining what is garbage:
  22299. // -------------------------------------------------------
  22300. // !d.parentNode
  22301. // no parentNode == direct orphan, definitely garbage
  22302. // -------------------------------------------------------
  22303. // !d.offsetParent && !document.getElementById(eid)
  22304. // display none elements have no offsetParent so we will
  22305. // also try to look it up by it's id. However, check
  22306. // offsetParent first so we don't do unneeded lookups.
  22307. // This enables collection of elements that are not orphans
  22308. // directly, but somewhere up the line they have an orphan
  22309. // parent.
  22310. // -------------------------------------------------------
  22311. if (!d.parentNode || (!d.offsetParent && !Ext.getElementById(eid))) {
  22312. if (d && Ext.enableListenerCollection) {
  22313. Ext.EventManager.removeAll(d);
  22314. }
  22315. delete EC[eid];
  22316. }
  22317. }
  22318. // Cleanup IE Object leaks
  22319. if (Ext.isIE) {
  22320. t = {};
  22321. for (eid in EC) {
  22322. if (!EC.hasOwnProperty(eid)) {
  22323. continue;
  22324. }
  22325. t[eid] = EC[eid];
  22326. }
  22327. EC = Ext.cache = t;
  22328. }
  22329. }
  22330. }
  22331. El.collectorThreadId = setInterval(garbageCollect, 30000);
  22332. //Stuff from Element-more.js
  22333. El.addMethods({
  22334. /**
  22335. * Monitors this Element for the mouse leaving. Calls the function after the specified delay only if
  22336. * the mouse was not moved back into the Element within the delay. If the mouse *was* moved
  22337. * back in, the function is not called.
  22338. * @param {Number} delay The delay **in milliseconds** to wait for possible mouse re-entry before calling the handler function.
  22339. * @param {Function} handler The function to call if the mouse remains outside of this Element for the specified time.
  22340. * @param {Object} [scope] The scope (`this` reference) in which the handler function executes. Defaults to this Element.
  22341. * @return {Object} The listeners object which was added to this element so that monitoring can be stopped. Example usage:
  22342. *
  22343. * // Hide the menu if the mouse moves out for 250ms or more
  22344. * this.mouseLeaveMonitor = this.menuEl.monitorMouseLeave(250, this.hideMenu, this);
  22345. *
  22346. * ...
  22347. * // Remove mouseleave monitor on menu destroy
  22348. * this.menuEl.un(this.mouseLeaveMonitor);
  22349. *
  22350. */
  22351. monitorMouseLeave: function(delay, handler, scope) {
  22352. var me = this,
  22353. timer,
  22354. listeners = {
  22355. mouseleave: function(e) {
  22356. timer = setTimeout(Ext.Function.bind(handler, scope||me, [e]), delay);
  22357. },
  22358. mouseenter: function() {
  22359. clearTimeout(timer);
  22360. },
  22361. freezeEvent: true
  22362. };
  22363. me.on(listeners);
  22364. return listeners;
  22365. },
  22366. /**
  22367. * Stops the specified event(s) from bubbling and optionally prevents the default action
  22368. * @param {String/String[]} eventName an event / array of events to stop from bubbling
  22369. * @param {Boolean} [preventDefault] true to prevent the default action too
  22370. * @return {Ext.dom.Element} this
  22371. */
  22372. swallowEvent : function(eventName, preventDefault) {
  22373. var me = this,
  22374. e, eLen;
  22375. function fn(e) {
  22376. e.stopPropagation();
  22377. if (preventDefault) {
  22378. e.preventDefault();
  22379. }
  22380. }
  22381. if (Ext.isArray(eventName)) {
  22382. eLen = eventName.length;
  22383. for (e = 0; e < eLen; e++) {
  22384. me.on(eventName[e], fn);
  22385. }
  22386. return me;
  22387. }
  22388. me.on(eventName, fn);
  22389. return me;
  22390. },
  22391. /**
  22392. * Create an event handler on this element such that when the event fires and is handled by this element,
  22393. * it will be relayed to another object (i.e., fired again as if it originated from that object instead).
  22394. * @param {String} eventName The type of event to relay
  22395. * @param {Object} observable Any object that extends {@link Ext.util.Observable} that will provide the context
  22396. * for firing the relayed event
  22397. */
  22398. relayEvent : function(eventName, observable) {
  22399. this.on(eventName, function(e) {
  22400. observable.fireEvent(eventName, e);
  22401. });
  22402. },
  22403. /**
  22404. * Removes Empty, or whitespace filled text nodes. Combines adjacent text nodes.
  22405. * @param {Boolean} [forceReclean=false] By default the element keeps track if it has been cleaned already
  22406. * so you can call this over and over. However, if you update the element and need to force a reclean, you
  22407. * can pass true.
  22408. */
  22409. clean : function(forceReclean) {
  22410. var me = this,
  22411. dom = me.dom,
  22412. data = (me.$cache || me.getCache()).data,
  22413. n = dom.firstChild,
  22414. ni = -1,
  22415. nx;
  22416. if (data.isCleaned && forceReclean !== true) {
  22417. return me;
  22418. }
  22419. while (n) {
  22420. nx = n.nextSibling;
  22421. if (n.nodeType == 3) {
  22422. // Remove empty/whitespace text nodes
  22423. if (!(nonSpaceRe.test(n.nodeValue))) {
  22424. dom.removeChild(n);
  22425. // Combine adjacent text nodes
  22426. } else if (nx && nx.nodeType == 3) {
  22427. n.appendData(Ext.String.trim(nx.data));
  22428. dom.removeChild(nx);
  22429. nx = n.nextSibling;
  22430. n.nodeIndex = ++ni;
  22431. }
  22432. } else {
  22433. // Recursively clean
  22434. Ext.fly(n).clean();
  22435. n.nodeIndex = ++ni;
  22436. }
  22437. n = nx;
  22438. }
  22439. data.isCleaned = true;
  22440. return me;
  22441. },
  22442. /**
  22443. * Direct access to the Ext.ElementLoader {@link Ext.ElementLoader#method-load} method. The method takes the same object
  22444. * parameter as {@link Ext.ElementLoader#method-load}
  22445. * @return {Ext.dom.Element} this
  22446. */
  22447. load : function(options) {
  22448. this.getLoader().load(options);
  22449. return this;
  22450. },
  22451. /**
  22452. * Gets this element's {@link Ext.ElementLoader ElementLoader}
  22453. * @return {Ext.ElementLoader} The loader
  22454. */
  22455. getLoader : function() {
  22456. var me = this,
  22457. data = (me.$cache || me.getCache()).data,
  22458. loader = data.loader;
  22459. if (!loader) {
  22460. data.loader = loader = new Ext.ElementLoader({
  22461. target: me
  22462. });
  22463. }
  22464. return loader;
  22465. },
  22466. /**
  22467. * @private.
  22468. * Currently used for updating grid cells without modifying DOM structure
  22469. *
  22470. * Synchronizes content of this Element with the content of the passed element.
  22471. *
  22472. * Style and CSS class are copied from source into this Element, and contents are synched
  22473. * recursively. If a child node is a text node, the textual data is copied.
  22474. */
  22475. syncContent: function(source) {
  22476. source = Ext.getDom(source);
  22477. var me = this,
  22478. sourceNodes = source.childNodes,
  22479. sourceLen = sourceNodes.length,
  22480. dest = me.dom,
  22481. destNodes = dest.childNodes,
  22482. destLen = destNodes.length,
  22483. i, destNode, sourceNode,
  22484. nodeType;
  22485. // Copy top node's style and CSS class
  22486. dest.style.cssText = source.style.cssText;
  22487. dest.className = source.className;
  22488. // If the number of child nodes does not match, fall back to replacing innerHTML
  22489. if (sourceLen !== destLen) {
  22490. source.innerHTML = dest.innerHTML;
  22491. return;
  22492. }
  22493. // Loop through source nodes.
  22494. // If there are fewer, we must remove excess
  22495. for (i = 0; i < sourceLen; i++) {
  22496. sourceNode = sourceNodes[i];
  22497. destNode = destNodes[i];
  22498. nodeType = sourceNode.nodeType;
  22499. // If node structure is out of sync, just drop innerHTML in and return
  22500. if (nodeType !== destNode.nodeType || (nodeType === 1 && sourceNode.tagName !== destNode.tagName)) {
  22501. dest.innerHTML = source.innerHTML;
  22502. return;
  22503. }
  22504. // Update text node
  22505. if (nodeType === 3) {
  22506. destNode.data = sourceNode.data;
  22507. }
  22508. // Sync element content
  22509. else {
  22510. if (sourceNode.id && destNode.id !== sourceNode.id) {
  22511. destNode.id = sourceNode.id;
  22512. }
  22513. destNode.style.cssText = sourceNode.style.cssText;
  22514. destNode.className = sourceNode.className;
  22515. Ext.fly(destNode).syncContent(sourceNode);
  22516. }
  22517. }
  22518. },
  22519. /**
  22520. * Updates the innerHTML of this element, optionally searching for and processing scripts.
  22521. * @param {String} html The new HTML
  22522. * @param {Boolean} [loadScripts] True to look for and process scripts (defaults to false)
  22523. * @param {Function} [callback] For async script loading you can be notified when the update completes
  22524. * @return {Ext.dom.Element} this
  22525. */
  22526. update : function(html, loadScripts, callback) {
  22527. var me = this,
  22528. id,
  22529. dom,
  22530. interval;
  22531. if (!me.dom) {
  22532. return me;
  22533. }
  22534. html = html || '';
  22535. dom = me.dom;
  22536. if (loadScripts !== true) {
  22537. dom.innerHTML = html;
  22538. Ext.callback(callback, me);
  22539. return me;
  22540. }
  22541. id = Ext.id();
  22542. html += '<span id="' + id + '"></span>';
  22543. interval = setInterval(function() {
  22544. var hd,
  22545. match,
  22546. attrs,
  22547. srcMatch,
  22548. typeMatch,
  22549. el,
  22550. s;
  22551. if (!(el = DOC.getElementById(id))) {
  22552. return false;
  22553. }
  22554. clearInterval(interval);
  22555. Ext.removeNode(el);
  22556. hd = Ext.getHead().dom;
  22557. while ((match = scriptTagRe.exec(html))) {
  22558. attrs = match[1];
  22559. srcMatch = attrs ? attrs.match(srcRe) : false;
  22560. if (srcMatch && srcMatch[2]) {
  22561. s = DOC.createElement("script");
  22562. s.src = srcMatch[2];
  22563. typeMatch = attrs.match(typeRe);
  22564. if (typeMatch && typeMatch[2]) {
  22565. s.type = typeMatch[2];
  22566. }
  22567. hd.appendChild(s);
  22568. } else if (match[2] && match[2].length > 0) {
  22569. if (window.execScript) {
  22570. window.execScript(match[2]);
  22571. } else {
  22572. window.eval(match[2]);
  22573. }
  22574. }
  22575. }
  22576. Ext.callback(callback, me);
  22577. }, 20);
  22578. dom.innerHTML = html.replace(replaceScriptTagRe, '');
  22579. return me;
  22580. },
  22581. // inherit docs, overridden so we can add removeAnchor
  22582. removeAllListeners : function() {
  22583. this.removeAnchor();
  22584. Ext.EventManager.removeAll(this.dom);
  22585. return this;
  22586. },
  22587. /**
  22588. * Creates a proxy element of this element
  22589. * @param {String/Object} config The class name of the proxy element or a DomHelper config object
  22590. * @param {String/HTMLElement} [renderTo] The element or element id to render the proxy to. Defaults to: document.body.
  22591. * @param {Boolean} [matchBox=false] True to align and size the proxy to this element now.
  22592. * @return {Ext.dom.Element} The new proxy element
  22593. */
  22594. createProxy : function(config, renderTo, matchBox) {
  22595. config = (typeof config == 'object') ? config : {tag : "div", cls: config};
  22596. var me = this,
  22597. proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) :
  22598. Ext.DomHelper.insertBefore(me.dom, config, true);
  22599. proxy.setVisibilityMode(Element.DISPLAY);
  22600. proxy.hide();
  22601. if (matchBox && me.setBox && me.getBox) { // check to make sure Element.position.js is loaded
  22602. proxy.setBox(me.getBox());
  22603. }
  22604. return proxy;
  22605. },
  22606. /**
  22607. * Gets the parent node of the current element taking into account Ext.scopeResetCSS
  22608. * @protected
  22609. * @return {HTMLElement} The parent element
  22610. */
  22611. getScopeParent: function() {
  22612. var parent = this.dom.parentNode;
  22613. if (Ext.scopeResetCSS) {
  22614. // If it's a normal reset, we will be wrapped in a single x-reset element, so grab the parent
  22615. parent = parent.parentNode;
  22616. if (!Ext.supports.CSS3LinearGradient || !Ext.supports.CSS3BorderRadius) {
  22617. // In the cases where we have nbr or nlg, it will be wrapped in a second element,
  22618. // so we need to go and get the parent again.
  22619. parent = parent.parentNode;
  22620. }
  22621. }
  22622. return parent;
  22623. },
  22624. /**
  22625. * Returns true if this element needs an explicit tabIndex to make it focusable. Input fields, text areas, buttons
  22626. * anchors elements **with an href** etc do not need a tabIndex, but structural elements do.
  22627. */
  22628. needsTabIndex: function() {
  22629. if (this.dom) {
  22630. if ((this.dom.nodeName === 'a') && (!this.dom.href)) {
  22631. return true;
  22632. }
  22633. return !focusRe.test(this.dom.nodeName);
  22634. }
  22635. },
  22636. /**
  22637. * Checks whether this element can be focused.
  22638. * @return {Boolean} True if the element is focusable
  22639. */
  22640. focusable: function () {
  22641. var dom = this.dom,
  22642. nodeName = dom.nodeName,
  22643. canFocus = false;
  22644. if (!dom.disabled) {
  22645. if (focusRe.test(nodeName)) {
  22646. if ((nodeName !== 'a') || dom.href) {
  22647. canFocus = true;
  22648. }
  22649. } else {
  22650. canFocus = !isNaN(dom.tabIndex);
  22651. }
  22652. }
  22653. return canFocus && this.isVisible(true);
  22654. }
  22655. });
  22656. if (Ext.isIE) {
  22657. El.prototype.getById = function (id, asDom) {
  22658. var dom = this.dom,
  22659. cacheItem, el, ret;
  22660. if (dom) {
  22661. // for normal elements getElementById is the best solution, but if the el is
  22662. // not part of the document.body, we need to use all[]
  22663. el = (useDocForId && DOC.getElementById(id)) || dom.all[id];
  22664. if (el) {
  22665. if (asDom) {
  22666. ret = el;
  22667. } else {
  22668. // calling El.get here is a real hit (2x slower) because it has to
  22669. // redetermine that we are giving it a dom el.
  22670. cacheItem = EC[id];
  22671. if (cacheItem && cacheItem.el) {
  22672. ret = Ext.updateCacheEntry(cacheItem, el).el;
  22673. } else {
  22674. ret = new Element(el);
  22675. }
  22676. }
  22677. return ret;
  22678. }
  22679. }
  22680. return asDom ? Ext.getDom(id) : El.get(id);
  22681. };
  22682. }
  22683. El.createAlias({
  22684. /**
  22685. * @method
  22686. * @inheritdoc Ext.dom.Element#on
  22687. * Shorthand for {@link #on}.
  22688. */
  22689. addListener: 'on',
  22690. /**
  22691. * @method
  22692. * @inheritdoc Ext.dom.Element#un
  22693. * Shorthand for {@link #un}.
  22694. */
  22695. removeListener: 'un',
  22696. /**
  22697. * @method
  22698. * @inheritdoc Ext.dom.Element#removeAllListeners
  22699. * Alias for {@link #removeAllListeners}.
  22700. */
  22701. clearListeners: 'removeAllListeners'
  22702. });
  22703. El.Fly = AbstractElement.Fly = new Ext.Class({
  22704. extend: El,
  22705. constructor: function(dom) {
  22706. this.dom = dom;
  22707. },
  22708. attach: AbstractElement.Fly.prototype.attach
  22709. });
  22710. if (Ext.isIE) {
  22711. Ext.getElementById = function (id) {
  22712. var el = DOC.getElementById(id),
  22713. detachedBodyEl;
  22714. if (!el && (detachedBodyEl = AbstractElement.detachedBodyEl)) {
  22715. el = detachedBodyEl.dom.all[id];
  22716. }
  22717. return el;
  22718. };
  22719. } else if (!DOC.querySelector) {
  22720. Ext.getDetachedBody = Ext.getBody;
  22721. Ext.getElementById = function (id) {
  22722. return DOC.getElementById(id);
  22723. };
  22724. }
  22725. });
  22726. }());
  22727. //@tag dom,core
  22728. //@require Element.js
  22729. //@define Ext.dom.Element-alignment
  22730. //@define Ext.dom.Element
  22731. /**
  22732. * @class Ext.dom.Element
  22733. */
  22734. Ext.dom.Element.override((function() {
  22735. var doc = document,
  22736. win = window,
  22737. alignRe = /^([a-z]+)-([a-z]+)(\?)?$/,
  22738. round = Math.round;
  22739. return {
  22740. /**
  22741. * Gets the x,y coordinates specified by the anchor position on the element.
  22742. * @param {String} [anchor='c'] The specified anchor position. See {@link #alignTo}
  22743. * for details on supported anchor positions.
  22744. * @param {Boolean} [local] True to get the local (element top/left-relative) anchor position instead
  22745. * of page coordinates
  22746. * @param {Object} [size] An object containing the size to use for calculating anchor position
  22747. * {width: (target width), height: (target height)} (defaults to the element's current size)
  22748. * @return {Number[]} [x, y] An array containing the element's x and y coordinates
  22749. */
  22750. getAnchorXY: function(anchor, local, mySize) {
  22751. //Passing a different size is useful for pre-calculating anchors,
  22752. //especially for anchored animations that change the el size.
  22753. anchor = (anchor || "tl").toLowerCase();
  22754. mySize = mySize || {};
  22755. var me = this,
  22756. isViewport = me.dom == doc.body || me.dom == doc,
  22757. myWidth = mySize.width || isViewport ? Ext.dom.Element.getViewWidth() : me.getWidth(),
  22758. myHeight = mySize.height || isViewport ? Ext.dom.Element.getViewHeight() : me.getHeight(),
  22759. xy,
  22760. myPos = me.getXY(),
  22761. scroll = me.getScroll(),
  22762. extraX = isViewport ? scroll.left : !local ? myPos[0] : 0,
  22763. extraY = isViewport ? scroll.top : !local ? myPos[1] : 0;
  22764. // Calculate anchor position.
  22765. // Test most common cases for picker alignment first.
  22766. switch (anchor) {
  22767. case 'tl' : xy = [ 0, 0];
  22768. break;
  22769. case 'bl' : xy = [ 0, myHeight];
  22770. break;
  22771. case 'tr' : xy = [ myWidth, 0];
  22772. break;
  22773. case 'c' : xy = [ round(myWidth * 0.5), round(myHeight * 0.5)];
  22774. break;
  22775. case 't' : xy = [ round(myWidth * 0.5), 0];
  22776. break;
  22777. case 'l' : xy = [ 0, round(myHeight * 0.5)];
  22778. break;
  22779. case 'r' : xy = [ myWidth, round(myHeight * 0.5)];
  22780. break;
  22781. case 'b' : xy = [ round(myWidth * 0.5), myHeight];
  22782. break;
  22783. case 'br' : xy = [ myWidth, myHeight];
  22784. }
  22785. return [xy[0] + extraX, xy[1] + extraY];
  22786. },
  22787. /**
  22788. * Gets the x,y coordinates to align this element with another element. See {@link #alignTo} for more info on the
  22789. * supported position values.
  22790. * @param {String/HTMLElement/Ext.Element} element The element to align to.
  22791. * @param {String} [position="tl-bl?"] The position to align to (defaults to )
  22792. * @param {Number[]} [offsets] Offset the positioning by [x, y]
  22793. * @return {Number[]} [x, y]
  22794. */
  22795. getAlignToXY : function(alignToEl, posSpec, offset) {
  22796. alignToEl = Ext.get(alignToEl);
  22797. if (!alignToEl || !alignToEl.dom) {
  22798. Ext.Error.raise({
  22799. sourceClass: 'Ext.dom.Element',
  22800. sourceMethod: 'getAlignToXY',
  22801. msg: 'Attempted to align an element that doesn\'t exist'
  22802. });
  22803. }
  22804. offset = offset || [0,0];
  22805. posSpec = (!posSpec || posSpec == "?" ? "tl-bl?" : (!(/-/).test(posSpec) && posSpec !== "" ? "tl-" + posSpec : posSpec || "tl-bl")).toLowerCase();
  22806. var me = this,
  22807. myPosition,
  22808. alignToElPosition,
  22809. x,
  22810. y,
  22811. myWidth,
  22812. myHeight,
  22813. alignToElRegion,
  22814. viewportWidth = Ext.dom.Element.getViewWidth() - 10, // 10px of margin for ie
  22815. viewportHeight = Ext.dom.Element.getViewHeight() - 10, // 10px of margin for ie
  22816. p1y,
  22817. p1x,
  22818. p2y,
  22819. p2x,
  22820. swapY,
  22821. swapX,
  22822. docElement = doc.documentElement,
  22823. docBody = doc.body,
  22824. scrollX = (docElement.scrollLeft || docBody.scrollLeft || 0),// + 5, WHY was 5 ever added?
  22825. scrollY = (docElement.scrollTop || docBody.scrollTop || 0),// + 5, It means align will fail if the alignTo el was at less than 5,5
  22826. constrain, //constrain to viewport
  22827. align1,
  22828. align2,
  22829. alignMatch = posSpec.match(alignRe);
  22830. if (!alignMatch) {
  22831. Ext.Error.raise({
  22832. sourceClass: 'Ext.dom.Element',
  22833. sourceMethod: 'getAlignToXY',
  22834. el: alignToEl,
  22835. position: posSpec,
  22836. offset: offset,
  22837. msg: 'Attemmpted to align an element with an invalid position: "' + posSpec + '"'
  22838. });
  22839. }
  22840. align1 = alignMatch[1];
  22841. align2 = alignMatch[2];
  22842. constrain = !!alignMatch[3];
  22843. //Subtract the aligned el's internal xy from the target's offset xy
  22844. //plus custom offset to get this Element's new offset xy
  22845. myPosition = me.getAnchorXY(align1, true);
  22846. alignToElPosition = alignToEl.getAnchorXY(align2, false);
  22847. x = alignToElPosition[0] - myPosition[0] + offset[0];
  22848. y = alignToElPosition[1] - myPosition[1] + offset[1];
  22849. // If position spec ended with a "?", then constrain to viewport is necessary
  22850. if (constrain) {
  22851. myWidth = me.getWidth();
  22852. myHeight = me.getHeight();
  22853. alignToElRegion = alignToEl.getRegion();
  22854. //If we are at a viewport boundary and the aligned el is anchored on a target border that is
  22855. //perpendicular to the vp border, allow the aligned el to slide on that border,
  22856. //otherwise swap the aligned el to the opposite border of the target.
  22857. p1y = align1.charAt(0);
  22858. p1x = align1.charAt(align1.length - 1);
  22859. p2y = align2.charAt(0);
  22860. p2x = align2.charAt(align2.length - 1);
  22861. swapY = ((p1y == "t" && p2y == "b") || (p1y == "b" && p2y == "t"));
  22862. swapX = ((p1x == "r" && p2x == "l") || (p1x == "l" && p2x == "r"));
  22863. if (x + myWidth > viewportWidth + scrollX) {
  22864. x = swapX ? alignToElRegion.left - myWidth : viewportWidth + scrollX - myWidth;
  22865. }
  22866. if (x < scrollX) {
  22867. x = swapX ? alignToElRegion.right : scrollX;
  22868. }
  22869. if (y + myHeight > viewportHeight + scrollY) {
  22870. y = swapY ? alignToElRegion.top - myHeight : viewportHeight + scrollY - myHeight;
  22871. }
  22872. if (y < scrollY) {
  22873. y = swapY ? alignToElRegion.bottom : scrollY;
  22874. }
  22875. }
  22876. return [x,y];
  22877. },
  22878. /**
  22879. * Anchors an element to another element and realigns it when the window is resized.
  22880. * @param {String/HTMLElement/Ext.Element} element The element to align to.
  22881. * @param {String} position The position to align to.
  22882. * @param {Number[]} [offsets] Offset the positioning by [x, y]
  22883. * @param {Boolean/Object} [animate] True for the default animation or a standard Element animation config object
  22884. * @param {Boolean/Number} [monitorScroll] True to monitor body scroll and reposition. If this parameter
  22885. * is a number, it is used as the buffer delay (defaults to 50ms).
  22886. * @param {Function} [callback] The function to call after the animation finishes
  22887. * @return {Ext.Element} this
  22888. */
  22889. anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback) {
  22890. var me = this,
  22891. dom = me.dom,
  22892. scroll = !Ext.isEmpty(monitorScroll),
  22893. action = function() {
  22894. Ext.fly(dom).alignTo(el, alignment, offsets, animate);
  22895. Ext.callback(callback, Ext.fly(dom));
  22896. },
  22897. anchor = this.getAnchor();
  22898. // previous listener anchor, remove it
  22899. this.removeAnchor();
  22900. Ext.apply(anchor, {
  22901. fn: action,
  22902. scroll: scroll
  22903. });
  22904. Ext.EventManager.onWindowResize(action, null);
  22905. if (scroll) {
  22906. Ext.EventManager.on(win, 'scroll', action, null,
  22907. {buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
  22908. }
  22909. action.call(me); // align immediately
  22910. return me;
  22911. },
  22912. /**
  22913. * Remove any anchor to this element. See {@link #anchorTo}.
  22914. * @return {Ext.dom.Element} this
  22915. */
  22916. removeAnchor : function() {
  22917. var me = this,
  22918. anchor = this.getAnchor();
  22919. if (anchor && anchor.fn) {
  22920. Ext.EventManager.removeResizeListener(anchor.fn);
  22921. if (anchor.scroll) {
  22922. Ext.EventManager.un(win, 'scroll', anchor.fn);
  22923. }
  22924. delete anchor.fn;
  22925. }
  22926. return me;
  22927. },
  22928. getAlignVector: function(el, spec, offset) {
  22929. var me = this,
  22930. myPos = me.getXY(),
  22931. alignedPos = me.getAlignToXY(el, spec, offset);
  22932. el = Ext.get(el);
  22933. if (!el || !el.dom) {
  22934. Ext.Error.raise({
  22935. sourceClass: 'Ext.dom.Element',
  22936. sourceMethod: 'getAlignVector',
  22937. msg: 'Attempted to align an element that doesn\'t exist'
  22938. });
  22939. }
  22940. alignedPos[0] -= myPos[0];
  22941. alignedPos[1] -= myPos[1];
  22942. return alignedPos;
  22943. },
  22944. /**
  22945. * Aligns this element with another element relative to the specified anchor points. If the other element is the
  22946. * document it aligns it to the viewport. The position parameter is optional, and can be specified in any one of
  22947. * the following formats:
  22948. *
  22949. * - **Blank**: Defaults to aligning the element's top-left corner to the target's bottom-left corner ("tl-bl").
  22950. * - **One anchor (deprecated)**: The passed anchor position is used as the target element's anchor point.
  22951. * The element being aligned will position its top-left corner (tl) to that point. *This method has been
  22952. * deprecated in favor of the newer two anchor syntax below*.
  22953. * - **Two anchors**: If two values from the table below are passed separated by a dash, the first value is used as the
  22954. * element's anchor point, and the second value is used as the target's anchor point.
  22955. *
  22956. * In addition to the anchor points, the position parameter also supports the "?" character. If "?" is passed at the end of
  22957. * the position string, the element will attempt to align as specified, but the position will be adjusted to constrain to
  22958. * the viewport if necessary. Note that the element being aligned might be swapped to align to a different position than
  22959. * that specified in order to enforce the viewport constraints.
  22960. * Following are all of the supported anchor positions:
  22961. *
  22962. * <pre>
  22963. * Value Description
  22964. * ----- -----------------------------
  22965. * tl The top left corner (default)
  22966. * t The center of the top edge
  22967. * tr The top right corner
  22968. * l The center of the left edge
  22969. * c In the center of the element
  22970. * r The center of the right edge
  22971. * bl The bottom left corner
  22972. * b The center of the bottom edge
  22973. * br The bottom right corner
  22974. * </pre>
  22975. *
  22976. * Example Usage:
  22977. *
  22978. * // align el to other-el using the default positioning ("tl-bl", non-constrained)
  22979. * el.alignTo("other-el");
  22980. *
  22981. * // align the top left corner of el with the top right corner of other-el (constrained to viewport)
  22982. * el.alignTo("other-el", "tr?");
  22983. *
  22984. * // align the bottom right corner of el with the center left edge of other-el
  22985. * el.alignTo("other-el", "br-l?");
  22986. *
  22987. * // align the center of el with the bottom left corner of other-el and
  22988. * // adjust the x position by -6 pixels (and the y position by 0)
  22989. * el.alignTo("other-el", "c-bl", [-6, 0]);
  22990. *
  22991. * @param {String/HTMLElement/Ext.Element} element The element to align to.
  22992. * @param {String} [position="tl-bl?"] The position to align to
  22993. * @param {Number[]} [offsets] Offset the positioning by [x, y]
  22994. * @param {Boolean/Object} [animate] true for the default animation or a standard Element animation config object
  22995. * @return {Ext.Element} this
  22996. */
  22997. alignTo: function(element, position, offsets, animate) {
  22998. var me = this;
  22999. return me.setXY(me.getAlignToXY(element, position, offsets),
  23000. me.anim && !!animate ? me.anim(animate) : false);
  23001. },
  23002. /**
  23003. * Returns the `[X, Y]` vector by which this element must be translated to make a best attempt
  23004. * to constrain within the passed constraint. Returns `false` is this element does not need to be moved.
  23005. *
  23006. * Priority is given to constraining the top and left within the constraint.
  23007. *
  23008. * The constraint may either be an existing element into which this element is to be constrained, or
  23009. * an {@link Ext.util.Region Region} into which this element is to be constrained.
  23010. *
  23011. * @param {Ext.Element/Ext.util.Region} constrainTo The Element or Region into which this element is to be constrained.
  23012. * @param {Number[]} proposedPosition A proposed `[X, Y]` position to test for validity and to produce a vector for instead
  23013. * of using this Element's current position;
  23014. * @returns {Number[]/Boolean} **If** this element *needs* to be translated, an `[X, Y]`
  23015. * vector by which this element must be translated. Otherwise, `false`.
  23016. */
  23017. getConstrainVector: function(constrainTo, proposedPosition) {
  23018. if (!(constrainTo instanceof Ext.util.Region)) {
  23019. constrainTo = Ext.get(constrainTo).getViewRegion();
  23020. }
  23021. var thisRegion = this.getRegion(),
  23022. vector = [0, 0],
  23023. shadowSize = (this.shadow && !this.shadowDisabled) ? this.shadow.getShadowSize() : undefined,
  23024. overflowed = false;
  23025. // Shift this region to occupy the proposed position
  23026. if (proposedPosition) {
  23027. thisRegion.translateBy(proposedPosition[0] - thisRegion.x, proposedPosition[1] - thisRegion.y);
  23028. }
  23029. // Reduce the constrain region to allow for shadow
  23030. if (shadowSize) {
  23031. constrainTo.adjust(shadowSize[0], -shadowSize[1], -shadowSize[2], shadowSize[3]);
  23032. }
  23033. // Constrain the X coordinate by however much this Element overflows
  23034. if (thisRegion.right > constrainTo.right) {
  23035. overflowed = true;
  23036. vector[0] = (constrainTo.right - thisRegion.right); // overflowed the right
  23037. }
  23038. if (thisRegion.left + vector[0] < constrainTo.left) {
  23039. overflowed = true;
  23040. vector[0] = (constrainTo.left - thisRegion.left); // overflowed the left
  23041. }
  23042. // Constrain the Y coordinate by however much this Element overflows
  23043. if (thisRegion.bottom > constrainTo.bottom) {
  23044. overflowed = true;
  23045. vector[1] = (constrainTo.bottom - thisRegion.bottom); // overflowed the bottom
  23046. }
  23047. if (thisRegion.top + vector[1] < constrainTo.top) {
  23048. overflowed = true;
  23049. vector[1] = (constrainTo.top - thisRegion.top); // overflowed the top
  23050. }
  23051. return overflowed ? vector : false;
  23052. },
  23053. /**
  23054. * Calculates the x, y to center this element on the screen
  23055. * @return {Number[]} The x, y values [x, y]
  23056. */
  23057. getCenterXY : function(){
  23058. return this.getAlignToXY(doc, 'c-c');
  23059. },
  23060. /**
  23061. * Centers the Element in either the viewport, or another Element.
  23062. * @param {String/HTMLElement/Ext.Element} [centerIn] The element in which to center the element.
  23063. */
  23064. center : function(centerIn){
  23065. return this.alignTo(centerIn || doc, 'c-c');
  23066. }
  23067. };
  23068. }()));
  23069. //@tag dom,core
  23070. //@require Ext.dom.Element-alignment
  23071. //@define Ext.dom.Element-anim
  23072. //@define Ext.dom.Element
  23073. /**
  23074. * @class Ext.dom.Element
  23075. */
  23076. /* ================================
  23077. * A Note About Wrapped Animations
  23078. * ================================
  23079. * A few of the effects below implement two different animations per effect, one wrapping
  23080. * animation that performs the visual effect and a "no-op" animation on this Element where
  23081. * no attributes of the element itself actually change. The purpose for this is that the
  23082. * wrapper is required for the effect to work and so it does the actual animation work, but
  23083. * we always animate `this` so that the element's events and callbacks work as expected to
  23084. * the callers of this API.
  23085. *
  23086. * Because of this, we always want each wrap animation to complete first (we don't want to
  23087. * cut off the visual effect early). To ensure that, we arbitrarily increase the duration of
  23088. * the element's no-op animation, also ensuring that it has a decent minimum value -- on slow
  23089. * systems, too-low durations can cause race conditions between the wrap animation and the
  23090. * element animation being removed out of order. Note that in each wrap's `afteranimate`
  23091. * callback it will explicitly terminate the element animation as soon as the wrap is complete,
  23092. * so there's no real danger in making the duration too long.
  23093. *
  23094. * This applies to all effects that get wrapped, including slideIn, slideOut, switchOff and frame.
  23095. */
  23096. Ext.dom.Element.override({
  23097. /**
  23098. * Performs custom animation on this Element.
  23099. *
  23100. * The following properties may be specified in `from`, `to`, and `keyframe` objects:
  23101. *
  23102. * - `x` - The page X position in pixels.
  23103. *
  23104. * - `y` - The page Y position in pixels
  23105. *
  23106. * - `left` - The element's CSS `left` value. Units must be supplied.
  23107. *
  23108. * - `top` - The element's CSS `top` value. Units must be supplied.
  23109. *
  23110. * - `width` - The element's CSS `width` value. Units must be supplied.
  23111. *
  23112. * - `height` - The element's CSS `height` value. Units must be supplied.
  23113. *
  23114. * - `scrollLeft` - The element's `scrollLeft` value.
  23115. *
  23116. * - `scrollTop` - The element's `scrollTop` value.
  23117. *
  23118. * - `opacity` - The element's `opacity` value. This must be a value between `0` and `1`.
  23119. *
  23120. * **Be aware** that animating an Element which is being used by an Ext Component without in some way informing the
  23121. * Component about the changed element state will result in incorrect Component behaviour. This is because the
  23122. * Component will be using the old state of the element. To avoid this problem, it is now possible to directly
  23123. * animate certain properties of Components.
  23124. *
  23125. * @param {Object} config Configuration for {@link Ext.fx.Anim}.
  23126. * Note that the {@link Ext.fx.Anim#to to} config is required.
  23127. * @return {Ext.dom.Element} this
  23128. */
  23129. animate: function(config) {
  23130. var me = this,
  23131. listeners,
  23132. anim,
  23133. animId = me.dom.id || Ext.id(me.dom);
  23134. if (!Ext.fx.Manager.hasFxBlock(animId)) {
  23135. // Bit of gymnastics here to ensure our internal listeners get bound first
  23136. if (config.listeners) {
  23137. listeners = config.listeners;
  23138. delete config.listeners;
  23139. }
  23140. if (config.internalListeners) {
  23141. config.listeners = config.internalListeners;
  23142. delete config.internalListeners;
  23143. }
  23144. anim = new Ext.fx.Anim(me.anim(config));
  23145. if (listeners) {
  23146. anim.on(listeners);
  23147. }
  23148. Ext.fx.Manager.queueFx(anim);
  23149. }
  23150. return me;
  23151. },
  23152. // @private - process the passed fx configuration.
  23153. anim: function(config) {
  23154. if (!Ext.isObject(config)) {
  23155. return (config) ? {} : false;
  23156. }
  23157. var me = this,
  23158. duration = config.duration || Ext.fx.Anim.prototype.duration,
  23159. easing = config.easing || 'ease',
  23160. animConfig;
  23161. if (config.stopAnimation) {
  23162. me.stopAnimation();
  23163. }
  23164. Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
  23165. // Clear any 'paused' defaults.
  23166. Ext.fx.Manager.setFxDefaults(me.id, {
  23167. delay: 0
  23168. });
  23169. animConfig = {
  23170. // Pass the DOM reference. That's tested first so will be converted to an Ext.fx.Target fastest.
  23171. target: me.dom,
  23172. remove: config.remove,
  23173. alternate: config.alternate || false,
  23174. duration: duration,
  23175. easing: easing,
  23176. callback: config.callback,
  23177. listeners: config.listeners,
  23178. iterations: config.iterations || 1,
  23179. scope: config.scope,
  23180. block: config.block,
  23181. concurrent: config.concurrent,
  23182. delay: config.delay || 0,
  23183. paused: true,
  23184. keyframes: config.keyframes,
  23185. from: config.from || {},
  23186. to: Ext.apply({}, config)
  23187. };
  23188. Ext.apply(animConfig.to, config.to);
  23189. // Anim API properties - backward compat
  23190. delete animConfig.to.to;
  23191. delete animConfig.to.from;
  23192. delete animConfig.to.remove;
  23193. delete animConfig.to.alternate;
  23194. delete animConfig.to.keyframes;
  23195. delete animConfig.to.iterations;
  23196. delete animConfig.to.listeners;
  23197. delete animConfig.to.target;
  23198. delete animConfig.to.paused;
  23199. delete animConfig.to.callback;
  23200. delete animConfig.to.scope;
  23201. delete animConfig.to.duration;
  23202. delete animConfig.to.easing;
  23203. delete animConfig.to.concurrent;
  23204. delete animConfig.to.block;
  23205. delete animConfig.to.stopAnimation;
  23206. delete animConfig.to.delay;
  23207. return animConfig;
  23208. },
  23209. /**
  23210. * Slides the element into view. An anchor point can be optionally passed to set the point of origin for the slide
  23211. * effect. This function automatically handles wrapping the element with a fixed-size container if needed. See the
  23212. * Fx class overview for valid anchor point options. Usage:
  23213. *
  23214. * // default: slide the element in from the top
  23215. * el.slideIn();
  23216. *
  23217. * // custom: slide the element in from the right with a 2-second duration
  23218. * el.slideIn('r', { duration: 2000 });
  23219. *
  23220. * // common config options shown with default values
  23221. * el.slideIn('t', {
  23222. * easing: 'easeOut',
  23223. * duration: 500
  23224. * });
  23225. *
  23226. * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
  23227. * @param {Object} options (optional) Object literal with any of the Fx config options
  23228. * @param {Boolean} options.preserveScroll Set to true if preservation of any descendant elements'
  23229. * `scrollTop` values is required. By default the DOM wrapping operation performed by `slideIn` and
  23230. * `slideOut` causes the browser to lose all scroll positions.
  23231. * @return {Ext.dom.Element} The Element
  23232. */
  23233. slideIn: function(anchor, obj, slideOut) {
  23234. var me = this,
  23235. elStyle = me.dom.style,
  23236. beforeAnim,
  23237. wrapAnim,
  23238. restoreScroll,
  23239. wrapDomParentNode;
  23240. anchor = anchor || "t";
  23241. obj = obj || {};
  23242. beforeAnim = function() {
  23243. var animScope = this,
  23244. listeners = obj.listeners,
  23245. box, originalStyles, anim, wrap;
  23246. if (!slideOut) {
  23247. me.fixDisplay();
  23248. }
  23249. box = me.getBox();
  23250. if ((anchor == 't' || anchor == 'b') && box.height === 0) {
  23251. box.height = me.dom.scrollHeight;
  23252. }
  23253. else if ((anchor == 'l' || anchor == 'r') && box.width === 0) {
  23254. box.width = me.dom.scrollWidth;
  23255. }
  23256. originalStyles = me.getStyles('width', 'height', 'left', 'right', 'top', 'bottom', 'position', 'z-index', true);
  23257. me.setSize(box.width, box.height);
  23258. // Cache all descendants' scrollTop & scrollLeft values if configured to preserve scroll.
  23259. if (obj.preserveScroll) {
  23260. restoreScroll = me.cacheScrollValues();
  23261. }
  23262. wrap = me.wrap({
  23263. id: Ext.id() + '-anim-wrap-for-' + me.id,
  23264. style: {
  23265. visibility: slideOut ? 'visible' : 'hidden'
  23266. }
  23267. });
  23268. wrapDomParentNode = wrap.dom.parentNode;
  23269. wrap.setPositioning(me.getPositioning());
  23270. if (wrap.isStyle('position', 'static')) {
  23271. wrap.position('relative');
  23272. }
  23273. me.clearPositioning('auto');
  23274. wrap.clip();
  23275. // The wrap will have reset all descendant scrollTops. Restore them if we cached them.
  23276. if (restoreScroll) {
  23277. restoreScroll();
  23278. }
  23279. // This element is temporarily positioned absolute within its wrapper.
  23280. // Restore to its default, CSS-inherited visibility setting.
  23281. // We cannot explicitly poke visibility:visible into its style because that overrides the visibility of the wrap.
  23282. me.setStyle({
  23283. visibility: '',
  23284. position: 'absolute'
  23285. });
  23286. if (slideOut) {
  23287. wrap.setSize(box.width, box.height);
  23288. }
  23289. switch (anchor) {
  23290. case 't':
  23291. anim = {
  23292. from: {
  23293. width: box.width + 'px',
  23294. height: '0px'
  23295. },
  23296. to: {
  23297. width: box.width + 'px',
  23298. height: box.height + 'px'
  23299. }
  23300. };
  23301. elStyle.bottom = '0px';
  23302. break;
  23303. case 'l':
  23304. anim = {
  23305. from: {
  23306. width: '0px',
  23307. height: box.height + 'px'
  23308. },
  23309. to: {
  23310. width: box.width + 'px',
  23311. height: box.height + 'px'
  23312. }
  23313. };
  23314. elStyle.right = '0px';
  23315. break;
  23316. case 'r':
  23317. anim = {
  23318. from: {
  23319. x: box.x + box.width,
  23320. width: '0px',
  23321. height: box.height + 'px'
  23322. },
  23323. to: {
  23324. x: box.x,
  23325. width: box.width + 'px',
  23326. height: box.height + 'px'
  23327. }
  23328. };
  23329. break;
  23330. case 'b':
  23331. anim = {
  23332. from: {
  23333. y: box.y + box.height,
  23334. width: box.width + 'px',
  23335. height: '0px'
  23336. },
  23337. to: {
  23338. y: box.y,
  23339. width: box.width + 'px',
  23340. height: box.height + 'px'
  23341. }
  23342. };
  23343. break;
  23344. case 'tl':
  23345. anim = {
  23346. from: {
  23347. x: box.x,
  23348. y: box.y,
  23349. width: '0px',
  23350. height: '0px'
  23351. },
  23352. to: {
  23353. width: box.width + 'px',
  23354. height: box.height + 'px'
  23355. }
  23356. };
  23357. elStyle.bottom = '0px';
  23358. elStyle.right = '0px';
  23359. break;
  23360. case 'bl':
  23361. anim = {
  23362. from: {
  23363. y: box.y + box.height,
  23364. width: '0px',
  23365. height: '0px'
  23366. },
  23367. to: {
  23368. y: box.y,
  23369. width: box.width + 'px',
  23370. height: box.height + 'px'
  23371. }
  23372. };
  23373. elStyle.bottom = '0px';
  23374. break;
  23375. case 'br':
  23376. anim = {
  23377. from: {
  23378. x: box.x + box.width,
  23379. y: box.y + box.height,
  23380. width: '0px',
  23381. height: '0px'
  23382. },
  23383. to: {
  23384. x: box.x,
  23385. y: box.y,
  23386. width: box.width + 'px',
  23387. height: box.height + 'px'
  23388. }
  23389. };
  23390. break;
  23391. case 'tr':
  23392. anim = {
  23393. from: {
  23394. x: box.x + box.width,
  23395. width: '0px',
  23396. height: '0px'
  23397. },
  23398. to: {
  23399. x: box.x,
  23400. width: box.width + 'px',
  23401. height: box.height + 'px'
  23402. }
  23403. };
  23404. elStyle.right = '0px';
  23405. break;
  23406. }
  23407. wrap.show();
  23408. wrapAnim = Ext.apply({}, obj);
  23409. delete wrapAnim.listeners;
  23410. wrapAnim = new Ext.fx.Anim(Ext.applyIf(wrapAnim, {
  23411. target: wrap,
  23412. duration: 500,
  23413. easing: 'ease-out',
  23414. from: slideOut ? anim.to : anim.from,
  23415. to: slideOut ? anim.from : anim.to
  23416. }));
  23417. // In the absence of a callback, this listener MUST be added first
  23418. wrapAnim.on('afteranimate', function() {
  23419. me.setStyle(originalStyles);
  23420. if (slideOut) {
  23421. if (obj.useDisplay) {
  23422. me.setDisplayed(false);
  23423. } else {
  23424. me.hide();
  23425. }
  23426. }
  23427. if (wrap.dom) {
  23428. if (wrap.dom.parentNode) {
  23429. wrap.dom.parentNode.insertBefore(me.dom, wrap.dom);
  23430. } else {
  23431. wrapDomParentNode.appendChild(me.dom);
  23432. }
  23433. wrap.remove();
  23434. }
  23435. // The unwrap will have reset all descendant scrollTops. Restore them if we cached them.
  23436. if (restoreScroll) {
  23437. restoreScroll();
  23438. }
  23439. // kill the no-op element animation created below
  23440. animScope.end();
  23441. });
  23442. // Add configured listeners after
  23443. if (listeners) {
  23444. wrapAnim.on(listeners);
  23445. }
  23446. };
  23447. me.animate({
  23448. // See "A Note About Wrapped Animations" at the top of this class:
  23449. duration: obj.duration ? Math.max(obj.duration, 500) * 2 : 1000,
  23450. listeners: {
  23451. beforeanimate: beforeAnim // kick off the wrap animation
  23452. }
  23453. });
  23454. return me;
  23455. },
  23456. /**
  23457. * Slides the element out of view. An anchor point can be optionally passed to set the end point for the slide
  23458. * effect. When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will
  23459. * still take up space in the document. The element must be removed from the DOM using the 'remove' config option if
  23460. * desired. This function automatically handles wrapping the element with a fixed-size container if needed. See the
  23461. * Fx class overview for valid anchor point options. Usage:
  23462. *
  23463. * // default: slide the element out to the top
  23464. * el.slideOut();
  23465. *
  23466. * // custom: slide the element out to the right with a 2-second duration
  23467. * el.slideOut('r', { duration: 2000 });
  23468. *
  23469. * // common config options shown with default values
  23470. * el.slideOut('t', {
  23471. * easing: 'easeOut',
  23472. * duration: 500,
  23473. * remove: false,
  23474. * useDisplay: false
  23475. * });
  23476. *
  23477. * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
  23478. * @param {Object} options (optional) Object literal with any of the Fx config options
  23479. * @return {Ext.dom.Element} The Element
  23480. */
  23481. slideOut: function(anchor, o) {
  23482. return this.slideIn(anchor, o, true);
  23483. },
  23484. /**
  23485. * Fades the element out while slowly expanding it in all directions. When the effect is completed, the element will
  23486. * be hidden (visibility = 'hidden') but block elements will still take up space in the document. Usage:
  23487. *
  23488. * // default
  23489. * el.puff();
  23490. *
  23491. * // common config options shown with default values
  23492. * el.puff({
  23493. * easing: 'easeOut',
  23494. * duration: 500,
  23495. * useDisplay: false
  23496. * });
  23497. *
  23498. * @param {Object} options (optional) Object literal with any of the Fx config options
  23499. * @return {Ext.dom.Element} The Element
  23500. */
  23501. puff: function(obj) {
  23502. var me = this,
  23503. beforeAnim,
  23504. box = me.getBox(),
  23505. originalStyles = me.getStyles('width', 'height', 'left', 'right', 'top', 'bottom', 'position', 'z-index', 'font-size', 'opacity', true);
  23506. obj = Ext.applyIf(obj || {}, {
  23507. easing: 'ease-out',
  23508. duration: 500,
  23509. useDisplay: false
  23510. });
  23511. beforeAnim = function() {
  23512. me.clearOpacity();
  23513. me.show();
  23514. this.to = {
  23515. width: box.width * 2,
  23516. height: box.height * 2,
  23517. x: box.x - (box.width / 2),
  23518. y: box.y - (box.height /2),
  23519. opacity: 0,
  23520. fontSize: '200%'
  23521. };
  23522. this.on('afteranimate',function() {
  23523. if (me.dom) {
  23524. if (obj.useDisplay) {
  23525. me.setDisplayed(false);
  23526. } else {
  23527. me.hide();
  23528. }
  23529. me.setStyle(originalStyles);
  23530. obj.callback.call(obj.scope);
  23531. }
  23532. });
  23533. };
  23534. me.animate({
  23535. duration: obj.duration,
  23536. easing: obj.easing,
  23537. listeners: {
  23538. beforeanimate: {
  23539. fn: beforeAnim
  23540. }
  23541. }
  23542. });
  23543. return me;
  23544. },
  23545. /**
  23546. * Blinks the element as if it was clicked and then collapses on its center (similar to switching off a television).
  23547. * When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will still
  23548. * take up space in the document. The element must be removed from the DOM using the 'remove' config option if
  23549. * desired. Usage:
  23550. *
  23551. * // default
  23552. * el.switchOff();
  23553. *
  23554. * // all config options shown with default values
  23555. * el.switchOff({
  23556. * easing: 'easeIn',
  23557. * duration: .3,
  23558. * remove: false,
  23559. * useDisplay: false
  23560. * });
  23561. *
  23562. * @param {Object} options (optional) Object literal with any of the Fx config options
  23563. * @return {Ext.dom.Element} The Element
  23564. */
  23565. switchOff: function(obj) {
  23566. var me = this,
  23567. beforeAnim;
  23568. obj = Ext.applyIf(obj || {}, {
  23569. easing: 'ease-in',
  23570. duration: 500,
  23571. remove: false,
  23572. useDisplay: false
  23573. });
  23574. beforeAnim = function() {
  23575. var animScope = this,
  23576. size = me.getSize(),
  23577. xy = me.getXY(),
  23578. keyframe, position;
  23579. me.clearOpacity();
  23580. me.clip();
  23581. position = me.getPositioning();
  23582. keyframe = new Ext.fx.Animator({
  23583. target: me,
  23584. duration: obj.duration,
  23585. easing: obj.easing,
  23586. keyframes: {
  23587. 33: {
  23588. opacity: 0.3
  23589. },
  23590. 66: {
  23591. height: 1,
  23592. y: xy[1] + size.height / 2
  23593. },
  23594. 100: {
  23595. width: 1,
  23596. x: xy[0] + size.width / 2
  23597. }
  23598. }
  23599. });
  23600. keyframe.on('afteranimate', function() {
  23601. if (obj.useDisplay) {
  23602. me.setDisplayed(false);
  23603. } else {
  23604. me.hide();
  23605. }
  23606. me.clearOpacity();
  23607. me.setPositioning(position);
  23608. me.setSize(size);
  23609. // kill the no-op element animation created below
  23610. animScope.end();
  23611. });
  23612. };
  23613. me.animate({
  23614. // See "A Note About Wrapped Animations" at the top of this class:
  23615. duration: (Math.max(obj.duration, 500) * 2),
  23616. listeners: {
  23617. beforeanimate: {
  23618. fn: beforeAnim
  23619. }
  23620. }
  23621. });
  23622. return me;
  23623. },
  23624. /**
  23625. * Shows a ripple of exploding, attenuating borders to draw attention to an Element. Usage:
  23626. *
  23627. * // default: a single light blue ripple
  23628. * el.frame();
  23629. *
  23630. * // custom: 3 red ripples lasting 3 seconds total
  23631. * el.frame("#ff0000", 3, { duration: 3000 });
  23632. *
  23633. * // common config options shown with default values
  23634. * el.frame("#C3DAF9", 1, {
  23635. * duration: 1000 // duration of each individual ripple.
  23636. * // Note: Easing is not configurable and will be ignored if included
  23637. * });
  23638. *
  23639. * @param {String} [color='#C3DAF9'] The hex color value for the border.
  23640. * @param {Number} [count=1] The number of ripples to display.
  23641. * @param {Object} [options] Object literal with any of the Fx config options
  23642. * @return {Ext.dom.Element} The Element
  23643. */
  23644. frame : function(color, count, obj){
  23645. var me = this,
  23646. beforeAnim;
  23647. color = color || '#C3DAF9';
  23648. count = count || 1;
  23649. obj = obj || {};
  23650. beforeAnim = function() {
  23651. me.show();
  23652. var animScope = this,
  23653. box = me.getBox(),
  23654. proxy = Ext.getBody().createChild({
  23655. id: me.id + '-anim-proxy',
  23656. style: {
  23657. position : 'absolute',
  23658. 'pointer-events': 'none',
  23659. 'z-index': 35000,
  23660. border : '0px solid ' + color
  23661. }
  23662. }),
  23663. proxyAnim;
  23664. proxyAnim = new Ext.fx.Anim({
  23665. target: proxy,
  23666. duration: obj.duration || 1000,
  23667. iterations: count,
  23668. from: {
  23669. top: box.y,
  23670. left: box.x,
  23671. borderWidth: 0,
  23672. opacity: 1,
  23673. height: box.height,
  23674. width: box.width
  23675. },
  23676. to: {
  23677. top: box.y - 20,
  23678. left: box.x - 20,
  23679. borderWidth: 10,
  23680. opacity: 0,
  23681. height: box.height + 40,
  23682. width: box.width + 40
  23683. }
  23684. });
  23685. proxyAnim.on('afteranimate', function() {
  23686. proxy.remove();
  23687. // kill the no-op element animation created below
  23688. animScope.end();
  23689. });
  23690. };
  23691. me.animate({
  23692. // See "A Note About Wrapped Animations" at the top of this class:
  23693. duration: (Math.max(obj.duration, 500) * 2) || 2000,
  23694. listeners: {
  23695. beforeanimate: {
  23696. fn: beforeAnim
  23697. }
  23698. }
  23699. });
  23700. return me;
  23701. },
  23702. /**
  23703. * Slides the element while fading it out of view. An anchor point can be optionally passed to set the ending point
  23704. * of the effect. Usage:
  23705. *
  23706. * // default: slide the element downward while fading out
  23707. * el.ghost();
  23708. *
  23709. * // custom: slide the element out to the right with a 2-second duration
  23710. * el.ghost('r', { duration: 2000 });
  23711. *
  23712. * // common config options shown with default values
  23713. * el.ghost('b', {
  23714. * easing: 'easeOut',
  23715. * duration: 500
  23716. * });
  23717. *
  23718. * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to bottom: 'b')
  23719. * @param {Object} options (optional) Object literal with any of the Fx config options
  23720. * @return {Ext.dom.Element} The Element
  23721. */
  23722. ghost: function(anchor, obj) {
  23723. var me = this,
  23724. beforeAnim;
  23725. anchor = anchor || "b";
  23726. beforeAnim = function() {
  23727. var width = me.getWidth(),
  23728. height = me.getHeight(),
  23729. xy = me.getXY(),
  23730. position = me.getPositioning(),
  23731. to = {
  23732. opacity: 0
  23733. };
  23734. switch (anchor) {
  23735. case 't':
  23736. to.y = xy[1] - height;
  23737. break;
  23738. case 'l':
  23739. to.x = xy[0] - width;
  23740. break;
  23741. case 'r':
  23742. to.x = xy[0] + width;
  23743. break;
  23744. case 'b':
  23745. to.y = xy[1] + height;
  23746. break;
  23747. case 'tl':
  23748. to.x = xy[0] - width;
  23749. to.y = xy[1] - height;
  23750. break;
  23751. case 'bl':
  23752. to.x = xy[0] - width;
  23753. to.y = xy[1] + height;
  23754. break;
  23755. case 'br':
  23756. to.x = xy[0] + width;
  23757. to.y = xy[1] + height;
  23758. break;
  23759. case 'tr':
  23760. to.x = xy[0] + width;
  23761. to.y = xy[1] - height;
  23762. break;
  23763. }
  23764. this.to = to;
  23765. this.on('afteranimate', function () {
  23766. if (me.dom) {
  23767. me.hide();
  23768. me.clearOpacity();
  23769. me.setPositioning(position);
  23770. }
  23771. });
  23772. };
  23773. me.animate(Ext.applyIf(obj || {}, {
  23774. duration: 500,
  23775. easing: 'ease-out',
  23776. listeners: {
  23777. beforeanimate: {
  23778. fn: beforeAnim
  23779. }
  23780. }
  23781. }));
  23782. return me;
  23783. },
  23784. /**
  23785. * Highlights the Element by setting a color (applies to the background-color by default, but can be changed using
  23786. * the "attr" config option) and then fading back to the original color. If no original color is available, you
  23787. * should provide the "endColor" config option which will be cleared after the animation. Usage:
  23788. *
  23789. * // default: highlight background to yellow
  23790. * el.highlight();
  23791. *
  23792. * // custom: highlight foreground text to blue for 2 seconds
  23793. * el.highlight("0000ff", { attr: 'color', duration: 2000 });
  23794. *
  23795. * // common config options shown with default values
  23796. * el.highlight("ffff9c", {
  23797. * attr: "backgroundColor", //can be any valid CSS property (attribute) that supports a color value
  23798. * endColor: (current color) or "ffffff",
  23799. * easing: 'easeIn',
  23800. * duration: 1000
  23801. * });
  23802. *
  23803. * @param {String} color (optional) The highlight color. Should be a 6 char hex color without the leading #
  23804. * (defaults to yellow: 'ffff9c')
  23805. * @param {Object} options (optional) Object literal with any of the Fx config options
  23806. * @return {Ext.dom.Element} The Element
  23807. */
  23808. highlight: function(color, o) {
  23809. var me = this,
  23810. dom = me.dom,
  23811. from = {},
  23812. restore, to, attr, lns, event, fn;
  23813. o = o || {};
  23814. lns = o.listeners || {};
  23815. attr = o.attr || 'backgroundColor';
  23816. from[attr] = color || 'ffff9c';
  23817. if (!o.to) {
  23818. to = {};
  23819. to[attr] = o.endColor || me.getColor(attr, 'ffffff', '');
  23820. }
  23821. else {
  23822. to = o.to;
  23823. }
  23824. // Don't apply directly on lns, since we reference it in our own callbacks below
  23825. o.listeners = Ext.apply(Ext.apply({}, lns), {
  23826. beforeanimate: function() {
  23827. restore = dom.style[attr];
  23828. me.clearOpacity();
  23829. me.show();
  23830. event = lns.beforeanimate;
  23831. if (event) {
  23832. fn = event.fn || event;
  23833. return fn.apply(event.scope || lns.scope || window, arguments);
  23834. }
  23835. },
  23836. afteranimate: function() {
  23837. if (dom) {
  23838. dom.style[attr] = restore;
  23839. }
  23840. event = lns.afteranimate;
  23841. if (event) {
  23842. fn = event.fn || event;
  23843. fn.apply(event.scope || lns.scope || window, arguments);
  23844. }
  23845. }
  23846. });
  23847. me.animate(Ext.apply({}, o, {
  23848. duration: 1000,
  23849. easing: 'ease-in',
  23850. from: from,
  23851. to: to
  23852. }));
  23853. return me;
  23854. },
  23855. /**
  23856. * Creates a pause before any subsequent queued effects begin. If there are no effects queued after the pause it will
  23857. * have no effect. Usage:
  23858. *
  23859. * el.pause(1);
  23860. *
  23861. * @deprecated 4.0 Use the `delay` config to {@link #animate} instead.
  23862. * @param {Number} seconds The length of time to pause (in seconds)
  23863. * @return {Ext.Element} The Element
  23864. */
  23865. pause: function(ms) {
  23866. var me = this;
  23867. Ext.fx.Manager.setFxDefaults(me.id, {
  23868. delay: ms
  23869. });
  23870. return me;
  23871. },
  23872. /**
  23873. * Fade an element in (from transparent to opaque). The ending opacity can be specified using the `opacity`
  23874. * config option. Usage:
  23875. *
  23876. * // default: fade in from opacity 0 to 100%
  23877. * el.fadeIn();
  23878. *
  23879. * // custom: fade in from opacity 0 to 75% over 2 seconds
  23880. * el.fadeIn({ opacity: .75, duration: 2000});
  23881. *
  23882. * // common config options shown with default values
  23883. * el.fadeIn({
  23884. * opacity: 1, //can be any value between 0 and 1 (e.g. .5)
  23885. * easing: 'easeOut',
  23886. * duration: 500
  23887. * });
  23888. *
  23889. * @param {Object} options (optional) Object literal with any of the Fx config options
  23890. * @return {Ext.Element} The Element
  23891. */
  23892. fadeIn: function(o) {
  23893. var me = this;
  23894. me.animate(Ext.apply({}, o, {
  23895. opacity: 1,
  23896. internalListeners: {
  23897. beforeanimate: function(anim){
  23898. // restore any visibility/display that may have
  23899. // been applied by a fadeout animation
  23900. if (me.isStyle('display', 'none')) {
  23901. me.setDisplayed('');
  23902. } else {
  23903. me.show();
  23904. }
  23905. }
  23906. }
  23907. }));
  23908. return this;
  23909. },
  23910. /**
  23911. * Fade an element out (from opaque to transparent). The ending opacity can be specified using the `opacity`
  23912. * config option. Note that IE may require `useDisplay:true` in order to redisplay correctly.
  23913. * Usage:
  23914. *
  23915. * // default: fade out from the element's current opacity to 0
  23916. * el.fadeOut();
  23917. *
  23918. * // custom: fade out from the element's current opacity to 25% over 2 seconds
  23919. * el.fadeOut({ opacity: .25, duration: 2000});
  23920. *
  23921. * // common config options shown with default values
  23922. * el.fadeOut({
  23923. * opacity: 0, //can be any value between 0 and 1 (e.g. .5)
  23924. * easing: 'easeOut',
  23925. * duration: 500,
  23926. * remove: false,
  23927. * useDisplay: false
  23928. * });
  23929. *
  23930. * @param {Object} options (optional) Object literal with any of the Fx config options
  23931. * @return {Ext.Element} The Element
  23932. */
  23933. fadeOut: function(o) {
  23934. var me = this;
  23935. o = Ext.apply({
  23936. opacity: 0,
  23937. internalListeners: {
  23938. afteranimate: function(anim){
  23939. var dom = me.dom;
  23940. if (dom && anim.to.opacity === 0) {
  23941. if (o.useDisplay) {
  23942. me.setDisplayed(false);
  23943. } else {
  23944. me.hide();
  23945. }
  23946. }
  23947. }
  23948. }
  23949. }, o);
  23950. me.animate(o);
  23951. return me;
  23952. },
  23953. /**
  23954. * Animates the transition of an element's dimensions from a starting height/width to an ending height/width. This
  23955. * method is a convenience implementation of {@link #shift}. Usage:
  23956. *
  23957. * // change height and width to 100x100 pixels
  23958. * el.scale(100, 100);
  23959. *
  23960. * // common config options shown with default values. The height and width will default to
  23961. * // the element's existing values if passed as null.
  23962. * el.scale(
  23963. * [element's width],
  23964. * [element's height], {
  23965. * easing: 'easeOut',
  23966. * duration: 350
  23967. * }
  23968. * );
  23969. *
  23970. * @deprecated 4.0 Just use {@link #animate} instead.
  23971. * @param {Number} width The new width (pass undefined to keep the original width)
  23972. * @param {Number} height The new height (pass undefined to keep the original height)
  23973. * @param {Object} options (optional) Object literal with any of the Fx config options
  23974. * @return {Ext.Element} The Element
  23975. */
  23976. scale: function(w, h, o) {
  23977. this.animate(Ext.apply({}, o, {
  23978. width: w,
  23979. height: h
  23980. }));
  23981. return this;
  23982. },
  23983. /**
  23984. * Animates the transition of any combination of an element's dimensions, xy position and/or opacity. Any of these
  23985. * properties not specified in the config object will not be changed. This effect requires that at least one new
  23986. * dimension, position or opacity setting must be passed in on the config object in order for the function to have
  23987. * any effect. Usage:
  23988. *
  23989. * // slide the element horizontally to x position 200 while changing the height and opacity
  23990. * el.shift({ x: 200, height: 50, opacity: .8 });
  23991. *
  23992. * // common config options shown with default values.
  23993. * el.shift({
  23994. * width: [element's width],
  23995. * height: [element's height],
  23996. * x: [element's x position],
  23997. * y: [element's y position],
  23998. * opacity: [element's opacity],
  23999. * easing: 'easeOut',
  24000. * duration: 350
  24001. * });
  24002. *
  24003. * @deprecated 4.0 Just use {@link #animate} instead.
  24004. * @param {Object} options Object literal with any of the Fx config options
  24005. * @return {Ext.Element} The Element
  24006. */
  24007. shift: function(config) {
  24008. this.animate(config);
  24009. return this;
  24010. }
  24011. });
  24012. //@tag dom,core
  24013. //@require Ext.dom.Element-anim
  24014. //@define Ext.dom.Element-dd
  24015. //@define Ext.dom.Element
  24016. /**
  24017. * @class Ext.dom.Element
  24018. */
  24019. Ext.dom.Element.override({
  24020. /**
  24021. * Initializes a {@link Ext.dd.DD} drag drop object for this element.
  24022. * @param {String} group The group the DD object is member of
  24023. * @param {Object} config The DD config object
  24024. * @param {Object} overrides An object containing methods to override/implement on the DD object
  24025. * @return {Ext.dd.DD} The DD object
  24026. */
  24027. initDD : function(group, config, overrides){
  24028. var dd = new Ext.dd.DD(Ext.id(this.dom), group, config);
  24029. return Ext.apply(dd, overrides);
  24030. },
  24031. /**
  24032. * Initializes a {@link Ext.dd.DDProxy} object for this element.
  24033. * @param {String} group The group the DDProxy object is member of
  24034. * @param {Object} config The DDProxy config object
  24035. * @param {Object} overrides An object containing methods to override/implement on the DDProxy object
  24036. * @return {Ext.dd.DDProxy} The DDProxy object
  24037. */
  24038. initDDProxy : function(group, config, overrides){
  24039. var dd = new Ext.dd.DDProxy(Ext.id(this.dom), group, config);
  24040. return Ext.apply(dd, overrides);
  24041. },
  24042. /**
  24043. * Initializes a {@link Ext.dd.DDTarget} object for this element.
  24044. * @param {String} group The group the DDTarget object is member of
  24045. * @param {Object} config The DDTarget config object
  24046. * @param {Object} overrides An object containing methods to override/implement on the DDTarget object
  24047. * @return {Ext.dd.DDTarget} The DDTarget object
  24048. */
  24049. initDDTarget : function(group, config, overrides){
  24050. var dd = new Ext.dd.DDTarget(Ext.id(this.dom), group, config);
  24051. return Ext.apply(dd, overrides);
  24052. }
  24053. });
  24054. //@tag dom,core
  24055. //@require Ext.dom.Element-dd
  24056. //@define Ext.dom.Element-fx
  24057. //@define Ext.dom.Element
  24058. /**
  24059. * @class Ext.dom.Element
  24060. */
  24061. (function() {
  24062. var Element = Ext.dom.Element,
  24063. VISIBILITY = "visibility",
  24064. DISPLAY = "display",
  24065. NONE = "none",
  24066. HIDDEN = 'hidden',
  24067. VISIBLE = 'visible',
  24068. OFFSETS = "offsets",
  24069. ASCLASS = "asclass",
  24070. NOSIZE = 'nosize',
  24071. ORIGINALDISPLAY = 'originalDisplay',
  24072. VISMODE = 'visibilityMode',
  24073. ISVISIBLE = 'isVisible',
  24074. OFFSETCLASS = Ext.baseCSSPrefix + 'hide-offsets',
  24075. getDisplay = function(el) {
  24076. var data = (el.$cache || el.getCache()).data,
  24077. display = data[ORIGINALDISPLAY];
  24078. if (display === undefined) {
  24079. data[ORIGINALDISPLAY] = display = '';
  24080. }
  24081. return display;
  24082. },
  24083. getVisMode = function(el){
  24084. var data = (el.$cache || el.getCache()).data,
  24085. visMode = data[VISMODE];
  24086. if (visMode === undefined) {
  24087. data[VISMODE] = visMode = Element.VISIBILITY;
  24088. }
  24089. return visMode;
  24090. };
  24091. Element.override({
  24092. /**
  24093. * The element's default display mode.
  24094. */
  24095. originalDisplay : "",
  24096. visibilityMode : 1,
  24097. /**
  24098. * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
  24099. * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
  24100. * @param {Boolean} visible Whether the element is visible
  24101. * @param {Boolean/Object} [animate] True for the default animation, or a standard Element animation config object
  24102. * @return {Ext.dom.Element} this
  24103. */
  24104. setVisible : function(visible, animate) {
  24105. var me = this,
  24106. dom = me.dom,
  24107. visMode = getVisMode(me);
  24108. // hideMode string override
  24109. if (typeof animate == 'string') {
  24110. switch (animate) {
  24111. case DISPLAY:
  24112. visMode = Element.DISPLAY;
  24113. break;
  24114. case VISIBILITY:
  24115. visMode = Element.VISIBILITY;
  24116. break;
  24117. case OFFSETS:
  24118. visMode = Element.OFFSETS;
  24119. break;
  24120. case NOSIZE:
  24121. case ASCLASS:
  24122. visMode = Element.ASCLASS;
  24123. break;
  24124. }
  24125. me.setVisibilityMode(visMode);
  24126. animate = false;
  24127. }
  24128. if (!animate || !me.anim) {
  24129. if (visMode == Element.DISPLAY) {
  24130. return me.setDisplayed(visible);
  24131. } else if (visMode == Element.OFFSETS) {
  24132. me[visible?'removeCls':'addCls'](OFFSETCLASS);
  24133. } else if (visMode == Element.VISIBILITY) {
  24134. me.fixDisplay();
  24135. // Show by clearing visibility style. Explicitly setting to "visible" overrides parent visibility setting
  24136. dom.style.visibility = visible ? '' : HIDDEN;
  24137. } else if (visMode == Element.ASCLASS) {
  24138. me[visible?'removeCls':'addCls'](me.visibilityCls || Element.visibilityCls);
  24139. }
  24140. } else {
  24141. // closure for composites
  24142. if (visible) {
  24143. me.setOpacity(0.01);
  24144. me.setVisible(true);
  24145. }
  24146. if (!Ext.isObject(animate)) {
  24147. animate = {
  24148. duration: 350,
  24149. easing: 'ease-in'
  24150. };
  24151. }
  24152. me.animate(Ext.applyIf({
  24153. callback: function() {
  24154. if (!visible) {
  24155. me.setVisible(false).setOpacity(1);
  24156. }
  24157. },
  24158. to: {
  24159. opacity: (visible) ? 1 : 0
  24160. }
  24161. }, animate));
  24162. }
  24163. (me.$cache || me.getCache()).data[ISVISIBLE] = visible;
  24164. return me;
  24165. },
  24166. /**
  24167. * @private
  24168. * Determine if the Element has a relevant height and width available based
  24169. * upon current logical visibility state
  24170. */
  24171. hasMetrics : function(){
  24172. var visMode = getVisMode(this);
  24173. return this.isVisible() || (visMode == Element.OFFSETS) || (visMode == Element.VISIBILITY);
  24174. },
  24175. /**
  24176. * Toggles the element's visibility or display, depending on visibility mode.
  24177. * @param {Boolean/Object} [animate] True for the default animation, or a standard Element animation config object
  24178. * @return {Ext.dom.Element} this
  24179. */
  24180. toggle : function(animate){
  24181. var me = this;
  24182. me.setVisible(!me.isVisible(), me.anim(animate));
  24183. return me;
  24184. },
  24185. /**
  24186. * Sets the CSS display property. Uses originalDisplay if the specified value is a boolean true.
  24187. * @param {Boolean/String} value Boolean value to display the element using its default display, or a string to set the display directly.
  24188. * @return {Ext.dom.Element} this
  24189. */
  24190. setDisplayed : function(value) {
  24191. if(typeof value == "boolean"){
  24192. value = value ? getDisplay(this) : NONE;
  24193. }
  24194. this.setStyle(DISPLAY, value);
  24195. return this;
  24196. },
  24197. // private
  24198. fixDisplay : function(){
  24199. var me = this;
  24200. if (me.isStyle(DISPLAY, NONE)) {
  24201. me.setStyle(VISIBILITY, HIDDEN);
  24202. me.setStyle(DISPLAY, getDisplay(me)); // first try reverting to default
  24203. if (me.isStyle(DISPLAY, NONE)) { // if that fails, default to block
  24204. me.setStyle(DISPLAY, "block");
  24205. }
  24206. }
  24207. },
  24208. /**
  24209. * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
  24210. * @param {Boolean/Object} [animate] true for the default animation or a standard Element animation config object
  24211. * @return {Ext.dom.Element} this
  24212. */
  24213. hide : function(animate){
  24214. // hideMode override
  24215. if (typeof animate == 'string'){
  24216. this.setVisible(false, animate);
  24217. return this;
  24218. }
  24219. this.setVisible(false, this.anim(animate));
  24220. return this;
  24221. },
  24222. /**
  24223. * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
  24224. * @param {Boolean/Object} [animate] true for the default animation or a standard Element animation config object
  24225. * @return {Ext.dom.Element} this
  24226. */
  24227. show : function(animate){
  24228. // hideMode override
  24229. if (typeof animate == 'string'){
  24230. this.setVisible(true, animate);
  24231. return this;
  24232. }
  24233. this.setVisible(true, this.anim(animate));
  24234. return this;
  24235. }
  24236. });
  24237. }());
  24238. //@tag dom,core
  24239. //@require Ext.dom.Element-fx
  24240. //@define Ext.dom.Element-position
  24241. //@define Ext.dom.Element
  24242. /**
  24243. * @class Ext.dom.Element
  24244. */
  24245. (function() {
  24246. var Element = Ext.dom.Element,
  24247. LEFT = "left",
  24248. RIGHT = "right",
  24249. TOP = "top",
  24250. BOTTOM = "bottom",
  24251. POSITION = "position",
  24252. STATIC = "static",
  24253. RELATIVE = "relative",
  24254. AUTO = "auto",
  24255. ZINDEX = "z-index",
  24256. BODY = 'BODY',
  24257. PADDING = 'padding',
  24258. BORDER = 'border',
  24259. SLEFT = '-left',
  24260. SRIGHT = '-right',
  24261. STOP = '-top',
  24262. SBOTTOM = '-bottom',
  24263. SWIDTH = '-width',
  24264. // special markup used throughout Ext when box wrapping elements
  24265. borders = {l: BORDER + SLEFT + SWIDTH, r: BORDER + SRIGHT + SWIDTH, t: BORDER + STOP + SWIDTH, b: BORDER + SBOTTOM + SWIDTH},
  24266. paddings = {l: PADDING + SLEFT, r: PADDING + SRIGHT, t: PADDING + STOP, b: PADDING + SBOTTOM},
  24267. paddingsTLRB = [paddings.l, paddings.r, paddings.t, paddings.b],
  24268. bordersTLRB = [borders.l, borders.r, borders.t, borders.b],
  24269. positionTopLeft = ['position', 'top', 'left'];
  24270. Element.override({
  24271. getX: function() {
  24272. return Element.getX(this.dom);
  24273. },
  24274. getY: function() {
  24275. return Element.getY(this.dom);
  24276. },
  24277. /**
  24278. * Gets the current position of the element based on page coordinates.
  24279. * Element must be part of the DOM tree to have page coordinates
  24280. * (display:none or elements not appended return false).
  24281. * @return {Number[]} The XY position of the element
  24282. */
  24283. getXY: function() {
  24284. return Element.getXY(this.dom);
  24285. },
  24286. /**
  24287. * Returns the offsets of this element from the passed element. Both element must be part
  24288. * of the DOM tree and not have display:none to have page coordinates.
  24289. * @param {String/HTMLElement/Ext.Element} element The element to get the offsets from.
  24290. * @return {Number[]} The XY page offsets (e.g. `[100, -200]`)
  24291. */
  24292. getOffsetsTo : function(el){
  24293. var o = this.getXY(),
  24294. e = Ext.fly(el, '_internal').getXY();
  24295. return [o[0] - e[0],o[1] - e[1]];
  24296. },
  24297. setX: function(x, animate) {
  24298. return this.setXY([x, this.getY()], animate);
  24299. },
  24300. setY: function(y, animate) {
  24301. return this.setXY([this.getX(), y], animate);
  24302. },
  24303. setLeft: function(left) {
  24304. this.setStyle(LEFT, this.addUnits(left));
  24305. return this;
  24306. },
  24307. setTop: function(top) {
  24308. this.setStyle(TOP, this.addUnits(top));
  24309. return this;
  24310. },
  24311. setRight: function(right) {
  24312. this.setStyle(RIGHT, this.addUnits(right));
  24313. return this;
  24314. },
  24315. setBottom: function(bottom) {
  24316. this.setStyle(BOTTOM, this.addUnits(bottom));
  24317. return this;
  24318. },
  24319. /**
  24320. * Sets the position of the element in page coordinates, regardless of how the element
  24321. * is positioned. The element must be part of the DOM tree to have page coordinates
  24322. * (`display:none` or elements not appended return false).
  24323. * @param {Number[]} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
  24324. * @param {Boolean/Object} [animate] True for the default animation, or a standard Element
  24325. * animation config object
  24326. * @return {Ext.Element} this
  24327. */
  24328. setXY: function(pos, animate) {
  24329. var me = this;
  24330. if (!animate || !me.anim) {
  24331. Element.setXY(me.dom, pos);
  24332. }
  24333. else {
  24334. if (!Ext.isObject(animate)) {
  24335. animate = {};
  24336. }
  24337. me.animate(Ext.applyIf({ to: { x: pos[0], y: pos[1] } }, animate));
  24338. }
  24339. return me;
  24340. },
  24341. pxRe: /^\d+(?:\.\d*)?px$/i,
  24342. /**
  24343. * Returns the x-coordinate of this element reletive to its `offsetParent`.
  24344. * @return {Number} The local x-coordinate (relative to the `offsetParent`).
  24345. */
  24346. getLocalX: function() {
  24347. var me = this,
  24348. offsetParent,
  24349. x = me.getStyle(LEFT);
  24350. if (!x || x === AUTO) {
  24351. return 0;
  24352. }
  24353. if (x && me.pxRe.test(x)) {
  24354. return parseFloat(x);
  24355. }
  24356. x = me.getX();
  24357. offsetParent = me.dom.offsetParent;
  24358. if (offsetParent) {
  24359. x -= Ext.fly(offsetParent).getX();
  24360. }
  24361. return x;
  24362. },
  24363. /**
  24364. * Returns the y-coordinate of this element reletive to its `offsetParent`.
  24365. * @return {Number} The local y-coordinate (relative to the `offsetParent`).
  24366. */
  24367. getLocalY: function() {
  24368. var me = this,
  24369. offsetParent,
  24370. y = me.getStyle(TOP);
  24371. if (!y || y === AUTO) {
  24372. return 0;
  24373. }
  24374. if (y && me.pxRe.test(y)) {
  24375. return parseFloat(y);
  24376. }
  24377. y = me.getY();
  24378. offsetParent = me.dom.offsetParent;
  24379. if (offsetParent) {
  24380. y -= Ext.fly(offsetParent).getY();
  24381. }
  24382. return y;
  24383. },
  24384. getLeft: function(local) {
  24385. return local ? this.getLocalX() : this.getX();
  24386. },
  24387. getRight: function(local) {
  24388. return (local ? this.getLocalX() : this.getX()) + this.getWidth();
  24389. },
  24390. getTop: function(local) {
  24391. return local ? this.getLocalY() : this.getY();
  24392. },
  24393. getBottom: function(local) {
  24394. return (local ? this.getLocalY() : this.getY()) + this.getHeight();
  24395. },
  24396. translatePoints: function(x, y) {
  24397. var me = this,
  24398. styles = me.getStyle(positionTopLeft),
  24399. relative = styles.position == 'relative',
  24400. left = parseFloat(styles.left),
  24401. top = parseFloat(styles.top),
  24402. xy = me.getXY();
  24403. if (Ext.isArray(x)) {
  24404. y = x[1];
  24405. x = x[0];
  24406. }
  24407. if (isNaN(left)) {
  24408. left = relative ? 0 : me.dom.offsetLeft;
  24409. }
  24410. if (isNaN(top)) {
  24411. top = relative ? 0 : me.dom.offsetTop;
  24412. }
  24413. left = (typeof x == 'number') ? x - xy[0] + left : undefined;
  24414. top = (typeof y == 'number') ? y - xy[1] + top : undefined;
  24415. return {
  24416. left: left,
  24417. top: top
  24418. };
  24419. },
  24420. setBox: function(box, adjust, animate) {
  24421. var me = this,
  24422. w = box.width,
  24423. h = box.height;
  24424. if ((adjust && !me.autoBoxAdjust) && !me.isBorderBox()) {
  24425. w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
  24426. h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
  24427. }
  24428. me.setBounds(box.x, box.y, w, h, animate);
  24429. return me;
  24430. },
  24431. getBox: function(contentBox, local) {
  24432. var me = this,
  24433. xy,
  24434. left,
  24435. top,
  24436. paddingWidth,
  24437. bordersWidth,
  24438. l, r, t, b, w, h, bx;
  24439. if (!local) {
  24440. xy = me.getXY();
  24441. } else {
  24442. xy = me.getStyle([LEFT, TOP]);
  24443. xy = [ parseFloat(xy.left) || 0, parseFloat(xy.top) || 0];
  24444. }
  24445. w = me.getWidth();
  24446. h = me.getHeight();
  24447. if (!contentBox) {
  24448. bx = {
  24449. x: xy[0],
  24450. y: xy[1],
  24451. 0: xy[0],
  24452. 1: xy[1],
  24453. width: w,
  24454. height: h
  24455. };
  24456. } else {
  24457. paddingWidth = me.getStyle(paddingsTLRB);
  24458. bordersWidth = me.getStyle(bordersTLRB);
  24459. l = (parseFloat(bordersWidth[borders.l]) || 0) + (parseFloat(paddingWidth[paddings.l]) || 0);
  24460. r = (parseFloat(bordersWidth[borders.r]) || 0) + (parseFloat(paddingWidth[paddings.r]) || 0);
  24461. t = (parseFloat(bordersWidth[borders.t]) || 0) + (parseFloat(paddingWidth[paddings.t]) || 0);
  24462. b = (parseFloat(bordersWidth[borders.b]) || 0) + (parseFloat(paddingWidth[paddings.b]) || 0);
  24463. bx = {
  24464. x: xy[0] + l,
  24465. y: xy[1] + t,
  24466. 0: xy[0] + l,
  24467. 1: xy[1] + t,
  24468. width: w - (l + r),
  24469. height: h - (t + b)
  24470. };
  24471. }
  24472. bx.right = bx.x + bx.width;
  24473. bx.bottom = bx.y + bx.height;
  24474. return bx;
  24475. },
  24476. getPageBox: function(getRegion) {
  24477. var me = this,
  24478. el = me.dom,
  24479. isDoc = el.nodeName == BODY,
  24480. w = isDoc ? Ext.dom.AbstractElement.getViewWidth() : el.offsetWidth,
  24481. h = isDoc ? Ext.dom.AbstractElement.getViewHeight() : el.offsetHeight,
  24482. xy = me.getXY(),
  24483. t = xy[1],
  24484. r = xy[0] + w,
  24485. b = xy[1] + h,
  24486. l = xy[0];
  24487. if (getRegion) {
  24488. return new Ext.util.Region(t, r, b, l);
  24489. }
  24490. else {
  24491. return {
  24492. left: l,
  24493. top: t,
  24494. width: w,
  24495. height: h,
  24496. right: r,
  24497. bottom: b
  24498. };
  24499. }
  24500. },
  24501. /**
  24502. * Sets the position of the element in page coordinates, regardless of how the element
  24503. * is positioned. The element must be part of the DOM tree to have page coordinates
  24504. * (`display:none` or elements not appended return false).
  24505. * @param {Number} x X value for new position (coordinates are page-based)
  24506. * @param {Number} y Y value for new position (coordinates are page-based)
  24507. * @param {Boolean/Object} [animate] True for the default animation, or a standard Element
  24508. * animation config object
  24509. * @return {Ext.dom.AbstractElement} this
  24510. */
  24511. setLocation : function(x, y, animate) {
  24512. return this.setXY([x, y], animate);
  24513. },
  24514. /**
  24515. * Sets the position of the element in page coordinates, regardless of how the element
  24516. * is positioned. The element must be part of the DOM tree to have page coordinates
  24517. * (`display:none` or elements not appended return false).
  24518. * @param {Number} x X value for new position (coordinates are page-based)
  24519. * @param {Number} y Y value for new position (coordinates are page-based)
  24520. * @param {Boolean/Object} [animate] True for the default animation, or a standard Element
  24521. * animation config object
  24522. * @return {Ext.dom.AbstractElement} this
  24523. */
  24524. moveTo : function(x, y, animate) {
  24525. return this.setXY([x, y], animate);
  24526. },
  24527. /**
  24528. * Initializes positioning on this element. If a desired position is not passed, it will make the
  24529. * the element positioned relative IF it is not already positioned.
  24530. * @param {String} [pos] Positioning to use "relative", "absolute" or "fixed"
  24531. * @param {Number} [zIndex] The zIndex to apply
  24532. * @param {Number} [x] Set the page X position
  24533. * @param {Number} [y] Set the page Y position
  24534. */
  24535. position : function(pos, zIndex, x, y) {
  24536. var me = this;
  24537. if (!pos && me.isStyle(POSITION, STATIC)) {
  24538. me.setStyle(POSITION, RELATIVE);
  24539. } else if (pos) {
  24540. me.setStyle(POSITION, pos);
  24541. }
  24542. if (zIndex) {
  24543. me.setStyle(ZINDEX, zIndex);
  24544. }
  24545. if (x || y) {
  24546. me.setXY([x || false, y || false]);
  24547. }
  24548. },
  24549. /**
  24550. * Clears positioning back to the default when the document was loaded.
  24551. * @param {String} [value=''] The value to use for the left, right, top, bottom. You could use 'auto'.
  24552. * @return {Ext.dom.AbstractElement} this
  24553. */
  24554. clearPositioning : function(value) {
  24555. value = value || '';
  24556. this.setStyle({
  24557. left : value,
  24558. right : value,
  24559. top : value,
  24560. bottom : value,
  24561. "z-index" : "",
  24562. position : STATIC
  24563. });
  24564. return this;
  24565. },
  24566. /**
  24567. * Gets an object with all CSS positioning properties. Useful along with #setPostioning to get
  24568. * snapshot before performing an update and then restoring the element.
  24569. * @return {Object}
  24570. */
  24571. getPositioning : function(){
  24572. var styles = this.getStyle([LEFT, TOP, POSITION, RIGHT, BOTTOM, ZINDEX]);
  24573. styles[RIGHT] = styles[LEFT] ? '' : styles[RIGHT];
  24574. styles[BOTTOM] = styles[TOP] ? '' : styles[BOTTOM];
  24575. return styles;
  24576. },
  24577. /**
  24578. * Set positioning with an object returned by #getPositioning.
  24579. * @param {Object} posCfg
  24580. * @return {Ext.dom.AbstractElement} this
  24581. */
  24582. setPositioning : function(pc) {
  24583. var me = this,
  24584. style = me.dom.style;
  24585. me.setStyle(pc);
  24586. if (pc.right == AUTO) {
  24587. style.right = "";
  24588. }
  24589. if (pc.bottom == AUTO) {
  24590. style.bottom = "";
  24591. }
  24592. return me;
  24593. },
  24594. /**
  24595. * Move this element relative to its current position.
  24596. * @param {String} direction Possible values are:
  24597. *
  24598. * - `"l"` (or `"left"`)
  24599. * - `"r"` (or `"right"`)
  24600. * - `"t"` (or `"top"`, or `"up"`)
  24601. * - `"b"` (or `"bottom"`, or `"down"`)
  24602. *
  24603. * @param {Number} distance How far to move the element in pixels
  24604. * @param {Boolean/Object} [animate] true for the default animation or a standard Element
  24605. * animation config object
  24606. */
  24607. move: function(direction, distance, animate) {
  24608. var me = this,
  24609. xy = me.getXY(),
  24610. x = xy[0],
  24611. y = xy[1],
  24612. left = [x - distance, y],
  24613. right = [x + distance, y],
  24614. top = [x, y - distance],
  24615. bottom = [x, y + distance],
  24616. hash = {
  24617. l: left,
  24618. left: left,
  24619. r: right,
  24620. right: right,
  24621. t: top,
  24622. top: top,
  24623. up: top,
  24624. b: bottom,
  24625. bottom: bottom,
  24626. down: bottom
  24627. };
  24628. direction = direction.toLowerCase();
  24629. me.moveTo(hash[direction][0], hash[direction][1], animate);
  24630. },
  24631. /**
  24632. * Conveniently sets left and top adding default units.
  24633. * @param {String} left The left CSS property value
  24634. * @param {String} top The top CSS property value
  24635. * @return {Ext.dom.Element} this
  24636. */
  24637. setLeftTop: function(left, top) {
  24638. var style = this.dom.style;
  24639. style.left = Element.addUnits(left);
  24640. style.top = Element.addUnits(top);
  24641. return this;
  24642. },
  24643. /**
  24644. * Returns the region of this element.
  24645. * The element must be part of the DOM tree to have a region
  24646. * (display:none or elements not appended return false).
  24647. * @return {Ext.util.Region} A Region containing "top, left, bottom, right" member data.
  24648. */
  24649. getRegion: function() {
  24650. return this.getPageBox(true);
  24651. },
  24652. /**
  24653. * Returns the **content** region of this element. That is the region within the borders and padding.
  24654. * @return {Ext.util.Region} A Region containing "top, left, bottom, right" member data.
  24655. */
  24656. getViewRegion: function() {
  24657. var me = this,
  24658. isBody = me.dom.nodeName == BODY,
  24659. scroll, pos, top, left, width, height;
  24660. // For the body we want to do some special logic
  24661. if (isBody) {
  24662. scroll = me.getScroll();
  24663. left = scroll.left;
  24664. top = scroll.top;
  24665. width = Ext.dom.AbstractElement.getViewportWidth();
  24666. height = Ext.dom.AbstractElement.getViewportHeight();
  24667. }
  24668. else {
  24669. pos = me.getXY();
  24670. left = pos[0] + me.getBorderWidth('l') + me.getPadding('l');
  24671. top = pos[1] + me.getBorderWidth('t') + me.getPadding('t');
  24672. width = me.getWidth(true);
  24673. height = me.getHeight(true);
  24674. }
  24675. return new Ext.util.Region(top, left + width - 1, top + height - 1, left);
  24676. },
  24677. /**
  24678. * Sets the element's position and size in one shot. If animation is true then width, height,
  24679. * x and y will be animated concurrently.
  24680. *
  24681. * @param {Number} x X value for new position (coordinates are page-based)
  24682. * @param {Number} y Y value for new position (coordinates are page-based)
  24683. * @param {Number/String} width The new width. This may be one of:
  24684. *
  24685. * - A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)
  24686. * - A String used to set the CSS width style. Animation may **not** be used.
  24687. *
  24688. * @param {Number/String} height The new height. This may be one of:
  24689. *
  24690. * - A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)
  24691. * - A String used to set the CSS height style. Animation may **not** be used.
  24692. *
  24693. * @param {Boolean/Object} [animate] true for the default animation or a standard Element
  24694. * animation config object
  24695. *
  24696. * @return {Ext.dom.AbstractElement} this
  24697. */
  24698. setBounds: function(x, y, width, height, animate) {
  24699. var me = this;
  24700. if (!animate || !me.anim) {
  24701. me.setSize(width, height);
  24702. me.setLocation(x, y);
  24703. } else {
  24704. if (!Ext.isObject(animate)) {
  24705. animate = {};
  24706. }
  24707. me.animate(Ext.applyIf({
  24708. to: {
  24709. x: x,
  24710. y: y,
  24711. width: me.adjustWidth(width),
  24712. height: me.adjustHeight(height)
  24713. }
  24714. }, animate));
  24715. }
  24716. return me;
  24717. },
  24718. /**
  24719. * Sets the element's position and size the specified region. If animation is true then width, height,
  24720. * x and y will be animated concurrently.
  24721. *
  24722. * @param {Ext.util.Region} region The region to fill
  24723. * @param {Boolean/Object} [animate] true for the default animation or a standard Element
  24724. * animation config object
  24725. * @return {Ext.dom.AbstractElement} this
  24726. */
  24727. setRegion: function(region, animate) {
  24728. return this.setBounds(region.left, region.top, region.right - region.left, region.bottom - region.top, animate);
  24729. }
  24730. });
  24731. }());
  24732. //@tag dom,core
  24733. //@require Ext.dom.Element-position
  24734. //@define Ext.dom.Element-scroll
  24735. //@define Ext.dom.Element
  24736. /**
  24737. * @class Ext.dom.Element
  24738. */
  24739. Ext.dom.Element.override({
  24740. /**
  24741. * Returns true if this element is scrollable.
  24742. * @return {Boolean}
  24743. */
  24744. isScrollable: function() {
  24745. var dom = this.dom;
  24746. return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
  24747. },
  24748. /**
  24749. * Returns the current scroll position of the element.
  24750. * @return {Object} An object containing the scroll position in the format
  24751. * `{left: (scrollLeft), top: (scrollTop)}`
  24752. */
  24753. getScroll: function() {
  24754. var d = this.dom,
  24755. doc = document,
  24756. body = doc.body,
  24757. docElement = doc.documentElement,
  24758. l,
  24759. t,
  24760. ret;
  24761. if (d == doc || d == body) {
  24762. if (Ext.isIE && Ext.isStrict) {
  24763. l = docElement.scrollLeft;
  24764. t = docElement.scrollTop;
  24765. } else {
  24766. l = window.pageXOffset;
  24767. t = window.pageYOffset;
  24768. }
  24769. ret = {
  24770. left: l || (body ? body.scrollLeft : 0),
  24771. top : t || (body ? body.scrollTop : 0)
  24772. };
  24773. } else {
  24774. ret = {
  24775. left: d.scrollLeft,
  24776. top : d.scrollTop
  24777. };
  24778. }
  24779. return ret;
  24780. },
  24781. /**
  24782. * Scrolls this element by the passed delta values, optionally animating.
  24783. *
  24784. * All of the following are equivalent:
  24785. *
  24786. * el.scrollBy(10, 10, true);
  24787. * el.scrollBy([10, 10], true);
  24788. * el.scrollBy({ x: 10, y: 10 }, true);
  24789. *
  24790. * @param {Number/Number[]/Object} deltaX Either the x delta, an Array specifying x and y deltas or
  24791. * an object with "x" and "y" properties.
  24792. * @param {Number/Boolean/Object} deltaY Either the y delta, or an animate flag or config object.
  24793. * @param {Boolean/Object} animate Animate flag/config object if the delta values were passed separately.
  24794. * @return {Ext.Element} this
  24795. */
  24796. scrollBy: function(deltaX, deltaY, animate) {
  24797. var me = this,
  24798. dom = me.dom;
  24799. // Extract args if deltas were passed as an Array.
  24800. if (deltaX.length) {
  24801. animate = deltaY;
  24802. deltaY = deltaX[1];
  24803. deltaX = deltaX[0];
  24804. } else if (typeof deltaX != 'number') { // or an object
  24805. animate = deltaY;
  24806. deltaY = deltaX.y;
  24807. deltaX = deltaX.x;
  24808. }
  24809. if (deltaX) {
  24810. me.scrollTo('left', Math.max(Math.min(dom.scrollLeft + deltaX, dom.scrollWidth - dom.clientWidth), 0), animate);
  24811. }
  24812. if (deltaY) {
  24813. me.scrollTo('top', Math.max(Math.min(dom.scrollTop + deltaY, dom.scrollHeight - dom.clientHeight), 0), animate);
  24814. }
  24815. return me;
  24816. },
  24817. /**
  24818. * Scrolls this element the specified scroll point. It does NOT do bounds checking so
  24819. * if you scroll to a weird value it will try to do it. For auto bounds checking, use #scroll.
  24820. * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
  24821. * @param {Number} value The new scroll value
  24822. * @param {Boolean/Object} [animate] true for the default animation or a standard Element
  24823. * animation config object
  24824. * @return {Ext.Element} this
  24825. */
  24826. scrollTo: function(side, value, animate) {
  24827. //check if we're scrolling top or left
  24828. var top = /top/i.test(side),
  24829. me = this,
  24830. dom = me.dom,
  24831. animCfg,
  24832. prop;
  24833. if (!animate || !me.anim) {
  24834. // just setting the value, so grab the direction
  24835. prop = 'scroll' + (top ? 'Top' : 'Left');
  24836. dom[prop] = value;
  24837. // corrects IE, other browsers will ignore
  24838. dom[prop] = value;
  24839. }
  24840. else {
  24841. animCfg = {
  24842. to: {}
  24843. };
  24844. animCfg.to['scroll' + (top ? 'Top' : 'Left')] = value;
  24845. if (Ext.isObject(animate)) {
  24846. Ext.applyIf(animCfg, animate);
  24847. }
  24848. me.animate(animCfg);
  24849. }
  24850. return me;
  24851. },
  24852. /**
  24853. * Scrolls this element into view within the passed container.
  24854. * @param {String/HTMLElement/Ext.Element} [container=document.body] The container element
  24855. * to scroll. Should be a string (id), dom node, or Ext.Element.
  24856. * @param {Boolean} [hscroll=true] False to disable horizontal scroll.
  24857. * @param {Boolean/Object} [animate] true for the default animation or a standard Element
  24858. * animation config object
  24859. * @return {Ext.dom.Element} this
  24860. */
  24861. scrollIntoView: function(container, hscroll, animate) {
  24862. container = Ext.getDom(container) || Ext.getBody().dom;
  24863. var el = this.dom,
  24864. offsets = this.getOffsetsTo(container),
  24865. // el's box
  24866. left = offsets[0] + container.scrollLeft,
  24867. top = offsets[1] + container.scrollTop,
  24868. bottom = top + el.offsetHeight,
  24869. right = left + el.offsetWidth,
  24870. // ct's box
  24871. ctClientHeight = container.clientHeight,
  24872. ctScrollTop = parseInt(container.scrollTop, 10),
  24873. ctScrollLeft = parseInt(container.scrollLeft, 10),
  24874. ctBottom = ctScrollTop + ctClientHeight,
  24875. ctRight = ctScrollLeft + container.clientWidth,
  24876. newPos;
  24877. if (el.offsetHeight > ctClientHeight || top < ctScrollTop) {
  24878. newPos = top;
  24879. } else if (bottom > ctBottom) {
  24880. newPos = bottom - ctClientHeight;
  24881. }
  24882. if (newPos != null) {
  24883. Ext.get(container).scrollTo('top', newPos, animate);
  24884. }
  24885. if (hscroll !== false) {
  24886. newPos = null;
  24887. if (el.offsetWidth > container.clientWidth || left < ctScrollLeft) {
  24888. newPos = left;
  24889. } else if (right > ctRight) {
  24890. newPos = right - container.clientWidth;
  24891. }
  24892. if (newPos != null) {
  24893. Ext.get(container).scrollTo('left', newPos, animate);
  24894. }
  24895. }
  24896. return this;
  24897. },
  24898. // @private
  24899. scrollChildIntoView: function(child, hscroll) {
  24900. Ext.fly(child, '_scrollChildIntoView').scrollIntoView(this, hscroll);
  24901. },
  24902. /**
  24903. * Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
  24904. * within this element's scrollable range.
  24905. * @param {String} direction Possible values are:
  24906. *
  24907. * - `"l"` (or `"left"`)
  24908. * - `"r"` (or `"right"`)
  24909. * - `"t"` (or `"top"`, or `"up"`)
  24910. * - `"b"` (or `"bottom"`, or `"down"`)
  24911. *
  24912. * @param {Number} distance How far to scroll the element in pixels
  24913. * @param {Boolean/Object} [animate] true for the default animation or a standard Element
  24914. * animation config object
  24915. * @return {Boolean} Returns true if a scroll was triggered or false if the element
  24916. * was scrolled as far as it could go.
  24917. */
  24918. scroll: function(direction, distance, animate) {
  24919. if (!this.isScrollable()) {
  24920. return false;
  24921. }
  24922. var el = this.dom,
  24923. l = el.scrollLeft, t = el.scrollTop,
  24924. w = el.scrollWidth, h = el.scrollHeight,
  24925. cw = el.clientWidth, ch = el.clientHeight,
  24926. scrolled = false, v,
  24927. hash = {
  24928. l: Math.min(l + distance, w - cw),
  24929. r: v = Math.max(l - distance, 0),
  24930. t: Math.max(t - distance, 0),
  24931. b: Math.min(t + distance, h - ch)
  24932. };
  24933. hash.d = hash.b;
  24934. hash.u = hash.t;
  24935. direction = direction.substr(0, 1);
  24936. if ((v = hash[direction]) > -1) {
  24937. scrolled = true;
  24938. this.scrollTo(direction == 'l' || direction == 'r' ? 'left' : 'top', v, this.anim(animate));
  24939. }
  24940. return scrolled;
  24941. }
  24942. });
  24943. //@tag dom,core
  24944. //@require Ext.dom.Element-scroll
  24945. //@define Ext.dom.Element-style
  24946. //@define Ext.dom.Element
  24947. /**
  24948. * @class Ext.dom.Element
  24949. */
  24950. (function() {
  24951. var Element = Ext.dom.Element,
  24952. view = document.defaultView,
  24953. adjustDirect2DTableRe = /table-row|table-.*-group/,
  24954. INTERNAL = '_internal',
  24955. HIDDEN = 'hidden',
  24956. HEIGHT = 'height',
  24957. WIDTH = 'width',
  24958. ISCLIPPED = 'isClipped',
  24959. OVERFLOW = 'overflow',
  24960. OVERFLOWX = 'overflow-x',
  24961. OVERFLOWY = 'overflow-y',
  24962. ORIGINALCLIP = 'originalClip',
  24963. DOCORBODYRE = /#document|body/i,
  24964. // This reduces the lookup of 'me.styleHooks' by one hop in the prototype chain. It is
  24965. // the same object.
  24966. styleHooks,
  24967. edges, k, edge, borderWidth;
  24968. if (!view || !view.getComputedStyle) {
  24969. Element.prototype.getStyle = function (property, inline) {
  24970. var me = this,
  24971. dom = me.dom,
  24972. multiple = typeof property != 'string',
  24973. hooks = me.styleHooks,
  24974. prop = property,
  24975. props = prop,
  24976. len = 1,
  24977. isInline = inline,
  24978. camel, domStyle, values, hook, out, style, i;
  24979. if (multiple) {
  24980. values = {};
  24981. prop = props[0];
  24982. i = 0;
  24983. if (!(len = props.length)) {
  24984. return values;
  24985. }
  24986. }
  24987. if (!dom || dom.documentElement) {
  24988. return values || '';
  24989. }
  24990. domStyle = dom.style;
  24991. if (inline) {
  24992. style = domStyle;
  24993. } else {
  24994. style = dom.currentStyle;
  24995. // fallback to inline style if rendering context not available
  24996. if (!style) {
  24997. isInline = true;
  24998. style = domStyle;
  24999. }
  25000. }
  25001. do {
  25002. hook = hooks[prop];
  25003. if (!hook) {
  25004. hooks[prop] = hook = { name: Element.normalize(prop) };
  25005. }
  25006. if (hook.get) {
  25007. out = hook.get(dom, me, isInline, style);
  25008. } else {
  25009. camel = hook.name;
  25010. // In some cases, IE6 will throw Invalid Argument exceptions for properties
  25011. // like fontSize (/examples/tabs/tabs.html in 4.0 used to exhibit this but
  25012. // no longer does due to font style changes). There is a real cost to a try
  25013. // block, so we avoid it where possible...
  25014. if (hook.canThrow) {
  25015. try {
  25016. out = style[camel];
  25017. } catch (e) {
  25018. out = '';
  25019. }
  25020. } else {
  25021. // EXTJSIV-5657 - In IE9 quirks mode there is a chance that VML root element
  25022. // has neither `currentStyle` nor `style`. Return '' this case.
  25023. out = style ? style[camel] : '';
  25024. }
  25025. }
  25026. if (!multiple) {
  25027. return out;
  25028. }
  25029. values[prop] = out;
  25030. prop = props[++i];
  25031. } while (i < len);
  25032. return values;
  25033. };
  25034. }
  25035. Element.override({
  25036. getHeight: function(contentHeight, preciseHeight) {
  25037. var me = this,
  25038. dom = me.dom,
  25039. hidden = me.isStyle('display', 'none'),
  25040. height,
  25041. floating;
  25042. if (hidden) {
  25043. return 0;
  25044. }
  25045. height = Math.max(dom.offsetHeight, dom.clientHeight) || 0;
  25046. // IE9 Direct2D dimension rounding bug
  25047. if (Ext.supports.Direct2DBug) {
  25048. floating = me.adjustDirect2DDimension(HEIGHT);
  25049. if (preciseHeight) {
  25050. height += floating;
  25051. }
  25052. else if (floating > 0 && floating < 0.5) {
  25053. height++;
  25054. }
  25055. }
  25056. if (contentHeight) {
  25057. height -= me.getBorderWidth("tb") + me.getPadding("tb");
  25058. }
  25059. return (height < 0) ? 0 : height;
  25060. },
  25061. getWidth: function(contentWidth, preciseWidth) {
  25062. var me = this,
  25063. dom = me.dom,
  25064. hidden = me.isStyle('display', 'none'),
  25065. rect, width, floating;
  25066. if (hidden) {
  25067. return 0;
  25068. }
  25069. // Gecko will in some cases report an offsetWidth that is actually less than the width of the
  25070. // text contents, because it measures fonts with sub-pixel precision but rounds the calculated
  25071. // value down. Using getBoundingClientRect instead of offsetWidth allows us to get the precise
  25072. // subpixel measurements so we can force them to always be rounded up. See
  25073. // https://bugzilla.mozilla.org/show_bug.cgi?id=458617
  25074. // Rounding up ensures that the width includes the full width of the text contents.
  25075. if (Ext.supports.BoundingClientRect) {
  25076. rect = dom.getBoundingClientRect();
  25077. width = rect.right - rect.left;
  25078. width = preciseWidth ? width : Math.ceil(width);
  25079. } else {
  25080. width = dom.offsetWidth;
  25081. }
  25082. width = Math.max(width, dom.clientWidth) || 0;
  25083. // IE9 Direct2D dimension rounding bug
  25084. if (Ext.supports.Direct2DBug) {
  25085. // get the fractional portion of the sub-pixel precision width of the element's text contents
  25086. floating = me.adjustDirect2DDimension(WIDTH);
  25087. if (preciseWidth) {
  25088. width += floating;
  25089. }
  25090. // IE9 also measures fonts with sub-pixel precision, but unlike Gecko, instead of rounding the offsetWidth down,
  25091. // it rounds to the nearest integer. This means that in order to ensure that the width includes the full
  25092. // width of the text contents we need to increment the width by 1 only if the fractional portion is less than 0.5
  25093. else if (floating > 0 && floating < 0.5) {
  25094. width++;
  25095. }
  25096. }
  25097. if (contentWidth) {
  25098. width -= me.getBorderWidth("lr") + me.getPadding("lr");
  25099. }
  25100. return (width < 0) ? 0 : width;
  25101. },
  25102. setWidth: function(width, animate) {
  25103. var me = this;
  25104. width = me.adjustWidth(width);
  25105. if (!animate || !me.anim) {
  25106. me.dom.style.width = me.addUnits(width);
  25107. }
  25108. else {
  25109. if (!Ext.isObject(animate)) {
  25110. animate = {};
  25111. }
  25112. me.animate(Ext.applyIf({
  25113. to: {
  25114. width: width
  25115. }
  25116. }, animate));
  25117. }
  25118. return me;
  25119. },
  25120. setHeight : function(height, animate) {
  25121. var me = this;
  25122. height = me.adjustHeight(height);
  25123. if (!animate || !me.anim) {
  25124. me.dom.style.height = me.addUnits(height);
  25125. }
  25126. else {
  25127. if (!Ext.isObject(animate)) {
  25128. animate = {};
  25129. }
  25130. me.animate(Ext.applyIf({
  25131. to: {
  25132. height: height
  25133. }
  25134. }, animate));
  25135. }
  25136. return me;
  25137. },
  25138. applyStyles: function(style) {
  25139. Ext.DomHelper.applyStyles(this.dom, style);
  25140. return this;
  25141. },
  25142. setSize: function(width, height, animate) {
  25143. var me = this;
  25144. if (Ext.isObject(width)) { // in case of object from getSize()
  25145. animate = height;
  25146. height = width.height;
  25147. width = width.width;
  25148. }
  25149. width = me.adjustWidth(width);
  25150. height = me.adjustHeight(height);
  25151. if (!animate || !me.anim) {
  25152. me.dom.style.width = me.addUnits(width);
  25153. me.dom.style.height = me.addUnits(height);
  25154. }
  25155. else {
  25156. if (animate === true) {
  25157. animate = {};
  25158. }
  25159. me.animate(Ext.applyIf({
  25160. to: {
  25161. width: width,
  25162. height: height
  25163. }
  25164. }, animate));
  25165. }
  25166. return me;
  25167. },
  25168. getViewSize : function() {
  25169. var me = this,
  25170. dom = me.dom,
  25171. isDoc = DOCORBODYRE.test(dom.nodeName),
  25172. ret;
  25173. // If the body, use static methods
  25174. if (isDoc) {
  25175. ret = {
  25176. width : Element.getViewWidth(),
  25177. height : Element.getViewHeight()
  25178. };
  25179. } else {
  25180. ret = {
  25181. width : dom.clientWidth,
  25182. height : dom.clientHeight
  25183. };
  25184. }
  25185. return ret;
  25186. },
  25187. getSize: function(contentSize) {
  25188. return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
  25189. },
  25190. // TODO: Look at this
  25191. // private ==> used by Fx
  25192. adjustWidth : function(width) {
  25193. var me = this,
  25194. isNum = (typeof width == 'number');
  25195. if (isNum && me.autoBoxAdjust && !me.isBorderBox()) {
  25196. width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
  25197. }
  25198. return (isNum && width < 0) ? 0 : width;
  25199. },
  25200. // private ==> used by Fx
  25201. adjustHeight : function(height) {
  25202. var me = this,
  25203. isNum = (typeof height == "number");
  25204. if (isNum && me.autoBoxAdjust && !me.isBorderBox()) {
  25205. height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
  25206. }
  25207. return (isNum && height < 0) ? 0 : height;
  25208. },
  25209. /**
  25210. * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like `#fff`) and valid values
  25211. * are convert to standard 6 digit hex color.
  25212. * @param {String} attr The css attribute
  25213. * @param {String} defaultValue The default value to use when a valid color isn't found
  25214. * @param {String} [prefix] defaults to #. Use an empty string when working with
  25215. * color anims.
  25216. */
  25217. getColor : function(attr, defaultValue, prefix) {
  25218. var v = this.getStyle(attr),
  25219. color = prefix || prefix === '' ? prefix : '#',
  25220. h, len, i=0;
  25221. if (!v || (/transparent|inherit/.test(v))) {
  25222. return defaultValue;
  25223. }
  25224. if (/^r/.test(v)) {
  25225. v = v.slice(4, v.length - 1).split(',');
  25226. len = v.length;
  25227. for (; i<len; i++) {
  25228. h = parseInt(v[i], 10);
  25229. color += (h < 16 ? '0' : '') + h.toString(16);
  25230. }
  25231. } else {
  25232. v = v.replace('#', '');
  25233. color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
  25234. }
  25235. return(color.length > 5 ? color.toLowerCase() : defaultValue);
  25236. },
  25237. /**
  25238. * Set the opacity of the element
  25239. * @param {Number} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
  25240. * @param {Boolean/Object} [animate] a standard Element animation config object or `true` for
  25241. * the default animation (`{duration: 350, easing: 'easeIn'}`)
  25242. * @return {Ext.dom.Element} this
  25243. */
  25244. setOpacity: function(opacity, animate) {
  25245. var me = this;
  25246. if (!me.dom) {
  25247. return me;
  25248. }
  25249. if (!animate || !me.anim) {
  25250. me.setStyle('opacity', opacity);
  25251. }
  25252. else {
  25253. if (typeof animate != 'object') {
  25254. animate = {
  25255. duration: 350,
  25256. easing: 'ease-in'
  25257. };
  25258. }
  25259. me.animate(Ext.applyIf({
  25260. to: {
  25261. opacity: opacity
  25262. }
  25263. }, animate));
  25264. }
  25265. return me;
  25266. },
  25267. /**
  25268. * Clears any opacity settings from this element. Required in some cases for IE.
  25269. * @return {Ext.dom.Element} this
  25270. */
  25271. clearOpacity : function() {
  25272. return this.setOpacity('');
  25273. },
  25274. /**
  25275. * @private
  25276. * Returns 1 if the browser returns the subpixel dimension rounded to the lowest pixel.
  25277. * @return {Number} 0 or 1
  25278. */
  25279. adjustDirect2DDimension: function(dimension) {
  25280. var me = this,
  25281. dom = me.dom,
  25282. display = me.getStyle('display'),
  25283. inlineDisplay = dom.style.display,
  25284. inlinePosition = dom.style.position,
  25285. originIndex = dimension === WIDTH ? 0 : 1,
  25286. currentStyle = dom.currentStyle,
  25287. floating;
  25288. if (display === 'inline') {
  25289. dom.style.display = 'inline-block';
  25290. }
  25291. dom.style.position = display.match(adjustDirect2DTableRe) ? 'absolute' : 'static';
  25292. // floating will contain digits that appears after the decimal point
  25293. // if height or width are set to auto we fallback to msTransformOrigin calculation
  25294. // Use currentStyle here instead of getStyle. In some difficult to reproduce
  25295. // instances it resets the scrollWidth of the element
  25296. floating = (parseFloat(currentStyle[dimension]) || parseFloat(currentStyle.msTransformOrigin.split(' ')[originIndex]) * 2) % 1;
  25297. dom.style.position = inlinePosition;
  25298. if (display === 'inline') {
  25299. dom.style.display = inlineDisplay;
  25300. }
  25301. return floating;
  25302. },
  25303. /**
  25304. * Store the current overflow setting and clip overflow on the element - use {@link #unclip} to remove
  25305. * @return {Ext.dom.Element} this
  25306. */
  25307. clip : function() {
  25308. var me = this,
  25309. data = (me.$cache || me.getCache()).data,
  25310. style;
  25311. if (!data[ISCLIPPED]) {
  25312. data[ISCLIPPED] = true;
  25313. style = me.getStyle([OVERFLOW, OVERFLOWX, OVERFLOWY]);
  25314. data[ORIGINALCLIP] = {
  25315. o: style[OVERFLOW],
  25316. x: style[OVERFLOWX],
  25317. y: style[OVERFLOWY]
  25318. };
  25319. me.setStyle(OVERFLOW, HIDDEN);
  25320. me.setStyle(OVERFLOWX, HIDDEN);
  25321. me.setStyle(OVERFLOWY, HIDDEN);
  25322. }
  25323. return me;
  25324. },
  25325. /**
  25326. * Return clipping (overflow) to original clipping before {@link #clip} was called
  25327. * @return {Ext.dom.Element} this
  25328. */
  25329. unclip : function() {
  25330. var me = this,
  25331. data = (me.$cache || me.getCache()).data,
  25332. clip;
  25333. if (data[ISCLIPPED]) {
  25334. data[ISCLIPPED] = false;
  25335. clip = data[ORIGINALCLIP];
  25336. if (clip.o) {
  25337. me.setStyle(OVERFLOW, clip.o);
  25338. }
  25339. if (clip.x) {
  25340. me.setStyle(OVERFLOWX, clip.x);
  25341. }
  25342. if (clip.y) {
  25343. me.setStyle(OVERFLOWY, clip.y);
  25344. }
  25345. }
  25346. return me;
  25347. },
  25348. /**
  25349. * Wraps the specified element with a special 9 element markup/CSS block that renders by default as
  25350. * a gray container with a gradient background, rounded corners and a 4-way shadow.
  25351. *
  25352. * This special markup is used throughout Ext when box wrapping elements ({@link Ext.button.Button},
  25353. * {@link Ext.panel.Panel} when {@link Ext.panel.Panel#frame frame=true}, {@link Ext.window.Window}).
  25354. * The markup is of this form:
  25355. *
  25356. * Ext.dom.Element.boxMarkup =
  25357. * '<div class="{0}-tl"><div class="{0}-tr"><div class="{0}-tc"></div></div></div>
  25358. * <div class="{0}-ml"><div class="{0}-mr"><div class="{0}-mc"></div></div></div>
  25359. * <div class="{0}-bl"><div class="{0}-br"><div class="{0}-bc"></div></div></div>';
  25360. *
  25361. * Example usage:
  25362. *
  25363. * // Basic box wrap
  25364. * Ext.get("foo").boxWrap();
  25365. *
  25366. * // You can also add a custom class and use CSS inheritance rules to customize the box look.
  25367. * // 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
  25368. * // for how to create a custom box wrap style.
  25369. * Ext.get("foo").boxWrap().addCls("x-box-blue");
  25370. *
  25371. * @param {String} [class='x-box'] A base CSS class to apply to the containing wrapper element.
  25372. * Note that there are a number of CSS rules that are dependent on this name to make the overall effect work,
  25373. * so if you supply an alternate base class, make sure you also supply all of the necessary rules.
  25374. * @return {Ext.dom.Element} The outermost wrapping element of the created box structure.
  25375. */
  25376. boxWrap : function(cls) {
  25377. cls = cls || Ext.baseCSSPrefix + 'box';
  25378. var el = Ext.get(this.insertHtml("beforeBegin", "<div class='" + cls + "'>" + Ext.String.format(Element.boxMarkup, cls) + "</div>"));
  25379. Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
  25380. return el;
  25381. },
  25382. /**
  25383. * Returns either the offsetHeight or the height of this element based on CSS height adjusted by padding or borders
  25384. * when needed to simulate offsetHeight when offsets aren't available. This may not work on display:none elements
  25385. * if a height has not been set using CSS.
  25386. * @return {Number}
  25387. */
  25388. getComputedHeight : function() {
  25389. var me = this,
  25390. h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
  25391. if (!h) {
  25392. h = parseFloat(me.getStyle(HEIGHT)) || 0;
  25393. if (!me.isBorderBox()) {
  25394. h += me.getFrameWidth('tb');
  25395. }
  25396. }
  25397. return h;
  25398. },
  25399. /**
  25400. * Returns either the offsetWidth or the width of this element based on CSS width adjusted by padding or borders
  25401. * when needed to simulate offsetWidth when offsets aren't available. This may not work on display:none elements
  25402. * if a width has not been set using CSS.
  25403. * @return {Number}
  25404. */
  25405. getComputedWidth : function() {
  25406. var me = this,
  25407. w = Math.max(me.dom.offsetWidth, me.dom.clientWidth);
  25408. if (!w) {
  25409. w = parseFloat(me.getStyle(WIDTH)) || 0;
  25410. if (!me.isBorderBox()) {
  25411. w += me.getFrameWidth('lr');
  25412. }
  25413. }
  25414. return w;
  25415. },
  25416. /**
  25417. * Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
  25418. * for more information about the sides.
  25419. * @param {String} sides
  25420. * @return {Number}
  25421. */
  25422. getFrameWidth : function(sides, onlyContentBox) {
  25423. return (onlyContentBox && this.isBorderBox()) ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
  25424. },
  25425. /**
  25426. * Sets up event handlers to add and remove a css class when the mouse is over this element
  25427. * @param {String} className The class to add
  25428. * @param {Function} [testFn] A test function to execute before adding the class. The passed parameter
  25429. * will be the Element instance. If this functions returns false, the class will not be added.
  25430. * @param {Object} [scope] The scope to execute the testFn in.
  25431. * @return {Ext.dom.Element} this
  25432. */
  25433. addClsOnOver : function(className, testFn, scope) {
  25434. var me = this,
  25435. dom = me.dom,
  25436. hasTest = Ext.isFunction(testFn);
  25437. me.hover(
  25438. function() {
  25439. if (hasTest && testFn.call(scope || me, me) === false) {
  25440. return;
  25441. }
  25442. Ext.fly(dom, INTERNAL).addCls(className);
  25443. },
  25444. function() {
  25445. Ext.fly(dom, INTERNAL).removeCls(className);
  25446. }
  25447. );
  25448. return me;
  25449. },
  25450. /**
  25451. * Sets up event handlers to add and remove a css class when this element has the focus
  25452. * @param {String} className The class to add
  25453. * @param {Function} [testFn] A test function to execute before adding the class. The passed parameter
  25454. * will be the Element instance. If this functions returns false, the class will not be added.
  25455. * @param {Object} [scope] The scope to execute the testFn in.
  25456. * @return {Ext.dom.Element} this
  25457. */
  25458. addClsOnFocus : function(className, testFn, scope) {
  25459. var me = this,
  25460. dom = me.dom,
  25461. hasTest = Ext.isFunction(testFn);
  25462. me.on("focus", function() {
  25463. if (hasTest && testFn.call(scope || me, me) === false) {
  25464. return false;
  25465. }
  25466. Ext.fly(dom, INTERNAL).addCls(className);
  25467. });
  25468. me.on("blur", function() {
  25469. Ext.fly(dom, INTERNAL).removeCls(className);
  25470. });
  25471. return me;
  25472. },
  25473. /**
  25474. * Sets up event handlers to add and remove a css class when the mouse is down and then up on this element (a click effect)
  25475. * @param {String} className The class to add
  25476. * @param {Function} [testFn] A test function to execute before adding the class. The passed parameter
  25477. * will be the Element instance. If this functions returns false, the class will not be added.
  25478. * @param {Object} [scope] The scope to execute the testFn in.
  25479. * @return {Ext.dom.Element} this
  25480. */
  25481. addClsOnClick : function(className, testFn, scope) {
  25482. var me = this,
  25483. dom = me.dom,
  25484. hasTest = Ext.isFunction(testFn);
  25485. me.on("mousedown", function() {
  25486. if (hasTest && testFn.call(scope || me, me) === false) {
  25487. return false;
  25488. }
  25489. Ext.fly(dom, INTERNAL).addCls(className);
  25490. var d = Ext.getDoc(),
  25491. fn = function() {
  25492. Ext.fly(dom, INTERNAL).removeCls(className);
  25493. d.removeListener("mouseup", fn);
  25494. };
  25495. d.on("mouseup", fn);
  25496. });
  25497. return me;
  25498. },
  25499. /**
  25500. * Returns the dimensions of the element available to lay content out in.
  25501. *
  25502. * getStyleSize utilizes prefers style sizing if present, otherwise it chooses the larger of offsetHeight/clientHeight and
  25503. * offsetWidth/clientWidth. To obtain the size excluding scrollbars, use getViewSize.
  25504. *
  25505. * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
  25506. *
  25507. * @return {Object} Object describing width and height.
  25508. * @return {Number} return.width
  25509. * @return {Number} return.height
  25510. */
  25511. getStyleSize : function() {
  25512. var me = this,
  25513. d = this.dom,
  25514. isDoc = DOCORBODYRE.test(d.nodeName),
  25515. s ,
  25516. w, h;
  25517. // If the body, use static methods
  25518. if (isDoc) {
  25519. return {
  25520. width : Element.getViewWidth(),
  25521. height : Element.getViewHeight()
  25522. };
  25523. }
  25524. s = me.getStyle([HEIGHT, WIDTH], true); //seek inline
  25525. // Use Styles if they are set
  25526. if (s.width && s.width != 'auto') {
  25527. w = parseFloat(s.width);
  25528. if (me.isBorderBox()) {
  25529. w -= me.getFrameWidth('lr');
  25530. }
  25531. }
  25532. // Use Styles if they are set
  25533. if (s.height && s.height != 'auto') {
  25534. h = parseFloat(s.height);
  25535. if (me.isBorderBox()) {
  25536. h -= me.getFrameWidth('tb');
  25537. }
  25538. }
  25539. // Use getWidth/getHeight if style not set.
  25540. return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
  25541. },
  25542. /**
  25543. * Enable text selection for this element (normalized across browsers)
  25544. * @return {Ext.Element} this
  25545. */
  25546. selectable : function() {
  25547. var me = this;
  25548. me.dom.unselectable = "off";
  25549. // Prevent it from bubles up and enables it to be selectable
  25550. me.on('selectstart', function (e) {
  25551. e.stopPropagation();
  25552. return true;
  25553. });
  25554. me.applyStyles("-moz-user-select: text; -khtml-user-select: text;");
  25555. me.removeCls(Ext.baseCSSPrefix + 'unselectable');
  25556. return me;
  25557. },
  25558. /**
  25559. * Disables text selection for this element (normalized across browsers)
  25560. * @return {Ext.dom.Element} this
  25561. */
  25562. unselectable : function() {
  25563. var me = this;
  25564. me.dom.unselectable = "on";
  25565. me.swallowEvent("selectstart", true);
  25566. me.applyStyles("-moz-user-select:-moz-none;-khtml-user-select:none;");
  25567. me.addCls(Ext.baseCSSPrefix + 'unselectable');
  25568. return me;
  25569. }
  25570. });
  25571. Element.prototype.styleHooks = styleHooks = Ext.dom.AbstractElement.prototype.styleHooks;
  25572. if (Ext.isIE6 || Ext.isIE7) {
  25573. styleHooks.fontSize = styleHooks['font-size'] = {
  25574. name: 'fontSize',
  25575. canThrow: true
  25576. };
  25577. styleHooks.fontStyle = styleHooks['font-style'] = {
  25578. name: 'fontStyle',
  25579. canThrow: true
  25580. };
  25581. styleHooks.fontFamily = styleHooks['font-family'] = {
  25582. name: 'fontFamily',
  25583. canThrow: true
  25584. };
  25585. }
  25586. // override getStyle for border-*-width
  25587. if (Ext.isIEQuirks || Ext.isIE && Ext.ieVersion <= 8) {
  25588. function getBorderWidth (dom, el, inline, style) {
  25589. if (style[this.styleName] == 'none') {
  25590. return '0px';
  25591. }
  25592. return style[this.name];
  25593. }
  25594. edges = ['Top','Right','Bottom','Left'];
  25595. k = edges.length;
  25596. while (k--) {
  25597. edge = edges[k];
  25598. borderWidth = 'border' + edge + 'Width';
  25599. styleHooks['border-'+edge.toLowerCase()+'-width'] = styleHooks[borderWidth] = {
  25600. name: borderWidth,
  25601. styleName: 'border' + edge + 'Style',
  25602. get: getBorderWidth
  25603. };
  25604. }
  25605. }
  25606. }());
  25607. Ext.onReady(function () {
  25608. var opacityRe = /alpha\(opacity=(.*)\)/i,
  25609. trimRe = /^\s+|\s+$/g,
  25610. hooks = Ext.dom.Element.prototype.styleHooks;
  25611. // Ext.supports flags are not populated until onReady...
  25612. hooks.opacity = {
  25613. name: 'opacity',
  25614. afterSet: function(dom, value, el) {
  25615. if (el.isLayer) {
  25616. el.onOpacitySet(value);
  25617. }
  25618. }
  25619. };
  25620. if (!Ext.supports.Opacity && Ext.isIE) {
  25621. Ext.apply(hooks.opacity, {
  25622. get: function (dom) {
  25623. var filter = dom.style.filter,
  25624. match, opacity;
  25625. if (filter.match) {
  25626. match = filter.match(opacityRe);
  25627. if (match) {
  25628. opacity = parseFloat(match[1]);
  25629. if (!isNaN(opacity)) {
  25630. return opacity ? opacity / 100 : 0;
  25631. }
  25632. }
  25633. }
  25634. return 1;
  25635. },
  25636. set: function (dom, value) {
  25637. var style = dom.style,
  25638. val = style.filter.replace(opacityRe, '').replace(trimRe, '');
  25639. style.zoom = 1; // ensure dom.hasLayout
  25640. // value can be a number or '' or null... so treat falsey as no opacity
  25641. if (typeof(value) == 'number' && value >= 0 && value < 1) {
  25642. value *= 100;
  25643. style.filter = val + (val.length ? ' ' : '') + 'alpha(opacity='+value+')';
  25644. } else {
  25645. style.filter = val;
  25646. }
  25647. }
  25648. });
  25649. }
  25650. // else there is no work around for the lack of opacity support. Should not be a
  25651. // problem given that this has been supported for a long time now...
  25652. });
  25653. //@tag dom,core
  25654. //@require Ext.dom.Element-style
  25655. //@define Ext.dom.Element-traversal
  25656. //@define Ext.dom.Element
  25657. /**
  25658. * @class Ext.dom.Element
  25659. */
  25660. Ext.dom.Element.override({
  25661. select: function(selector) {
  25662. return Ext.dom.Element.select(selector, false, this.dom);
  25663. }
  25664. });
  25665. //@tag dom,core
  25666. //@require Ext.dom.Element-traversal
  25667. /**
  25668. * This class encapsulates a *collection* of DOM elements, providing methods to filter members, or to perform collective
  25669. * actions upon the whole set.
  25670. *
  25671. * Although they are not listed, this class supports all of the methods of {@link Ext.dom.Element} and
  25672. * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.
  25673. *
  25674. * Example:
  25675. *
  25676. * var els = Ext.select("#some-el div.some-class");
  25677. * // or select directly from an existing element
  25678. * var el = Ext.get('some-el');
  25679. * el.select('div.some-class');
  25680. *
  25681. * els.setWidth(100); // all elements become 100 width
  25682. * els.hide(true); // all elements fade out and hide
  25683. * // or
  25684. * els.setWidth(100).hide(true);
  25685. */
  25686. Ext.define('Ext.dom.CompositeElementLite', {
  25687. alternateClassName: 'Ext.CompositeElementLite',
  25688. requires: ['Ext.dom.Element', 'Ext.dom.Query'],
  25689. statics: {
  25690. /**
  25691. * @private
  25692. * Copies all of the functions from Ext.dom.Element's prototype onto CompositeElementLite's prototype.
  25693. * This is called twice - once immediately below, and once again after additional Ext.dom.Element
  25694. * are added in Ext JS
  25695. */
  25696. importElementMethods: function() {
  25697. var name,
  25698. elementPrototype = Ext.dom.Element.prototype,
  25699. prototype = this.prototype;
  25700. for (name in elementPrototype) {
  25701. if (typeof elementPrototype[name] == 'function'){
  25702. (function(key) {
  25703. prototype[key] = prototype[key] || function() {
  25704. return this.invoke(key, arguments);
  25705. };
  25706. }).call(prototype, name);
  25707. }
  25708. }
  25709. }
  25710. },
  25711. constructor: function(elements, root) {
  25712. /**
  25713. * @property {HTMLElement[]} elements
  25714. * The Array of DOM elements which this CompositeElement encapsulates.
  25715. *
  25716. * This will not *usually* be accessed in developers' code, but developers wishing to augment the capabilities
  25717. * of the CompositeElementLite class may use it when adding methods to the class.
  25718. *
  25719. * For example to add the `nextAll` method to the class to **add** all following siblings of selected elements,
  25720. * the code would be
  25721. *
  25722. * Ext.override(Ext.dom.CompositeElementLite, {
  25723. * nextAll: function() {
  25724. * var elements = this.elements, i, l = elements.length, n, r = [], ri = -1;
  25725. *
  25726. * // Loop through all elements in this Composite, accumulating
  25727. * // an Array of all siblings.
  25728. * for (i = 0; i < l; i++) {
  25729. * for (n = elements[i].nextSibling; n; n = n.nextSibling) {
  25730. * r[++ri] = n;
  25731. * }
  25732. * }
  25733. *
  25734. * // Add all found siblings to this Composite
  25735. * return this.add(r);
  25736. * }
  25737. * });
  25738. *
  25739. * @readonly
  25740. */
  25741. this.elements = [];
  25742. this.add(elements, root);
  25743. this.el = new Ext.dom.AbstractElement.Fly();
  25744. },
  25745. /**
  25746. * @property {Boolean} isComposite
  25747. * `true` in this class to identify an object as an instantiated CompositeElement, or subclass thereof.
  25748. */
  25749. isComposite: true,
  25750. // private
  25751. getElement: function(el) {
  25752. // Set the shared flyweight dom property to the current element
  25753. return this.el.attach(el);
  25754. },
  25755. // private
  25756. transformElement: function(el) {
  25757. return Ext.getDom(el);
  25758. },
  25759. /**
  25760. * Returns the number of elements in this Composite.
  25761. * @return {Number}
  25762. */
  25763. getCount: function() {
  25764. return this.elements.length;
  25765. },
  25766. /**
  25767. * Adds elements to this Composite object.
  25768. * @param {HTMLElement[]/Ext.dom.CompositeElement} els Either an Array of DOM elements to add, or another Composite
  25769. * object who's elements should be added.
  25770. * @return {Ext.dom.CompositeElement} This Composite object.
  25771. */
  25772. add: function(els, root) {
  25773. var elements = this.elements,
  25774. i, ln;
  25775. if (!els) {
  25776. return this;
  25777. }
  25778. if (typeof els == "string") {
  25779. els = Ext.dom.Element.selectorFunction(els, root);
  25780. }
  25781. else if (els.isComposite) {
  25782. els = els.elements;
  25783. }
  25784. else if (!Ext.isIterable(els)) {
  25785. els = [els];
  25786. }
  25787. for (i = 0, ln = els.length; i < ln; ++i) {
  25788. elements.push(this.transformElement(els[i]));
  25789. }
  25790. return this;
  25791. },
  25792. invoke: function(fn, args) {
  25793. var elements = this.elements,
  25794. ln = elements.length,
  25795. element,
  25796. i;
  25797. fn = Ext.dom.Element.prototype[fn];
  25798. for (i = 0; i < ln; i++) {
  25799. element = elements[i];
  25800. if (element) {
  25801. fn.apply(this.getElement(element), args);
  25802. }
  25803. }
  25804. return this;
  25805. },
  25806. /**
  25807. * Returns a flyweight Element of the dom element object at the specified index
  25808. * @param {Number} index
  25809. * @return {Ext.dom.Element}
  25810. */
  25811. item: function(index) {
  25812. var el = this.elements[index],
  25813. out = null;
  25814. if (el) {
  25815. out = this.getElement(el);
  25816. }
  25817. return out;
  25818. },
  25819. // fixes scope with flyweight
  25820. addListener: function(eventName, handler, scope, opt) {
  25821. var els = this.elements,
  25822. len = els.length,
  25823. i, e;
  25824. for (i = 0; i < len; i++) {
  25825. e = els[i];
  25826. if (e) {
  25827. Ext.EventManager.on(e, eventName, handler, scope || e, opt);
  25828. }
  25829. }
  25830. return this;
  25831. },
  25832. /**
  25833. * Calls the passed function for each element in this composite.
  25834. * @param {Function} fn The function to call.
  25835. * @param {Ext.dom.Element} fn.el The current Element in the iteration. **This is the flyweight
  25836. * (shared) Ext.dom.Element instance, so if you require a a reference to the dom node, use el.dom.**
  25837. * @param {Ext.dom.CompositeElement} fn.c This Composite object.
  25838. * @param {Number} fn.index The zero-based index in the iteration.
  25839. * @param {Object} [scope] The scope (this reference) in which the function is executed.
  25840. * Defaults to the Element.
  25841. * @return {Ext.dom.CompositeElement} this
  25842. */
  25843. each: function(fn, scope) {
  25844. var me = this,
  25845. els = me.elements,
  25846. len = els.length,
  25847. i, e;
  25848. for (i = 0; i < len; i++) {
  25849. e = els[i];
  25850. if (e) {
  25851. e = this.getElement(e);
  25852. if (fn.call(scope || e, e, me, i) === false) {
  25853. break;
  25854. }
  25855. }
  25856. }
  25857. return me;
  25858. },
  25859. /**
  25860. * Clears this Composite and adds the elements passed.
  25861. * @param {HTMLElement[]/Ext.dom.CompositeElement} els Either an array of DOM elements, or another Composite from which
  25862. * to fill this Composite.
  25863. * @return {Ext.dom.CompositeElement} this
  25864. */
  25865. fill: function(els) {
  25866. var me = this;
  25867. me.elements = [];
  25868. me.add(els);
  25869. return me;
  25870. },
  25871. /**
  25872. * Filters this composite to only elements that match the passed selector.
  25873. * @param {String/Function} selector A string CSS selector or a comparison function. The comparison function will be
  25874. * called with the following arguments:
  25875. * @param {Ext.dom.Element} selector.el The current DOM element.
  25876. * @param {Number} selector.index The current index within the collection.
  25877. * @return {Ext.dom.CompositeElement} this
  25878. */
  25879. filter: function(selector) {
  25880. var me = this,
  25881. els = me.elements,
  25882. len = els.length,
  25883. out = [],
  25884. i = 0,
  25885. isFunc = typeof selector == 'function',
  25886. add,
  25887. el;
  25888. for (; i < len; i++) {
  25889. el = els[i];
  25890. add = false;
  25891. if (el) {
  25892. el = me.getElement(el);
  25893. if (isFunc) {
  25894. add = selector.call(el, el, me, i) !== false;
  25895. } else {
  25896. add = el.is(selector);
  25897. }
  25898. if (add) {
  25899. out.push(me.transformElement(el));
  25900. }
  25901. }
  25902. }
  25903. me.elements = out;
  25904. return me;
  25905. },
  25906. /**
  25907. * Find the index of the passed element within the composite collection.
  25908. * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, or an Ext.dom.Element, or an HtmlElement
  25909. * to find within the composite collection.
  25910. * @return {Number} The index of the passed Ext.dom.Element in the composite collection, or -1 if not found.
  25911. */
  25912. indexOf: function(el) {
  25913. return Ext.Array.indexOf(this.elements, this.transformElement(el));
  25914. },
  25915. /**
  25916. * Replaces the specified element with the passed element.
  25917. * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the
  25918. * element in this composite to replace.
  25919. * @param {String/Ext.Element} replacement The id of an element or the Element itself.
  25920. * @param {Boolean} [domReplace] True to remove and replace the element in the document too.
  25921. * @return {Ext.dom.CompositeElement} this
  25922. */
  25923. replaceElement: function(el, replacement, domReplace) {
  25924. var index = !isNaN(el) ? el : this.indexOf(el),
  25925. d;
  25926. if (index > -1) {
  25927. replacement = Ext.getDom(replacement);
  25928. if (domReplace) {
  25929. d = this.elements[index];
  25930. d.parentNode.insertBefore(replacement, d);
  25931. Ext.removeNode(d);
  25932. }
  25933. Ext.Array.splice(this.elements, index, 1, replacement);
  25934. }
  25935. return this;
  25936. },
  25937. /**
  25938. * Removes all elements.
  25939. */
  25940. clear: function() {
  25941. this.elements = [];
  25942. },
  25943. addElements: function(els, root) {
  25944. if (!els) {
  25945. return this;
  25946. }
  25947. if (typeof els == "string") {
  25948. els = Ext.dom.Element.selectorFunction(els, root);
  25949. }
  25950. var yels = this.elements,
  25951. eLen = els.length,
  25952. e;
  25953. for (e = 0; e < eLen; e++) {
  25954. yels.push(Ext.get(els[e]));
  25955. }
  25956. return this;
  25957. },
  25958. /**
  25959. * Returns the first Element
  25960. * @return {Ext.dom.Element}
  25961. */
  25962. first: function() {
  25963. return this.item(0);
  25964. },
  25965. /**
  25966. * Returns the last Element
  25967. * @return {Ext.dom.Element}
  25968. */
  25969. last: function() {
  25970. return this.item(this.getCount() - 1);
  25971. },
  25972. /**
  25973. * Returns true if this composite contains the passed element
  25974. * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, or an Ext.Element, or an HtmlElement to
  25975. * find within the composite collection.
  25976. * @return {Boolean}
  25977. */
  25978. contains: function(el) {
  25979. return this.indexOf(el) != -1;
  25980. },
  25981. /**
  25982. * Removes the specified element(s).
  25983. * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the
  25984. * element in this composite or an array of any of those.
  25985. * @param {Boolean} [removeDom] True to also remove the element from the document
  25986. * @return {Ext.dom.CompositeElement} this
  25987. */
  25988. removeElement: function(keys, removeDom) {
  25989. keys = [].concat(keys);
  25990. var me = this,
  25991. elements = me.elements,
  25992. kLen = keys.length,
  25993. val, el, k;
  25994. for (k = 0; k < kLen; k++) {
  25995. val = keys[k];
  25996. if ((el = (elements[val] || elements[val = me.indexOf(val)]))) {
  25997. if (removeDom) {
  25998. if (el.dom) {
  25999. el.remove();
  26000. } else {
  26001. Ext.removeNode(el);
  26002. }
  26003. }
  26004. Ext.Array.erase(elements, val, 1);
  26005. }
  26006. }
  26007. return me;
  26008. }
  26009. }, function() {
  26010. this.importElementMethods();
  26011. this.prototype.on = this.prototype.addListener;
  26012. if (Ext.DomQuery){
  26013. Ext.dom.Element.selectorFunction = Ext.DomQuery.select;
  26014. }
  26015. /**
  26016. * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
  26017. * to be applied to many related elements in one statement through the returned
  26018. * {@link Ext.dom.CompositeElement CompositeElement} or
  26019. * {@link Ext.dom.CompositeElementLite CompositeElementLite} object.
  26020. * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
  26021. * @param {HTMLElement/String} [root] The root element of the query or id of the root
  26022. * @return {Ext.dom.CompositeElementLite/Ext.dom.CompositeElement}
  26023. * @member Ext.dom.Element
  26024. * @method select
  26025. * @static
  26026. * @ignore
  26027. */
  26028. Ext.dom.Element.select = function(selector, root) {
  26029. var elements;
  26030. if (typeof selector == "string") {
  26031. elements = Ext.dom.Element.selectorFunction(selector, root);
  26032. }
  26033. else if (selector.length !== undefined) {
  26034. elements = selector;
  26035. }
  26036. else {
  26037. throw new Error("[Ext.select] Invalid selector specified: " + selector);
  26038. }
  26039. return new Ext.CompositeElementLite(elements);
  26040. };
  26041. /**
  26042. * @member Ext
  26043. * @method select
  26044. * @inheritdoc Ext.dom.Element#select
  26045. * @ignore
  26046. */
  26047. Ext.select = function() {
  26048. return Ext.dom.Element.select.apply(Ext.dom.Element, arguments);
  26049. };
  26050. });
  26051. /**
  26052. * This animation class is a mixin.
  26053. *
  26054. * Ext.util.Animate provides an API for the creation of animated transitions of properties and styles.
  26055. * This class is used as a mixin and currently applied to {@link Ext.Element}, {@link Ext.CompositeElement},
  26056. * {@link Ext.draw.Sprite}, {@link Ext.draw.CompositeSprite}, and {@link Ext.Component}. Note that Components
  26057. * have a limited subset of what attributes can be animated such as top, left, x, y, height, width, and
  26058. * opacity (color, paddings, and margins can not be animated).
  26059. *
  26060. * ## Animation Basics
  26061. *
  26062. * All animations require three things - `easing`, `duration`, and `to` (the final end value for each property)
  26063. * you wish to animate. Easing and duration are defaulted values specified below.
  26064. * Easing describes how the intermediate values used during a transition will be calculated.
  26065. * {@link Ext.fx.Anim#easing Easing} allows for a transition to change speed over its duration.
  26066. * You may use the defaults for easing and duration, but you must always set a
  26067. * {@link Ext.fx.Anim#to to} property which is the end value for all animations.
  26068. *
  26069. * Popular element 'to' configurations are:
  26070. *
  26071. * - opacity
  26072. * - x
  26073. * - y
  26074. * - color
  26075. * - height
  26076. * - width
  26077. *
  26078. * Popular sprite 'to' configurations are:
  26079. *
  26080. * - translation
  26081. * - path
  26082. * - scale
  26083. * - stroke
  26084. * - rotation
  26085. *
  26086. * The default duration for animations is 250 (which is a 1/4 of a second). Duration is denoted in
  26087. * milliseconds. Therefore 1 second is 1000, 1 minute would be 60000, and so on. The default easing curve
  26088. * used for all animations is 'ease'. Popular easing functions are included and can be found in {@link Ext.fx.Anim#easing Easing}.
  26089. *
  26090. * For example, a simple animation to fade out an element with a default easing and duration:
  26091. *
  26092. * var p1 = Ext.get('myElementId');
  26093. *
  26094. * p1.animate({
  26095. * to: {
  26096. * opacity: 0
  26097. * }
  26098. * });
  26099. *
  26100. * To make this animation fade out in a tenth of a second:
  26101. *
  26102. * var p1 = Ext.get('myElementId');
  26103. *
  26104. * p1.animate({
  26105. * duration: 100,
  26106. * to: {
  26107. * opacity: 0
  26108. * }
  26109. * });
  26110. *
  26111. * ## Animation Queues
  26112. *
  26113. * By default all animations are added to a queue which allows for animation via a chain-style API.
  26114. * For example, the following code will queue 4 animations which occur sequentially (one right after the other):
  26115. *
  26116. * p1.animate({
  26117. * to: {
  26118. * x: 500
  26119. * }
  26120. * }).animate({
  26121. * to: {
  26122. * y: 150
  26123. * }
  26124. * }).animate({
  26125. * to: {
  26126. * backgroundColor: '#f00' //red
  26127. * }
  26128. * }).animate({
  26129. * to: {
  26130. * opacity: 0
  26131. * }
  26132. * });
  26133. *
  26134. * You can change this behavior by calling the {@link Ext.util.Animate#syncFx syncFx} method and all
  26135. * subsequent animations for the specified target will be run concurrently (at the same time).
  26136. *
  26137. * p1.syncFx(); //this will make all animations run at the same time
  26138. *
  26139. * p1.animate({
  26140. * to: {
  26141. * x: 500
  26142. * }
  26143. * }).animate({
  26144. * to: {
  26145. * y: 150
  26146. * }
  26147. * }).animate({
  26148. * to: {
  26149. * backgroundColor: '#f00' //red
  26150. * }
  26151. * }).animate({
  26152. * to: {
  26153. * opacity: 0
  26154. * }
  26155. * });
  26156. *
  26157. * This works the same as:
  26158. *
  26159. * p1.animate({
  26160. * to: {
  26161. * x: 500,
  26162. * y: 150,
  26163. * backgroundColor: '#f00' //red
  26164. * opacity: 0
  26165. * }
  26166. * });
  26167. *
  26168. * The {@link Ext.util.Animate#stopAnimation stopAnimation} method can be used to stop any
  26169. * currently running animations and clear any queued animations.
  26170. *
  26171. * ## Animation Keyframes
  26172. *
  26173. * You can also set up complex animations with {@link Ext.fx.Anim#keyframes keyframes} which follow the
  26174. * CSS3 Animation configuration pattern. Note rotation, translation, and scaling can only be done for sprites.
  26175. * The previous example can be written with the following syntax:
  26176. *
  26177. * p1.animate({
  26178. * duration: 1000, //one second total
  26179. * keyframes: {
  26180. * 25: { //from 0 to 250ms (25%)
  26181. * x: 0
  26182. * },
  26183. * 50: { //from 250ms to 500ms (50%)
  26184. * y: 0
  26185. * },
  26186. * 75: { //from 500ms to 750ms (75%)
  26187. * backgroundColor: '#f00' //red
  26188. * },
  26189. * 100: { //from 750ms to 1sec
  26190. * opacity: 0
  26191. * }
  26192. * }
  26193. * });
  26194. *
  26195. * ## Animation Events
  26196. *
  26197. * Each animation you create has events for {@link Ext.fx.Anim#beforeanimate beforeanimate},
  26198. * {@link Ext.fx.Anim#afteranimate afteranimate}, and {@link Ext.fx.Anim#lastframe lastframe}.
  26199. * Keyframed animations adds an additional {@link Ext.fx.Animator#keyframe keyframe} event which
  26200. * fires for each keyframe in your animation.
  26201. *
  26202. * All animations support the {@link Ext.util.Observable#listeners listeners} configuration to attact functions to these events.
  26203. *
  26204. * startAnimate: function() {
  26205. * var p1 = Ext.get('myElementId');
  26206. * p1.animate({
  26207. * duration: 100,
  26208. * to: {
  26209. * opacity: 0
  26210. * },
  26211. * listeners: {
  26212. * beforeanimate: function() {
  26213. * // Execute my custom method before the animation
  26214. * this.myBeforeAnimateFn();
  26215. * },
  26216. * afteranimate: function() {
  26217. * // Execute my custom method after the animation
  26218. * this.myAfterAnimateFn();
  26219. * },
  26220. * scope: this
  26221. * });
  26222. * },
  26223. * myBeforeAnimateFn: function() {
  26224. * // My custom logic
  26225. * },
  26226. * myAfterAnimateFn: function() {
  26227. * // My custom logic
  26228. * }
  26229. *
  26230. * Due to the fact that animations run asynchronously, you can determine if an animation is currently
  26231. * running on any target by using the {@link Ext.util.Animate#getActiveAnimation getActiveAnimation}
  26232. * method. This method will return false if there are no active animations or return the currently
  26233. * running {@link Ext.fx.Anim} instance.
  26234. *
  26235. * In this example, we're going to wait for the current animation to finish, then stop any other
  26236. * queued animations before we fade our element's opacity to 0:
  26237. *
  26238. * var curAnim = p1.getActiveAnimation();
  26239. * if (curAnim) {
  26240. * curAnim.on('afteranimate', function() {
  26241. * p1.stopAnimation();
  26242. * p1.animate({
  26243. * to: {
  26244. * opacity: 0
  26245. * }
  26246. * });
  26247. * });
  26248. * }
  26249. */
  26250. Ext.define('Ext.util.Animate', {
  26251. requires: ['Ext.Element', 'Ext.CompositeElementLite'],
  26252. uses: ['Ext.fx.Manager', 'Ext.fx.Anim'],
  26253. /**
  26254. * Performs custom animation on this object.
  26255. *
  26256. * This method is applicable to both the {@link Ext.Component Component} class and the {@link Ext.draw.Sprite Sprite}
  26257. * class. It performs animated transitions of certain properties of this object over a specified timeline.
  26258. *
  26259. * ### Animating a {@link Ext.Component Component}
  26260. *
  26261. * When animating a Component, the following properties may be specified in `from`, `to`, and `keyframe` objects:
  26262. *
  26263. * - `x` - The Component's page X position in pixels.
  26264. *
  26265. * - `y` - The Component's page Y position in pixels
  26266. *
  26267. * - `left` - The Component's `left` value in pixels.
  26268. *
  26269. * - `top` - The Component's `top` value in pixels.
  26270. *
  26271. * - `width` - The Component's `width` value in pixels.
  26272. *
  26273. * - `width` - The Component's `width` value in pixels.
  26274. *
  26275. * - `dynamic` - Specify as true to update the Component's layout (if it is a Container) at every frame of the animation.
  26276. * *Use sparingly as laying out on every intermediate size change is an expensive operation.*
  26277. *
  26278. * For example, to animate a Window to a new size, ensuring that its internal layout and any shadow is correct:
  26279. *
  26280. * myWindow = Ext.create('Ext.window.Window', {
  26281. * title: 'Test Component animation',
  26282. * width: 500,
  26283. * height: 300,
  26284. * layout: {
  26285. * type: 'hbox',
  26286. * align: 'stretch'
  26287. * },
  26288. * items: [{
  26289. * title: 'Left: 33%',
  26290. * margins: '5 0 5 5',
  26291. * flex: 1
  26292. * }, {
  26293. * title: 'Left: 66%',
  26294. * margins: '5 5 5 5',
  26295. * flex: 2
  26296. * }]
  26297. * });
  26298. * myWindow.show();
  26299. * myWindow.header.el.on('click', function() {
  26300. * myWindow.animate({
  26301. * to: {
  26302. * width: (myWindow.getWidth() == 500) ? 700 : 500,
  26303. * height: (myWindow.getHeight() == 300) ? 400 : 300
  26304. * }
  26305. * });
  26306. * });
  26307. *
  26308. * For performance reasons, by default, the internal layout is only updated when the Window reaches its final `"to"`
  26309. * size. If dynamic updating of the Window's child Components is required, then configure the animation with
  26310. * `dynamic: true` and the two child items will maintain their proportions during the animation.
  26311. *
  26312. * @param {Object} config Configuration for {@link Ext.fx.Anim}.
  26313. * Note that the {@link Ext.fx.Anim#to to} config is required.
  26314. * @return {Object} this
  26315. */
  26316. animate: function(animObj) {
  26317. var me = this;
  26318. if (Ext.fx.Manager.hasFxBlock(me.id)) {
  26319. return me;
  26320. }
  26321. Ext.fx.Manager.queueFx(new Ext.fx.Anim(me.anim(animObj)));
  26322. return this;
  26323. },
  26324. // @private - process the passed fx configuration.
  26325. anim: function(config) {
  26326. if (!Ext.isObject(config)) {
  26327. return (config) ? {} : false;
  26328. }
  26329. var me = this;
  26330. if (config.stopAnimation) {
  26331. me.stopAnimation();
  26332. }
  26333. Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
  26334. return Ext.apply({
  26335. target: me,
  26336. paused: true
  26337. }, config);
  26338. },
  26339. /**
  26340. * Stops any running effects and clears this object's internal effects queue if it contains any additional effects
  26341. * that haven't started yet.
  26342. * @deprecated 4.0 Replaced by {@link #stopAnimation}
  26343. * @return {Ext.Element} The Element
  26344. * @method
  26345. */
  26346. stopFx: Ext.Function.alias(Ext.util.Animate, 'stopAnimation'),
  26347. /**
  26348. * Stops any running effects and clears this object's internal effects queue if it contains any additional effects
  26349. * that haven't started yet.
  26350. * @return {Ext.Element} The Element
  26351. */
  26352. stopAnimation: function() {
  26353. Ext.fx.Manager.stopAnimation(this.id);
  26354. return this;
  26355. },
  26356. /**
  26357. * Ensures that all effects queued after syncFx is called on this object are run concurrently. This is the opposite
  26358. * of {@link #sequenceFx}.
  26359. * @return {Object} this
  26360. */
  26361. syncFx: function() {
  26362. Ext.fx.Manager.setFxDefaults(this.id, {
  26363. concurrent: true
  26364. });
  26365. return this;
  26366. },
  26367. /**
  26368. * Ensures that all effects queued after sequenceFx is called on this object are run in sequence. This is the
  26369. * opposite of {@link #syncFx}.
  26370. * @return {Object} this
  26371. */
  26372. sequenceFx: function() {
  26373. Ext.fx.Manager.setFxDefaults(this.id, {
  26374. concurrent: false
  26375. });
  26376. return this;
  26377. },
  26378. /**
  26379. * @deprecated 4.0 Replaced by {@link #getActiveAnimation}
  26380. * @inheritdoc Ext.util.Animate#getActiveAnimation
  26381. * @method
  26382. */
  26383. hasActiveFx: Ext.Function.alias(Ext.util.Animate, 'getActiveAnimation'),
  26384. /**
  26385. * Returns the current animation if this object has any effects actively running or queued, else returns false.
  26386. * @return {Ext.fx.Anim/Boolean} Anim if element has active effects, else false
  26387. */
  26388. getActiveAnimation: function() {
  26389. return Ext.fx.Manager.getActiveAnimation(this.id);
  26390. }
  26391. }, function(){
  26392. // Apply Animate mixin manually until Element is defined in the proper 4.x way
  26393. Ext.applyIf(Ext.Element.prototype, this.prototype);
  26394. // We need to call this again so the animation methods get copied over to CE
  26395. Ext.CompositeElementLite.importElementMethods();
  26396. });
  26397. /**
  26398. * This mixin enables classes to declare relationships to child elements and provides the
  26399. * mechanics for acquiring the {@link Ext.Element elements} and storing them on an object
  26400. * instance as properties.
  26401. *
  26402. * This class is used by {@link Ext.Component components} and {@link Ext.layout.container.Container container layouts} to
  26403. * manage their child elements.
  26404. *
  26405. * A typical component that uses these features might look something like this:
  26406. *
  26407. * Ext.define('Ext.ux.SomeComponent', {
  26408. * extend: 'Ext.Component',
  26409. *
  26410. * childEls: [
  26411. * 'bodyEl'
  26412. * ],
  26413. *
  26414. * renderTpl: [
  26415. * '<div id="{id}-bodyEl"></div>'
  26416. * ],
  26417. *
  26418. * // ...
  26419. * });
  26420. *
  26421. * The `childEls` array lists one or more relationships to child elements managed by the
  26422. * component. The items in this array can be either of the following types:
  26423. *
  26424. * - String: the id suffix and property name in one. For example, "bodyEl" in the above
  26425. * example means a "bodyEl" property will be added to the instance with the result of
  26426. * {@link Ext#get} given "componentId-bodyEl" where "componentId" is the component instance's
  26427. * id.
  26428. * - Object: with a `name` property that names the instance property for the element, and
  26429. * one of the following additional properties:
  26430. * - `id`: The full id of the child element.
  26431. * - `itemId`: The suffix part of the id to which "componentId-" is prepended.
  26432. * - `select`: A selector that will be passed to {@link Ext#select}.
  26433. * - `selectNode`: A selector that will be passed to {@link Ext.DomQuery#selectNode}.
  26434. *
  26435. * The example above could have used this instead to achieve the same result:
  26436. *
  26437. * childEls: [
  26438. * { name: 'bodyEl', itemId: 'bodyEl' }
  26439. * ]
  26440. *
  26441. * When using `select`, the property will be an instance of {@link Ext.CompositeElement}. In
  26442. * all other cases, the property will be an {@link Ext.Element} or `null` if not found.
  26443. *
  26444. * Care should be taken when using `select` or `selectNode` to find child elements. The
  26445. * following issues should be considered:
  26446. *
  26447. * - Performance: using selectors can be slower than id lookup by a factor 10x or more.
  26448. * - Over-selecting: selectors are applied after the DOM elements for all children have
  26449. * been rendered, so selectors can match elements from child components (including nested
  26450. * versions of the same component) accidentally.
  26451. *
  26452. * This above issues are most important when using `select` since it returns multiple
  26453. * elements.
  26454. *
  26455. * **IMPORTANT**
  26456. * Unlike a `renderTpl` where there is a single value for an instance, `childEls` are aggregated
  26457. * up the class hierarchy so that they are effectively inherited. In other words, if a
  26458. * class where to derive from `Ext.ux.SomeComponent` in the example above, it could also
  26459. * have a `childEls` property in the same way as `Ext.ux.SomeComponent`.
  26460. *
  26461. * Ext.define('Ext.ux.AnotherComponent', {
  26462. * extend: 'Ext.ux.SomeComponent',
  26463. *
  26464. * childEls: [
  26465. * // 'bodyEl' is inherited
  26466. * 'innerEl'
  26467. * ],
  26468. *
  26469. * renderTpl: [
  26470. * '<div id="{id}-bodyEl">'
  26471. * '<div id="{id}-innerEl"></div>'
  26472. * '</div>'
  26473. * ],
  26474. *
  26475. * // ...
  26476. * });
  26477. *
  26478. * The `renderTpl` contains both child elements and unites them in the desired markup, but
  26479. * the `childEls` only contains the new child element. The {@link #applyChildEls} method
  26480. * takes care of looking up all `childEls` for an instance and considers `childEls`
  26481. * properties on all the super classes and mixins.
  26482. *
  26483. * @private
  26484. */
  26485. Ext.define('Ext.util.ElementContainer', {
  26486. childEls: [
  26487. // empty - this solves a couple problems:
  26488. // 1. It ensures that all classes have a childEls (avoid null ptr)
  26489. // 2. It prevents mixins from smashing on their own childEls (these are gathered
  26490. // specifically)
  26491. ],
  26492. constructor: function () {
  26493. var me = this,
  26494. childEls;
  26495. // if we have configured childEls, we need to merge them with those from this
  26496. // class, its bases and the set of mixins...
  26497. if (me.hasOwnProperty('childEls')) {
  26498. childEls = me.childEls;
  26499. delete me.childEls;
  26500. me.addChildEls.apply(me, childEls);
  26501. }
  26502. },
  26503. destroy: function () {
  26504. var me = this,
  26505. childEls = me.getChildEls(),
  26506. child, childName, i, k;
  26507. for (i = childEls.length; i--; ) {
  26508. childName = childEls[i];
  26509. if (typeof childName != 'string') {
  26510. childName = childName.name;
  26511. }
  26512. child = me[childName];
  26513. if (child) {
  26514. me[childName] = null; // better than delete since that changes the "shape"
  26515. child.remove();
  26516. }
  26517. }
  26518. },
  26519. /**
  26520. * Adds each argument passed to this method to the {@link Ext.AbstractComponent#cfg-childEls childEls} array.
  26521. */
  26522. addChildEls: function () {
  26523. var me = this,
  26524. args = arguments;
  26525. if (me.hasOwnProperty('childEls')) {
  26526. me.childEls.push.apply(me.childEls, args);
  26527. } else {
  26528. me.childEls = me.getChildEls().concat(Array.prototype.slice.call(args));
  26529. }
  26530. me.prune(me.childEls, false);
  26531. },
  26532. /**
  26533. * Sets references to elements inside the component.
  26534. * @private
  26535. */
  26536. applyChildEls: function(el, id) {
  26537. var me = this,
  26538. childEls = me.getChildEls(),
  26539. baseId, childName, i, selector, value;
  26540. baseId = (id || me.id) + '-';
  26541. for (i = childEls.length; i--; ) {
  26542. childName = childEls[i];
  26543. if (typeof childName == 'string') {
  26544. // We don't use Ext.get because that is 3x (or more) slower on IE6-8. Since
  26545. // we know the el's are children of our el we use getById instead:
  26546. value = el.getById(baseId + childName);
  26547. } else {
  26548. if ((selector = childName.select)) {
  26549. value = Ext.select(selector, true, el.dom); // a CompositeElement
  26550. } else if ((selector = childName.selectNode)) {
  26551. value = Ext.get(Ext.DomQuery.selectNode(selector, el.dom));
  26552. } else {
  26553. // see above re:getById...
  26554. value = el.getById(childName.id || (baseId + childName.itemId));
  26555. }
  26556. childName = childName.name;
  26557. }
  26558. me[childName] = value;
  26559. }
  26560. },
  26561. getChildEls: function () {
  26562. var me = this,
  26563. self;
  26564. // If an instance has its own childEls, that is the complete set:
  26565. if (me.hasOwnProperty('childEls')) {
  26566. return me.childEls;
  26567. }
  26568. // Typically, however, the childEls is a class-level concept, so check to see if
  26569. // we have cached the complete set on the class:
  26570. self = me.self;
  26571. return self.$childEls || me.getClassChildEls(self);
  26572. },
  26573. getClassChildEls: function (cls) {
  26574. var me = this,
  26575. result = cls.$childEls,
  26576. childEls, i, length, forked, mixin, mixins, name, parts, proto, supr, superMixins;
  26577. if (!result) {
  26578. // We put the various childEls arrays into parts in the order of superclass,
  26579. // new mixins and finally from cls. These parts can be null or undefined and
  26580. // we will skip them later.
  26581. supr = cls.superclass;
  26582. if (supr) {
  26583. supr = supr.self;
  26584. parts = [supr.$childEls || me.getClassChildEls(supr)]; // super+mixins
  26585. superMixins = supr.prototype.mixins || {};
  26586. } else {
  26587. parts = [];
  26588. superMixins = {};
  26589. }
  26590. proto = cls.prototype;
  26591. mixins = proto.mixins; // since we are a mixin, there will be at least us
  26592. for (name in mixins) {
  26593. if (mixins.hasOwnProperty(name) && !superMixins.hasOwnProperty(name)) {
  26594. mixin = mixins[name].self;
  26595. parts.push(mixin.$childEls || me.getClassChildEls(mixin));
  26596. }
  26597. }
  26598. parts.push(proto.hasOwnProperty('childEls') && proto.childEls);
  26599. for (i = 0, length = parts.length; i < length; ++i) {
  26600. childEls = parts[i];
  26601. if (childEls && childEls.length) {
  26602. if (!result) {
  26603. result = childEls;
  26604. } else {
  26605. if (!forked) {
  26606. forked = true;
  26607. result = result.slice(0);
  26608. }
  26609. result.push.apply(result, childEls);
  26610. }
  26611. }
  26612. }
  26613. cls.$childEls = result = (result ? me.prune(result, !forked) : []);
  26614. }
  26615. return result;
  26616. },
  26617. prune: function (childEls, shared) {
  26618. var index = childEls.length,
  26619. map = {},
  26620. name;
  26621. while (index--) {
  26622. name = childEls[index];
  26623. if (typeof name != 'string') {
  26624. name = name.name;
  26625. }
  26626. if (!map[name]) {
  26627. map[name] = 1;
  26628. } else {
  26629. if (shared) {
  26630. shared = false;
  26631. childEls = childEls.slice(0);
  26632. }
  26633. Ext.Array.erase(childEls, index, 1);
  26634. }
  26635. }
  26636. return childEls;
  26637. },
  26638. /**
  26639. * Removes items in the childEls array based on the return value of a supplied test
  26640. * function. The function is called with a entry in childEls and if the test function
  26641. * return true, that entry is removed. If false, that entry is kept.
  26642. *
  26643. * @param {Function} testFn The test function.
  26644. */
  26645. removeChildEls: function (testFn) {
  26646. var me = this,
  26647. old = me.getChildEls(),
  26648. keepers = (me.childEls = []),
  26649. n, i, cel;
  26650. for (i = 0, n = old.length; i < n; ++i) {
  26651. cel = old[i];
  26652. if (!testFn(cel)) {
  26653. keepers.push(cel);
  26654. }
  26655. }
  26656. }
  26657. });
  26658. /**
  26659. * Given a component hierarchy of this:
  26660. *
  26661. * {
  26662. * xtype: 'panel',
  26663. * id: 'ContainerA',
  26664. * layout: 'hbox',
  26665. * renderTo: Ext.getBody(),
  26666. * items: [
  26667. * {
  26668. * id: 'ContainerB',
  26669. * xtype: 'container',
  26670. * items: [
  26671. * { id: 'ComponentA' }
  26672. * ]
  26673. * }
  26674. * ]
  26675. * }
  26676. *
  26677. * The rendering of the above proceeds roughly like this:
  26678. *
  26679. * - ContainerA's initComponent calls #render passing the `renderTo` property as the
  26680. * container argument.
  26681. * - `render` calls the `getRenderTree` method to get a complete {@link Ext.DomHelper} spec.
  26682. * - `getRenderTree` fires the "beforerender" event and calls the #beforeRender
  26683. * method. Its result is obtained by calling #getElConfig.
  26684. * - The #getElConfig method uses the `renderTpl` and its render data as the content
  26685. * of the `autoEl` described element.
  26686. * - The result of `getRenderTree` is passed to {@link Ext.DomHelper#append}.
  26687. * - The `renderTpl` contains calls to render things like docked items, container items
  26688. * and raw markup (such as the `html` or `tpl` config properties). These calls are to
  26689. * methods added to the {@link Ext.XTemplate} instance by #setupRenderTpl.
  26690. * - The #setupRenderTpl method adds methods such as `renderItems`, `renderContent`, etc.
  26691. * to the template. These are directed to "doRenderItems", "doRenderContent" etc..
  26692. * - The #setupRenderTpl calls traverse from components to their {@link Ext.layout.Layout}
  26693. * object.
  26694. * - When a container is rendered, it also has a `renderTpl`. This is processed when the
  26695. * `renderContainer` method is called in the component's `renderTpl`. This call goes to
  26696. * Ext.layout.container.Container#doRenderContainer. This method repeats this
  26697. * process for all components in the container.
  26698. * - After the top-most component's markup is generated and placed in to the DOM, the next
  26699. * step is to link elements to their components and finish calling the component methods
  26700. * `onRender` and `afterRender` as well as fire the corresponding events.
  26701. * - The first step in this is to call #finishRender. This method descends the
  26702. * component hierarchy and calls `onRender` and fires the `render` event. These calls
  26703. * are delivered top-down to approximate the timing of these calls/events from previous
  26704. * versions.
  26705. * - During the pass, the component's `el` is set. Likewise, the `renderSelectors` and
  26706. * `childEls` are applied to capture references to the component's elements.
  26707. * - These calls are also made on the {@link Ext.layout.container.Container} layout to
  26708. * capture its elements. Both of these classes use {@link Ext.util.ElementContainer} to
  26709. * handle `childEls` processing.
  26710. * - Once this is complete, a similar pass is made by calling #finishAfterRender.
  26711. * This call also descends the component hierarchy, but this time the calls are made in
  26712. * a bottom-up order to `afterRender`.
  26713. *
  26714. * @private
  26715. */
  26716. Ext.define('Ext.util.Renderable', {
  26717. requires: [
  26718. 'Ext.dom.Element'
  26719. ],
  26720. frameCls: Ext.baseCSSPrefix + 'frame',
  26721. frameIdRegex: /[\-]frame\d+[TMB][LCR]$/,
  26722. frameElementCls: {
  26723. tl: [],
  26724. tc: [],
  26725. tr: [],
  26726. ml: [],
  26727. mc: [],
  26728. mr: [],
  26729. bl: [],
  26730. bc: [],
  26731. br: []
  26732. },
  26733. frameElNames: ['TL','TC','TR','ML','MC','MR','BL','BC','BR'],
  26734. frameTpl: [
  26735. '{%this.renderDockedItems(out,values,0);%}',
  26736. '<tpl if="top">',
  26737. '<tpl if="left"><div id="{fgid}TL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl>" style="background-position: {tl}; padding-left: {frameWidth}px" role="presentation"></tpl>',
  26738. '<tpl if="right"><div id="{fgid}TR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl>" style="background-position: {tr}; padding-right: {frameWidth}px" role="presentation"></tpl>',
  26739. '<div id="{fgid}TC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl>" style="background-position: {tc}; height: {frameWidth}px" role="presentation"></div>',
  26740. '<tpl if="right"></div></tpl>',
  26741. '<tpl if="left"></div></tpl>',
  26742. '</tpl>',
  26743. '<tpl if="left"><div id="{fgid}ML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl>" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"></tpl>',
  26744. '<tpl if="right"><div id="{fgid}MR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl>" style="background-position: {mr}; padding-right: {frameWidth}px" role="presentation"></tpl>',
  26745. '<div id="{fgid}MC" class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl>" role="presentation">',
  26746. '{%this.applyRenderTpl(out, values)%}',
  26747. '</div>',
  26748. '<tpl if="right"></div></tpl>',
  26749. '<tpl if="left"></div></tpl>',
  26750. '<tpl if="bottom">',
  26751. '<tpl if="left"><div id="{fgid}BL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl>" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"></tpl>',
  26752. '<tpl if="right"><div id="{fgid}BR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl>" style="background-position: {br}; padding-right: {frameWidth}px" role="presentation"></tpl>',
  26753. '<div id="{fgid}BC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl>" style="background-position: {bc}; height: {frameWidth}px" role="presentation"></div>',
  26754. '<tpl if="right"></div></tpl>',
  26755. '<tpl if="left"></div></tpl>',
  26756. '</tpl>',
  26757. '{%this.renderDockedItems(out,values,1);%}'
  26758. ],
  26759. frameTableTpl: [
  26760. '{%this.renderDockedItems(out,values,0);%}',
  26761. '<table><tbody>',
  26762. '<tpl if="top">',
  26763. '<tr>',
  26764. '<tpl if="left"><td id="{fgid}TL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl>" style="background-position: {tl}; padding-left:{frameWidth}px" role="presentation"></td></tpl>',
  26765. '<td id="{fgid}TC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl>" style="background-position: {tc}; height: {frameWidth}px" role="presentation"></td>',
  26766. '<tpl if="right"><td id="{fgid}TR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl>" style="background-position: {tr}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
  26767. '</tr>',
  26768. '</tpl>',
  26769. '<tr>',
  26770. '<tpl if="left"><td id="{fgid}ML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl>" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
  26771. '<td id="{fgid}MC" class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl>" style="background-position: 0 0;" role="presentation">',
  26772. '{%this.applyRenderTpl(out, values)%}',
  26773. '</td>',
  26774. '<tpl if="right"><td id="{fgid}MR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl>" style="background-position: {mr}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
  26775. '</tr>',
  26776. '<tpl if="bottom">',
  26777. '<tr>',
  26778. '<tpl if="left"><td id="{fgid}BL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl>" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
  26779. '<td id="{fgid}BC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl>" style="background-position: {bc}; height: {frameWidth}px" role="presentation"></td>',
  26780. '<tpl if="right"><td id="{fgid}BR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl>" style="background-position: {br}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
  26781. '</tr>',
  26782. '</tpl>',
  26783. '</tbody></table>',
  26784. '{%this.renderDockedItems(out,values,1);%}'
  26785. ],
  26786. /**
  26787. * Allows addition of behavior after rendering is complete. At this stage the Component’s Element
  26788. * will have been styled according to the configuration, will have had any configured CSS class
  26789. * names added, and will be in the configured visibility and the configured enable state.
  26790. *
  26791. * @template
  26792. * @protected
  26793. */
  26794. afterRender : function() {
  26795. var me = this,
  26796. data = {},
  26797. protoEl = me.protoEl,
  26798. target = me.getTargetEl(),
  26799. item;
  26800. me.finishRenderChildren();
  26801. if (me.styleHtmlContent) {
  26802. target.addCls(me.styleHtmlCls);
  26803. }
  26804. protoEl.writeTo(data);
  26805. // Here we apply any styles that were set on the protoEl during the rendering phase
  26806. // A majority of times this will not happen, but we still need to handle it
  26807. item = data.removed;
  26808. if (item) {
  26809. target.removeCls(item);
  26810. }
  26811. item = data.cls;
  26812. if (item.length) {
  26813. target.addCls(item);
  26814. }
  26815. item = data.style;
  26816. if (data.style) {
  26817. target.setStyle(item);
  26818. }
  26819. me.protoEl = null;
  26820. // If this is the outermost Container, lay it out as soon as it is rendered.
  26821. if (!me.ownerCt) {
  26822. me.updateLayout();
  26823. }
  26824. },
  26825. afterFirstLayout : function(width, height) {
  26826. var me = this,
  26827. hasX = Ext.isDefined(me.x),
  26828. hasY = Ext.isDefined(me.y),
  26829. pos, xy;
  26830. // For floaters, calculate x and y if they aren't defined by aligning
  26831. // the sized element to the center of either the container or the ownerCt
  26832. if (me.floating && (!hasX || !hasY)) {
  26833. if (me.floatParent) {
  26834. pos = me.floatParent.getTargetEl().getViewRegion();
  26835. xy = me.el.getAlignToXY(me.floatParent.getTargetEl(), 'c-c');
  26836. pos.left = xy[0] - pos.left;
  26837. pos.top = xy[1] - pos.top;
  26838. } else {
  26839. xy = me.el.getAlignToXY(me.container, 'c-c');
  26840. pos = me.container.translatePoints(xy[0], xy[1]);
  26841. }
  26842. me.x = hasX ? me.x : pos.left;
  26843. me.y = hasY ? me.y : pos.top;
  26844. hasX = hasY = true;
  26845. }
  26846. if (hasX || hasY) {
  26847. me.setPosition(me.x, me.y);
  26848. }
  26849. me.onBoxReady(width, height);
  26850. if (me.hasListeners.boxready) {
  26851. me.fireEvent('boxready', me, width, height);
  26852. }
  26853. },
  26854. onBoxReady: Ext.emptyFn,
  26855. /**
  26856. * Sets references to elements inside the component. This applies {@link Ext.AbstractComponent#cfg-renderSelectors renderSelectors}
  26857. * as well as {@link Ext.AbstractComponent#cfg-childEls childEls}.
  26858. * @private
  26859. */
  26860. applyRenderSelectors: function() {
  26861. var me = this,
  26862. selectors = me.renderSelectors,
  26863. el = me.el,
  26864. dom = el.dom,
  26865. selector;
  26866. me.applyChildEls(el);
  26867. // We still support renderSelectors. There are a few places in the framework that
  26868. // need them and they are a documented part of the API. In fact, we support mixing
  26869. // childEls and renderSelectors (no reason not to).
  26870. if (selectors) {
  26871. for (selector in selectors) {
  26872. if (selectors.hasOwnProperty(selector) && selectors[selector]) {
  26873. me[selector] = Ext.get(Ext.DomQuery.selectNode(selectors[selector], dom));
  26874. }
  26875. }
  26876. }
  26877. },
  26878. beforeRender: function () {
  26879. var me = this,
  26880. target = me.getTargetEl(),
  26881. layout = me.getComponentLayout();
  26882. // Just before rendering, set the frame flag if we are an always-framed component like Window or Tip.
  26883. me.frame = me.frame || me.alwaysFramed;
  26884. if (!layout.initialized) {
  26885. layout.initLayout();
  26886. }
  26887. // Attempt to set overflow style prior to render if the targetEl can be accessed.
  26888. // If the targetEl does not exist yet, this will take place in finishRender
  26889. if (target) {
  26890. target.setStyle(me.getOverflowStyle());
  26891. me.overflowStyleSet = true;
  26892. }
  26893. me.setUI(me.ui);
  26894. if (me.disabled) {
  26895. // pass silent so the event doesn't fire the first time.
  26896. me.disable(true);
  26897. }
  26898. },
  26899. /**
  26900. * @private
  26901. * Called from the selected frame generation template to insert this Component's inner structure inside the framing structure.
  26902. *
  26903. * When framing is used, a selected frame generation template is used as the primary template of the #getElConfig instead
  26904. * of the configured {@link Ext.AbstractComponent#renderTpl renderTpl}. The renderTpl is invoked by this method which is injected into the framing template.
  26905. */
  26906. doApplyRenderTpl: function(out, values) {
  26907. // Careful! This method is bolted on to the frameTpl so all we get for context is
  26908. // the renderData! The "this" pointer is the frameTpl instance!
  26909. var me = values.$comp,
  26910. tpl;
  26911. // Don't do this if the component is already rendered:
  26912. if (!me.rendered) {
  26913. tpl = me.initRenderTpl();
  26914. tpl.applyOut(values.renderData, out);
  26915. }
  26916. },
  26917. /**
  26918. * Handles autoRender.
  26919. * Floating Components may have an ownerCt. If they are asking to be constrained, constrain them within that
  26920. * ownerCt, and have their z-index managed locally. Floating Components are always rendered to document.body
  26921. */
  26922. doAutoRender: function() {
  26923. var me = this;
  26924. if (!me.rendered) {
  26925. if (me.floating) {
  26926. me.render(document.body);
  26927. } else {
  26928. me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender);
  26929. }
  26930. }
  26931. },
  26932. doRenderContent: function (out, renderData) {
  26933. // Careful! This method is bolted on to the renderTpl so all we get for context is
  26934. // the renderData! The "this" pointer is the renderTpl instance!
  26935. var me = renderData.$comp;
  26936. if (me.html) {
  26937. Ext.DomHelper.generateMarkup(me.html, out);
  26938. delete me.html;
  26939. }
  26940. if (me.tpl) {
  26941. // Make sure this.tpl is an instantiated XTemplate
  26942. if (!me.tpl.isTemplate) {
  26943. me.tpl = new Ext.XTemplate(me.tpl);
  26944. }
  26945. if (me.data) {
  26946. //me.tpl[me.tplWriteMode](target, me.data);
  26947. me.tpl.applyOut(me.data, out);
  26948. delete me.data;
  26949. }
  26950. }
  26951. },
  26952. doRenderFramingDockedItems: function (out, renderData, after) {
  26953. // Careful! This method is bolted on to the frameTpl so all we get for context is
  26954. // the renderData! The "this" pointer is the frameTpl instance!
  26955. var me = renderData.$comp;
  26956. // Most components don't have dockedItems, so check for doRenderDockedItems on the
  26957. // component (also, don't do this if the component is already rendered):
  26958. if (!me.rendered && me.doRenderDockedItems) {
  26959. // The "renderData" property is placed in scope for the renderTpl, but we don't
  26960. // want to render docked items at that level in addition to the framing level:
  26961. renderData.renderData.$skipDockedItems = true;
  26962. // doRenderDockedItems requires the $comp property on renderData, but this is
  26963. // set on the frameTpl's renderData as well:
  26964. me.doRenderDockedItems.call(this, out, renderData, after);
  26965. }
  26966. },
  26967. /**
  26968. * This method visits the rendered component tree in a "top-down" order. That is, this
  26969. * code runs on a parent component before running on a child. This method calls the
  26970. * {@link #onRender} method of each component.
  26971. * @param {Number} containerIdx The index into the Container items of this Component.
  26972. *
  26973. * @private
  26974. */
  26975. finishRender: function(containerIdx) {
  26976. var me = this,
  26977. tpl, data, contentEl, el, pre, hide;
  26978. // We are typically called w/me.el==null as a child of some ownerCt that is being
  26979. // rendered. We are also called by render for a normal component (w/o a configured
  26980. // me.el). In this case, render sets me.el and me.rendering (indirectly). Lastly
  26981. // we are also called on a component (like a Viewport) that has a configured me.el
  26982. // (body for a Viewport) when render is called. In this case, it is not flagged as
  26983. // "me.rendering" yet becasue it does not produce a renderTree. We use this to know
  26984. // not to regen the renderTpl.
  26985. if (!me.el || me.$pid) {
  26986. if (me.container) {
  26987. el = me.container.getById(me.id, true);
  26988. } else {
  26989. el = Ext.getDom(me.id);
  26990. }
  26991. if (!me.el) {
  26992. // Typical case: we produced the el during render
  26993. me.wrapPrimaryEl(el);
  26994. } else {
  26995. // We were configured with an el and created a proxy, so now we can swap
  26996. // the proxy for me.el:
  26997. delete me.$pid;
  26998. if (!me.el.dom) {
  26999. // make sure me.el is an Element
  27000. me.wrapPrimaryEl(me.el);
  27001. }
  27002. el.parentNode.insertBefore(me.el.dom, el);
  27003. Ext.removeNode(el); // remove placeholder el
  27004. // TODO - what about class/style?
  27005. }
  27006. } else if (!me.rendering) {
  27007. // We were configured with an el and then told to render (e.g., Viewport). We
  27008. // need to generate the proper DOM. Insert first because the layout system
  27009. // insists that child Component elements indices match the Component indices.
  27010. tpl = me.initRenderTpl();
  27011. if (tpl) {
  27012. data = me.initRenderData();
  27013. tpl.insertFirst(me.getTargetEl(), data);
  27014. }
  27015. }
  27016. // else we are rendering
  27017. if (!me.container) {
  27018. // top-level rendered components will already have me.container set up
  27019. me.container = Ext.get(me.el.dom.parentNode);
  27020. }
  27021. if (me.ctCls) {
  27022. me.container.addCls(me.ctCls);
  27023. }
  27024. // Sets the rendered flag and clears the redering flag
  27025. me.onRender(me.container, containerIdx);
  27026. // If we could not access a target protoEl in bewforeRender, we have to set the overflow styles here.
  27027. if (!me.overflowStyleSet) {
  27028. me.getTargetEl().setStyle(me.getOverflowStyle());
  27029. }
  27030. // Tell the encapsulating element to hide itself in the way the Component is configured to hide
  27031. // This means DISPLAY, VISIBILITY or OFFSETS.
  27032. me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
  27033. if (me.overCls) {
  27034. me.el.hover(me.addOverCls, me.removeOverCls, me);
  27035. }
  27036. if (me.hasListeners.render) {
  27037. me.fireEvent('render', me);
  27038. }
  27039. if (me.contentEl) {
  27040. pre = Ext.baseCSSPrefix;
  27041. hide = pre + 'hide-';
  27042. contentEl = Ext.get(me.contentEl);
  27043. contentEl.removeCls([pre+'hidden', hide+'display', hide+'offsets', hide+'nosize']);
  27044. me.getTargetEl().appendChild(contentEl.dom);
  27045. }
  27046. me.afterRender(); // this can cause a layout
  27047. if (me.hasListeners.afterrender) {
  27048. me.fireEvent('afterrender', me);
  27049. }
  27050. me.initEvents();
  27051. if (me.hidden) {
  27052. // Hiding during the render process should not perform any ancillary
  27053. // actions that the full hide process does; It is not hiding, it begins in a hidden state.'
  27054. // So just make the element hidden according to the configured hideMode
  27055. me.el.hide();
  27056. }
  27057. },
  27058. finishRenderChildren: function () {
  27059. var layout = this.getComponentLayout();
  27060. layout.finishRender();
  27061. },
  27062. getElConfig : function() {
  27063. var me = this,
  27064. autoEl = me.autoEl,
  27065. frameInfo = me.getFrameInfo(),
  27066. config = {
  27067. tag: 'div',
  27068. tpl: frameInfo ? me.initFramingTpl(frameInfo.table) : me.initRenderTpl()
  27069. },
  27070. i, frameElNames, len, suffix, frameGenId;
  27071. me.initStyles(me.protoEl);
  27072. me.protoEl.writeTo(config);
  27073. me.protoEl.flush();
  27074. if (Ext.isString(autoEl)) {
  27075. config.tag = autoEl;
  27076. } else {
  27077. Ext.apply(config, autoEl); // harmless if !autoEl
  27078. }
  27079. // It's important to assign the id here as an autoEl.id could have been (wrongly) applied and this would get things out of sync
  27080. config.id = me.id;
  27081. if (config.tpl) {
  27082. // Use the framingTpl as the main content creating template. It will call out to this.applyRenderTpl(out, values)
  27083. if (frameInfo) {
  27084. frameElNames = me.frameElNames;
  27085. len = frameElNames.length;
  27086. frameGenId = me.id + '-frame1';
  27087. me.frameGenId = 1;
  27088. config.tplData = Ext.apply({}, {
  27089. $comp: me,
  27090. fgid: frameGenId,
  27091. ui: me.ui,
  27092. uiCls: me.uiCls,
  27093. frameCls: me.frameCls,
  27094. baseCls: me.baseCls,
  27095. frameWidth: frameInfo.maxWidth,
  27096. top: !!frameInfo.top,
  27097. left: !!frameInfo.left,
  27098. right: !!frameInfo.right,
  27099. bottom: !!frameInfo.bottom,
  27100. renderData: me.initRenderData()
  27101. }, me.getFramePositions(frameInfo));
  27102. // Add the childEls for each of the frame elements
  27103. for (i = 0; i < len; i++) {
  27104. suffix = frameElNames[i];
  27105. me.addChildEls({ name: 'frame' + suffix, id: frameGenId + suffix });
  27106. }
  27107. // Panel must have a frameBody
  27108. me.addChildEls({
  27109. name: 'frameBody',
  27110. id: frameGenId + 'MC'
  27111. });
  27112. } else {
  27113. config.tplData = me.initRenderData();
  27114. }
  27115. }
  27116. return config;
  27117. },
  27118. // Create the framingTpl from the string.
  27119. // Poke in a reference to applyRenderTpl(frameInfo, out)
  27120. initFramingTpl: function(table) {
  27121. var tpl = table ? this.getTpl('frameTableTpl') : this.getTpl('frameTpl');
  27122. if (tpl && !tpl.applyRenderTpl) {
  27123. this.setupFramingTpl(tpl);
  27124. }
  27125. return tpl;
  27126. },
  27127. /**
  27128. * @private
  27129. * Inject a reference to the function which applies the render template into the framing template. The framing template
  27130. * wraps the content.
  27131. */
  27132. setupFramingTpl: function(frameTpl) {
  27133. frameTpl.applyRenderTpl = this.doApplyRenderTpl;
  27134. frameTpl.renderDockedItems = this.doRenderFramingDockedItems;
  27135. },
  27136. /**
  27137. * This function takes the position argument passed to onRender and returns a
  27138. * DOM element that you can use in the insertBefore.
  27139. * @param {String/Number/Ext.dom.Element/HTMLElement} position Index, element id or element you want
  27140. * to put this component before.
  27141. * @return {HTMLElement} DOM element that you can use in the insertBefore
  27142. */
  27143. getInsertPosition: function(position) {
  27144. // Convert the position to an element to insert before
  27145. if (position !== undefined) {
  27146. if (Ext.isNumber(position)) {
  27147. position = this.container.dom.childNodes[position];
  27148. }
  27149. else {
  27150. position = Ext.getDom(position);
  27151. }
  27152. }
  27153. return position;
  27154. },
  27155. getRenderTree: function() {
  27156. var me = this;
  27157. if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) {
  27158. me.beforeRender();
  27159. // Flag to let the layout's finishRenderItems and afterFinishRenderItems
  27160. // know which items to process
  27161. me.rendering = true;
  27162. if (me.el) {
  27163. // Since we are producing a render tree, we produce a "proxy el" that will
  27164. // sit in the rendered DOM precisely where me.el belongs. We replace the
  27165. // proxy el in the finishRender phase.
  27166. return {
  27167. tag: 'div',
  27168. id: (me.$pid = Ext.id())
  27169. };
  27170. }
  27171. return me.getElConfig();
  27172. }
  27173. return null;
  27174. },
  27175. initContainer: function(container) {
  27176. var me = this;
  27177. // If you render a component specifying the el, we get the container
  27178. // of the el, and make sure we dont move the el around in the dom
  27179. // during the render
  27180. if (!container && me.el) {
  27181. container = me.el.dom.parentNode;
  27182. me.allowDomMove = false;
  27183. }
  27184. me.container = container.dom ? container : Ext.get(container);
  27185. return me.container;
  27186. },
  27187. /**
  27188. * Initialized the renderData to be used when rendering the renderTpl.
  27189. * @return {Object} Object with keys and values that are going to be applied to the renderTpl
  27190. * @private
  27191. */
  27192. initRenderData: function() {
  27193. var me = this;
  27194. return Ext.apply({
  27195. $comp: me,
  27196. id: me.id,
  27197. ui: me.ui,
  27198. uiCls: me.uiCls,
  27199. baseCls: me.baseCls,
  27200. componentCls: me.componentCls,
  27201. frame: me.frame
  27202. }, me.renderData);
  27203. },
  27204. /**
  27205. * Initializes the renderTpl.
  27206. * @return {Ext.XTemplate} The renderTpl XTemplate instance.
  27207. * @private
  27208. */
  27209. initRenderTpl: function() {
  27210. var tpl = this.getTpl('renderTpl');
  27211. if (tpl && !tpl.renderContent) {
  27212. this.setupRenderTpl(tpl);
  27213. }
  27214. return tpl;
  27215. },
  27216. /**
  27217. * Template method called when this Component's DOM structure is created.
  27218. *
  27219. * At this point, this Component's (and all descendants') DOM structure *exists* but it has not
  27220. * been layed out (positioned and sized).
  27221. *
  27222. * Subclasses which override this to gain access to the structure at render time should
  27223. * call the parent class's method before attempting to access any child elements of the Component.
  27224. *
  27225. * @param {Ext.core.Element} parentNode The parent Element in which this Component's encapsulating element is contained.
  27226. * @param {Number} containerIdx The index within the parent Container's child collection of this Component.
  27227. *
  27228. * @template
  27229. * @protected
  27230. */
  27231. onRender: function(parentNode, containerIdx) {
  27232. var me = this,
  27233. x = me.x,
  27234. y = me.y,
  27235. lastBox, width, height,
  27236. el = me.el,
  27237. body = Ext.getBody().dom;
  27238. // Wrap this Component in a reset wraper if necessary
  27239. if (Ext.scopeResetCSS && !me.ownerCt) {
  27240. // If this component's el is the body element, we add the reset class to the html tag
  27241. if (el.dom === body) {
  27242. el.parent().addCls(Ext.resetCls);
  27243. }
  27244. // Otherwise, we ensure that there is a wrapper which has the reset class
  27245. else {
  27246. // Floaters rendered into the body can all be bumped into the common reset element
  27247. if (me.floating && me.el.dom.parentNode === body) {
  27248. Ext.resetElement.appendChild(me.el);
  27249. }
  27250. // Else we wrap this element in an element that adds the reset class.
  27251. else {
  27252. // Wrap this Component's DOM with a reset structure as determined in EventManager's initExtCss closure.
  27253. me.resetEl = el.wrap(Ext.resetElementSpec, false, Ext.supports.CSS3LinearGradient ? undefined : '*');
  27254. }
  27255. }
  27256. }
  27257. me.applyRenderSelectors();
  27258. // Flag set on getRenderTree to flag to the layout's postprocessing routine that
  27259. // the Component is in the process of being rendered and needs postprocessing.
  27260. delete me.rendering;
  27261. me.rendered = true;
  27262. // We need to remember these to avoid writing them during the initial layout:
  27263. lastBox = null;
  27264. if (x !== undefined) {
  27265. lastBox = lastBox || {};
  27266. lastBox.x = x;
  27267. }
  27268. if (y !== undefined) {
  27269. lastBox = lastBox || {};
  27270. lastBox.y = y;
  27271. }
  27272. // Framed components need their width/height to apply to the frame, which is
  27273. // best handled in layout at present.
  27274. // If we're using the content box model, we also cannot assign initial sizes since we do not know the border widths to subtract
  27275. if (!me.getFrameInfo() && Ext.isBorderBox) {
  27276. width = me.width;
  27277. height = me.height;
  27278. if (typeof width == 'number') {
  27279. lastBox = lastBox || {};
  27280. lastBox.width = width;
  27281. }
  27282. if (typeof height == 'number') {
  27283. lastBox = lastBox || {};
  27284. lastBox.height = height;
  27285. }
  27286. }
  27287. me.lastBox = me.el.lastBox = lastBox;
  27288. },
  27289. /**
  27290. * Renders the Component into the passed HTML element.
  27291. *
  27292. * **If you are using a {@link Ext.container.Container Container} object to house this
  27293. * Component, then do not use the render method.**
  27294. *
  27295. * A Container's child Components are rendered by that Container's
  27296. * {@link Ext.container.Container#layout layout} manager when the Container is first rendered.
  27297. *
  27298. * If the Container is already rendered when a new child Component is added, you may need to call
  27299. * the Container's {@link Ext.container.Container#doLayout doLayout} to refresh the view which
  27300. * causes any unrendered child Components to be rendered. This is required so that you can add
  27301. * multiple child components if needed while only refreshing the layout once.
  27302. *
  27303. * When creating complex UIs, it is important to remember that sizing and positioning
  27304. * of child items is the responsibility of the Container's {@link Ext.container.Container#layout layout}
  27305. * manager. If you expect child items to be sized in response to user interactions, you must
  27306. * configure the Container with a layout manager which creates and manages the type of layout you
  27307. * have in mind.
  27308. *
  27309. * **Omitting the Container's {@link Ext.Container#layout layout} config means that a basic
  27310. * layout manager is used which does nothing but render child components sequentially into the
  27311. * Container. No sizing or positioning will be performed in this situation.**
  27312. *
  27313. * @param {Ext.Element/HTMLElement/String} [container] The element this Component should be
  27314. * rendered into. If it is being created from existing markup, this should be omitted.
  27315. * @param {String/Number} [position] The element ID or DOM node index within the container **before**
  27316. * which this component will be inserted (defaults to appending to the end of the container)
  27317. */
  27318. render: function(container, position) {
  27319. var me = this,
  27320. el = me.el && (me.el = Ext.get(me.el)), // ensure me.el is wrapped
  27321. vetoed,
  27322. tree,
  27323. nextSibling;
  27324. Ext.suspendLayouts();
  27325. container = me.initContainer(container);
  27326. nextSibling = me.getInsertPosition(position);
  27327. if (!el) {
  27328. tree = me.getRenderTree();
  27329. if (me.ownerLayout && me.ownerLayout.transformItemRenderTree) {
  27330. tree = me.ownerLayout.transformItemRenderTree(tree);
  27331. }
  27332. // tree will be null if a beforerender listener returns false
  27333. if (tree) {
  27334. if (nextSibling) {
  27335. el = Ext.DomHelper.insertBefore(nextSibling, tree);
  27336. } else {
  27337. el = Ext.DomHelper.append(container, tree);
  27338. }
  27339. me.wrapPrimaryEl(el);
  27340. }
  27341. } else {
  27342. if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) {
  27343. // Set configured styles on pre-rendered Component's element
  27344. me.initStyles(el);
  27345. if (me.allowDomMove !== false) {
  27346. //debugger; // TODO
  27347. if (nextSibling) {
  27348. container.dom.insertBefore(el.dom, nextSibling);
  27349. } else {
  27350. container.dom.appendChild(el.dom);
  27351. }
  27352. }
  27353. } else {
  27354. vetoed = true;
  27355. }
  27356. }
  27357. if (el && !vetoed) {
  27358. me.finishRender(position);
  27359. }
  27360. Ext.resumeLayouts(!container.isDetachedBody);
  27361. },
  27362. /**
  27363. * Ensures that this component is attached to `document.body`. If the component was
  27364. * rendered to {@link Ext#getDetachedBody}, then it will be appended to `document.body`.
  27365. * Any configured position is also restored.
  27366. * @param {Boolean} [runLayout=false] True to run the component's layout.
  27367. */
  27368. ensureAttachedToBody: function (runLayout) {
  27369. var comp = this,
  27370. body;
  27371. while (comp.ownerCt) {
  27372. comp = comp.ownerCt;
  27373. }
  27374. if (comp.container.isDetachedBody) {
  27375. comp.container = body = Ext.resetElement;
  27376. body.appendChild(comp.el.dom);
  27377. if (runLayout) {
  27378. comp.updateLayout();
  27379. }
  27380. if (typeof comp.x == 'number' || typeof comp.y == 'number') {
  27381. comp.setPosition(comp.x, comp.y);
  27382. }
  27383. }
  27384. },
  27385. setupRenderTpl: function (renderTpl) {
  27386. renderTpl.renderBody = renderTpl.renderContent = this.doRenderContent;
  27387. },
  27388. wrapPrimaryEl: function (dom) {
  27389. this.el = Ext.get(dom, true);
  27390. },
  27391. /**
  27392. * @private
  27393. */
  27394. initFrame : function() {
  27395. if (Ext.supports.CSS3BorderRadius || !this.frame) {
  27396. return;
  27397. }
  27398. var me = this,
  27399. frameInfo = me.getFrameInfo(),
  27400. frameWidth, frameTpl, frameGenId,
  27401. i,
  27402. frameElNames = me.frameElNames,
  27403. len = frameElNames.length,
  27404. suffix;
  27405. if (frameInfo) {
  27406. frameWidth = frameInfo.maxWidth;
  27407. frameTpl = me.getFrameTpl(frameInfo.table);
  27408. // since we render id's into the markup and id's NEED to be unique, we have a
  27409. // simple strategy for numbering their generations.
  27410. me.frameGenId = frameGenId = (me.frameGenId || 0) + 1;
  27411. frameGenId = me.id + '-frame' + frameGenId;
  27412. // Here we render the frameTpl to this component. This inserts the 9point div or the table framing.
  27413. frameTpl.insertFirst(me.el, Ext.apply({
  27414. $comp: me,
  27415. fgid: frameGenId,
  27416. ui: me.ui,
  27417. uiCls: me.uiCls,
  27418. frameCls: me.frameCls,
  27419. baseCls: me.baseCls,
  27420. frameWidth: frameWidth,
  27421. top: !!frameInfo.top,
  27422. left: !!frameInfo.left,
  27423. right: !!frameInfo.right,
  27424. bottom: !!frameInfo.bottom
  27425. }, me.getFramePositions(frameInfo)));
  27426. // The frameBody is returned in getTargetEl, so that layouts render items to the correct target.
  27427. me.frameBody = me.el.down('.' + me.frameCls + '-mc');
  27428. // Clean out the childEls for the old frame elements (the majority of the els)
  27429. me.removeChildEls(function (c) {
  27430. return c.id && me.frameIdRegex.test(c.id);
  27431. });
  27432. // Grab references to the childEls for each of the new frame elements
  27433. for (i = 0; i < len; i++) {
  27434. suffix = frameElNames[i];
  27435. me['frame' + suffix] = me.el.getById(frameGenId + suffix);
  27436. }
  27437. }
  27438. },
  27439. updateFrame: function() {
  27440. if (Ext.supports.CSS3BorderRadius || !this.frame) {
  27441. return;
  27442. }
  27443. var me = this,
  27444. wasTable = this.frameSize && this.frameSize.table,
  27445. oldFrameTL = this.frameTL,
  27446. oldFrameBL = this.frameBL,
  27447. oldFrameML = this.frameML,
  27448. oldFrameMC = this.frameMC,
  27449. newMCClassName;
  27450. this.initFrame();
  27451. if (oldFrameMC) {
  27452. if (me.frame) {
  27453. // Store the class names set on the new MC
  27454. newMCClassName = this.frameMC.dom.className;
  27455. // Framing elements have been selected in initFrame, no need to run applyRenderSelectors
  27456. // Replace the new mc with the old mc
  27457. oldFrameMC.insertAfter(this.frameMC);
  27458. this.frameMC.remove();
  27459. // Restore the reference to the old frame mc as the framebody
  27460. this.frameBody = this.frameMC = oldFrameMC;
  27461. // Apply the new mc classes to the old mc element
  27462. oldFrameMC.dom.className = newMCClassName;
  27463. // Remove the old framing
  27464. if (wasTable) {
  27465. me.el.query('> table')[1].remove();
  27466. }
  27467. else {
  27468. if (oldFrameTL) {
  27469. oldFrameTL.remove();
  27470. }
  27471. if (oldFrameBL) {
  27472. oldFrameBL.remove();
  27473. }
  27474. if (oldFrameML) {
  27475. oldFrameML.remove();
  27476. }
  27477. }
  27478. }
  27479. }
  27480. else if (me.frame) {
  27481. this.applyRenderSelectors();
  27482. }
  27483. },
  27484. /**
  27485. * @private
  27486. * On render, reads an encoded style attribute, "background-position" from the style of this Component's element.
  27487. * This information is memoized based upon the CSS class name of this Component's element.
  27488. * Because child Components are rendered as textual HTML as part of the topmost Container, a dummy div is inserted
  27489. * into the document to receive the document element's CSS class name, and therefore style attributes.
  27490. */
  27491. getFrameInfo: function() {
  27492. // If native framing can be used, or this component is not going to be framed, then do not attempt to read CSS framing info.
  27493. if (Ext.supports.CSS3BorderRadius || !this.frame) {
  27494. return false;
  27495. }
  27496. var me = this,
  27497. frameInfoCache = me.frameInfoCache,
  27498. el = me.el || me.protoEl,
  27499. cls = el.dom ? el.dom.className : el.classList.join(' '),
  27500. frameInfo = frameInfoCache[cls],
  27501. styleEl, left, top, info;
  27502. if (frameInfo == null) {
  27503. // Get the singleton frame style proxy with our el class name stamped into it.
  27504. styleEl = Ext.fly(me.getStyleProxy(cls), 'frame-style-el');
  27505. left = styleEl.getStyle('background-position-x');
  27506. top = styleEl.getStyle('background-position-y');
  27507. // Some browsers don't support background-position-x and y, so for those
  27508. // browsers let's split background-position into two parts.
  27509. if (!left && !top) {
  27510. info = styleEl.getStyle('background-position').split(' ');
  27511. left = info[0];
  27512. top = info[1];
  27513. }
  27514. frameInfo = me.calculateFrame(left, top);
  27515. if (frameInfo) {
  27516. // Just to be sure we set the background image of the el to none.
  27517. el.setStyle('background-image', 'none');
  27518. }
  27519. // This happens when you set frame: true explicitly without using the x-frame mixin in sass.
  27520. // This way IE can't figure out what sizes to use and thus framing can't work.
  27521. if (me.frame === true && !frameInfo) {
  27522. Ext.log.error('You have set frame: true explicity on this component (' + me.getXType() + ') and it ' +
  27523. 'does not have any framing defined in the CSS template. In this case IE cannot figure out ' +
  27524. 'what sizes to use and thus framing on this component will be disabled.');
  27525. }
  27526. frameInfoCache[cls] = frameInfo;
  27527. }
  27528. me.frame = !!frameInfo;
  27529. me.frameSize = frameInfo;
  27530. return frameInfo;
  27531. },
  27532. calculateFrame: function(left, top){
  27533. // We actually pass a string in the form of '[type][tl][tr]px [direction][br][bl]px' as
  27534. // the background position of this.el from the CSS to indicate to IE that this component needs
  27535. // framing. We parse it here.
  27536. if (!(parseInt(left, 10) >= 1000000 && parseInt(top, 10) >= 1000000)) {
  27537. return false;
  27538. }
  27539. var max = Math.max,
  27540. tl = parseInt(left.substr(3, 2), 10),
  27541. tr = parseInt(left.substr(5, 2), 10),
  27542. br = parseInt(top.substr(3, 2), 10),
  27543. bl = parseInt(top.substr(5, 2), 10),
  27544. frameInfo = {
  27545. // Table markup starts with 110, div markup with 100.
  27546. table: left.substr(0, 3) == '110',
  27547. // Determine if we are dealing with a horizontal or vertical component
  27548. vertical: top.substr(0, 3) == '110',
  27549. // Get and parse the different border radius sizes
  27550. top: max(tl, tr),
  27551. right: max(tr, br),
  27552. bottom: max(bl, br),
  27553. left: max(tl, bl)
  27554. };
  27555. frameInfo.maxWidth = max(frameInfo.top, frameInfo.right, frameInfo.bottom, frameInfo.left);
  27556. frameInfo.width = frameInfo.left + frameInfo.right;
  27557. frameInfo.height = frameInfo.top + frameInfo.bottom;
  27558. return frameInfo;
  27559. },
  27560. /**
  27561. * @private
  27562. * Returns an offscreen div with the same class name as the element this is being rendered.
  27563. * This is because child item rendering takes place in a detached div which, being not part of the document, has no styling.
  27564. */
  27565. getStyleProxy: function(cls) {
  27566. var result = this.styleProxyEl || (Ext.AbstractComponent.prototype.styleProxyEl = Ext.resetElement.createChild({
  27567. style: {
  27568. position: 'absolute',
  27569. top: '-10000px'
  27570. }
  27571. }, null, true));
  27572. result.className = cls;
  27573. return result;
  27574. },
  27575. getFramePositions: function(frameInfo) {
  27576. var me = this,
  27577. frameWidth = frameInfo.maxWidth,
  27578. dock = me.dock,
  27579. positions, tc, bc, ml, mr;
  27580. if (frameInfo.vertical) {
  27581. tc = '0 -' + (frameWidth * 0) + 'px';
  27582. bc = '0 -' + (frameWidth * 1) + 'px';
  27583. if (dock && dock == "right") {
  27584. tc = 'right -' + (frameWidth * 0) + 'px';
  27585. bc = 'right -' + (frameWidth * 1) + 'px';
  27586. }
  27587. positions = {
  27588. tl: '0 -' + (frameWidth * 0) + 'px',
  27589. tr: '0 -' + (frameWidth * 1) + 'px',
  27590. bl: '0 -' + (frameWidth * 2) + 'px',
  27591. br: '0 -' + (frameWidth * 3) + 'px',
  27592. ml: '-' + (frameWidth * 1) + 'px 0',
  27593. mr: 'right 0',
  27594. tc: tc,
  27595. bc: bc
  27596. };
  27597. } else {
  27598. ml = '-' + (frameWidth * 0) + 'px 0';
  27599. mr = 'right 0';
  27600. if (dock && dock == "bottom") {
  27601. ml = 'left bottom';
  27602. mr = 'right bottom';
  27603. }
  27604. positions = {
  27605. tl: '0 -' + (frameWidth * 2) + 'px',
  27606. tr: 'right -' + (frameWidth * 3) + 'px',
  27607. bl: '0 -' + (frameWidth * 4) + 'px',
  27608. br: 'right -' + (frameWidth * 5) + 'px',
  27609. ml: ml,
  27610. mr: mr,
  27611. tc: '0 -' + (frameWidth * 0) + 'px',
  27612. bc: '0 -' + (frameWidth * 1) + 'px'
  27613. };
  27614. }
  27615. return positions;
  27616. },
  27617. /**
  27618. * @private
  27619. */
  27620. getFrameTpl : function(table) {
  27621. return this.getTpl(table ? 'frameTableTpl' : 'frameTpl');
  27622. },
  27623. // Cache the frame information object so as not to cause style recalculations
  27624. frameInfoCache: {}
  27625. });
  27626. /**
  27627. * @class Ext.state.Provider
  27628. * <p>Abstract base class for state provider implementations. The provider is responsible
  27629. * for setting values and extracting values to/from the underlying storage source. The
  27630. * storage source can vary and the details should be implemented in a subclass. For example
  27631. * a provider could use a server side database or the browser localstorage where supported.</p>
  27632. *
  27633. * <p>This class provides methods for encoding and decoding <b>typed</b> variables including
  27634. * dates and defines the Provider interface. By default these methods put the value and the
  27635. * type information into a delimited string that can be stored. These should be overridden in
  27636. * a subclass if you want to change the format of the encoded value and subsequent decoding.</p>
  27637. */
  27638. Ext.define('Ext.state.Provider', {
  27639. mixins: {
  27640. observable: 'Ext.util.Observable'
  27641. },
  27642. /**
  27643. * @cfg {String} prefix A string to prefix to items stored in the underlying state store.
  27644. * Defaults to <tt>'ext-'</tt>
  27645. */
  27646. prefix: 'ext-',
  27647. constructor : function(config){
  27648. config = config || {};
  27649. var me = this;
  27650. Ext.apply(me, config);
  27651. /**
  27652. * @event statechange
  27653. * Fires when a state change occurs.
  27654. * @param {Ext.state.Provider} this This state provider
  27655. * @param {String} key The state key which was changed
  27656. * @param {String} value The encoded value for the state
  27657. */
  27658. me.addEvents("statechange");
  27659. me.state = {};
  27660. me.mixins.observable.constructor.call(me);
  27661. },
  27662. /**
  27663. * Returns the current value for a key
  27664. * @param {String} name The key name
  27665. * @param {Object} defaultValue A default value to return if the key's value is not found
  27666. * @return {Object} The state data
  27667. */
  27668. get : function(name, defaultValue){
  27669. return typeof this.state[name] == "undefined" ?
  27670. defaultValue : this.state[name];
  27671. },
  27672. /**
  27673. * Clears a value from the state
  27674. * @param {String} name The key name
  27675. */
  27676. clear : function(name){
  27677. var me = this;
  27678. delete me.state[name];
  27679. me.fireEvent("statechange", me, name, null);
  27680. },
  27681. /**
  27682. * Sets the value for a key
  27683. * @param {String} name The key name
  27684. * @param {Object} value The value to set
  27685. */
  27686. set : function(name, value){
  27687. var me = this;
  27688. me.state[name] = value;
  27689. me.fireEvent("statechange", me, name, value);
  27690. },
  27691. /**
  27692. * Decodes a string previously encoded with {@link #encodeValue}.
  27693. * @param {String} value The value to decode
  27694. * @return {Object} The decoded value
  27695. */
  27696. decodeValue : function(value){
  27697. // a -> Array
  27698. // n -> Number
  27699. // d -> Date
  27700. // b -> Boolean
  27701. // s -> String
  27702. // o -> Object
  27703. // -> Empty (null)
  27704. var me = this,
  27705. re = /^(a|n|d|b|s|o|e)\:(.*)$/,
  27706. matches = re.exec(unescape(value)),
  27707. all,
  27708. type,
  27709. keyValue,
  27710. values,
  27711. vLen,
  27712. v;
  27713. if(!matches || !matches[1]){
  27714. return; // non state
  27715. }
  27716. type = matches[1];
  27717. value = matches[2];
  27718. switch (type) {
  27719. case 'e':
  27720. return null;
  27721. case 'n':
  27722. return parseFloat(value);
  27723. case 'd':
  27724. return new Date(Date.parse(value));
  27725. case 'b':
  27726. return (value == '1');
  27727. case 'a':
  27728. all = [];
  27729. if(value != ''){
  27730. values = value.split('^');
  27731. vLen = values.length;
  27732. for (v = 0; v < vLen; v++) {
  27733. value = values[v];
  27734. all.push(me.decodeValue(value));
  27735. }
  27736. }
  27737. return all;
  27738. case 'o':
  27739. all = {};
  27740. if(value != ''){
  27741. values = value.split('^');
  27742. vLen = values.length;
  27743. for (v = 0; v < vLen; v++) {
  27744. value = values[v];
  27745. keyValue = value.split('=');
  27746. all[keyValue[0]] = me.decodeValue(keyValue[1]);
  27747. }
  27748. }
  27749. return all;
  27750. default:
  27751. return value;
  27752. }
  27753. },
  27754. /**
  27755. * Encodes a value including type information. Decode with {@link #decodeValue}.
  27756. * @param {Object} value The value to encode
  27757. * @return {String} The encoded value
  27758. */
  27759. encodeValue : function(value){
  27760. var flat = '',
  27761. i = 0,
  27762. enc,
  27763. len,
  27764. key;
  27765. if (value == null) {
  27766. return 'e:1';
  27767. } else if(typeof value == 'number') {
  27768. enc = 'n:' + value;
  27769. } else if(typeof value == 'boolean') {
  27770. enc = 'b:' + (value ? '1' : '0');
  27771. } else if(Ext.isDate(value)) {
  27772. enc = 'd:' + value.toGMTString();
  27773. } else if(Ext.isArray(value)) {
  27774. for (len = value.length; i < len; i++) {
  27775. flat += this.encodeValue(value[i]);
  27776. if (i != len - 1) {
  27777. flat += '^';
  27778. }
  27779. }
  27780. enc = 'a:' + flat;
  27781. } else if (typeof value == 'object') {
  27782. for (key in value) {
  27783. if (typeof value[key] != 'function' && value[key] !== undefined) {
  27784. flat += key + '=' + this.encodeValue(value[key]) + '^';
  27785. }
  27786. }
  27787. enc = 'o:' + flat.substring(0, flat.length-1);
  27788. } else {
  27789. enc = 's:' + value;
  27790. }
  27791. return escape(enc);
  27792. }
  27793. });
  27794. /**
  27795. * @class Ext.state.Manager
  27796. * This is the global state manager. By default all components that are "state aware" check this class
  27797. * for state information if you don't pass them a custom state provider. In order for this class
  27798. * to be useful, it must be initialized with a provider when your application initializes. Example usage:
  27799. <pre><code>
  27800. // in your initialization function
  27801. init : function(){
  27802. Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
  27803. }
  27804. </code></pre>
  27805. * This class passes on calls from components to the underlying {@link Ext.state.Provider} so that
  27806. * there is a common interface that can be used without needing to refer to a specific provider instance
  27807. * in every component.
  27808. * @singleton
  27809. * @docauthor Evan Trimboli <evan@sencha.com>
  27810. */
  27811. Ext.define('Ext.state.Manager', {
  27812. singleton: true,
  27813. requires: ['Ext.state.Provider'],
  27814. constructor: function() {
  27815. this.provider = new Ext.state.Provider();
  27816. },
  27817. /**
  27818. * Configures the default state provider for your application
  27819. * @param {Ext.state.Provider} stateProvider The state provider to set
  27820. */
  27821. setProvider : function(stateProvider){
  27822. this.provider = stateProvider;
  27823. },
  27824. /**
  27825. * Returns the current value for a key
  27826. * @param {String} name The key name
  27827. * @param {Object} defaultValue The default value to return if the key lookup does not match
  27828. * @return {Object} The state data
  27829. */
  27830. get : function(key, defaultValue){
  27831. return this.provider.get(key, defaultValue);
  27832. },
  27833. /**
  27834. * Sets the value for a key
  27835. * @param {String} name The key name
  27836. * @param {Object} value The state data
  27837. */
  27838. set : function(key, value){
  27839. this.provider.set(key, value);
  27840. },
  27841. /**
  27842. * Clears a value from the state
  27843. * @param {String} name The key name
  27844. */
  27845. clear : function(key){
  27846. this.provider.clear(key);
  27847. },
  27848. /**
  27849. * Gets the currently configured state provider
  27850. * @return {Ext.state.Provider} The state provider
  27851. */
  27852. getProvider : function(){
  27853. return this.provider;
  27854. }
  27855. });
  27856. /**
  27857. * @class Ext.state.Stateful
  27858. * A mixin for being able to save the state of an object to an underlying
  27859. * {@link Ext.state.Provider}.
  27860. */
  27861. Ext.define('Ext.state.Stateful', {
  27862. /* Begin Definitions */
  27863. mixins: {
  27864. observable: 'Ext.util.Observable'
  27865. },
  27866. requires: ['Ext.state.Manager'],
  27867. /* End Definitions */
  27868. /**
  27869. * @cfg {Boolean} stateful
  27870. * A flag which causes the object to attempt to restore the state of
  27871. * internal properties from a saved state on startup. The object must have
  27872. * a {@link #stateId} for state to be managed.
  27873. *
  27874. * Auto-generated ids are not guaranteed to be stable across page loads and
  27875. * cannot be relied upon to save and restore the same state for a object.<p>
  27876. *
  27877. * For state saving to work, the state manager's provider must have been
  27878. * set to an implementation of {@link Ext.state.Provider} which overrides the
  27879. * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
  27880. * methods to save and recall name/value pairs. A built-in implementation,
  27881. * {@link Ext.state.CookieProvider} is available.
  27882. *
  27883. * To set the state provider for the current page:
  27884. *
  27885. * Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
  27886. * expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
  27887. * }));
  27888. *
  27889. * A stateful object attempts to save state when one of the events
  27890. * listed in the {@link #stateEvents} configuration fires.
  27891. *
  27892. * To save state, a stateful object first serializes its state by
  27893. * calling *{@link #getState}*.
  27894. *
  27895. * The Component base class implements {@link #getState} to save its width and height within the state
  27896. * only if they were initially configured, and have changed from the configured value.
  27897. *
  27898. * The Panel class saves its collapsed state in addition to that.
  27899. *
  27900. * The Grid class saves its column state in addition to its superclass state.
  27901. *
  27902. * If there is more application state to be save, the developer must provide an implementation which
  27903. * first calls the superclass method to inherit the above behaviour, and then injects new properties
  27904. * into the returned object.
  27905. *
  27906. * The value yielded by getState is passed to {@link Ext.state.Manager#set}
  27907. * which uses the configured {@link Ext.state.Provider} to save the object
  27908. * keyed by the {@link #stateId}.
  27909. *
  27910. * During construction, a stateful object attempts to *restore* its state by calling
  27911. * {@link Ext.state.Manager#get} passing the {@link #stateId}
  27912. *
  27913. * The resulting object is passed to {@link #applyState}*. The default implementation of
  27914. * {@link #applyState} simply copies properties into the object, but a developer may
  27915. * override this to support restoration of more complex application state.
  27916. *
  27917. * You can perform extra processing on state save and restore by attaching
  27918. * handlers to the {@link #beforestaterestore}, {@link #staterestore},
  27919. * {@link #beforestatesave} and {@link #statesave} events.
  27920. */
  27921. stateful: false,
  27922. /**
  27923. * @cfg {String} stateId
  27924. * The unique id for this object to use for state management purposes.
  27925. * <p>See {@link #stateful} for an explanation of saving and restoring state.</p>
  27926. */
  27927. /**
  27928. * @cfg {String[]} stateEvents
  27929. * <p>An array of events that, when fired, should trigger this object to
  27930. * save its state. Defaults to none. <code>stateEvents</code> may be any type
  27931. * of event supported by this object, including browser or custom events
  27932. * (e.g., <tt>['click', 'customerchange']</tt>).</p>
  27933. * <p>See <code>{@link #stateful}</code> for an explanation of saving and
  27934. * restoring object state.</p>
  27935. */
  27936. /**
  27937. * @cfg {Number} saveDelay
  27938. * A buffer to be applied if many state events are fired within a short period.
  27939. */
  27940. saveDelay: 100,
  27941. constructor: function(config) {
  27942. var me = this;
  27943. config = config || {};
  27944. if (config.stateful !== undefined) {
  27945. me.stateful = config.stateful;
  27946. }
  27947. if (config.saveDelay !== undefined) {
  27948. me.saveDelay = config.saveDelay;
  27949. }
  27950. me.stateId = me.stateId || config.stateId;
  27951. if (!me.stateEvents) {
  27952. me.stateEvents = [];
  27953. }
  27954. if (config.stateEvents) {
  27955. me.stateEvents.concat(config.stateEvents);
  27956. }
  27957. this.addEvents(
  27958. /**
  27959. * @event beforestaterestore
  27960. * Fires before the state of the object is restored. Return false from an event handler to stop the restore.
  27961. * @param {Ext.state.Stateful} this
  27962. * @param {Object} state The hash of state values returned from the StateProvider. If this
  27963. * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
  27964. * that simply copies property values into this object. The method maybe overriden to
  27965. * provide custom state restoration.
  27966. */
  27967. 'beforestaterestore',
  27968. /**
  27969. * @event staterestore
  27970. * Fires after the state of the object is restored.
  27971. * @param {Ext.state.Stateful} this
  27972. * @param {Object} state The hash of state values returned from the StateProvider. This is passed
  27973. * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
  27974. * object. The method maybe overriden to provide custom state restoration.
  27975. */
  27976. 'staterestore',
  27977. /**
  27978. * @event beforestatesave
  27979. * Fires before the state of the object is saved to the configured state provider. Return false to stop the save.
  27980. * @param {Ext.state.Stateful} this
  27981. * @param {Object} state The hash of state values. This is determined by calling
  27982. * <b><tt>getState()</tt></b> on the object. This method must be provided by the
  27983. * developer to return whetever representation of state is required, by default, Ext.state.Stateful
  27984. * has a null implementation.
  27985. */
  27986. 'beforestatesave',
  27987. /**
  27988. * @event statesave
  27989. * Fires after the state of the object is saved to the configured state provider.
  27990. * @param {Ext.state.Stateful} this
  27991. * @param {Object} state The hash of state values. This is determined by calling
  27992. * <b><tt>getState()</tt></b> on the object. This method must be provided by the
  27993. * developer to return whetever representation of state is required, by default, Ext.state.Stateful
  27994. * has a null implementation.
  27995. */
  27996. 'statesave'
  27997. );
  27998. me.mixins.observable.constructor.call(me);
  27999. if (me.stateful !== false) {
  28000. me.addStateEvents(me.stateEvents);
  28001. me.initState();
  28002. }
  28003. },
  28004. /**
  28005. * Add events that will trigger the state to be saved. If the first argument is an
  28006. * array, each element of that array is the name of a state event. Otherwise, each
  28007. * argument passed to this method is the name of a state event.
  28008. *
  28009. * @param {String/String[]} events The event name or an array of event names.
  28010. */
  28011. addStateEvents: function (events) {
  28012. var me = this,
  28013. i, event, stateEventsByName;
  28014. if (me.stateful && me.getStateId()) {
  28015. if (typeof events == 'string') {
  28016. events = Array.prototype.slice.call(arguments, 0);
  28017. }
  28018. stateEventsByName = me.stateEventsByName || (me.stateEventsByName = {});
  28019. for (i = events.length; i--; ) {
  28020. event = events[i];
  28021. if (!stateEventsByName[event]) {
  28022. stateEventsByName[event] = 1;
  28023. me.on(event, me.onStateChange, me);
  28024. }
  28025. }
  28026. }
  28027. },
  28028. /**
  28029. * This method is called when any of the {@link #stateEvents} are fired.
  28030. * @private
  28031. */
  28032. onStateChange: function(){
  28033. var me = this,
  28034. delay = me.saveDelay,
  28035. statics, runner;
  28036. if (!me.stateful) {
  28037. return;
  28038. }
  28039. if (delay) {
  28040. if (!me.stateTask) {
  28041. statics = Ext.state.Stateful;
  28042. runner = statics.runner || (statics.runner = new Ext.util.TaskRunner());
  28043. me.stateTask = runner.newTask({
  28044. run: me.saveState,
  28045. scope: me,
  28046. interval: delay,
  28047. repeat: 1
  28048. });
  28049. }
  28050. me.stateTask.start();
  28051. } else {
  28052. me.saveState();
  28053. }
  28054. },
  28055. /**
  28056. * Saves the state of the object to the persistence store.
  28057. */
  28058. saveState: function() {
  28059. var me = this,
  28060. id = me.stateful && me.getStateId(),
  28061. hasListeners = me.hasListeners,
  28062. state;
  28063. if (id) {
  28064. state = me.getState() || {}; //pass along for custom interactions
  28065. if (!hasListeners.beforestatesave || me.fireEvent('beforestatesave', me, state) !== false) {
  28066. Ext.state.Manager.set(id, state);
  28067. if (hasListeners.statesave) {
  28068. me.fireEvent('statesave', me, state);
  28069. }
  28070. }
  28071. }
  28072. },
  28073. /**
  28074. * Gets the current state of the object. By default this function returns null,
  28075. * it should be overridden in subclasses to implement methods for getting the state.
  28076. * @return {Object} The current state
  28077. */
  28078. getState: function(){
  28079. return null;
  28080. },
  28081. /**
  28082. * Applies the state to the object. This should be overridden in subclasses to do
  28083. * more complex state operations. By default it applies the state properties onto
  28084. * the current object.
  28085. * @param {Object} state The state
  28086. */
  28087. applyState: function(state) {
  28088. if (state) {
  28089. Ext.apply(this, state);
  28090. }
  28091. },
  28092. /**
  28093. * Gets the state id for this object.
  28094. * @return {String} The 'stateId' or the implicit 'id' specified by component configuration.
  28095. * @private
  28096. */
  28097. getStateId: function() {
  28098. var me = this;
  28099. return me.stateId || (me.autoGenId ? null : me.id);
  28100. },
  28101. /**
  28102. * Initializes the state of the object upon construction.
  28103. * @private
  28104. */
  28105. initState: function(){
  28106. var me = this,
  28107. id = me.stateful && me.getStateId(),
  28108. hasListeners = me.hasListeners,
  28109. state;
  28110. if (id) {
  28111. state = Ext.state.Manager.get(id);
  28112. if (state) {
  28113. state = Ext.apply({}, state);
  28114. if (!hasListeners.beforestaterestore || me.fireEvent('beforestaterestore', me, state) !== false) {
  28115. me.applyState(state);
  28116. if (hasListeners.staterestore) {
  28117. me.fireEvent('staterestore', me, state);
  28118. }
  28119. }
  28120. }
  28121. }
  28122. },
  28123. /**
  28124. * Conditionally saves a single property from this object to the given state object.
  28125. * The idea is to only save state which has changed from the initial state so that
  28126. * current software settings do not override future software settings. Only those
  28127. * values that are user-changed state should be saved.
  28128. *
  28129. * @param {String} propName The name of the property to save.
  28130. * @param {Object} state The state object in to which to save the property.
  28131. * @param {String} stateName (optional) The name to use for the property in state.
  28132. * @return {Boolean} True if the property was saved, false if not.
  28133. */
  28134. savePropToState: function (propName, state, stateName) {
  28135. var me = this,
  28136. value = me[propName],
  28137. config = me.initialConfig;
  28138. if (me.hasOwnProperty(propName)) {
  28139. if (!config || config[propName] !== value) {
  28140. if (state) {
  28141. state[stateName || propName] = value;
  28142. }
  28143. return true;
  28144. }
  28145. }
  28146. return false;
  28147. },
  28148. /**
  28149. * Gathers additional named properties of the instance and adds their current values
  28150. * to the passed state object.
  28151. * @param {String/String[]} propNames The name (or array of names) of the property to save.
  28152. * @param {Object} state The state object in to which to save the property values.
  28153. * @return {Object} state
  28154. */
  28155. savePropsToState: function (propNames, state) {
  28156. var me = this,
  28157. i, n;
  28158. if (typeof propNames == 'string') {
  28159. me.savePropToState(propNames, state);
  28160. } else {
  28161. for (i = 0, n = propNames.length; i < n; ++i) {
  28162. me.savePropToState(propNames[i], state);
  28163. }
  28164. }
  28165. return state;
  28166. },
  28167. /**
  28168. * Destroys this stateful object.
  28169. */
  28170. destroy: function(){
  28171. var me = this,
  28172. task = me.stateTask;
  28173. if (task) {
  28174. task.destroy();
  28175. me.stateTask = null;
  28176. }
  28177. me.clearListeners();
  28178. }
  28179. });
  28180. /**
  28181. * An abstract base class which provides shared methods for Components across the Sencha product line.
  28182. *
  28183. * Please refer to sub class's documentation
  28184. * @private
  28185. */
  28186. Ext.define('Ext.AbstractComponent', {
  28187. /* Begin Definitions */
  28188. requires: [
  28189. 'Ext.ComponentQuery',
  28190. 'Ext.ComponentManager',
  28191. 'Ext.util.ProtoElement'
  28192. ],
  28193. mixins: {
  28194. observable: 'Ext.util.Observable',
  28195. animate: 'Ext.util.Animate',
  28196. elementCt: 'Ext.util.ElementContainer',
  28197. renderable: 'Ext.util.Renderable',
  28198. state: 'Ext.state.Stateful'
  28199. },
  28200. // The "uses" property specifies class which are used in an instantiated AbstractComponent.
  28201. // They do *not* have to be loaded before this class may be defined - that is what "requires" is for.
  28202. uses: [
  28203. 'Ext.PluginManager',
  28204. 'Ext.Element',
  28205. 'Ext.DomHelper',
  28206. 'Ext.XTemplate',
  28207. 'Ext.ComponentQuery',
  28208. 'Ext.ComponentLoader',
  28209. 'Ext.EventManager',
  28210. 'Ext.layout.Context',
  28211. 'Ext.layout.Layout',
  28212. 'Ext.layout.component.Auto',
  28213. 'Ext.LoadMask',
  28214. 'Ext.ZIndexManager'
  28215. ],
  28216. statics: {
  28217. AUTO_ID: 1000,
  28218. pendingLayouts: null,
  28219. layoutSuspendCount: 0,
  28220. /**
  28221. * Cancels layout of a component.
  28222. * @param {Ext.Component} comp
  28223. */
  28224. cancelLayout: function(comp, isDestroying) {
  28225. var context = this.runningLayoutContext || this.pendingLayouts;
  28226. if (context) {
  28227. context.cancelComponent(comp, false, isDestroying);
  28228. }
  28229. },
  28230. /**
  28231. * Performs all pending layouts that were sceduled while
  28232. * {@link Ext.AbstractComponent#suspendLayouts suspendLayouts} was in effect.
  28233. * @static
  28234. */
  28235. flushLayouts: function () {
  28236. var me = this,
  28237. context = me.pendingLayouts;
  28238. if (context && context.invalidQueue.length) {
  28239. me.pendingLayouts = null;
  28240. me.runningLayoutContext = context;
  28241. Ext.override(context, {
  28242. runComplete: function () {
  28243. // we need to release the layout queue before running any of the
  28244. // finishedLayout calls because they call afterComponentLayout
  28245. // which can re-enter by calling doLayout/doComponentLayout.
  28246. me.runningLayoutContext = null;
  28247. return this.callParent(); // not "me" here!
  28248. }
  28249. });
  28250. context.run();
  28251. }
  28252. },
  28253. /**
  28254. * Resumes layout activity in the whole framework.
  28255. *
  28256. * {@link Ext#suspendLayouts} is alias of {@link Ext.AbstractComponent#suspendLayouts}.
  28257. *
  28258. * @param {Boolean} [flush=false] True to perform all the pending layouts. This can also be
  28259. * achieved by calling {@link Ext.AbstractComponent#flushLayouts flushLayouts} directly.
  28260. * @static
  28261. */
  28262. resumeLayouts: function (flush) {
  28263. if (this.layoutSuspendCount && ! --this.layoutSuspendCount) {
  28264. if (flush) {
  28265. this.flushLayouts();
  28266. }
  28267. }
  28268. },
  28269. /**
  28270. * Stops layouts from happening in the whole framework.
  28271. *
  28272. * It's useful to suspend the layout activity while updating multiple components and
  28273. * containers:
  28274. *
  28275. * Ext.suspendLayouts();
  28276. * // batch of updates...
  28277. * Ext.resumeLayouts(true);
  28278. *
  28279. * {@link Ext#suspendLayouts} is alias of {@link Ext.AbstractComponent#suspendLayouts}.
  28280. *
  28281. * See also {@link Ext#batchLayouts} for more abstract way of doing this.
  28282. *
  28283. * @static
  28284. */
  28285. suspendLayouts: function () {
  28286. ++this.layoutSuspendCount;
  28287. },
  28288. /**
  28289. * Updates layout of a component.
  28290. *
  28291. * @param {Ext.Component} comp The component to update.
  28292. * @param {Boolean} [defer=false] True to just queue the layout if this component.
  28293. * @static
  28294. */
  28295. updateLayout: function (comp, defer) {
  28296. var me = this,
  28297. running = me.runningLayoutContext,
  28298. pending;
  28299. if (running) {
  28300. running.queueInvalidate(comp);
  28301. } else {
  28302. pending = me.pendingLayouts || (me.pendingLayouts = new Ext.layout.Context());
  28303. pending.queueInvalidate(comp);
  28304. if (!defer && !me.layoutSuspendCount && !comp.isLayoutSuspended()) {
  28305. me.flushLayouts();
  28306. }
  28307. }
  28308. }
  28309. },
  28310. /* End Definitions */
  28311. /**
  28312. * @property {Boolean} isComponent
  28313. * `true` in this class to identify an object as an instantiated Component, or subclass thereof.
  28314. */
  28315. isComponent: true,
  28316. /**
  28317. * @private
  28318. */
  28319. getAutoId: function() {
  28320. this.autoGenId = true;
  28321. return ++Ext.AbstractComponent.AUTO_ID;
  28322. },
  28323. deferLayouts: false,
  28324. /**
  28325. * @cfg {String} id
  28326. * The **unique id of this component instance.**
  28327. *
  28328. * It should not be necessary to use this configuration except for singleton objects in your application. Components
  28329. * created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.
  28330. *
  28331. * Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery}
  28332. * which provides selector-based searching for Sencha Components analogous to DOM querying. The {@link
  28333. * Ext.container.Container Container} class contains {@link Ext.container.Container#down shortcut methods} to query
  28334. * its descendant Components by selector.
  28335. *
  28336. * Note that this id will also be used as the element id for the containing HTML element that is rendered to the
  28337. * page for this component. This allows you to write id-based CSS rules to style the specific instance of this
  28338. * component uniquely, and also to select sub-elements using this component's id as the parent.
  28339. *
  28340. * **Note**: to avoid complications imposed by a unique id also see `{@link #itemId}`.
  28341. *
  28342. * **Note**: to access the container of a Component see `{@link #ownerCt}`.
  28343. *
  28344. * Defaults to an {@link #getId auto-assigned id}.
  28345. */
  28346. /**
  28347. * @property {Boolean} autoGenId
  28348. * `true` indicates an id was auto-generated rather than provided by configuration.
  28349. * @private
  28350. */
  28351. autoGenId: false,
  28352. /**
  28353. * @cfg {String} itemId
  28354. * An itemId can be used as an alternative way to get a reference to a component when no object reference is
  28355. * available. Instead of using an `{@link #id}` with {@link Ext}.{@link Ext#getCmp getCmp}, use `itemId` with
  28356. * {@link Ext.container.Container}.{@link Ext.container.Container#getComponent getComponent} which will retrieve
  28357. * `itemId`'s or {@link #id}'s. Since `itemId`'s are an index to the container's internal MixedCollection, the
  28358. * `itemId` is scoped locally to the container -- avoiding potential conflicts with {@link Ext.ComponentManager}
  28359. * which requires a **unique** `{@link #id}`.
  28360. *
  28361. * var c = new Ext.panel.Panel({ //
  28362. * {@link Ext.Component#height height}: 300,
  28363. * {@link #renderTo}: document.body,
  28364. * {@link Ext.container.Container#layout layout}: 'auto',
  28365. * {@link Ext.container.Container#cfg-items items}: [
  28366. * {
  28367. * itemId: 'p1',
  28368. * {@link Ext.panel.Panel#title title}: 'Panel 1',
  28369. * {@link Ext.Component#height height}: 150
  28370. * },
  28371. * {
  28372. * itemId: 'p2',
  28373. * {@link Ext.panel.Panel#title title}: 'Panel 2',
  28374. * {@link Ext.Component#height height}: 150
  28375. * }
  28376. * ]
  28377. * })
  28378. * p1 = c.{@link Ext.container.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
  28379. * p2 = p1.{@link #ownerCt}.{@link Ext.container.Container#getComponent getComponent}('p2'); // reference via a sibling
  28380. *
  28381. * Also see {@link #id}, `{@link Ext.container.Container#query}`, `{@link Ext.container.Container#down}` and
  28382. * `{@link Ext.container.Container#child}`.
  28383. *
  28384. * **Note**: to access the container of an item see {@link #ownerCt}.
  28385. */
  28386. /**
  28387. * @property {Ext.Container} ownerCt
  28388. * This Component's owner {@link Ext.container.Container Container} (is set automatically
  28389. * when this Component is added to a Container).
  28390. *
  28391. * **Note**: to access items within the Container see {@link #itemId}.
  28392. * @readonly
  28393. */
  28394. /**
  28395. * @cfg {String/Object} autoEl
  28396. * A tag name or {@link Ext.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
  28397. * encapsulate this Component.
  28398. *
  28399. * You do not normally need to specify this. For the base classes {@link Ext.Component} and
  28400. * {@link Ext.container.Container}, this defaults to **'div'**. The more complex Sencha classes use a more
  28401. * complex DOM structure specified by their own {@link #renderTpl}s.
  28402. *
  28403. * This is intended to allow the developer to create application-specific utility Components encapsulated by
  28404. * different DOM elements. Example usage:
  28405. *
  28406. * {
  28407. * xtype: 'component',
  28408. * autoEl: {
  28409. * tag: 'img',
  28410. * src: 'http://www.example.com/example.jpg'
  28411. * }
  28412. * }, {
  28413. * xtype: 'component',
  28414. * autoEl: {
  28415. * tag: 'blockquote',
  28416. * html: 'autoEl is cool!'
  28417. * }
  28418. * }, {
  28419. * xtype: 'container',
  28420. * autoEl: 'ul',
  28421. * cls: 'ux-unordered-list',
  28422. * items: {
  28423. * xtype: 'component',
  28424. * autoEl: 'li',
  28425. * html: 'First list item'
  28426. * }
  28427. * }
  28428. */
  28429. /**
  28430. * @cfg {Ext.XTemplate/String/String[]} renderTpl
  28431. * An {@link Ext.XTemplate XTemplate} used to create the internal structure inside this Component's encapsulating
  28432. * {@link #getEl Element}.
  28433. *
  28434. * You do not normally need to specify this. For the base classes {@link Ext.Component} and
  28435. * {@link Ext.container.Container}, this defaults to **`null`** which means that they will be initially rendered
  28436. * with no internal structure; they render their {@link #getEl Element} empty. The more specialized ExtJS and Touch
  28437. * classes which use a more complex DOM structure, provide their own template definitions.
  28438. *
  28439. * This is intended to allow the developer to create application-specific utility Components with customized
  28440. * internal structure.
  28441. *
  28442. * Upon rendering, any created child elements may be automatically imported into object properties using the
  28443. * {@link #renderSelectors} and {@link #cfg-childEls} options.
  28444. * @protected
  28445. */
  28446. renderTpl: '{%this.renderContent(out,values)%}',
  28447. /**
  28448. * @cfg {Object} renderData
  28449. *
  28450. * The data used by {@link #renderTpl} in addition to the following property values of the component:
  28451. *
  28452. * - id
  28453. * - ui
  28454. * - uiCls
  28455. * - baseCls
  28456. * - componentCls
  28457. * - frame
  28458. *
  28459. * See {@link #renderSelectors} and {@link #cfg-childEls} for usage examples.
  28460. */
  28461. /**
  28462. * @cfg {Object} renderSelectors
  28463. * An object containing properties specifying {@link Ext.DomQuery DomQuery} selectors which identify child elements
  28464. * created by the render process.
  28465. *
  28466. * After the Component's internal structure is rendered according to the {@link #renderTpl}, this object is iterated through,
  28467. * and the found Elements are added as properties to the Component using the `renderSelector` property name.
  28468. *
  28469. * For example, a Component which renderes a title and description into its element:
  28470. *
  28471. * Ext.create('Ext.Component', {
  28472. * renderTo: Ext.getBody(),
  28473. * renderTpl: [
  28474. * '<h1 class="title">{title}</h1>',
  28475. * '<p>{desc}</p>'
  28476. * ],
  28477. * renderData: {
  28478. * title: "Error",
  28479. * desc: "Something went wrong"
  28480. * },
  28481. * renderSelectors: {
  28482. * titleEl: 'h1.title',
  28483. * descEl: 'p'
  28484. * },
  28485. * listeners: {
  28486. * afterrender: function(cmp){
  28487. * // After rendering the component will have a titleEl and descEl properties
  28488. * cmp.titleEl.setStyle({color: "red"});
  28489. * }
  28490. * }
  28491. * });
  28492. *
  28493. * For a faster, but less flexible, alternative that achieves the same end result (properties for child elements on the
  28494. * Component after render), see {@link #cfg-childEls} and {@link #addChildEls}.
  28495. */
  28496. /**
  28497. * @cfg {Object[]} childEls
  28498. * An array describing the child elements of the Component. Each member of the array
  28499. * is an object with these properties:
  28500. *
  28501. * - `name` - The property name on the Component for the child element.
  28502. * - `itemId` - The id to combine with the Component's id that is the id of the child element.
  28503. * - `id` - The id of the child element.
  28504. *
  28505. * If the array member is a string, it is equivalent to `{ name: m, itemId: m }`.
  28506. *
  28507. * For example, a Component which renders a title and body text:
  28508. *
  28509. * Ext.create('Ext.Component', {
  28510. * renderTo: Ext.getBody(),
  28511. * renderTpl: [
  28512. * '<h1 id="{id}-title">{title}</h1>',
  28513. * '<p>{msg}</p>',
  28514. * ],
  28515. * renderData: {
  28516. * title: "Error",
  28517. * msg: "Something went wrong"
  28518. * },
  28519. * childEls: ["title"],
  28520. * listeners: {
  28521. * afterrender: function(cmp){
  28522. * // After rendering the component will have a title property
  28523. * cmp.title.setStyle({color: "red"});
  28524. * }
  28525. * }
  28526. * });
  28527. *
  28528. * A more flexible, but somewhat slower, approach is {@link #renderSelectors}.
  28529. */
  28530. /**
  28531. * @cfg {String/HTMLElement/Ext.Element} renderTo
  28532. * Specify the id of the element, a DOM element or an existing Element that this component will be rendered into.
  28533. *
  28534. * **Notes:**
  28535. *
  28536. * Do *not* use this option if the Component is to be a child item of a {@link Ext.container.Container Container}.
  28537. * It is the responsibility of the {@link Ext.container.Container Container}'s
  28538. * {@link Ext.container.Container#layout layout manager} to render and manage its child items.
  28539. *
  28540. * When using this config, a call to render() is not required.
  28541. *
  28542. * See also: {@link #method-render}.
  28543. */
  28544. /**
  28545. * @cfg {Boolean} frame
  28546. * Specify as `true` to have the Component inject framing elements within the Component at render time to provide a
  28547. * graphical rounded frame around the Component content.
  28548. *
  28549. * This is only necessary when running on outdated, or non standard-compliant browsers such as Microsoft's Internet
  28550. * Explorer prior to version 9 which do not support rounded corners natively.
  28551. *
  28552. * The extra space taken up by this framing is available from the read only property {@link #frameSize}.
  28553. */
  28554. /**
  28555. * @property {Object} frameSize
  28556. * @readonly
  28557. * Indicates the width of any framing elements which were added within the encapsulating element
  28558. * to provide graphical, rounded borders. See the {@link #frame} config.
  28559. *
  28560. * This is an object containing the frame width in pixels for all four sides of the Component containing the
  28561. * following properties:
  28562. *
  28563. * @property {Number} [frameSize.top=0] The width of the top framing element in pixels.
  28564. * @property {Number} [frameSize.right=0] The width of the right framing element in pixels.
  28565. * @property {Number} [frameSize.bottom=0] The width of the bottom framing element in pixels.
  28566. * @property {Number} [frameSize.left=0] The width of the left framing element in pixels.
  28567. * @property {Number} [frameSize.width=0] The total width of the left and right framing elements in pixels.
  28568. * @property {Number} [frameSize.height=0] The total height of the top and right bottom elements in pixels.
  28569. */
  28570. frameSize: { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 },
  28571. /**
  28572. * @cfg {String/Object} componentLayout
  28573. * The sizing and positioning of a Component's internal Elements is the responsibility of the Component's layout
  28574. * manager which sizes a Component's internal structure in response to the Component being sized.
  28575. *
  28576. * Generally, developers will not use this configuration as all provided Components which need their internal
  28577. * elements sizing (Such as {@link Ext.form.field.Base input fields}) come with their own componentLayout managers.
  28578. *
  28579. * The {@link Ext.layout.container.Auto default layout manager} will be used on instances of the base Ext.Component
  28580. * class which simply sizes the Component's encapsulating element to the height and width specified in the
  28581. * {@link #setSize} method.
  28582. */
  28583. /**
  28584. * @cfg {Ext.XTemplate/Ext.Template/String/String[]} tpl
  28585. * An {@link Ext.Template}, {@link Ext.XTemplate} or an array of strings to form an Ext.XTemplate. Used in
  28586. * conjunction with the `{@link #data}` and `{@link #tplWriteMode}` configurations.
  28587. */
  28588. /**
  28589. * @cfg {Object} data
  28590. * The initial set of data to apply to the `{@link #tpl}` to update the content area of the Component.
  28591. */
  28592. /**
  28593. * @cfg {String} xtype
  28594. * This property provides a shorter alternative to creating objects than using a full
  28595. * class name. Using `xtype` is the most common way to define component instances,
  28596. * especially in a container. For example, the items in a form containing text fields
  28597. * could be created explicitly like so:
  28598. *
  28599. * items: [
  28600. * Ext.create('Ext.form.field.Text', {
  28601. * fieldLabel: 'Foo'
  28602. * }),
  28603. * Ext.create('Ext.form.field.Text', {
  28604. * fieldLabel: 'Bar'
  28605. * }),
  28606. * Ext.create('Ext.form.field.Number', {
  28607. * fieldLabel: 'Num'
  28608. * })
  28609. * ]
  28610. *
  28611. * But by using `xtype`, the above becomes:
  28612. *
  28613. * items: [
  28614. * {
  28615. * xtype: 'textfield',
  28616. * fieldLabel: 'Foo'
  28617. * },
  28618. * {
  28619. * xtype: 'textfield',
  28620. * fieldLabel: 'Bar'
  28621. * },
  28622. * {
  28623. * xtype: 'numberfield',
  28624. * fieldLabel: 'Num'
  28625. * }
  28626. * ]
  28627. *
  28628. * When the `xtype` is common to many items, {@link Ext.container.AbstractContainer#defaultType}
  28629. * is another way to specify the `xtype` for all items that don't have an explicit `xtype`:
  28630. *
  28631. * defaultType: 'textfield',
  28632. * items: [
  28633. * { fieldLabel: 'Foo' },
  28634. * { fieldLabel: 'Bar' },
  28635. * { fieldLabel: 'Num', xtype: 'numberfield' }
  28636. * ]
  28637. *
  28638. * Each member of the `items` array is now just a "configuration object". These objects
  28639. * are used to create and configure component instances. A configuration object can be
  28640. * manually used to instantiate a component using {@link Ext#widget}:
  28641. *
  28642. * var text1 = Ext.create('Ext.form.field.Text', {
  28643. * fieldLabel: 'Foo'
  28644. * });
  28645. *
  28646. * // or alternatively:
  28647. *
  28648. * var text1 = Ext.widget({
  28649. * xtype: 'textfield',
  28650. * fieldLabel: 'Foo'
  28651. * });
  28652. *
  28653. * This conversion of configuration objects into instantiated components is done when
  28654. * a container is created as part of its {Ext.container.AbstractContainer#initComponent}
  28655. * process. As part of the same process, the `items` array is converted from its raw
  28656. * array form into a {@link Ext.util.MixedCollection} instance.
  28657. *
  28658. * You can define your own `xtype` on a custom {@link Ext.Component component} by specifying
  28659. * the `xtype` property in {@link Ext#define}. For example:
  28660. *
  28661. * Ext.define('MyApp.PressMeButton', {
  28662. * extend: 'Ext.button.Button',
  28663. * xtype: 'pressmebutton',
  28664. * text: 'Press Me'
  28665. * });
  28666. *
  28667. * Care should be taken when naming an `xtype` in a custom component because there is
  28668. * a single, shared scope for all xtypes. Third part components should consider using
  28669. * a prefix to avoid collisions.
  28670. *
  28671. * Ext.define('Foo.form.CoolButton', {
  28672. * extend: 'Ext.button.Button',
  28673. * xtype: 'ux-coolbutton',
  28674. * text: 'Cool!'
  28675. * });
  28676. */
  28677. /**
  28678. * @cfg {String} tplWriteMode
  28679. * The Ext.(X)Template method to use when updating the content area of the Component.
  28680. * See `{@link Ext.XTemplate#overwrite}` for information on default mode.
  28681. */
  28682. tplWriteMode: 'overwrite',
  28683. /**
  28684. * @cfg {String} [baseCls='x-component']
  28685. * The base CSS class to apply to this components's element. This will also be prepended to elements within this
  28686. * component like Panel's body will get a class x-panel-body. This means that if you create a subclass of Panel, and
  28687. * you want it to get all the Panels styling for the element and the body, you leave the baseCls x-panel and use
  28688. * componentCls to add specific styling for this component.
  28689. */
  28690. baseCls: Ext.baseCSSPrefix + 'component',
  28691. /**
  28692. * @cfg {String} componentCls
  28693. * CSS Class to be added to a components root level element to give distinction to it via styling.
  28694. */
  28695. /**
  28696. * @cfg {String} [cls='']
  28697. * An optional extra CSS class that will be added to this component's Element. This can be useful
  28698. * for adding customized styles to the component or any of its children using standard CSS rules.
  28699. */
  28700. /**
  28701. * @cfg {String} [overCls='']
  28702. * An optional extra CSS class that will be added to this component's Element when the mouse moves over the Element,
  28703. * and removed when the mouse moves out. This can be useful for adding customized 'active' or 'hover' styles to the
  28704. * component or any of its children using standard CSS rules.
  28705. */
  28706. /**
  28707. * @cfg {String} [disabledCls='x-item-disabled']
  28708. * CSS class to add when the Component is disabled. Defaults to 'x-item-disabled'.
  28709. */
  28710. disabledCls: Ext.baseCSSPrefix + 'item-disabled',
  28711. /**
  28712. * @cfg {String} ui
  28713. * A UI style for a component.
  28714. */
  28715. ui: 'default',
  28716. /**
  28717. * @cfg {String[]} uiCls
  28718. * An array of of classNames which are currently applied to this component
  28719. * @private
  28720. */
  28721. uiCls: [],
  28722. /**
  28723. * @cfg {String/Object} style
  28724. * A custom style specification to be applied to this component's Element. Should be a valid argument to
  28725. * {@link Ext.Element#applyStyles}.
  28726. *
  28727. * new Ext.panel.Panel({
  28728. * title: 'Some Title',
  28729. * renderTo: Ext.getBody(),
  28730. * width: 400, height: 300,
  28731. * layout: 'form',
  28732. * items: [{
  28733. * xtype: 'textarea',
  28734. * style: {
  28735. * width: '95%',
  28736. * marginBottom: '10px'
  28737. * }
  28738. * },
  28739. * new Ext.button.Button({
  28740. * text: 'Send',
  28741. * minWidth: '100',
  28742. * style: {
  28743. * marginBottom: '10px'
  28744. * }
  28745. * })
  28746. * ]
  28747. * });
  28748. */
  28749. /**
  28750. * @cfg {Number} width
  28751. * The width of this component in pixels.
  28752. */
  28753. /**
  28754. * @cfg {Number} height
  28755. * The height of this component in pixels.
  28756. */
  28757. /**
  28758. * @cfg {Number/String/Boolean} border
  28759. * Specifies the border size for this component. The border can be a single numeric value to apply to all sides or it can
  28760. * be a CSS style specification for each style, for example: '10 5 3 10'.
  28761. *
  28762. * For components that have no border by default, setting this won't make the border appear by itself.
  28763. * You also need to specify border color and style:
  28764. *
  28765. * border: 5,
  28766. * style: {
  28767. * borderColor: 'red',
  28768. * borderStyle: 'solid'
  28769. * }
  28770. *
  28771. * To turn off the border, use `border: false`.
  28772. */
  28773. /**
  28774. * @cfg {Number/String} padding
  28775. * Specifies the padding for this component. The padding can be a single numeric value to apply to all sides or it
  28776. * can be a CSS style specification for each style, for example: '10 5 3 10'.
  28777. */
  28778. /**
  28779. * @cfg {Number/String} margin
  28780. * Specifies the margin for this component. The margin can be a single numeric value to apply to all sides or it can
  28781. * be a CSS style specification for each style, for example: '10 5 3 10'.
  28782. */
  28783. /**
  28784. * @cfg {Boolean} hidden
  28785. * True to hide the component.
  28786. */
  28787. hidden: false,
  28788. /**
  28789. * @cfg {Boolean} disabled
  28790. * True to disable the component.
  28791. */
  28792. disabled: false,
  28793. /**
  28794. * @cfg {Boolean} [draggable=false]
  28795. * Allows the component to be dragged.
  28796. */
  28797. /**
  28798. * @property {Boolean} draggable
  28799. * Indicates whether or not the component can be dragged.
  28800. * @readonly
  28801. */
  28802. draggable: false,
  28803. /**
  28804. * @cfg {Boolean} floating
  28805. * Create the Component as a floating and use absolute positioning.
  28806. *
  28807. * The z-index of floating Components is handled by a ZIndexManager. If you simply render a floating Component into the DOM, it will be managed
  28808. * by the global {@link Ext.WindowManager WindowManager}.
  28809. *
  28810. * If you include a floating Component as a child item of a Container, then upon render, ExtJS will seek an ancestor floating Component to house a new
  28811. * ZIndexManager instance to manage its descendant floaters. If no floating ancestor can be found, the global WindowManager will be used.
  28812. *
  28813. * When a floating Component which has a ZindexManager managing descendant floaters is destroyed, those descendant floaters will also be destroyed.
  28814. */
  28815. floating: false,
  28816. /**
  28817. * @cfg {String} hideMode
  28818. * A String which specifies how this Component's encapsulating DOM element will be hidden. Values may be:
  28819. *
  28820. * - `'display'` : The Component will be hidden using the `display: none` style.
  28821. * - `'visibility'` : The Component will be hidden using the `visibility: hidden` style.
  28822. * - `'offsets'` : The Component will be hidden by absolutely positioning it out of the visible area of the document.
  28823. * This is useful when a hidden Component must maintain measurable dimensions. Hiding using `display` results in a
  28824. * Component having zero dimensions.
  28825. */
  28826. hideMode: 'display',
  28827. /**
  28828. * @cfg {String} contentEl
  28829. * Specify an existing HTML element, or the `id` of an existing HTML element to use as the content for this component.
  28830. *
  28831. * This config option is used to take an existing HTML element and place it in the layout element of a new component
  28832. * (it simply moves the specified DOM element _after the Component is rendered_ to use as the content.
  28833. *
  28834. * **Notes:**
  28835. *
  28836. * The specified HTML element is appended to the layout element of the component _after any configured
  28837. * {@link #html HTML} has been inserted_, and so the document will not contain this element at the time
  28838. * the {@link #event-render} event is fired.
  28839. *
  28840. * The specified HTML element used will not participate in any **`{@link Ext.container.Container#layout layout}`**
  28841. * scheme that the Component may use. It is just HTML. Layouts operate on child
  28842. * **`{@link Ext.container.Container#cfg-items items}`**.
  28843. *
  28844. * Add either the `x-hidden` or the `x-hide-display` CSS class to prevent a brief flicker of the content before it
  28845. * is rendered to the panel.
  28846. */
  28847. /**
  28848. * @cfg {String/Object} [html='']
  28849. * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the layout element content.
  28850. * The HTML content is added after the component is rendered, so the document will not contain this HTML at the time
  28851. * the {@link #event-render} event is fired. This content is inserted into the body _before_ any configured {@link #contentEl}
  28852. * is appended.
  28853. */
  28854. /**
  28855. * @cfg {Boolean} styleHtmlContent
  28856. * True to automatically style the html inside the content target of this component (body for panels).
  28857. */
  28858. styleHtmlContent: false,
  28859. /**
  28860. * @cfg {String} [styleHtmlCls='x-html']
  28861. * The class that is added to the content target when you set styleHtmlContent to true.
  28862. */
  28863. styleHtmlCls: Ext.baseCSSPrefix + 'html',
  28864. /**
  28865. * @cfg {Number} minHeight
  28866. * The minimum value in pixels which this Component will set its height to.
  28867. *
  28868. * **Warning:** This will override any size management applied by layout managers.
  28869. */
  28870. /**
  28871. * @cfg {Number} minWidth
  28872. * The minimum value in pixels which this Component will set its width to.
  28873. *
  28874. * **Warning:** This will override any size management applied by layout managers.
  28875. */
  28876. /**
  28877. * @cfg {Number} maxHeight
  28878. * The maximum value in pixels which this Component will set its height to.
  28879. *
  28880. * **Warning:** This will override any size management applied by layout managers.
  28881. */
  28882. /**
  28883. * @cfg {Number} maxWidth
  28884. * The maximum value in pixels which this Component will set its width to.
  28885. *
  28886. * **Warning:** This will override any size management applied by layout managers.
  28887. */
  28888. /**
  28889. * @cfg {Ext.ComponentLoader/Object} loader
  28890. * A configuration object or an instance of a {@link Ext.ComponentLoader} to load remote content
  28891. * for this Component.
  28892. *
  28893. * Ext.create('Ext.Component', {
  28894. * loader: {
  28895. * url: 'content.html',
  28896. * autoLoad: true
  28897. * },
  28898. * renderTo: Ext.getBody()
  28899. * });
  28900. */
  28901. /**
  28902. * @cfg {Ext.ComponentLoader/Object/String/Boolean} autoLoad
  28903. * An alias for {@link #loader} config which also allows to specify just a string which will be
  28904. * used as the url that's automatically loaded:
  28905. *
  28906. * Ext.create('Ext.Component', {
  28907. * autoLoad: 'content.html',
  28908. * renderTo: Ext.getBody()
  28909. * });
  28910. *
  28911. * The above is the same as:
  28912. *
  28913. * Ext.create('Ext.Component', {
  28914. * loader: {
  28915. * url: 'content.html',
  28916. * autoLoad: true
  28917. * },
  28918. * renderTo: Ext.getBody()
  28919. * });
  28920. *
  28921. * Don't use it together with {@link #loader} config.
  28922. *
  28923. * @deprecated 4.1.1 Use {@link #loader} config instead.
  28924. */
  28925. /**
  28926. * @cfg {Boolean} autoShow
  28927. * True to automatically show the component upon creation. This config option may only be used for
  28928. * {@link #floating} components or components that use {@link #autoRender}. Defaults to false.
  28929. */
  28930. autoShow: false,
  28931. /**
  28932. * @cfg {Boolean/String/HTMLElement/Ext.Element} autoRender
  28933. * This config is intended mainly for non-{@link #floating} Components which may or may not be shown. Instead of using
  28934. * {@link #renderTo} in the configuration, and rendering upon construction, this allows a Component to render itself
  28935. * upon first _{@link Ext.Component#method-show show}_. If {@link #floating} is true, the value of this config is omited as if it is `true`.
  28936. *
  28937. * Specify as `true` to have this Component render to the document body upon first show.
  28938. *
  28939. * Specify as an element, or the ID of an element to have this Component render to a specific element upon first
  28940. * show.
  28941. */
  28942. autoRender: false,
  28943. // @private
  28944. allowDomMove: true,
  28945. /**
  28946. * @cfg {Object/Object[]} plugins
  28947. * An object or array of objects that will provide custom functionality for this component. The only requirement for
  28948. * a valid plugin is that it contain an init method that accepts a reference of type Ext.Component. When a component
  28949. * is created, if any plugins are available, the component will call the init method on each plugin, passing a
  28950. * reference to itself. Each plugin can then call methods or respond to events on the component as needed to provide
  28951. * its functionality.
  28952. */
  28953. /**
  28954. * @property {Boolean} rendered
  28955. * Indicates whether or not the component has been rendered.
  28956. * @readonly
  28957. */
  28958. rendered: false,
  28959. /**
  28960. * @property {Number} componentLayoutCounter
  28961. * @private
  28962. * The number of component layout calls made on this object.
  28963. */
  28964. componentLayoutCounter: 0,
  28965. /**
  28966. * @cfg {Boolean/Number} [shrinkWrap=2]
  28967. *
  28968. * If this property is a number, it is interpreted as follows:
  28969. *
  28970. * - 0: Neither width nor height depend on content. This is equivalent to `false`.
  28971. * - 1: Width depends on content (shrink wraps), but height does not.
  28972. * - 2: Height depends on content (shrink wraps), but width does not. The default.
  28973. * - 3: Both width and height depend on content (shrink wrap). This is equivalent to `true`.
  28974. *
  28975. * In CSS terms, shrink-wrap width is analogous to an inline-block element as opposed
  28976. * to a block-level element. Some container layouts always shrink-wrap their children,
  28977. * effectively ignoring this property (e.g., {@link Ext.layout.container.HBox},
  28978. * {@link Ext.layout.container.VBox}, {@link Ext.layout.component.Dock}).
  28979. */
  28980. shrinkWrap: 2,
  28981. weight: 0,
  28982. /**
  28983. * @property {Boolean} maskOnDisable
  28984. * This is an internal flag that you use when creating custom components. By default this is set to true which means
  28985. * that every component gets a mask when it's disabled. Components like FieldContainer, FieldSet, Field, Button, Tab
  28986. * override this property to false since they want to implement custom disable logic.
  28987. */
  28988. maskOnDisable: true,
  28989. /**
  28990. * @property {Boolean} [_isLayoutRoot=false]
  28991. * Setting this property to `true` causes the {@link #isLayoutRoot} method to return
  28992. * `true` and stop the search for the top-most component for a layout.
  28993. * @protected
  28994. */
  28995. _isLayoutRoot: false,
  28996. /**
  28997. * Creates new Component.
  28998. * @param {Object} config (optional) Config object.
  28999. */
  29000. constructor : function(config) {
  29001. var me = this,
  29002. i, len, xhooks;
  29003. if (config) {
  29004. Ext.apply(me, config);
  29005. xhooks = me.xhooks;
  29006. if (xhooks) {
  29007. delete me.xhooks;
  29008. Ext.override(me, xhooks);
  29009. }
  29010. } else {
  29011. config = {};
  29012. }
  29013. me.initialConfig = config;
  29014. me.mixins.elementCt.constructor.call(me);
  29015. me.addEvents(
  29016. /**
  29017. * @event beforeactivate
  29018. * Fires before a Component has been visually activated. Returning false from an event listener can prevent
  29019. * the activate from occurring.
  29020. * @param {Ext.Component} this
  29021. */
  29022. 'beforeactivate',
  29023. /**
  29024. * @event activate
  29025. * Fires after a Component has been visually activated.
  29026. * @param {Ext.Component} this
  29027. */
  29028. 'activate',
  29029. /**
  29030. * @event beforedeactivate
  29031. * Fires before a Component has been visually deactivated. Returning false from an event listener can
  29032. * prevent the deactivate from occurring.
  29033. * @param {Ext.Component} this
  29034. */
  29035. 'beforedeactivate',
  29036. /**
  29037. * @event deactivate
  29038. * Fires after a Component has been visually deactivated.
  29039. * @param {Ext.Component} this
  29040. */
  29041. 'deactivate',
  29042. /**
  29043. * @event added
  29044. * Fires after a Component had been added to a Container.
  29045. * @param {Ext.Component} this
  29046. * @param {Ext.container.Container} container Parent Container
  29047. * @param {Number} pos position of Component
  29048. */
  29049. 'added',
  29050. /**
  29051. * @event disable
  29052. * Fires after the component is disabled.
  29053. * @param {Ext.Component} this
  29054. */
  29055. 'disable',
  29056. /**
  29057. * @event enable
  29058. * Fires after the component is enabled.
  29059. * @param {Ext.Component} this
  29060. */
  29061. 'enable',
  29062. /**
  29063. * @event beforeshow
  29064. * Fires before the component is shown when calling the {@link Ext.Component#method-show show} method. Return false from an event
  29065. * handler to stop the show.
  29066. * @param {Ext.Component} this
  29067. */
  29068. 'beforeshow',
  29069. /**
  29070. * @event show
  29071. * Fires after the component is shown when calling the {@link Ext.Component#method-show show} method.
  29072. * @param {Ext.Component} this
  29073. */
  29074. 'show',
  29075. /**
  29076. * @event beforehide
  29077. * Fires before the component is hidden when calling the {@link Ext.Component#method-hide hide} method. Return false from an event
  29078. * handler to stop the hide.
  29079. * @param {Ext.Component} this
  29080. */
  29081. 'beforehide',
  29082. /**
  29083. * @event hide
  29084. * Fires after the component is hidden. Fires after the component is hidden when calling the {@link Ext.Component#method-hide hide}
  29085. * method.
  29086. * @param {Ext.Component} this
  29087. */
  29088. 'hide',
  29089. /**
  29090. * @event removed
  29091. * Fires when a component is removed from an Ext.container.Container
  29092. * @param {Ext.Component} this
  29093. * @param {Ext.container.Container} ownerCt Container which holds the component
  29094. */
  29095. 'removed',
  29096. /**
  29097. * @event beforerender
  29098. * Fires before the component is {@link #rendered}. Return false from an event handler to stop the
  29099. * {@link #method-render}.
  29100. * @param {Ext.Component} this
  29101. */
  29102. 'beforerender',
  29103. /**
  29104. * @event render
  29105. * Fires after the component markup is {@link #rendered}.
  29106. * @param {Ext.Component} this
  29107. */
  29108. 'render',
  29109. /**
  29110. * @event afterrender
  29111. * Fires after the component rendering is finished.
  29112. *
  29113. * The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed by any
  29114. * afterRender method defined for the Component.
  29115. * @param {Ext.Component} this
  29116. */
  29117. 'afterrender',
  29118. /**
  29119. * @event boxready
  29120. * Fires *one time* - after the component has been layed out for the first time at its initial size.
  29121. * @param {Ext.Component} this
  29122. * @param {Number} width The initial width
  29123. * @param {Number} height The initial height
  29124. */
  29125. 'boxready',
  29126. /**
  29127. * @event beforedestroy
  29128. * Fires before the component is {@link #method-destroy}ed. Return false from an event handler to stop the
  29129. * {@link #method-destroy}.
  29130. * @param {Ext.Component} this
  29131. */
  29132. 'beforedestroy',
  29133. /**
  29134. * @event destroy
  29135. * Fires after the component is {@link #method-destroy}ed.
  29136. * @param {Ext.Component} this
  29137. */
  29138. 'destroy',
  29139. /**
  29140. * @event resize
  29141. * Fires after the component is resized. Note that this does *not* fire when the component is first layed out at its initial
  29142. * size. To hook that point in the lifecycle, use the {@link #boxready} event.
  29143. * @param {Ext.Component} this
  29144. * @param {Number} width The new width that was set
  29145. * @param {Number} height The new height that was set
  29146. * @param {Number} oldWidth The previous width
  29147. * @param {Number} oldHeight The previous height
  29148. */
  29149. 'resize',
  29150. /**
  29151. * @event move
  29152. * Fires after the component is moved.
  29153. * @param {Ext.Component} this
  29154. * @param {Number} x The new x position
  29155. * @param {Number} y The new y position
  29156. */
  29157. 'move',
  29158. /**
  29159. * @event focus
  29160. * Fires when this Component receives focus.
  29161. * @param {Ext.Component} this
  29162. * @param {Ext.EventObject} The focus event.
  29163. */
  29164. 'focus',
  29165. /**
  29166. * @event blur
  29167. * Fires when this Component loses focus.
  29168. * @param {Ext.Component} this
  29169. * @param {Ext.EventObject} The blur event.
  29170. */
  29171. 'blur'
  29172. );
  29173. me.getId();
  29174. me.setupProtoEl();
  29175. // initComponent, beforeRender, or event handlers may have set the style or cls property since the protoEl was set up
  29176. // so we must apply styles and classes here too.
  29177. if (me.cls) {
  29178. me.initialCls = me.cls;
  29179. me.protoEl.addCls(me.cls);
  29180. }
  29181. if (me.style) {
  29182. me.initialStyle = me.style;
  29183. me.protoEl.setStyle(me.style);
  29184. }
  29185. me.mons = [];
  29186. me.renderData = me.renderData || {};
  29187. me.renderSelectors = me.renderSelectors || {};
  29188. if (me.plugins) {
  29189. me.plugins = me.constructPlugins();
  29190. }
  29191. // we need this before we call initComponent
  29192. if (!me.hasListeners) {
  29193. me.hasListeners = new me.HasListeners();
  29194. }
  29195. me.initComponent();
  29196. // ititComponent gets a chance to change the id property before registering
  29197. Ext.ComponentManager.register(me);
  29198. // Dont pass the config so that it is not applied to 'this' again
  29199. me.mixins.observable.constructor.call(me);
  29200. me.mixins.state.constructor.call(me, config);
  29201. // Save state on resize.
  29202. this.addStateEvents('resize');
  29203. // Move this into Observable?
  29204. if (me.plugins) {
  29205. for (i = 0, len = me.plugins.length; i < len; i++) {
  29206. me.plugins[i] = me.initPlugin(me.plugins[i]);
  29207. }
  29208. }
  29209. me.loader = me.getLoader();
  29210. if (me.renderTo) {
  29211. me.render(me.renderTo);
  29212. // EXTJSIV-1935 - should be a way to do afterShow or something, but that
  29213. // won't work. Likewise, rendering hidden and then showing (w/autoShow) has
  29214. // implications to afterRender so we cannot do that.
  29215. }
  29216. // Auto show only works unilaterally on *uncontained* Components.
  29217. // If contained, then it is the Container's responsibility to do the showing at next layout time.
  29218. if (me.autoShow && !me.isContained) {
  29219. me.show();
  29220. }
  29221. if (Ext.isDefined(me.disabledClass)) {
  29222. if (Ext.isDefined(Ext.global.console)) {
  29223. Ext.global.console.warn('Ext.Component: disabledClass has been deprecated. Please use disabledCls.');
  29224. }
  29225. me.disabledCls = me.disabledClass;
  29226. delete me.disabledClass;
  29227. }
  29228. },
  29229. initComponent: function () {
  29230. // This is called again here to allow derived classes to add plugin configs to the
  29231. // plugins array before calling down to this, the base initComponent.
  29232. this.plugins = this.constructPlugins();
  29233. // this will properly (ignore or) constrain the configured width/height to their
  29234. // min/max values for consistency.
  29235. this.setSize(this.width, this.height);
  29236. },
  29237. /**
  29238. * The supplied default state gathering method for the AbstractComponent class.
  29239. *
  29240. * This method returns dimension settings such as `flex`, `anchor`, `width` and `height` along with `collapsed`
  29241. * state.
  29242. *
  29243. * Subclasses which implement more complex state should call the superclass's implementation, and apply their state
  29244. * to the result if this basic state is to be saved.
  29245. *
  29246. * Note that Component state will only be saved if the Component has a {@link #stateId} and there as a StateProvider
  29247. * configured for the document.
  29248. *
  29249. * @return {Object}
  29250. */
  29251. getState: function() {
  29252. var me = this,
  29253. state = null,
  29254. sizeModel = me.getSizeModel();
  29255. if (sizeModel.width.configured) {
  29256. state = me.addPropertyToState(state, 'width');
  29257. }
  29258. if (sizeModel.height.configured) {
  29259. state = me.addPropertyToState(state, 'height');
  29260. }
  29261. return state;
  29262. },
  29263. /**
  29264. * Save a property to the given state object if it is not its default or configured
  29265. * value.
  29266. *
  29267. * @param {Object} state The state object
  29268. * @param {String} propName The name of the property on this object to save.
  29269. * @param {String} [value] The value of the state property (defaults to `this[propName]`).
  29270. * @return {Boolean} The state object or a new object if state was null and the property
  29271. * was saved.
  29272. * @protected
  29273. */
  29274. addPropertyToState: function (state, propName, value) {
  29275. var me = this,
  29276. len = arguments.length;
  29277. // If the property is inherited, it is a default and we don't want to save it to
  29278. // the state, however if we explicitly specify a value, always save it
  29279. if (len == 3 || me.hasOwnProperty(propName)) {
  29280. if (len < 3) {
  29281. value = me[propName];
  29282. }
  29283. // If the property has the same value as was initially configured, again, we
  29284. // don't want to save it.
  29285. if (value !== me.initialConfig[propName]) {
  29286. (state || (state = {}))[propName] = value;
  29287. }
  29288. }
  29289. return state;
  29290. },
  29291. show: Ext.emptyFn,
  29292. animate: function(animObj) {
  29293. var me = this,
  29294. hasToWidth,
  29295. hasToHeight,
  29296. toHeight,
  29297. toWidth,
  29298. to,
  29299. clearWidth,
  29300. clearHeight,
  29301. curWidth, w, curHeight, h, needsResize;
  29302. animObj = animObj || {};
  29303. to = animObj.to || {};
  29304. if (Ext.fx.Manager.hasFxBlock(me.id)) {
  29305. return me;
  29306. }
  29307. hasToWidth = Ext.isDefined(to.width);
  29308. if (hasToWidth) {
  29309. toWidth = Ext.Number.constrain(to.width, me.minWidth, me.maxWidth);
  29310. }
  29311. hasToHeight = Ext.isDefined(to.height);
  29312. if (hasToHeight) {
  29313. toHeight = Ext.Number.constrain(to.height, me.minHeight, me.maxHeight);
  29314. }
  29315. // Special processing for animating Component dimensions.
  29316. if (!animObj.dynamic && (hasToWidth || hasToHeight)) {
  29317. curWidth = (animObj.from ? animObj.from.width : undefined) || me.getWidth();
  29318. w = curWidth;
  29319. curHeight = (animObj.from ? animObj.from.height : undefined) || me.getHeight();
  29320. h = curHeight;
  29321. needsResize = false;
  29322. if (hasToHeight && toHeight > curHeight) {
  29323. h = toHeight;
  29324. needsResize = true;
  29325. }
  29326. if (hasToWidth && toWidth > curWidth) {
  29327. w = toWidth;
  29328. needsResize = true;
  29329. }
  29330. // If any dimensions are being increased, we must resize the internal structure
  29331. // of the Component, but then clip it by sizing its encapsulating element back to original dimensions.
  29332. // The animation will then progressively reveal the larger content.
  29333. if (needsResize) {
  29334. clearWidth = !Ext.isNumber(me.width);
  29335. clearHeight = !Ext.isNumber(me.height);
  29336. me.setSize(w, h);
  29337. me.el.setSize(curWidth, curHeight);
  29338. if (clearWidth) {
  29339. delete me.width;
  29340. }
  29341. if (clearHeight) {
  29342. delete me.height;
  29343. }
  29344. }
  29345. if (hasToWidth) {
  29346. to.width = toWidth;
  29347. }
  29348. if (hasToHeight) {
  29349. to.height = toHeight;
  29350. }
  29351. }
  29352. return me.mixins.animate.animate.apply(me, arguments);
  29353. },
  29354. onHide: function() {
  29355. this.updateLayout({ isRoot: false });
  29356. },
  29357. onShow : function() {
  29358. this.updateLayout({ isRoot: false });
  29359. },
  29360. constructPlugin: function(plugin) {
  29361. // If a config object with a ptype
  29362. if (plugin.ptype && typeof plugin.init != 'function') {
  29363. plugin.cmp = this;
  29364. plugin = Ext.PluginManager.create(plugin);
  29365. }
  29366. // Just a ptype
  29367. else if (typeof plugin == 'string') {
  29368. plugin = Ext.PluginManager.create({
  29369. ptype: plugin,
  29370. cmp: this
  29371. });
  29372. }
  29373. return plugin;
  29374. },
  29375. /**
  29376. * @private
  29377. * Returns an array of fully constructed plugin instances. This converts any configs into their
  29378. * appropriate instances.
  29379. *
  29380. * It does not mutate the plugins array. It creates a new array.
  29381. *
  29382. * This is borrowed by {@link Ext.grid.Lockable Lockable} which clones and distributes Plugins
  29383. * to both child grids of a locking grid, so must keep to that contract.
  29384. */
  29385. constructPlugins: function() {
  29386. var me = this,
  29387. plugins,
  29388. result = [],
  29389. i, len;
  29390. if (me.plugins) {
  29391. plugins = Ext.isArray(me.plugins) ? me.plugins : [ me.plugins ];
  29392. for (i = 0, len = plugins.length; i < len; i++) {
  29393. // this just returns already-constructed plugin instances...
  29394. result[i] = me.constructPlugin(plugins[i]);
  29395. }
  29396. return result;
  29397. }
  29398. },
  29399. // @private
  29400. initPlugin : function(plugin) {
  29401. plugin.init(this);
  29402. return plugin;
  29403. },
  29404. /**
  29405. * @private
  29406. * Injected as an override by Ext.Aria.initialize
  29407. */
  29408. updateAria: Ext.emptyFn,
  29409. /**
  29410. * Called by Component#doAutoRender
  29411. *
  29412. * Register a Container configured `floating: true` with this Component's {@link Ext.ZIndexManager ZIndexManager}.
  29413. *
  29414. * Components added in ths way will not participate in any layout, but will be rendered
  29415. * upon first show in the way that {@link Ext.window.Window Window}s are.
  29416. */
  29417. registerFloatingItem: function(cmp) {
  29418. var me = this;
  29419. if (!me.floatingDescendants) {
  29420. me.floatingDescendants = new Ext.ZIndexManager(me);
  29421. }
  29422. me.floatingDescendants.register(cmp);
  29423. },
  29424. unregisterFloatingItem: function(cmp) {
  29425. var me = this;
  29426. if (me.floatingDescendants) {
  29427. me.floatingDescendants.unregister(cmp);
  29428. }
  29429. },
  29430. layoutSuspendCount: 0,
  29431. suspendLayouts: function () {
  29432. var me = this;
  29433. if (!me.rendered) {
  29434. return;
  29435. }
  29436. if (++me.layoutSuspendCount == 1) {
  29437. me.suspendLayout = true;
  29438. }
  29439. },
  29440. resumeLayouts: function (flushOptions) {
  29441. var me = this;
  29442. if (!me.rendered) {
  29443. return;
  29444. }
  29445. if (! --me.layoutSuspendCount) {
  29446. me.suspendLayout = false;
  29447. if (flushOptions && !me.isLayoutSuspended()) {
  29448. me.updateLayout(flushOptions);
  29449. }
  29450. }
  29451. },
  29452. setupProtoEl: function() {
  29453. var me = this,
  29454. cls = [ me.baseCls, me.getComponentLayout().targetCls ];
  29455. if (Ext.isDefined(me.cmpCls)) {
  29456. if (Ext.isDefined(Ext.global.console)) {
  29457. Ext.global.console.warn('Ext.Component: cmpCls has been deprecated. Please use componentCls.');
  29458. }
  29459. me.componentCls = me.cmpCls;
  29460. delete me.cmpCls;
  29461. }
  29462. if (me.componentCls) {
  29463. cls.push(me.componentCls);
  29464. } else {
  29465. me.componentCls = me.baseCls;
  29466. }
  29467. me.protoEl = new Ext.util.ProtoElement({
  29468. cls: cls.join(' ') // in case any of the parts have multiple classes
  29469. });
  29470. },
  29471. /**
  29472. * Sets the UI for the component. This will remove any existing UIs on the component. It will also loop through any
  29473. * uiCls set on the component and rename them so they include the new UI
  29474. * @param {String} ui The new UI for the component
  29475. */
  29476. setUI: function(ui) {
  29477. var me = this,
  29478. oldUICls = Ext.Array.clone(me.uiCls),
  29479. newUICls = [],
  29480. classes = [],
  29481. cls,
  29482. i;
  29483. //loop through all existing uiCls and update the ui in them
  29484. for (i = 0; i < oldUICls.length; i++) {
  29485. cls = oldUICls[i];
  29486. classes = classes.concat(me.removeClsWithUI(cls, true));
  29487. newUICls.push(cls);
  29488. }
  29489. if (classes.length) {
  29490. me.removeCls(classes);
  29491. }
  29492. //remove the UI from the element
  29493. me.removeUIFromElement();
  29494. //set the UI
  29495. me.ui = ui;
  29496. //add the new UI to the element
  29497. me.addUIToElement();
  29498. //loop through all existing uiCls and update the ui in them
  29499. classes = [];
  29500. for (i = 0; i < newUICls.length; i++) {
  29501. cls = newUICls[i];
  29502. classes = classes.concat(me.addClsWithUI(cls, true));
  29503. }
  29504. if (classes.length) {
  29505. me.addCls(classes);
  29506. }
  29507. // Changing the ui can lead to significant changes to a component's appearance, so the layout needs to be
  29508. // updated. Internally most calls to setUI are pre-render. Buttons are a notable exception as setScale changes
  29509. // the ui and often requires the layout to be updated.
  29510. if (me.rendered) {
  29511. me.updateLayout();
  29512. }
  29513. },
  29514. /**
  29515. * Adds a cls to the uiCls array, which will also call {@link #addUIClsToElement} and adds to all elements of this
  29516. * component.
  29517. * @param {String/String[]} classes A string or an array of strings to add to the uiCls
  29518. * @param {Object} skip (Boolean) skip True to skip adding it to the class and do it later (via the return)
  29519. */
  29520. addClsWithUI: function(classes, skip) {
  29521. var me = this,
  29522. clsArray = [],
  29523. length,
  29524. i = 0,
  29525. cls;
  29526. if (typeof classes === "string") {
  29527. classes = (classes.indexOf(' ') < 0) ? [classes] : Ext.String.splitWords(classes);
  29528. }
  29529. length = classes.length;
  29530. me.uiCls = Ext.Array.clone(me.uiCls);
  29531. for (; i < length; i++) {
  29532. cls = classes[i];
  29533. if (cls && !me.hasUICls(cls)) {
  29534. me.uiCls.push(cls);
  29535. clsArray = clsArray.concat(me.addUIClsToElement(cls));
  29536. }
  29537. }
  29538. if (skip !== true) {
  29539. me.addCls(clsArray);
  29540. }
  29541. return clsArray;
  29542. },
  29543. /**
  29544. * Removes a cls to the uiCls array, which will also call {@link #removeUIClsFromElement} and removes it from all
  29545. * elements of this component.
  29546. * @param {String/String[]} cls A string or an array of strings to remove to the uiCls
  29547. */
  29548. removeClsWithUI: function(classes, skip) {
  29549. var me = this,
  29550. clsArray = [],
  29551. i = 0,
  29552. length, cls;
  29553. if (typeof classes === "string") {
  29554. classes = (classes.indexOf(' ') < 0) ? [classes] : Ext.String.splitWords(classes);
  29555. }
  29556. length = classes.length;
  29557. for (i = 0; i < length; i++) {
  29558. cls = classes[i];
  29559. if (cls && me.hasUICls(cls)) {
  29560. me.uiCls = Ext.Array.remove(me.uiCls, cls);
  29561. clsArray = clsArray.concat(me.removeUIClsFromElement(cls));
  29562. }
  29563. }
  29564. if (skip !== true) {
  29565. me.removeCls(clsArray);
  29566. }
  29567. return clsArray;
  29568. },
  29569. /**
  29570. * Checks if there is currently a specified uiCls
  29571. * @param {String} cls The cls to check
  29572. */
  29573. hasUICls: function(cls) {
  29574. var me = this,
  29575. uiCls = me.uiCls || [];
  29576. return Ext.Array.contains(uiCls, cls);
  29577. },
  29578. frameElementsArray: ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
  29579. /**
  29580. * Method which adds a specified UI + uiCls to the components element. Can be overridden to remove the UI from more
  29581. * than just the components element.
  29582. * @param {String} ui The UI to remove from the element
  29583. */
  29584. addUIClsToElement: function(cls) {
  29585. var me = this,
  29586. baseClsUi = me.baseCls + '-' + me.ui + '-' + cls,
  29587. result = [Ext.baseCSSPrefix + cls, me.baseCls + '-' + cls, baseClsUi],
  29588. frameElementCls = me.frameElementCls,
  29589. frameElementsArray, frameElementsLength, i, el, frameElement, c;
  29590. if (me.frame && !Ext.supports.CSS3BorderRadius) {
  29591. // define each element of the frame
  29592. frameElementsArray = me.frameElementsArray;
  29593. frameElementsLength = frameElementsArray.length;
  29594. i = 0;
  29595. // loop through each of them, and if they are defined add the ui
  29596. for (; i < frameElementsLength; i++) {
  29597. frameElement = frameElementsArray[i];
  29598. el = me['frame' + frameElement.toUpperCase()];
  29599. c = baseClsUi + '-' + frameElement;
  29600. if (el && el.dom) {
  29601. el.addCls(c);
  29602. } else if (Ext.Array.indexOf(frameElementCls[frameElement], c) == -1) {
  29603. frameElementCls[frameElement].push(c);
  29604. }
  29605. }
  29606. }
  29607. me.frameElementCls = frameElementCls;
  29608. return result;
  29609. },
  29610. /**
  29611. * Method which removes a specified UI + uiCls from the components element. The cls which is added to the element
  29612. * will be: `this.baseCls + '-' + ui`
  29613. * @param {String} ui The UI to add to the element
  29614. */
  29615. removeUIClsFromElement: function(cls) {
  29616. var me = this,
  29617. baseClsUi = me.baseCls + '-' + me.ui + '-' + cls,
  29618. result = [Ext.baseCSSPrefix + cls, me.baseCls + '-' + cls, baseClsUi],
  29619. frameElementCls = me.frameElementCls,
  29620. frameElementsArray, frameElementsLength, i, el, frameElement, c;
  29621. if (me.frame && !Ext.supports.CSS3BorderRadius) {
  29622. // define each element of the frame
  29623. frameElementsArray = me.frameElementsArray;
  29624. frameElementsLength = frameElementsArray.length;
  29625. i = 0;
  29626. // loop through each of them, and if they are defined add the ui
  29627. for (; i < frameElementsLength; i++) {
  29628. frameElement = frameElementsArray[i];
  29629. el = me['frame' + frameElement.toUpperCase()];
  29630. c = baseClsUi + '-' + frameElement;
  29631. if (el && el.dom) {
  29632. el.addCls(c);
  29633. } else {
  29634. Ext.Array.remove(frameElementCls[frameElement], c);
  29635. }
  29636. }
  29637. }
  29638. me.frameElementCls = frameElementCls;
  29639. return result;
  29640. },
  29641. /**
  29642. * Method which adds a specified UI to the components element.
  29643. * @private
  29644. */
  29645. addUIToElement: function() {
  29646. var me = this,
  29647. baseClsUI = me.baseCls + '-' + me.ui,
  29648. frameElementCls = me.frameElementCls,
  29649. frameElementsArray, frameElementsLength, i, el, frameElement, c;
  29650. me.addCls(baseClsUI);
  29651. if (me.frame && !Ext.supports.CSS3BorderRadius) {
  29652. // define each element of the frame
  29653. frameElementsArray = me.frameElementsArray;
  29654. frameElementsLength = frameElementsArray.length;
  29655. i = 0;
  29656. // loop through each of them, and if they are defined add the ui
  29657. for (; i < frameElementsLength; i++) {
  29658. frameElement = frameElementsArray[i];
  29659. el = me['frame' + frameElement.toUpperCase()];
  29660. c = baseClsUI + '-' + frameElement;
  29661. if (el) {
  29662. el.addCls(c);
  29663. } else {
  29664. if (!Ext.Array.contains(frameElementCls[frameElement], c)) {
  29665. frameElementCls[frameElement].push(c);
  29666. }
  29667. }
  29668. }
  29669. }
  29670. },
  29671. /**
  29672. * Method which removes a specified UI from the components element.
  29673. * @private
  29674. */
  29675. removeUIFromElement: function() {
  29676. var me = this,
  29677. baseClsUI = me.baseCls + '-' + me.ui,
  29678. frameElementCls = me.frameElementCls,
  29679. frameElementsArray, frameElementsLength, i, el, frameElement, c;
  29680. me.removeCls(baseClsUI);
  29681. if (me.frame && !Ext.supports.CSS3BorderRadius) {
  29682. // define each element of the frame
  29683. frameElementsArray = me.frameElementsArray;
  29684. frameElementsLength = frameElementsArray.length;
  29685. i = 0;
  29686. for (; i < frameElementsLength; i++) {
  29687. frameElement = frameElementsArray[i];
  29688. el = me['frame' + frameElement.toUpperCase()];
  29689. c = baseClsUI + '-' + frameElement;
  29690. if (el) {
  29691. el.removeCls(c);
  29692. } else {
  29693. Ext.Array.remove(frameElementCls[frameElement], c);
  29694. }
  29695. }
  29696. }
  29697. },
  29698. /**
  29699. * @private
  29700. */
  29701. getTpl: function(name) {
  29702. return Ext.XTemplate.getTpl(this, name);
  29703. },
  29704. /**
  29705. * Converts style definitions to String.
  29706. * @return {String} A CSS style string with style, padding, margin and border.
  29707. * @private
  29708. */
  29709. initStyles: function(targetEl) {
  29710. var me = this,
  29711. Element = Ext.Element,
  29712. padding = me.padding,
  29713. margin = me.margin,
  29714. x = me.x,
  29715. y = me.y,
  29716. width, height;
  29717. // Convert the padding, margin and border properties from a space separated string
  29718. // into a proper style string
  29719. if (padding !== undefined) {
  29720. targetEl.setStyle('padding', Element.unitizeBox((padding === true) ? 5 : padding));
  29721. }
  29722. if (margin !== undefined) {
  29723. targetEl.setStyle('margin', Element.unitizeBox((margin === true) ? 5 : margin));
  29724. }
  29725. if (me.border !== undefined) {
  29726. me.setBorder(me.border, targetEl);
  29727. }
  29728. // initComponent, beforeRender, or event handlers may have set the style or cls property since the protoEl was set up
  29729. // so we must apply styles and classes here too.
  29730. if (me.cls && me.cls != me.initialCls) {
  29731. targetEl.addCls(me.cls);
  29732. delete me.cls;
  29733. delete me.initialCls;
  29734. }
  29735. if (me.style && me.style != me.initialStyle) {
  29736. targetEl.setStyle(me.style);
  29737. delete me.style;
  29738. delete me.initialStyle;
  29739. }
  29740. if (x !== undefined) {
  29741. targetEl.setStyle('left', (typeof x == 'number') ? (x + 'px') : x);
  29742. }
  29743. if (y !== undefined) {
  29744. targetEl.setStyle('top', (typeof y == 'number') ? (y + 'px') : y);
  29745. }
  29746. // Framed components need their width/height to apply to the frame, which is
  29747. // best handled in layout at present.
  29748. if (!me.getFrameInfo()) {
  29749. width = me.width;
  29750. height = me.height;
  29751. // If we're using the content box model, we also cannot assign numeric initial sizes since we do not know the border widths to subtract
  29752. if (width !== undefined) {
  29753. if (typeof width === 'number') {
  29754. if (Ext.isBorderBox) {
  29755. targetEl.setStyle('width', width + 'px');
  29756. }
  29757. } else {
  29758. targetEl.setStyle('width', width);
  29759. }
  29760. }
  29761. if (height !== undefined) {
  29762. if (typeof height === 'number') {
  29763. if (Ext.isBorderBox) {
  29764. targetEl.setStyle('height', height + 'px');
  29765. }
  29766. } else {
  29767. targetEl.setStyle('height', height);
  29768. }
  29769. }
  29770. }
  29771. },
  29772. // @private
  29773. initEvents : function() {
  29774. var me = this,
  29775. afterRenderEvents = me.afterRenderEvents,
  29776. el,
  29777. property,
  29778. fn = function(listeners){
  29779. me.mon(el, listeners);
  29780. };
  29781. if (afterRenderEvents) {
  29782. for (property in afterRenderEvents) {
  29783. if (afterRenderEvents.hasOwnProperty(property)) {
  29784. el = me[property];
  29785. if (el && el.on) {
  29786. Ext.each(afterRenderEvents[property], fn);
  29787. }
  29788. }
  29789. }
  29790. }
  29791. // This will add focus/blur listeners to the getFocusEl() element if that is naturally focusable.
  29792. // If *not* naturally focusable, then the FocusManager must be enabled to get it to listen for focus so that
  29793. // the FocusManager can track and highlight focus.
  29794. me.addFocusListener();
  29795. },
  29796. /**
  29797. * @private
  29798. * <p>Sets up the focus listener on this Component's {@link #getFocusEl focusEl} if it has one.</p>
  29799. * <p>Form Components which must implicitly participate in tabbing order usually have a naturally focusable
  29800. * element as their {@link #getFocusEl focusEl}, and it is the DOM event of that recieving focus which drives
  29801. * the Component's onFocus handling, and the DOM event of it being blurred which drives the onBlur handling.</p>
  29802. * <p>If the {@link #getFocusEl focusEl} is <b>not</b> naturally focusable, then the listeners are only added
  29803. * if the {@link Ext.FocusManager FocusManager} is enabled.</p>
  29804. */
  29805. addFocusListener: function() {
  29806. var me = this,
  29807. focusEl = me.getFocusEl(),
  29808. needsTabIndex;
  29809. // All Containers may be focusable, not only "form" type elements, but also
  29810. // Panels, Toolbars, Windows etc.
  29811. // Usually, the <DIV> element they will return as their focusEl will not be able to recieve focus
  29812. // However, if the FocusManager is invoked, its non-default navigation handlers (invoked when
  29813. // tabbing/arrowing off of certain Components) may explicitly focus a Panel or Container or FieldSet etc.
  29814. // Add listeners to the focus and blur events on the focus element
  29815. // If this Component returns a focusEl, we might need to add a focus listener to it.
  29816. if (focusEl) {
  29817. // getFocusEl might return a Component if a Container wishes to delegate focus to a descendant.
  29818. // Window can do this via its defaultFocus configuration which can reference a Button.
  29819. if (focusEl.isComponent) {
  29820. return focusEl.addFocusListener();
  29821. }
  29822. // If the focusEl is naturally focusable, then we always need a focus listener to drive the Component's
  29823. // onFocus handling.
  29824. // If *not* naturally focusable, then we only need the focus listener if the FocusManager is enabled.
  29825. needsTabIndex = focusEl.needsTabIndex();
  29826. if (!me.focusListenerAdded && (!needsTabIndex || Ext.FocusManager.enabled)) {
  29827. if (needsTabIndex) {
  29828. focusEl.dom.tabIndex = -1;
  29829. }
  29830. focusEl.on({
  29831. focus: me.onFocus,
  29832. blur: me.onBlur,
  29833. scope: me
  29834. });
  29835. me.focusListenerAdded = true;
  29836. }
  29837. }
  29838. },
  29839. /**
  29840. * @private
  29841. * <p>Returns the focus holder element associated with this Component. At the Component base class level, this function returns <code>undefined</code>.</p>
  29842. * <p>Subclasses which use embedded focusable elements (such as Window, Field and Button) should override this for use by the {@link #focus} method.</p>
  29843. * <p>Containers which need to participate in the {@link Ext.FocusManager FocusManager}'s navigation and Container focusing scheme also
  29844. * need to return a focusEl, although focus is only listened for in this case if the {@link Ext.FocusManager FocusManager} is {@link Ext.FocusManager#method-enable enable}d.</p>
  29845. * @returns {undefined} <code>undefined</code> because raw Components cannot by default hold focus.
  29846. */
  29847. getFocusEl: Ext.emptyFn,
  29848. isFocusable: function(c) {
  29849. var me = this,
  29850. focusEl;
  29851. if ((me.focusable !== false) && (focusEl = me.getFocusEl()) && me.rendered && !me.destroying && !me.isDestroyed && !me.disabled && me.isVisible(true)) {
  29852. // getFocusEl might return a Component if a Container wishes to delegate focus to a descendant.
  29853. // Window can do this via its defaultFocus configuration which can reference a Button.
  29854. if (focusEl.isComponent) {
  29855. return focusEl.isFocusable();
  29856. }
  29857. return focusEl && focusEl.dom && focusEl.isVisible();
  29858. }
  29859. },
  29860. // private
  29861. preFocus: Ext.emptyFn,
  29862. // private
  29863. onFocus: function(e) {
  29864. var me = this,
  29865. focusCls = me.focusCls,
  29866. focusEl = me.getFocusEl();
  29867. if (!me.disabled) {
  29868. me.preFocus(e);
  29869. if (focusCls && focusEl) {
  29870. focusEl.addCls(me.addClsWithUI(focusCls, true));
  29871. }
  29872. if (!me.hasFocus) {
  29873. me.hasFocus = true;
  29874. me.fireEvent('focus', me, e);
  29875. }
  29876. }
  29877. },
  29878. // private
  29879. beforeBlur : Ext.emptyFn,
  29880. // private
  29881. onBlur : function(e) {
  29882. var me = this,
  29883. focusCls = me.focusCls,
  29884. focusEl = me.getFocusEl();
  29885. if (me.destroying) {
  29886. return;
  29887. }
  29888. me.beforeBlur(e);
  29889. if (focusCls && focusEl) {
  29890. focusEl.removeCls(me.removeClsWithUI(focusCls, true));
  29891. }
  29892. if (me.validateOnBlur) {
  29893. me.validate();
  29894. }
  29895. me.hasFocus = false;
  29896. me.fireEvent('blur', me, e);
  29897. me.postBlur(e);
  29898. },
  29899. // private
  29900. postBlur : Ext.emptyFn,
  29901. /**
  29902. * Tests whether this Component matches the selector string.
  29903. * @param {String} selector The selector string to test against.
  29904. * @return {Boolean} True if this Component matches the selector.
  29905. */
  29906. is: function(selector) {
  29907. return Ext.ComponentQuery.is(this, selector);
  29908. },
  29909. /**
  29910. * Walks up the `ownerCt` axis looking for an ancestor Container which matches the passed simple selector.
  29911. *
  29912. * Example:
  29913. *
  29914. * var owningTabPanel = grid.up('tabpanel');
  29915. *
  29916. * @param {String} [selector] The simple selector to test.
  29917. * @return {Ext.container.Container} The matching ancestor Container (or `undefined` if no match was found).
  29918. */
  29919. up: function(selector) {
  29920. // Use bubble target to navigate upwards so that Components can implement their own hierarchy.
  29921. // For example Menus implement getBubbleTarget because they have a parentMenu or ownerButton as an
  29922. // upward link depending upon how they are owned and triggered.
  29923. var result = this.getBubbleTarget();
  29924. if (selector) {
  29925. for (; result; result = result.getBubbleTarget()) {
  29926. if (Ext.ComponentQuery.is(result, selector)) {
  29927. return result;
  29928. }
  29929. }
  29930. }
  29931. return result;
  29932. },
  29933. /**
  29934. * Returns the next sibling of this Component.
  29935. *
  29936. * Optionally selects the next sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.
  29937. *
  29938. * May also be refered to as **`next()`**
  29939. *
  29940. * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
  29941. * {@link #nextNode}
  29942. * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following items.
  29943. * @return {Ext.Component} The next sibling (or the next sibling which matches the selector).
  29944. * Returns null if there is no matching sibling.
  29945. */
  29946. nextSibling: function(selector) {
  29947. var o = this.ownerCt, it, last, idx, c;
  29948. if (o) {
  29949. it = o.items;
  29950. idx = it.indexOf(this) + 1;
  29951. if (idx) {
  29952. if (selector) {
  29953. for (last = it.getCount(); idx < last; idx++) {
  29954. if ((c = it.getAt(idx)).is(selector)) {
  29955. return c;
  29956. }
  29957. }
  29958. } else {
  29959. if (idx < it.getCount()) {
  29960. return it.getAt(idx);
  29961. }
  29962. }
  29963. }
  29964. }
  29965. return null;
  29966. },
  29967. /**
  29968. * Returns the previous sibling of this Component.
  29969. *
  29970. * Optionally selects the previous sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery}
  29971. * selector.
  29972. *
  29973. * May also be refered to as **`prev()`**
  29974. *
  29975. * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
  29976. * {@link #previousNode}
  29977. * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding items.
  29978. * @return {Ext.Component} The previous sibling (or the previous sibling which matches the selector).
  29979. * Returns null if there is no matching sibling.
  29980. */
  29981. previousSibling: function(selector) {
  29982. var o = this.ownerCt, it, idx, c;
  29983. if (o) {
  29984. it = o.items;
  29985. idx = it.indexOf(this);
  29986. if (idx != -1) {
  29987. if (selector) {
  29988. for (--idx; idx >= 0; idx--) {
  29989. if ((c = it.getAt(idx)).is(selector)) {
  29990. return c;
  29991. }
  29992. }
  29993. } else {
  29994. if (idx) {
  29995. return it.getAt(--idx);
  29996. }
  29997. }
  29998. }
  29999. }
  30000. return null;
  30001. },
  30002. /**
  30003. * Returns the previous node in the Component tree in tree traversal order.
  30004. *
  30005. * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
  30006. * tree in reverse order to attempt to find a match. Contrast with {@link #previousSibling}.
  30007. * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding nodes.
  30008. * @return {Ext.Component} The previous node (or the previous node which matches the selector).
  30009. * Returns null if there is no matching node.
  30010. */
  30011. previousNode: function(selector, /* private */ includeSelf) {
  30012. var node = this,
  30013. ownerCt = node.ownerCt,
  30014. result,
  30015. it, i, sib;
  30016. // If asked to include self, test me
  30017. if (includeSelf && node.is(selector)) {
  30018. return node;
  30019. }
  30020. if (ownerCt) {
  30021. for (it = ownerCt.items.items, i = Ext.Array.indexOf(it, node) - 1; i > -1; i--) {
  30022. sib = it[i];
  30023. if (sib.query) {
  30024. result = sib.query(selector);
  30025. result = result[result.length - 1];
  30026. if (result) {
  30027. return result;
  30028. }
  30029. }
  30030. if (sib.is(selector)) {
  30031. return sib;
  30032. }
  30033. }
  30034. return ownerCt.previousNode(selector, true);
  30035. }
  30036. return null;
  30037. },
  30038. /**
  30039. * Returns the next node in the Component tree in tree traversal order.
  30040. *
  30041. * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
  30042. * tree to attempt to find a match. Contrast with {@link #nextSibling}.
  30043. * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following nodes.
  30044. * @return {Ext.Component} The next node (or the next node which matches the selector).
  30045. * Returns null if there is no matching node.
  30046. */
  30047. nextNode: function(selector, /* private */ includeSelf) {
  30048. var node = this,
  30049. ownerCt = node.ownerCt,
  30050. result,
  30051. it, len, i, sib;
  30052. // If asked to include self, test me
  30053. if (includeSelf && node.is(selector)) {
  30054. return node;
  30055. }
  30056. if (ownerCt) {
  30057. for (it = ownerCt.items.items, i = Ext.Array.indexOf(it, node) + 1, len = it.length; i < len; i++) {
  30058. sib = it[i];
  30059. if (sib.is(selector)) {
  30060. return sib;
  30061. }
  30062. if (sib.down) {
  30063. result = sib.down(selector);
  30064. if (result) {
  30065. return result;
  30066. }
  30067. }
  30068. }
  30069. return ownerCt.nextNode(selector);
  30070. }
  30071. return null;
  30072. },
  30073. /**
  30074. * Retrieves the id of this component. Will autogenerate an id if one has not already been set.
  30075. * @return {String}
  30076. */
  30077. getId : function() {
  30078. return this.id || (this.id = 'ext-comp-' + (this.getAutoId()));
  30079. },
  30080. /**
  30081. * Returns the value of {@link #itemId} assigned to this component, or when that
  30082. * is not set, returns the value of {@link #id}.
  30083. * @return {String}
  30084. */
  30085. getItemId : function() {
  30086. return this.itemId || this.id;
  30087. },
  30088. /**
  30089. * Retrieves the top level element representing this component.
  30090. * @return {Ext.dom.Element}
  30091. */
  30092. getEl : function() {
  30093. return this.el;
  30094. },
  30095. /**
  30096. * This is used to determine where to insert the 'html', 'contentEl' and 'items' in this component.
  30097. * @private
  30098. */
  30099. getTargetEl: function() {
  30100. return this.frameBody || this.el;
  30101. },
  30102. /**
  30103. * @private
  30104. * Returns the CSS style object which will set the Component's scroll styles. This must be applied
  30105. * to the {@link #getTargetEl target element}.
  30106. */
  30107. getOverflowStyle: function() {
  30108. var me = this,
  30109. result = null;
  30110. if (typeof me.autoScroll == 'boolean') {
  30111. result = {
  30112. overflow: me.autoScroll ? 'auto' : ''
  30113. };
  30114. } else if (me.overflowX !== undefined || me.overflowY !== undefined) {
  30115. result = {
  30116. 'overflow-x': (me.overflowX||''),
  30117. 'overflow-y': (me.overflowY||'')
  30118. };
  30119. }
  30120. // The scrollable container element must be non-statically positioned or IE6/7 will make
  30121. // positioned children stay in place rather than scrolling with the rest of the content
  30122. if (result && (Ext.isIE6 || Ext.isIE7)) {
  30123. result.position = 'relative';
  30124. }
  30125. return result;
  30126. },
  30127. /**
  30128. * Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
  30129. * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).
  30130. *
  30131. * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
  30132. * determination of inherited xtypes.**
  30133. *
  30134. * For a list of all available xtypes, see the {@link Ext.Component} header.
  30135. *
  30136. * Example usage:
  30137. *
  30138. * var t = new Ext.form.field.Text();
  30139. * var isText = t.isXType('textfield'); // true
  30140. * var isBoxSubclass = t.isXType('field'); // true, descended from Ext.form.field.Base
  30141. * var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.form.field.Base instance
  30142. *
  30143. * @param {String} xtype The xtype to check for this Component
  30144. * @param {Boolean} [shallow=false] True to check whether this Component is directly of the specified xtype, false to
  30145. * check whether this Component is descended from the xtype.
  30146. * @return {Boolean} True if this component descends from the specified xtype, false otherwise.
  30147. */
  30148. isXType: function(xtype, shallow) {
  30149. if (shallow) {
  30150. return this.xtype === xtype;
  30151. }
  30152. else {
  30153. return this.xtypesMap[xtype];
  30154. }
  30155. },
  30156. /**
  30157. * Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all available xtypes, see the
  30158. * {@link Ext.Component} header.
  30159. *
  30160. * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
  30161. * determination of inherited xtypes.**
  30162. *
  30163. * Example usage:
  30164. *
  30165. * var t = new Ext.form.field.Text();
  30166. * alert(t.getXTypes()); // alerts 'component/field/textfield'
  30167. *
  30168. * @return {String} The xtype hierarchy string
  30169. */
  30170. getXTypes: function() {
  30171. var self = this.self,
  30172. xtypes, parentPrototype, parentXtypes;
  30173. if (!self.xtypes) {
  30174. xtypes = [];
  30175. parentPrototype = this;
  30176. while (parentPrototype) {
  30177. parentXtypes = parentPrototype.xtypes;
  30178. if (parentXtypes !== undefined) {
  30179. xtypes.unshift.apply(xtypes, parentXtypes);
  30180. }
  30181. parentPrototype = parentPrototype.superclass;
  30182. }
  30183. self.xtypeChain = xtypes;
  30184. self.xtypes = xtypes.join('/');
  30185. }
  30186. return self.xtypes;
  30187. },
  30188. /**
  30189. * Update the content area of a component.
  30190. * @param {String/Object} htmlOrData If this component has been configured with a template via the tpl config then
  30191. * it will use this argument as data to populate the template. If this component was not configured with a template,
  30192. * the components content area will be updated via Ext.Element update
  30193. * @param {Boolean} [loadScripts=false] Only legitimate when using the html configuration.
  30194. * @param {Function} [callback] Only legitimate when using the html configuration. Callback to execute when
  30195. * scripts have finished loading
  30196. */
  30197. update : function(htmlOrData, loadScripts, cb) {
  30198. var me = this;
  30199. if (me.tpl && !Ext.isString(htmlOrData)) {
  30200. me.data = htmlOrData;
  30201. if (me.rendered) {
  30202. me.tpl[me.tplWriteMode](me.getTargetEl(), htmlOrData || {});
  30203. }
  30204. } else {
  30205. me.html = Ext.isObject(htmlOrData) ? Ext.DomHelper.markup(htmlOrData) : htmlOrData;
  30206. if (me.rendered) {
  30207. me.getTargetEl().update(me.html, loadScripts, cb);
  30208. }
  30209. }
  30210. if (me.rendered) {
  30211. me.updateLayout();
  30212. }
  30213. },
  30214. /**
  30215. * Convenience function to hide or show this component by boolean.
  30216. * @param {Boolean} visible True to show, false to hide
  30217. * @return {Ext.Component} this
  30218. */
  30219. setVisible : function(visible) {
  30220. return this[visible ? 'show': 'hide']();
  30221. },
  30222. /**
  30223. * Returns true if this component is visible.
  30224. *
  30225. * @param {Boolean} [deep=false] Pass `true` to interrogate the visibility status of all parent Containers to
  30226. * determine whether this Component is truly visible to the user.
  30227. *
  30228. * Generally, to determine whether a Component is hidden, the no argument form is needed. For example when creating
  30229. * dynamically laid out UIs in a hidden Container before showing them.
  30230. *
  30231. * @return {Boolean} True if this component is visible, false otherwise.
  30232. */
  30233. isVisible: function(deep) {
  30234. var me = this,
  30235. child = me,
  30236. visible = me.rendered && !me.hidden,
  30237. ancestor = me.ownerCt;
  30238. // Clear hiddenOwnerCt property
  30239. me.hiddenAncestor = false;
  30240. if (me.destroyed) {
  30241. return false;
  30242. }
  30243. if (deep && visible && ancestor) {
  30244. while (ancestor) {
  30245. // If any ancestor is hidden, then this is hidden.
  30246. // If an ancestor Panel (only Panels have a collapse method) is collapsed,
  30247. // then its layoutTarget (body) is hidden, so this is hidden unless its within a
  30248. // docked item; they are still visible when collapsed (Unless they themseves are hidden)
  30249. if (ancestor.hidden || (ancestor.collapsed &&
  30250. !(ancestor.getDockedItems && Ext.Array.contains(ancestor.getDockedItems(), child)))) {
  30251. // Store hiddenOwnerCt property if needed
  30252. me.hiddenAncestor = ancestor;
  30253. visible = false;
  30254. break;
  30255. }
  30256. child = ancestor;
  30257. ancestor = ancestor.ownerCt;
  30258. }
  30259. }
  30260. return visible;
  30261. },
  30262. onBoxReady: function(){
  30263. var me = this;
  30264. if (me.disableOnBoxReady) {
  30265. me.onDisable();
  30266. } else if (me.enableOnBoxReady) {
  30267. me.onEnable();
  30268. }
  30269. if (me.resizable) {
  30270. me.initResizable(me.resizable);
  30271. }
  30272. // Draggability must be initialized after resizability
  30273. // Because if we have to be wrapped, the resizer wrapper must be dragged as a pseudo-Component
  30274. if (me.draggable) {
  30275. me.initDraggable();
  30276. }
  30277. },
  30278. /**
  30279. * Enable the component
  30280. * @param {Boolean} [silent=false] Passing true will supress the 'enable' event from being fired.
  30281. */
  30282. enable: function(silent) {
  30283. var me = this;
  30284. delete me.disableOnBoxReady;
  30285. me.removeCls(me.disabledCls);
  30286. if (me.rendered) {
  30287. me.onEnable();
  30288. } else {
  30289. me.enableOnBoxReady = true;
  30290. }
  30291. me.disabled = false;
  30292. delete me.resetDisable;
  30293. if (silent !== true) {
  30294. me.fireEvent('enable', me);
  30295. }
  30296. return me;
  30297. },
  30298. /**
  30299. * Disable the component.
  30300. * @param {Boolean} [silent=false] Passing true will supress the 'disable' event from being fired.
  30301. */
  30302. disable: function(silent) {
  30303. var me = this;
  30304. delete me.enableOnBoxReady;
  30305. me.addCls(me.disabledCls);
  30306. if (me.rendered) {
  30307. me.onDisable();
  30308. } else {
  30309. me.disableOnBoxReady = true;
  30310. }
  30311. me.disabled = true;
  30312. if (silent !== true) {
  30313. delete me.resetDisable;
  30314. me.fireEvent('disable', me);
  30315. }
  30316. return me;
  30317. },
  30318. /**
  30319. * Allows addition of behavior to the enable operation.
  30320. * After calling the superclass’s onEnable, the Component will be enabled.
  30321. *
  30322. * @template
  30323. * @protected
  30324. */
  30325. onEnable: function() {
  30326. if (this.maskOnDisable) {
  30327. this.el.dom.disabled = false;
  30328. this.unmask();
  30329. }
  30330. },
  30331. /**
  30332. * Allows addition of behavior to the disable operation.
  30333. * After calling the superclass’s onDisable, the Component will be disabled.
  30334. *
  30335. * @template
  30336. * @protected
  30337. */
  30338. onDisable : function() {
  30339. var me = this,
  30340. focusCls = me.focusCls,
  30341. focusEl = me.getFocusEl();
  30342. if (focusCls && focusEl) {
  30343. focusEl.removeCls(me.removeClsWithUI(focusCls, true));
  30344. }
  30345. if (me.maskOnDisable) {
  30346. me.el.dom.disabled = true;
  30347. me.mask();
  30348. }
  30349. },
  30350. mask: function() {
  30351. var box = this.lastBox,
  30352. target = this.getMaskTarget(),
  30353. args = [];
  30354. // Pass it the height of our element if we know it.
  30355. if (box) {
  30356. args[2] = box.height;
  30357. }
  30358. target.mask.apply(target, args);
  30359. },
  30360. unmask: function() {
  30361. this.getMaskTarget().unmask();
  30362. },
  30363. getMaskTarget: function(){
  30364. return this.el;
  30365. },
  30366. /**
  30367. * Method to determine whether this Component is currently disabled.
  30368. * @return {Boolean} the disabled state of this Component.
  30369. */
  30370. isDisabled : function() {
  30371. return this.disabled;
  30372. },
  30373. /**
  30374. * Enable or disable the component.
  30375. * @param {Boolean} disabled True to disable.
  30376. */
  30377. setDisabled : function(disabled) {
  30378. return this[disabled ? 'disable': 'enable']();
  30379. },
  30380. /**
  30381. * Method to determine whether this Component is currently set to hidden.
  30382. * @return {Boolean} the hidden state of this Component.
  30383. */
  30384. isHidden : function() {
  30385. return this.hidden;
  30386. },
  30387. /**
  30388. * Adds a CSS class to the top level element representing this component.
  30389. * @param {String/String[]} cls The CSS class name to add
  30390. * @return {Ext.Component} Returns the Component to allow method chaining.
  30391. */
  30392. addCls : function(cls) {
  30393. var me = this,
  30394. el = me.rendered ? me.el : me.protoEl;
  30395. el.addCls.apply(el, arguments);
  30396. return me;
  30397. },
  30398. /**
  30399. * @inheritdoc Ext.AbstractComponent#addCls
  30400. * @deprecated 4.1 Use {@link #addCls} instead.
  30401. */
  30402. addClass : function() {
  30403. return this.addCls.apply(this, arguments);
  30404. },
  30405. /**
  30406. * Checks if the specified CSS class exists on this element's DOM node.
  30407. * @param {String} className The CSS class to check for
  30408. * @return {Boolean} True if the class exists, else false
  30409. * @method
  30410. */
  30411. hasCls: function (cls) {
  30412. var me = this,
  30413. el = me.rendered ? me.el : me.protoEl;
  30414. return el.hasCls.apply(el, arguments);
  30415. },
  30416. /**
  30417. * Removes a CSS class from the top level element representing this component.
  30418. * @param {String/String[]} cls The CSS class name to remove
  30419. * @returns {Ext.Component} Returns the Component to allow method chaining.
  30420. */
  30421. removeCls : function(cls) {
  30422. var me = this,
  30423. el = me.rendered ? me.el : me.protoEl;
  30424. el.removeCls.apply(el, arguments);
  30425. return me;
  30426. },
  30427. removeClass : function() {
  30428. if (Ext.isDefined(Ext.global.console)) {
  30429. Ext.global.console.warn('Ext.Component: removeClass has been deprecated. Please use removeCls.');
  30430. }
  30431. return this.removeCls.apply(this, arguments);
  30432. },
  30433. addOverCls: function() {
  30434. var me = this;
  30435. if (!me.disabled) {
  30436. me.el.addCls(me.overCls);
  30437. }
  30438. },
  30439. removeOverCls: function() {
  30440. this.el.removeCls(this.overCls);
  30441. },
  30442. addListener : function(element, listeners, scope, options) {
  30443. var me = this,
  30444. fn,
  30445. option;
  30446. if (Ext.isString(element) && (Ext.isObject(listeners) || options && options.element)) {
  30447. if (options.element) {
  30448. fn = listeners;
  30449. listeners = {};
  30450. listeners[element] = fn;
  30451. element = options.element;
  30452. if (scope) {
  30453. listeners.scope = scope;
  30454. }
  30455. for (option in options) {
  30456. if (options.hasOwnProperty(option)) {
  30457. if (me.eventOptionsRe.test(option)) {
  30458. listeners[option] = options[option];
  30459. }
  30460. }
  30461. }
  30462. }
  30463. // At this point we have a variable called element,
  30464. // and a listeners object that can be passed to on
  30465. if (me[element] && me[element].on) {
  30466. me.mon(me[element], listeners);
  30467. } else {
  30468. me.afterRenderEvents = me.afterRenderEvents || {};
  30469. if (!me.afterRenderEvents[element]) {
  30470. me.afterRenderEvents[element] = [];
  30471. }
  30472. me.afterRenderEvents[element].push(listeners);
  30473. }
  30474. }
  30475. return me.mixins.observable.addListener.apply(me, arguments);
  30476. },
  30477. // inherit docs
  30478. removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
  30479. var me = this,
  30480. element = managedListener.options ? managedListener.options.element : null;
  30481. if (element) {
  30482. element = me[element];
  30483. if (element && element.un) {
  30484. if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
  30485. element.un(managedListener.ename, managedListener.fn, managedListener.scope);
  30486. if (!isClear) {
  30487. Ext.Array.remove(me.managedListeners, managedListener);
  30488. }
  30489. }
  30490. }
  30491. } else {
  30492. return me.mixins.observable.removeManagedListenerItem.apply(me, arguments);
  30493. }
  30494. },
  30495. /**
  30496. * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
  30497. * @return {Ext.container.Container} the Container which owns this Component.
  30498. */
  30499. getBubbleTarget : function() {
  30500. return this.ownerCt;
  30501. },
  30502. /**
  30503. * Method to determine whether this Component is floating.
  30504. * @return {Boolean} the floating state of this component.
  30505. */
  30506. isFloating : function() {
  30507. return this.floating;
  30508. },
  30509. /**
  30510. * Method to determine whether this Component is draggable.
  30511. * @return {Boolean} the draggable state of this component.
  30512. */
  30513. isDraggable : function() {
  30514. return !!this.draggable;
  30515. },
  30516. /**
  30517. * Method to determine whether this Component is droppable.
  30518. * @return {Boolean} the droppable state of this component.
  30519. */
  30520. isDroppable : function() {
  30521. return !!this.droppable;
  30522. },
  30523. /**
  30524. * Method to manage awareness of when components are added to their
  30525. * respective Container, firing an #added event. References are
  30526. * established at add time rather than at render time.
  30527. *
  30528. * Allows addition of behavior when a Component is added to a
  30529. * Container. At this stage, the Component is in the parent
  30530. * Container's collection of child items. After calling the
  30531. * superclass's onAdded, the ownerCt reference will be present,
  30532. * and if configured with a ref, the refOwner will be set.
  30533. *
  30534. * @param {Ext.container.Container} container Container which holds the component
  30535. * @param {Number} pos Position at which the component was added
  30536. *
  30537. * @template
  30538. * @protected
  30539. */
  30540. onAdded : function(container, pos) {
  30541. var me = this;
  30542. me.ownerCt = container;
  30543. if (me.hasListeners.added) {
  30544. me.fireEvent('added', me, container, pos);
  30545. }
  30546. },
  30547. /**
  30548. * Method to manage awareness of when components are removed from their
  30549. * respective Container, firing a #removed event. References are properly
  30550. * cleaned up after removing a component from its owning container.
  30551. *
  30552. * Allows addition of behavior when a Component is removed from
  30553. * its parent Container. At this stage, the Component has been
  30554. * removed from its parent Container's collection of child items,
  30555. * but has not been destroyed (It will be destroyed if the parent
  30556. * Container's autoDestroy is true, or if the remove call was
  30557. * passed a truthy second parameter). After calling the
  30558. * superclass's onRemoved, the ownerCt and the refOwner will not
  30559. * be present.
  30560. * @param {Boolean} destroying Will be passed as true if the Container performing the remove operation will delete this
  30561. * Component upon remove.
  30562. *
  30563. * @template
  30564. * @protected
  30565. */
  30566. onRemoved : function(destroying) {
  30567. var me = this;
  30568. if (me.hasListeners.removed) {
  30569. me.fireEvent('removed', me, me.ownerCt);
  30570. }
  30571. delete me.ownerCt;
  30572. delete me.ownerLayout;
  30573. },
  30574. /**
  30575. * Invoked before the Component is destroyed.
  30576. *
  30577. * @method
  30578. * @template
  30579. * @protected
  30580. */
  30581. beforeDestroy : Ext.emptyFn,
  30582. /**
  30583. * Allows addition of behavior to the resize operation.
  30584. *
  30585. * Called when Ext.resizer.Resizer#drag event is fired.
  30586. *
  30587. * @method
  30588. * @template
  30589. * @protected
  30590. */
  30591. onResize : Ext.emptyFn,
  30592. /**
  30593. * Sets the width and height of this Component. This method fires the {@link #resize} event. This method can accept
  30594. * either width and height as separate arguments, or you can pass a size object like `{width:10, height:20}`.
  30595. *
  30596. * @param {Number/String/Object} width The new width to set. This may be one of:
  30597. *
  30598. * - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
  30599. * - A String used to set the CSS width style.
  30600. * - A size object in the format `{width: widthValue, height: heightValue}`.
  30601. * - `undefined` to leave the width unchanged.
  30602. *
  30603. * @param {Number/String} height The new height to set (not required if a size object is passed as the first arg).
  30604. * This may be one of:
  30605. *
  30606. * - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
  30607. * - A String used to set the CSS height style. Animation may **not** be used.
  30608. * - `undefined` to leave the height unchanged.
  30609. *
  30610. * @return {Ext.Component} this
  30611. */
  30612. setSize : function(width, height) {
  30613. var me = this;
  30614. // support for standard size objects
  30615. if (width && typeof width == 'object') {
  30616. height = width.height;
  30617. width = width.width;
  30618. }
  30619. // Constrain within configured maxima
  30620. if (typeof width == 'number') {
  30621. me.width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
  30622. } else if (width === null) {
  30623. delete me.width;
  30624. }
  30625. if (typeof height == 'number') {
  30626. me.height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
  30627. } else if (height === null) {
  30628. delete me.height;
  30629. }
  30630. // If not rendered, all we need to is set the properties.
  30631. // The initial layout will set the size
  30632. if (me.rendered && me.isVisible()) {
  30633. // If we are changing size, then we are not the root.
  30634. me.updateLayout({
  30635. isRoot: false
  30636. });
  30637. }
  30638. return me;
  30639. },
  30640. /**
  30641. * Determines whether this Component is the root of a layout. This returns `true` if
  30642. * this component can run its layout without assistance from or impact on its owner.
  30643. * If this component cannot run its layout given these restrictions, `false` is returned
  30644. * and its owner will be considered as the next candidate for the layout root.
  30645. *
  30646. * Setting the {@link #_isLayoutRoot} property to `true` causes this method to always
  30647. * return `true`. This may be useful when updating a layout of a Container which shrink
  30648. * wraps content, and you know that it will not change size, and so can safely be the
  30649. * topmost participant in the layout run.
  30650. * @protected
  30651. */
  30652. isLayoutRoot: function() {
  30653. var me = this,
  30654. ownerLayout = me.ownerLayout;
  30655. // Return true if we have been explicitly flagged as the layout root, or if we are floating.
  30656. // Sometimes floating Components get an ownerCt ref injected into them which is *not* a true ownerCt, merely
  30657. // an upward link for reference purposes. For example a grid column menu is linked to the
  30658. // owning header via an ownerCt reference.
  30659. if (!ownerLayout || me._isLayoutRoot || me.floating) {
  30660. return true;
  30661. }
  30662. return ownerLayout.isItemLayoutRoot(me);
  30663. },
  30664. /**
  30665. * Returns true if layout is suspended for this component. This can come from direct
  30666. * suspension of this component's layout activity ({@link Ext.Container#suspendLayout}) or if one
  30667. * of this component's containers is suspended.
  30668. *
  30669. * @return {Boolean} True layout of this component is suspended.
  30670. */
  30671. isLayoutSuspended: function () {
  30672. var comp = this,
  30673. ownerLayout;
  30674. while (comp) {
  30675. if (comp.layoutSuspendCount || comp.suspendLayout) {
  30676. return true;
  30677. }
  30678. ownerLayout = comp.ownerLayout;
  30679. if (!ownerLayout) {
  30680. break;
  30681. }
  30682. // TODO - what about suspending a Layout instance?
  30683. // this works better than ownerCt since ownerLayout means "is managed by" in
  30684. // the proper sense... some floating components have ownerCt but won't have an
  30685. // ownerLayout
  30686. comp = ownerLayout.owner;
  30687. }
  30688. return false;
  30689. },
  30690. /**
  30691. * Updates this component's layout. If this update effects this components {@link #ownerCt},
  30692. * that component's `updateLayout` method will be called to perform the layout instead.
  30693. * Otherwise, just this component (and its child items) will layout.
  30694. *
  30695. * @param {Object} options An object with layout options.
  30696. * @param {Boolean} options.defer True if this layout should be deferred.
  30697. * @param {Boolean} options.isRoot True if this layout should be the root of the layout.
  30698. */
  30699. updateLayout: function (options) {
  30700. var me = this,
  30701. defer,
  30702. isRoot = options && options.isRoot;
  30703. if (!me.rendered || me.layoutSuspendCount || me.suspendLayout) {
  30704. return;
  30705. }
  30706. if (me.hidden) {
  30707. Ext.AbstractComponent.cancelLayout(me);
  30708. } else if (typeof isRoot != 'boolean') {
  30709. isRoot = me.isLayoutRoot();
  30710. }
  30711. // if we aren't the root, see if our ownerLayout will handle it...
  30712. if (isRoot || !me.ownerLayout || !me.ownerLayout.onContentChange(me)) {
  30713. // either we are the root or our ownerLayout doesn't care
  30714. if (!me.isLayoutSuspended()) {
  30715. // we aren't suspended (knew that), but neither is any of our ownerCt's...
  30716. defer = (options && options.hasOwnProperty('defer')) ? options.defer : me.deferLayouts;
  30717. Ext.AbstractComponent.updateLayout(me, defer);
  30718. }
  30719. }
  30720. },
  30721. /**
  30722. * Returns an object that describes how this component's width and height are managed.
  30723. * All of these objects are shared and should not be modified.
  30724. *
  30725. * @return {Object} The size model for this component.
  30726. * @return {Ext.layout.SizeModel} return.width The {@link Ext.layout.SizeModel size model}
  30727. * for the width.
  30728. * @return {Ext.layout.SizeModel} return.height The {@link Ext.layout.SizeModel size model}
  30729. * for the height.
  30730. */
  30731. getSizeModel: function (ownerCtSizeModel) {
  30732. var me = this,
  30733. models = Ext.layout.SizeModel,
  30734. ownerContext = me.componentLayout.ownerContext,
  30735. width = me.width,
  30736. height = me.height,
  30737. typeofWidth, typeofHeight,
  30738. hasPixelWidth, hasPixelHeight,
  30739. heightModel, ownerLayout, policy, shrinkWrap, topLevel, widthModel;
  30740. if (ownerContext) {
  30741. // If we are in the middle of a running layout, always report the current,
  30742. // dynamic size model rather than recompute it. This is not (only) a time
  30743. // saving thing, but a correctness thing since we cannot get the right answer
  30744. // otherwise.
  30745. widthModel = ownerContext.widthModel;
  30746. heightModel = ownerContext.heightModel;
  30747. }
  30748. if (!widthModel || !heightModel) {
  30749. hasPixelWidth = ((typeofWidth = typeof width) == 'number');
  30750. hasPixelHeight = ((typeofHeight = typeof height) == 'number');
  30751. topLevel = me.floating || !(ownerLayout = me.ownerLayout);
  30752. // Floating or no owner layout, e.g. rendered using renderTo
  30753. if (topLevel) {
  30754. policy = Ext.layout.Layout.prototype.autoSizePolicy;
  30755. shrinkWrap = me.floating ? 3 : me.shrinkWrap;
  30756. if (hasPixelWidth) {
  30757. widthModel = models.configured;
  30758. }
  30759. if (hasPixelHeight) {
  30760. heightModel = models.configured;
  30761. }
  30762. } else {
  30763. policy = ownerLayout.getItemSizePolicy(me, ownerCtSizeModel);
  30764. shrinkWrap = ownerLayout.isItemShrinkWrap(me);
  30765. }
  30766. shrinkWrap = (shrinkWrap === true) ? 3 : (shrinkWrap || 0); // false->0, true->3
  30767. // Now that we have shrinkWrap as a 0-3 value, we need to turn off shrinkWrap
  30768. // bits for any dimension that has a configured size not in pixels. These must
  30769. // be read from the DOM.
  30770. //
  30771. if (topLevel && shrinkWrap) {
  30772. if (width && typeofWidth == 'string') {
  30773. shrinkWrap &= 2; // percentage, "30em" or whatever - not width shrinkWrap
  30774. }
  30775. if (height && typeofHeight == 'string') {
  30776. shrinkWrap &= 1; // percentage, "30em" or whatever - not height shrinkWrap
  30777. }
  30778. }
  30779. if (shrinkWrap !== 3) {
  30780. if (!ownerCtSizeModel) {
  30781. ownerCtSizeModel = me.ownerCt && me.ownerCt.getSizeModel();
  30782. }
  30783. if (ownerCtSizeModel) {
  30784. shrinkWrap |= (ownerCtSizeModel.width.shrinkWrap ? 1 : 0) | (ownerCtSizeModel.height.shrinkWrap ? 2 : 0);
  30785. }
  30786. }
  30787. if (!widthModel) {
  30788. if (!policy.setsWidth) {
  30789. if (hasPixelWidth) {
  30790. widthModel = models.configured;
  30791. } else {
  30792. widthModel = (shrinkWrap & 1) ? models.shrinkWrap : models.natural;
  30793. }
  30794. } else if (policy.readsWidth) {
  30795. if (hasPixelWidth) {
  30796. widthModel = models.calculatedFromConfigured;
  30797. } else {
  30798. widthModel = (shrinkWrap & 1) ? models.calculatedFromShrinkWrap :
  30799. models.calculatedFromNatural;
  30800. }
  30801. } else {
  30802. widthModel = models.calculated;
  30803. }
  30804. }
  30805. if (!heightModel) {
  30806. if (!policy.setsHeight) {
  30807. if (hasPixelHeight) {
  30808. heightModel = models.configured;
  30809. } else {
  30810. heightModel = (shrinkWrap & 2) ? models.shrinkWrap : models.natural;
  30811. }
  30812. } else if (policy.readsHeight) {
  30813. if (hasPixelHeight) {
  30814. heightModel = models.calculatedFromConfigured;
  30815. } else {
  30816. heightModel = (shrinkWrap & 2) ? models.calculatedFromShrinkWrap :
  30817. models.calculatedFromNatural;
  30818. }
  30819. } else {
  30820. heightModel = models.calculated;
  30821. }
  30822. }
  30823. }
  30824. // We return one of the cached objects with the proper "width" and "height" as the
  30825. // sizeModels we have determined.
  30826. return widthModel.pairsByHeightOrdinal[heightModel.ordinal];
  30827. },
  30828. isDescendant: function(ancestor) {
  30829. if (ancestor.isContainer) {
  30830. for (var c = this.ownerCt; c; c = c.ownerCt) {
  30831. if (c === ancestor) {
  30832. return true;
  30833. }
  30834. }
  30835. }
  30836. return false;
  30837. },
  30838. /**
  30839. * This method needs to be called whenever you change something on this component that requires the Component's
  30840. * layout to be recalculated.
  30841. * @return {Ext.container.Container} this
  30842. */
  30843. doComponentLayout : function() {
  30844. this.updateLayout();
  30845. return this;
  30846. },
  30847. /**
  30848. * Forces this component to redo its componentLayout.
  30849. * @deprecated 4.1.0 Use {@link #updateLayout} instead.
  30850. */
  30851. forceComponentLayout: function () {
  30852. this.updateLayout();
  30853. },
  30854. // @private
  30855. setComponentLayout : function(layout) {
  30856. var currentLayout = this.componentLayout;
  30857. if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
  30858. currentLayout.setOwner(null);
  30859. }
  30860. this.componentLayout = layout;
  30861. layout.setOwner(this);
  30862. },
  30863. getComponentLayout : function() {
  30864. var me = this;
  30865. if (!me.componentLayout || !me.componentLayout.isLayout) {
  30866. me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent'));
  30867. }
  30868. return me.componentLayout;
  30869. },
  30870. /**
  30871. * Called by the layout system after the Component has been layed out.
  30872. *
  30873. * @param {Number} width The width that was set
  30874. * @param {Number} height The height that was set
  30875. * @param {Number} oldWidth The old width. <code>undefined</code> if this was the initial layout.
  30876. * @param {Number} oldHeight The old height. <code>undefined</code> if this was the initial layout.
  30877. *
  30878. * @template
  30879. * @protected
  30880. */
  30881. afterComponentLayout: function(width, height, oldWidth, oldHeight) {
  30882. var me = this,
  30883. floaters, len, i, floater;
  30884. if (++me.componentLayoutCounter === 1) {
  30885. me.afterFirstLayout(width, height);
  30886. }
  30887. // Contained autoShow items must be shown upon next layout of the Container
  30888. if (me.floatingItems) {
  30889. floaters = me.floatingItems.items;
  30890. len = floaters.length;
  30891. for (i = 0; i < len; i++) {
  30892. floater = floaters[i];
  30893. if (!floater.rendered && floater.autoShow) {
  30894. floater.show();
  30895. }
  30896. }
  30897. }
  30898. if (me.hasListeners.resize && (width !== oldWidth || height !== oldHeight)) {
  30899. me.fireEvent('resize', me, width, height, oldWidth, oldHeight);
  30900. }
  30901. },
  30902. /**
  30903. * Occurs before componentLayout is run. Returning false from this method will prevent the componentLayout from
  30904. * being executed.
  30905. *
  30906. * @param {Number} adjWidth The box-adjusted width that was set
  30907. * @param {Number} adjHeight The box-adjusted height that was set
  30908. *
  30909. * @template
  30910. * @protected
  30911. */
  30912. beforeComponentLayout: function(width, height) {
  30913. return true;
  30914. },
  30915. /**
  30916. * Sets the left and top of the component. To set the page XY position instead, use {@link Ext.Component#setPagePosition setPagePosition}. This
  30917. * method fires the {@link #move} event.
  30918. * @param {Number} left The new left
  30919. * @param {Number} top The new top
  30920. * @param {Boolean/Object} [animate] If true, the Component is _animated_ into its new position. You may also pass an
  30921. * animation configuration.
  30922. * @return {Ext.Component} this
  30923. */
  30924. setPosition : function(x, y, animate) {
  30925. var me = this,
  30926. pos = me.beforeSetPosition.apply(me, arguments);
  30927. if (pos && me.rendered) {
  30928. // Convert position WRT RTL
  30929. pos = me.convertPosition(pos);
  30930. // Proceed only if the new position is different from the current one.
  30931. if (pos.left !== me.el.getLeft() || pos.top !== me.el.getTop()) {
  30932. if (animate) {
  30933. me.stopAnimation();
  30934. me.animate(Ext.apply({
  30935. duration: 1000,
  30936. listeners: {
  30937. afteranimate: Ext.Function.bind(me.afterSetPosition, me, [pos.left, pos.top])
  30938. },
  30939. to: pos
  30940. }, animate));
  30941. } else {
  30942. // Must use Element's methods to set element position because, if it is a Layer (floater), it may need to sync a shadow
  30943. // We must also only set the properties which are defined because Element.setLeftTop autos any undefined coordinates
  30944. if (pos.left !== undefined && pos.top !== undefined) {
  30945. me.el.setLeftTop(pos.left, pos.top);
  30946. } else if (pos.left !== undefined) {
  30947. me.el.setLeft(pos.left);
  30948. } else if (pos.top !==undefined) {
  30949. me.el.setTop(pos.top);
  30950. }
  30951. me.afterSetPosition(pos.left, pos.top);
  30952. }
  30953. }
  30954. }
  30955. return me;
  30956. },
  30957. /**
  30958. * @private Template method called before a Component is positioned.
  30959. */
  30960. beforeSetPosition: function (x, y, animate) {
  30961. var pos, x0;
  30962. // decode the position arguments:
  30963. if (!x || Ext.isNumber(x)) {
  30964. pos = { x: x, y : y, anim: animate };
  30965. } else if (Ext.isNumber(x0 = x[0])) { // an array of [x, y]
  30966. pos = { x : x0, y : x[1], anim: y };
  30967. } else {
  30968. pos = { x: x.x, y: x.y, anim: y }; // already an object w/ x & y properties
  30969. }
  30970. pos.hasX = Ext.isNumber(pos.x);
  30971. pos.hasY = Ext.isNumber(pos.y);
  30972. // store the position as specified:
  30973. this.x = pos.x;
  30974. this.y = pos.y;
  30975. return (pos.hasX || pos.hasY) ? pos : null;
  30976. },
  30977. /**
  30978. * Template method called after a Component has been positioned.
  30979. *
  30980. * @param {Number} x
  30981. * @param {Number} y
  30982. *
  30983. * @template
  30984. * @protected
  30985. */
  30986. afterSetPosition: function(x, y) {
  30987. var me = this;
  30988. me.onPosition(x, y);
  30989. if (me.hasListeners.move) {
  30990. me.fireEvent('move', me, x, y);
  30991. }
  30992. },
  30993. /**
  30994. * This method converts an "{x: x, y: y}" object to a "{left: x+'px', top: y+'px'}" object.
  30995. * The returned object contains the styles to set to effect the position. This is
  30996. * overridden in RTL mode to be "{right: x, top: y}".
  30997. * @private
  30998. */
  30999. convertPosition: function (pos, withUnits) {
  31000. var ret = {},
  31001. El = Ext.Element;
  31002. if (pos.hasX) {
  31003. ret.left = withUnits ? El.addUnits(pos.x) : pos.x;
  31004. }
  31005. if (pos.hasY) {
  31006. ret.top = withUnits ? El.addUnits(pos.y) : pos.y;
  31007. }
  31008. return ret;
  31009. },
  31010. /**
  31011. * Called after the component is moved, this method is empty by default but can be implemented by any
  31012. * subclass that needs to perform custom logic after a move occurs.
  31013. *
  31014. * @param {Number} x The new x position
  31015. * @param {Number} y The new y position
  31016. *
  31017. * @template
  31018. * @protected
  31019. */
  31020. onPosition: Ext.emptyFn,
  31021. /**
  31022. * Sets the width of the component. This method fires the {@link #resize} event.
  31023. *
  31024. * @param {Number} width The new width to setThis may be one of:
  31025. *
  31026. * - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
  31027. * - A String used to set the CSS width style.
  31028. *
  31029. * @return {Ext.Component} this
  31030. */
  31031. setWidth : function(width) {
  31032. return this.setSize(width);
  31033. },
  31034. /**
  31035. * Sets the height of the component. This method fires the {@link #resize} event.
  31036. *
  31037. * @param {Number} height The new height to set. This may be one of:
  31038. *
  31039. * - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
  31040. * - A String used to set the CSS height style.
  31041. * - _undefined_ to leave the height unchanged.
  31042. *
  31043. * @return {Ext.Component} this
  31044. */
  31045. setHeight : function(height) {
  31046. return this.setSize(undefined, height);
  31047. },
  31048. /**
  31049. * Gets the current size of the component's underlying element.
  31050. * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
  31051. */
  31052. getSize : function() {
  31053. return this.el.getSize();
  31054. },
  31055. /**
  31056. * Gets the current width of the component's underlying element.
  31057. * @return {Number}
  31058. */
  31059. getWidth : function() {
  31060. return this.el.getWidth();
  31061. },
  31062. /**
  31063. * Gets the current height of the component's underlying element.
  31064. * @return {Number}
  31065. */
  31066. getHeight : function() {
  31067. return this.el.getHeight();
  31068. },
  31069. /**
  31070. * Gets the {@link Ext.ComponentLoader} for this Component.
  31071. * @return {Ext.ComponentLoader} The loader instance, null if it doesn't exist.
  31072. */
  31073. getLoader: function(){
  31074. var me = this,
  31075. autoLoad = me.autoLoad ? (Ext.isObject(me.autoLoad) ? me.autoLoad : {url: me.autoLoad}) : null,
  31076. loader = me.loader || autoLoad;
  31077. if (loader) {
  31078. if (!loader.isLoader) {
  31079. me.loader = new Ext.ComponentLoader(Ext.apply({
  31080. target: me,
  31081. autoLoad: autoLoad
  31082. }, loader));
  31083. } else {
  31084. loader.setTarget(me);
  31085. }
  31086. return me.loader;
  31087. }
  31088. return null;
  31089. },
  31090. /**
  31091. * Sets the dock position of this component in its parent panel. Note that this only has effect if this item is part
  31092. * of the dockedItems collection of a parent that has a DockLayout (note that any Panel has a DockLayout by default)
  31093. * @param {Object} dock The dock position.
  31094. * @param {Boolean} [layoutParent=false] True to re-layout parent.
  31095. * @return {Ext.Component} this
  31096. */
  31097. setDocked : function(dock, layoutParent) {
  31098. var me = this;
  31099. me.dock = dock;
  31100. if (layoutParent && me.ownerCt && me.rendered) {
  31101. me.ownerCt.updateLayout();
  31102. }
  31103. return me;
  31104. },
  31105. /**
  31106. *
  31107. * @param {String/Number} border The border, see {@link #border}. If a falsey value is passed
  31108. * the border will be removed.
  31109. */
  31110. setBorder: function(border, /* private */ targetEl) {
  31111. var me = this,
  31112. initial = !!targetEl;
  31113. if (me.rendered || initial) {
  31114. if (!initial) {
  31115. targetEl = me.el;
  31116. }
  31117. if (!border) {
  31118. border = 0;
  31119. } else {
  31120. border = Ext.Element.unitizeBox((border === true) ? 1 : border);
  31121. }
  31122. targetEl.setStyle('border-width', border);
  31123. if (!initial) {
  31124. me.updateLayout();
  31125. }
  31126. }
  31127. me.border = border;
  31128. },
  31129. onDestroy : function() {
  31130. var me = this;
  31131. if (me.monitorResize && Ext.EventManager.resizeEvent) {
  31132. Ext.EventManager.resizeEvent.removeListener(me.setSize, me);
  31133. }
  31134. // Destroying the floatingItems ZIndexManager will also destroy descendant floating Components
  31135. Ext.destroy(
  31136. me.componentLayout,
  31137. me.loadMask,
  31138. me.floatingDescendants
  31139. );
  31140. },
  31141. /**
  31142. * Destroys the Component.
  31143. */
  31144. destroy : function() {
  31145. var me = this,
  31146. selectors = me.renderSelectors,
  31147. selector,
  31148. el;
  31149. if (!me.isDestroyed) {
  31150. if (!me.hasListeners.beforedestroy || me.fireEvent('beforedestroy', me) !== false) {
  31151. me.destroying = true;
  31152. me.beforeDestroy();
  31153. if (me.floating) {
  31154. delete me.floatParent;
  31155. // A zIndexManager is stamped into a *floating* Component when it is added to a Container.
  31156. // If it has no zIndexManager at render time, it is assigned to the global Ext.WindowManager instance.
  31157. if (me.zIndexManager) {
  31158. me.zIndexManager.unregister(me);
  31159. }
  31160. } else if (me.ownerCt && me.ownerCt.remove) {
  31161. me.ownerCt.remove(me, false);
  31162. }
  31163. me.onDestroy();
  31164. // Attempt to destroy all plugins
  31165. Ext.destroy(me.plugins);
  31166. if (me.hasListeners.destroy) {
  31167. me.fireEvent('destroy', me);
  31168. }
  31169. Ext.ComponentManager.unregister(me);
  31170. me.mixins.state.destroy.call(me);
  31171. me.clearListeners();
  31172. // make sure we clean up the element references after removing all events
  31173. if (me.rendered) {
  31174. if (!me.preserveElOnDestroy) {
  31175. me.el.remove();
  31176. }
  31177. me.mixins.elementCt.destroy.call(me); // removes childEls
  31178. if (selectors) {
  31179. for (selector in selectors) {
  31180. if (selectors.hasOwnProperty(selector)) {
  31181. el = me[selector];
  31182. if (el) { // in case any other code may have already removed it
  31183. delete me[selector];
  31184. el.remove();
  31185. }
  31186. }
  31187. }
  31188. }
  31189. delete me.el;
  31190. delete me.frameBody;
  31191. delete me.rendered;
  31192. }
  31193. me.destroying = false;
  31194. me.isDestroyed = true;
  31195. }
  31196. }
  31197. },
  31198. /**
  31199. * Retrieves a plugin by its pluginId which has been bound to this component.
  31200. * @param {String} pluginId
  31201. * @return {Ext.AbstractPlugin} plugin instance.
  31202. */
  31203. getPlugin: function(pluginId) {
  31204. var i = 0,
  31205. plugins = this.plugins,
  31206. ln = plugins.length;
  31207. for (; i < ln; i++) {
  31208. if (plugins[i].pluginId === pluginId) {
  31209. return plugins[i];
  31210. }
  31211. }
  31212. },
  31213. /**
  31214. * Determines whether this component is the descendant of a particular container.
  31215. * @param {Ext.Container} container
  31216. * @return {Boolean} True if it is.
  31217. */
  31218. isDescendantOf: function(container) {
  31219. return !!this.findParentBy(function(p){
  31220. return p === container;
  31221. });
  31222. }
  31223. }, function() {
  31224. var AbstractComponent = this;
  31225. AbstractComponent.createAlias({
  31226. on: 'addListener',
  31227. prev: 'previousSibling',
  31228. next: 'nextSibling'
  31229. });
  31230. /**
  31231. * @inheritdoc Ext.AbstractComponent#resumeLayouts
  31232. * @member Ext
  31233. */
  31234. Ext.resumeLayouts = function (flush) {
  31235. AbstractComponent.resumeLayouts(flush);
  31236. };
  31237. /**
  31238. * @inheritdoc Ext.AbstractComponent#suspendLayouts
  31239. * @member Ext
  31240. */
  31241. Ext.suspendLayouts = function () {
  31242. AbstractComponent.suspendLayouts();
  31243. };
  31244. /**
  31245. * Utility wrapper that suspends layouts of all components for the duration of a given function.
  31246. * @param {Function} fn The function to execute.
  31247. * @param {Object} [scope] The scope (`this` reference) in which the specified function is executed.
  31248. * @member Ext
  31249. */
  31250. Ext.batchLayouts = function(fn, scope) {
  31251. AbstractComponent.suspendLayouts();
  31252. // Invoke the function
  31253. fn.call(scope);
  31254. AbstractComponent.resumeLayouts(true);
  31255. };
  31256. });
  31257. /**
  31258. * The AbstractPlugin class is the base class from which user-implemented plugins should inherit.
  31259. *
  31260. * This class defines the essential API of plugins as used by Components by defining the following methods:
  31261. *
  31262. * - `init` : The plugin initialization method which the owning Component calls at Component initialization time.
  31263. *
  31264. * The Component passes itself as the sole parameter.
  31265. *
  31266. * Subclasses should set up bidirectional links between the plugin and its client Component here.
  31267. *
  31268. * - `destroy` : The plugin cleanup method which the owning Component calls at Component destruction time.
  31269. *
  31270. * Use this method to break links between the plugin and the Component and to free any allocated resources.
  31271. *
  31272. * - `enable` : The base implementation just sets the plugin's `disabled` flag to `false`
  31273. *
  31274. * - `disable` : The base implementation just sets the plugin's `disabled` flag to `true`
  31275. */
  31276. Ext.define('Ext.AbstractPlugin', {
  31277. disabled: false,
  31278. constructor: function(config) {
  31279. this.initialConfig = config;
  31280. Ext.apply(this, config);
  31281. },
  31282. clone: function() {
  31283. return new this.self(this.initialConfig);
  31284. },
  31285. getCmp: function() {
  31286. return this.cmp;
  31287. },
  31288. /**
  31289. * @cfg {String} pluginId
  31290. * A name for the plugin that can be set at creation time to then retrieve the plugin
  31291. * through {@link Ext.AbstractComponent#getPlugin getPlugin} method. For example:
  31292. *
  31293. * var grid = Ext.create('Ext.grid.Panel', {
  31294. * plugins: [{
  31295. * ptype: 'cellediting',
  31296. * clicksToEdit: 2,
  31297. * pluginId: 'cellplugin'
  31298. * }]
  31299. * });
  31300. *
  31301. * // later on:
  31302. * var plugin = grid.getPlugin('cellplugin');
  31303. */
  31304. /**
  31305. * @method
  31306. * The init method is invoked after initComponent method has been run for the client Component.
  31307. *
  31308. * The supplied implementation is empty. Subclasses should perform plugin initialization, and set up bidirectional
  31309. * links between the plugin and its client Component in their own implementation of this method.
  31310. * @param {Ext.Component} client The client Component which owns this plugin.
  31311. */
  31312. init: Ext.emptyFn,
  31313. /**
  31314. * @method
  31315. * The destroy method is invoked by the owning Component at the time the Component is being destroyed.
  31316. *
  31317. * The supplied implementation is empty. Subclasses should perform plugin cleanup in their own implementation of
  31318. * this method.
  31319. */
  31320. destroy: Ext.emptyFn,
  31321. /**
  31322. * The base implementation just sets the plugin's `disabled` flag to `false`
  31323. *
  31324. * Plugin subclasses which need more complex processing may implement an overriding implementation.
  31325. */
  31326. enable: function() {
  31327. this.disabled = false;
  31328. },
  31329. /**
  31330. * The base implementation just sets the plugin's `disabled` flag to `true`
  31331. *
  31332. * Plugin subclasses which need more complex processing may implement an overriding implementation.
  31333. */
  31334. disable: function() {
  31335. this.disabled = true;
  31336. }
  31337. });
  31338. /**
  31339. * An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it
  31340. * can be usefully shared among multiple components. Actions let you share handlers, configuration options and UI
  31341. * updates across any components that support the Action interface (primarily {@link Ext.toolbar.Toolbar},
  31342. * {@link Ext.button.Button} and {@link Ext.menu.Menu} components).
  31343. *
  31344. * Use a single Action instance as the config object for any number of UI Components which share the same configuration. The
  31345. * Action not only supplies the configuration, but allows all Components based upon it to have a common set of methods
  31346. * called at once through a single call to the Action.
  31347. *
  31348. * Any Component that is to be configured with an Action must also support
  31349. * the following methods:
  31350. *
  31351. * - setText(string)
  31352. * - setIconCls(string)
  31353. * - setDisabled(boolean)
  31354. * - setVisible(boolean)
  31355. * - setHandler(function)
  31356. *
  31357. * This allows the Action to control its associated Components.
  31358. *
  31359. * Example usage:
  31360. *
  31361. * // Define the shared Action. Each Component below will have the same
  31362. * // display text and icon, and will display the same message on click.
  31363. * var action = new Ext.Action({
  31364. * {@link #text}: 'Do something',
  31365. * {@link #handler}: function(){
  31366. * Ext.Msg.alert('Click', 'You did something.');
  31367. * },
  31368. * {@link #iconCls}: 'do-something',
  31369. * {@link #itemId}: 'myAction'
  31370. * });
  31371. *
  31372. * var panel = new Ext.panel.Panel({
  31373. * title: 'Actions',
  31374. * width: 500,
  31375. * height: 300,
  31376. * tbar: [
  31377. * // Add the Action directly to a toolbar as a menu button
  31378. * action,
  31379. * {
  31380. * text: 'Action Menu',
  31381. * // Add the Action to a menu as a text item
  31382. * menu: [action]
  31383. * }
  31384. * ],
  31385. * items: [
  31386. * // Add the Action to the panel body as a standard button
  31387. * new Ext.button.Button(action)
  31388. * ],
  31389. * renderTo: Ext.getBody()
  31390. * });
  31391. *
  31392. * // Change the text for all components using the Action
  31393. * action.setText('Something else');
  31394. *
  31395. * // Reference an Action through a container using the itemId
  31396. * var btn = panel.getComponent('myAction');
  31397. * var aRef = btn.baseAction;
  31398. * aRef.setText('New text');
  31399. */
  31400. Ext.define('Ext.Action', {
  31401. /* Begin Definitions */
  31402. /* End Definitions */
  31403. /**
  31404. * @cfg {String} [text='']
  31405. * The text to set for all components configured by this Action.
  31406. */
  31407. /**
  31408. * @cfg {String} [iconCls='']
  31409. * The CSS class selector that specifies a background image to be used as the header icon for
  31410. * all components configured by this Action.
  31411. *
  31412. * An example of specifying a custom icon class would be something like:
  31413. *
  31414. * // specify the property in the config for the class:
  31415. * ...
  31416. * iconCls: 'do-something'
  31417. *
  31418. * // css class that specifies background image to be used as the icon image:
  31419. * .do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
  31420. */
  31421. /**
  31422. * @cfg {Boolean} [disabled=false]
  31423. * True to disable all components configured by this Action, false to enable them.
  31424. */
  31425. /**
  31426. * @cfg {Boolean} [hidden=false]
  31427. * True to hide all components configured by this Action, false to show them.
  31428. */
  31429. /**
  31430. * @cfg {Function} handler
  31431. * The function that will be invoked by each component tied to this Action
  31432. * when the component's primary event is triggered.
  31433. */
  31434. /**
  31435. * @cfg {String} itemId
  31436. * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}.
  31437. */
  31438. /**
  31439. * @cfg {Object} scope
  31440. * The scope (this reference) in which the {@link #handler} is executed.
  31441. * Defaults to the browser window.
  31442. */
  31443. /**
  31444. * Creates new Action.
  31445. * @param {Object} config Config object.
  31446. */
  31447. constructor : function(config){
  31448. this.initialConfig = config;
  31449. this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
  31450. this.items = [];
  31451. },
  31452. /*
  31453. * @property {Boolean} isAction
  31454. * `true` in this class to identify an object as an instantiated Action, or subclass thereof.
  31455. */
  31456. isAction : true,
  31457. /**
  31458. * Sets the text to be displayed by all components configured by this Action.
  31459. * @param {String} text The text to display
  31460. */
  31461. setText : function(text){
  31462. this.initialConfig.text = text;
  31463. this.callEach('setText', [text]);
  31464. },
  31465. /**
  31466. * Gets the text currently displayed by all components configured by this Action.
  31467. */
  31468. getText : function(){
  31469. return this.initialConfig.text;
  31470. },
  31471. /**
  31472. * Sets the icon CSS class for all components configured by this Action. The class should supply
  31473. * a background image that will be used as the icon image.
  31474. * @param {String} cls The CSS class supplying the icon image
  31475. */
  31476. setIconCls : function(cls){
  31477. this.initialConfig.iconCls = cls;
  31478. this.callEach('setIconCls', [cls]);
  31479. },
  31480. /**
  31481. * Gets the icon CSS class currently used by all components configured by this Action.
  31482. */
  31483. getIconCls : function(){
  31484. return this.initialConfig.iconCls;
  31485. },
  31486. /**
  31487. * Sets the disabled state of all components configured by this Action. Shortcut method
  31488. * for {@link #enable} and {@link #disable}.
  31489. * @param {Boolean} disabled True to disable the component, false to enable it
  31490. */
  31491. setDisabled : function(v){
  31492. this.initialConfig.disabled = v;
  31493. this.callEach('setDisabled', [v]);
  31494. },
  31495. /**
  31496. * Enables all components configured by this Action.
  31497. */
  31498. enable : function(){
  31499. this.setDisabled(false);
  31500. },
  31501. /**
  31502. * Disables all components configured by this Action.
  31503. */
  31504. disable : function(){
  31505. this.setDisabled(true);
  31506. },
  31507. /**
  31508. * Returns true if the components using this Action are currently disabled, else returns false.
  31509. */
  31510. isDisabled : function(){
  31511. return this.initialConfig.disabled;
  31512. },
  31513. /**
  31514. * Sets the hidden state of all components configured by this Action. Shortcut method
  31515. * for `{@link #hide}` and `{@link #show}`.
  31516. * @param {Boolean} hidden True to hide the component, false to show it.
  31517. */
  31518. setHidden : function(v){
  31519. this.initialConfig.hidden = v;
  31520. this.callEach('setVisible', [!v]);
  31521. },
  31522. /**
  31523. * Shows all components configured by this Action.
  31524. */
  31525. show : function(){
  31526. this.setHidden(false);
  31527. },
  31528. /**
  31529. * Hides all components configured by this Action.
  31530. */
  31531. hide : function(){
  31532. this.setHidden(true);
  31533. },
  31534. /**
  31535. * Returns true if the components configured by this Action are currently hidden, else returns false.
  31536. */
  31537. isHidden : function(){
  31538. return this.initialConfig.hidden;
  31539. },
  31540. /**
  31541. * Sets the function that will be called by each Component using this action when its primary event is triggered.
  31542. * @param {Function} fn The function that will be invoked by the action's components. The function
  31543. * will be called with no arguments.
  31544. * @param {Object} scope The scope (this reference) in which the function is executed. Defaults to the Component
  31545. * firing the event.
  31546. */
  31547. setHandler : function(fn, scope){
  31548. this.initialConfig.handler = fn;
  31549. this.initialConfig.scope = scope;
  31550. this.callEach('setHandler', [fn, scope]);
  31551. },
  31552. /**
  31553. * Executes the specified function once for each Component currently tied to this Action. The function passed
  31554. * in should accept a single argument that will be an object that supports the basic Action config/method interface.
  31555. * @param {Function} fn The function to execute for each component
  31556. * @param {Object} scope The scope (this reference) in which the function is executed.
  31557. * Defaults to the Component.
  31558. */
  31559. each : function(fn, scope){
  31560. Ext.each(this.items, fn, scope);
  31561. },
  31562. // private
  31563. callEach : function(fnName, args){
  31564. var items = this.items,
  31565. i = 0,
  31566. len = items.length,
  31567. item;
  31568. Ext.suspendLayouts();
  31569. for(; i < len; i++){
  31570. item = items[i];
  31571. item[fnName].apply(item, args);
  31572. }
  31573. Ext.resumeLayouts(true);
  31574. },
  31575. // private
  31576. addComponent : function(comp){
  31577. this.items.push(comp);
  31578. comp.on('destroy', this.removeComponent, this);
  31579. },
  31580. // private
  31581. removeComponent : function(comp){
  31582. Ext.Array.remove(this.items, comp);
  31583. },
  31584. /**
  31585. * Executes this Action manually using the handler function specified in the original config object
  31586. * or the handler function set with {@link #setHandler}. Any arguments passed to this
  31587. * function will be passed on to the handler function.
  31588. * @param {Object...} args Variable number of arguments passed to the handler function
  31589. */
  31590. execute : function(){
  31591. this.initialConfig.handler.apply(this.initialConfig.scope || Ext.global, arguments);
  31592. }
  31593. });
  31594. /**
  31595. * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
  31596. * to a configured URL, or to a URL specified at request time.
  31597. *
  31598. * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
  31599. * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
  31600. * in the request options object, or an {@link #requestcomplete event listener}.
  31601. *
  31602. * # File Uploads
  31603. *
  31604. * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
  31605. * Instead the form is submitted in the standard manner with the DOM &lt;form&gt; element temporarily modified to have its
  31606. * target set to refer to a dynamically generated, hidden &lt;iframe&gt; which is inserted into the document but removed
  31607. * after the return data has been gathered.
  31608. *
  31609. * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
  31610. * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
  31611. * insert the text unchanged into the document body.
  31612. *
  31613. * Characters which are significant to an HTML parser must be sent as HTML entities, so encode `<` as `&lt;`, `&` as
  31614. * `&amp;` etc.
  31615. *
  31616. * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
  31617. * responseText property in order to conform to the requirements of event handlers and callbacks.
  31618. *
  31619. * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
  31620. * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
  31621. * packet content.
  31622. *
  31623. * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.
  31624. */
  31625. Ext.define('Ext.data.Connection', {
  31626. mixins: {
  31627. observable: 'Ext.util.Observable'
  31628. },
  31629. statics: {
  31630. requestId: 0
  31631. },
  31632. url: null,
  31633. async: true,
  31634. method: null,
  31635. username: '',
  31636. password: '',
  31637. /**
  31638. * @cfg {Boolean} disableCaching
  31639. * True to add a unique cache-buster param to GET requests.
  31640. */
  31641. disableCaching: true,
  31642. /**
  31643. * @cfg {Boolean} withCredentials
  31644. * True to set `withCredentials = true` on the XHR object
  31645. */
  31646. withCredentials: false,
  31647. /**
  31648. * @cfg {Boolean} cors
  31649. * True to enable CORS support on the XHR object. Currently the only effect of this option
  31650. * is to use the XDomainRequest object instead of XMLHttpRequest if the browser is IE8 or above.
  31651. */
  31652. cors: false,
  31653. /**
  31654. * @cfg {String} disableCachingParam
  31655. * Change the parameter which is sent went disabling caching through a cache buster.
  31656. */
  31657. disableCachingParam: '_dc',
  31658. /**
  31659. * @cfg {Number} timeout
  31660. * The timeout in milliseconds to be used for requests.
  31661. */
  31662. timeout : 30000,
  31663. /**
  31664. * @cfg {Object} extraParams
  31665. * Any parameters to be appended to the request.
  31666. */
  31667. /**
  31668. * @cfg {Boolean} [autoAbort=false]
  31669. * Whether this request should abort any pending requests.
  31670. */
  31671. /**
  31672. * @cfg {String} method
  31673. * The default HTTP method to be used for requests.
  31674. *
  31675. * If not set, but {@link #request} params are present, POST will be used;
  31676. * otherwise, GET will be used.
  31677. */
  31678. /**
  31679. * @cfg {Object} defaultHeaders
  31680. * An object containing request headers which are added to each request made by this object.
  31681. */
  31682. useDefaultHeader : true,
  31683. defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
  31684. useDefaultXhrHeader : true,
  31685. defaultXhrHeader : 'XMLHttpRequest',
  31686. constructor : function(config) {
  31687. config = config || {};
  31688. Ext.apply(this, config);
  31689. /**
  31690. * @event beforerequest
  31691. * Fires before a network request is made to retrieve a data object.
  31692. * @param {Ext.data.Connection} conn This Connection object.
  31693. * @param {Object} options The options config object passed to the {@link #request} method.
  31694. */
  31695. /**
  31696. * @event requestcomplete
  31697. * Fires if the request was successfully completed.
  31698. * @param {Ext.data.Connection} conn This Connection object.
  31699. * @param {Object} response The XHR object containing the response data.
  31700. * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
  31701. * @param {Object} options The options config object passed to the {@link #request} method.
  31702. */
  31703. /**
  31704. * @event requestexception
  31705. * Fires if an error HTTP status was returned from the server.
  31706. * See [HTTP Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
  31707. * for details of HTTP status codes.
  31708. * @param {Ext.data.Connection} conn This Connection object.
  31709. * @param {Object} response The XHR object containing the response data.
  31710. * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
  31711. * @param {Object} options The options config object passed to the {@link #request} method.
  31712. */
  31713. this.requests = {};
  31714. this.mixins.observable.constructor.call(this);
  31715. },
  31716. /**
  31717. * Sends an HTTP request to a remote server.
  31718. *
  31719. * **Important:** Ajax server requests are asynchronous, and this call will
  31720. * return before the response has been received. Process any returned data
  31721. * in a callback function.
  31722. *
  31723. * Ext.Ajax.request({
  31724. * url: 'ajax_demo/sample.json',
  31725. * success: function(response, opts) {
  31726. * var obj = Ext.decode(response.responseText);
  31727. * console.dir(obj);
  31728. * },
  31729. * failure: function(response, opts) {
  31730. * console.log('server-side failure with status code ' + response.status);
  31731. * }
  31732. * });
  31733. *
  31734. * To execute a callback function in the correct scope, use the `scope` option.
  31735. *
  31736. * @param {Object} options An object which may contain the following properties:
  31737. *
  31738. * (The options object may also contain any other property which might be needed to perform
  31739. * postprocessing in a callback because it is passed to callback functions.)
  31740. *
  31741. * @param {String/Function} options.url The URL to which to send the request, or a function
  31742. * to call which returns a URL string. The scope of the function is specified by the `scope` option.
  31743. * Defaults to the configured `url`.
  31744. *
  31745. * @param {Object/String/Function} options.params An object containing properties which are
  31746. * used as parameters to the request, a url encoded string or a function to call to get either. The scope
  31747. * of the function is specified by the `scope` option.
  31748. *
  31749. * @param {String} options.method The HTTP method to use
  31750. * for the request. Defaults to the configured method, or if no method was configured,
  31751. * "GET" if no parameters are being sent, and "POST" if parameters are being sent. Note that
  31752. * the method name is case-sensitive and should be all caps.
  31753. *
  31754. * @param {Function} options.callback The function to be called upon receipt of the HTTP response.
  31755. * The callback is called regardless of success or failure and is passed the following parameters:
  31756. * @param {Object} options.callback.options The parameter to the request call.
  31757. * @param {Boolean} options.callback.success True if the request succeeded.
  31758. * @param {Object} options.callback.response The XMLHttpRequest object containing the response data.
  31759. * See [www.w3.org/TR/XMLHttpRequest/](http://www.w3.org/TR/XMLHttpRequest/) for details about
  31760. * accessing elements of the response.
  31761. *
  31762. * @param {Function} options.success The function to be called upon success of the request.
  31763. * The callback is passed the following parameters:
  31764. * @param {Object} options.success.response The XMLHttpRequest object containing the response data.
  31765. * @param {Object} options.success.options The parameter to the request call.
  31766. *
  31767. * @param {Function} options.failure The function to be called upon failure of the request.
  31768. * The callback is passed the following parameters:
  31769. * @param {Object} options.failure.response The XMLHttpRequest object containing the response data.
  31770. * @param {Object} options.failure.options The parameter to the request call.
  31771. *
  31772. * @param {Object} options.scope The scope in which to execute the callbacks: The "this" object for
  31773. * the callback function. If the `url`, or `params` options were specified as functions from which to
  31774. * draw values, then this also serves as the scope for those function calls. Defaults to the browser
  31775. * window.
  31776. *
  31777. * @param {Number} options.timeout The timeout in milliseconds to be used for this request.
  31778. * Defaults to 30 seconds.
  31779. *
  31780. * @param {Ext.Element/HTMLElement/String} options.form The `<form>` Element or the id of the `<form>`
  31781. * to pull parameters from.
  31782. *
  31783. * @param {Boolean} options.isUpload **Only meaningful when used with the `form` option.**
  31784. *
  31785. * True if the form object is a file upload (will be set automatically if the form was configured
  31786. * with **`enctype`** `"multipart/form-data"`).
  31787. *
  31788. * File uploads are not performed using normal "Ajax" techniques, that is they are **not**
  31789. * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
  31790. * DOM `<form>` element temporarily modified to have its [target][] set to refer to a dynamically
  31791. * generated, hidden `<iframe>` which is inserted into the document but removed after the return data
  31792. * has been gathered.
  31793. *
  31794. * The server response is parsed by the browser to create the document for the IFRAME. If the
  31795. * server is using JSON to send the return object, then the [Content-Type][] header must be set to
  31796. * "text/html" in order to tell the browser to insert the text unchanged into the document body.
  31797. *
  31798. * The response text is retrieved from the document, and a fake XMLHttpRequest object is created
  31799. * containing a `responseText` property in order to conform to the requirements of event handlers
  31800. * and callbacks.
  31801. *
  31802. * Be aware that file upload packets are sent with the content type [multipart/form][] and some server
  31803. * technologies (notably JEE) may require some custom processing in order to retrieve parameter names
  31804. * and parameter values from the packet content.
  31805. *
  31806. * [target]: http://www.w3.org/TR/REC-html40/present/frames.html#adef-target
  31807. * [Content-Type]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
  31808. * [multipart/form]: http://www.faqs.org/rfcs/rfc2388.html
  31809. *
  31810. * @param {Object} options.headers Request headers to set for the request.
  31811. *
  31812. * @param {Object} options.xmlData XML document to use for the post. Note: This will be used instead
  31813. * of params for the post data. Any params will be appended to the URL.
  31814. *
  31815. * @param {Object/String} options.jsonData JSON data to use as the post. Note: This will be used
  31816. * instead of params for the post data. Any params will be appended to the URL.
  31817. *
  31818. * @param {Boolean} options.disableCaching True to add a unique cache-buster param to GET requests.
  31819. *
  31820. * @param {Boolean} options.withCredentials True to add the withCredentials property to the XHR object
  31821. *
  31822. * @return {Object} The request object. This may be used to cancel the request.
  31823. */
  31824. request : function(options) {
  31825. options = options || {};
  31826. var me = this,
  31827. scope = options.scope || window,
  31828. username = options.username || me.username,
  31829. password = options.password || me.password || '',
  31830. async,
  31831. requestOptions,
  31832. request,
  31833. headers,
  31834. xhr;
  31835. if (me.fireEvent('beforerequest', me, options) !== false) {
  31836. requestOptions = me.setOptions(options, scope);
  31837. if (me.isFormUpload(options)) {
  31838. me.upload(options.form, requestOptions.url, requestOptions.data, options);
  31839. return null;
  31840. }
  31841. // if autoabort is set, cancel the current transactions
  31842. if (options.autoAbort || me.autoAbort) {
  31843. me.abort();
  31844. }
  31845. // create a connection object
  31846. async = options.async !== false ? (options.async || me.async) : false;
  31847. xhr = me.openRequest(options, requestOptions, async, username, password);
  31848. headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
  31849. // create the transaction object
  31850. request = {
  31851. id: ++Ext.data.Connection.requestId,
  31852. xhr: xhr,
  31853. headers: headers,
  31854. options: options,
  31855. async: async,
  31856. timeout: setTimeout(function() {
  31857. request.timedout = true;
  31858. me.abort(request);
  31859. }, options.timeout || me.timeout)
  31860. };
  31861. me.requests[request.id] = request;
  31862. me.latestId = request.id;
  31863. // bind our statechange listener
  31864. if (async) {
  31865. xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
  31866. }
  31867. // start the request!
  31868. xhr.send(requestOptions.data);
  31869. if (!async) {
  31870. return me.onComplete(request);
  31871. }
  31872. return request;
  31873. } else {
  31874. Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
  31875. return null;
  31876. }
  31877. },
  31878. /**
  31879. * Uploads a form using a hidden iframe.
  31880. * @param {String/HTMLElement/Ext.Element} form The form to upload
  31881. * @param {String} url The url to post to
  31882. * @param {String} params Any extra parameters to pass
  31883. * @param {Object} options The initial options
  31884. */
  31885. upload: function(form, url, params, options) {
  31886. form = Ext.getDom(form);
  31887. options = options || {};
  31888. var id = Ext.id(),
  31889. frame = document.createElement('iframe'),
  31890. hiddens = [],
  31891. encoding = 'multipart/form-data',
  31892. buf = {
  31893. target: form.target,
  31894. method: form.method,
  31895. encoding: form.encoding,
  31896. enctype: form.enctype,
  31897. action: form.action
  31898. },
  31899. addField = function(name, value) {
  31900. hiddenItem = document.createElement('input');
  31901. Ext.fly(hiddenItem).set({
  31902. type: 'hidden',
  31903. value: value,
  31904. name: name
  31905. });
  31906. form.appendChild(hiddenItem);
  31907. hiddens.push(hiddenItem);
  31908. },
  31909. hiddenItem, obj, value, name, vLen, v, hLen, h;
  31910. /*
  31911. * Originally this behaviour was modified for Opera 10 to apply the secure URL after
  31912. * the frame had been added to the document. It seems this has since been corrected in
  31913. * Opera so the behaviour has been reverted, the URL will be set before being added.
  31914. */
  31915. Ext.fly(frame).set({
  31916. id: id,
  31917. name: id,
  31918. cls: Ext.baseCSSPrefix + 'hide-display',
  31919. src: Ext.SSL_SECURE_URL
  31920. });
  31921. document.body.appendChild(frame);
  31922. // This is required so that IE doesn't pop the response up in a new window.
  31923. if (document.frames) {
  31924. document.frames[id].name = id;
  31925. }
  31926. Ext.fly(form).set({
  31927. target: id,
  31928. method: 'POST',
  31929. enctype: encoding,
  31930. encoding: encoding,
  31931. action: url || buf.action
  31932. });
  31933. // add dynamic params
  31934. if (params) {
  31935. obj = Ext.Object.fromQueryString(params) || {};
  31936. for (name in obj) {
  31937. if (obj.hasOwnProperty(name)) {
  31938. value = obj[name];
  31939. if (Ext.isArray(value)) {
  31940. vLen = value.length;
  31941. for (v = 0; v < vLen; v++) {
  31942. addField(name, value[v]);
  31943. }
  31944. } else {
  31945. addField(name, value);
  31946. }
  31947. }
  31948. }
  31949. }
  31950. Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
  31951. form.submit();
  31952. Ext.fly(form).set(buf);
  31953. hLen = hiddens.length;
  31954. for (h = 0; h < hLen; h++) {
  31955. Ext.removeNode(hiddens[h]);
  31956. }
  31957. },
  31958. /**
  31959. * @private
  31960. * Callback handler for the upload function. After we've submitted the form via the iframe this creates a bogus
  31961. * response object to simulate an XHR and populates its responseText from the now-loaded iframe's document body
  31962. * (or a textarea inside the body). We then clean up by removing the iframe
  31963. */
  31964. onUploadComplete: function(frame, options) {
  31965. var me = this,
  31966. // bogus response object
  31967. response = {
  31968. responseText: '',
  31969. responseXML: null
  31970. }, doc, contentNode;
  31971. try {
  31972. doc = frame.contentWindow.document || frame.contentDocument || window.frames[frame.id].document;
  31973. if (doc) {
  31974. if (doc.body) {
  31975. // Response sent as Content-Type: text/json or text/plain. Browser will embed in a <pre> element
  31976. // Note: The statement below tests the result of an assignment.
  31977. if ((contentNode = doc.body.firstChild) && /pre/i.test(contentNode.tagName)) {
  31978. response.responseText = contentNode.innerText;
  31979. }
  31980. // Response sent as Content-Type: text/html. We must still support JSON response wrapped in textarea.
  31981. // Note: The statement below tests the result of an assignment.
  31982. else if (contentNode = doc.getElementsByTagName('textarea')[0]) {
  31983. response.responseText = contentNode.value;
  31984. }
  31985. // Response sent as Content-Type: text/html with no wrapping. Scrape JSON response out of text
  31986. else {
  31987. response.responseText = doc.body.textContent || doc.body.innerText;
  31988. }
  31989. }
  31990. //in IE the document may still have a body even if returns XML.
  31991. response.responseXML = doc.XMLDocument || doc;
  31992. }
  31993. } catch (e) {
  31994. }
  31995. me.fireEvent('requestcomplete', me, response, options);
  31996. Ext.callback(options.success, options.scope, [response, options]);
  31997. Ext.callback(options.callback, options.scope, [options, true, response]);
  31998. setTimeout(function() {
  31999. Ext.removeNode(frame);
  32000. }, 100);
  32001. },
  32002. /**
  32003. * Detects whether the form is intended to be used for an upload.
  32004. * @private
  32005. */
  32006. isFormUpload: function(options) {
  32007. var form = this.getForm(options);
  32008. if (form) {
  32009. return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
  32010. }
  32011. return false;
  32012. },
  32013. /**
  32014. * Gets the form object from options.
  32015. * @private
  32016. * @param {Object} options The request options
  32017. * @return {HTMLElement} The form, null if not passed
  32018. */
  32019. getForm: function(options) {
  32020. return Ext.getDom(options.form) || null;
  32021. },
  32022. /**
  32023. * Sets various options such as the url, params for the request
  32024. * @param {Object} options The initial options
  32025. * @param {Object} scope The scope to execute in
  32026. * @return {Object} The params for the request
  32027. */
  32028. setOptions: function(options, scope) {
  32029. var me = this,
  32030. params = options.params || {},
  32031. extraParams = me.extraParams,
  32032. urlParams = options.urlParams,
  32033. url = options.url || me.url,
  32034. jsonData = options.jsonData,
  32035. method,
  32036. disableCache,
  32037. data;
  32038. // allow params to be a method that returns the params object
  32039. if (Ext.isFunction(params)) {
  32040. params = params.call(scope, options);
  32041. }
  32042. // allow url to be a method that returns the actual url
  32043. if (Ext.isFunction(url)) {
  32044. url = url.call(scope, options);
  32045. }
  32046. url = this.setupUrl(options, url);
  32047. if (!url) {
  32048. Ext.Error.raise({
  32049. options: options,
  32050. msg: 'No URL specified'
  32051. });
  32052. }
  32053. // check for xml or json data, and make sure json data is encoded
  32054. data = options.rawData || options.xmlData || jsonData || null;
  32055. if (jsonData && !Ext.isPrimitive(jsonData)) {
  32056. data = Ext.encode(data);
  32057. }
  32058. // make sure params are a url encoded string and include any extraParams if specified
  32059. if (Ext.isObject(params)) {
  32060. params = Ext.Object.toQueryString(params);
  32061. }
  32062. if (Ext.isObject(extraParams)) {
  32063. extraParams = Ext.Object.toQueryString(extraParams);
  32064. }
  32065. params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
  32066. urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
  32067. params = this.setupParams(options, params);
  32068. // decide the proper method for this request
  32069. method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
  32070. this.setupMethod(options, method);
  32071. disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
  32072. // if the method is get append date to prevent caching
  32073. if (method === 'GET' && disableCache) {
  32074. url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
  32075. }
  32076. // if the method is get or there is json/xml data append the params to the url
  32077. if ((method == 'GET' || data) && params) {
  32078. url = Ext.urlAppend(url, params);
  32079. params = null;
  32080. }
  32081. // allow params to be forced into the url
  32082. if (urlParams) {
  32083. url = Ext.urlAppend(url, urlParams);
  32084. }
  32085. return {
  32086. url: url,
  32087. method: method,
  32088. data: data || params || null
  32089. };
  32090. },
  32091. /**
  32092. * Template method for overriding url
  32093. * @template
  32094. * @private
  32095. * @param {Object} options
  32096. * @param {String} url
  32097. * @return {String} The modified url
  32098. */
  32099. setupUrl: function(options, url) {
  32100. var form = this.getForm(options);
  32101. if (form) {
  32102. url = url || form.action;
  32103. }
  32104. return url;
  32105. },
  32106. /**
  32107. * Template method for overriding params
  32108. * @template
  32109. * @private
  32110. * @param {Object} options
  32111. * @param {String} params
  32112. * @return {String} The modified params
  32113. */
  32114. setupParams: function(options, params) {
  32115. var form = this.getForm(options),
  32116. serializedForm;
  32117. if (form && !this.isFormUpload(options)) {
  32118. serializedForm = Ext.Element.serializeForm(form);
  32119. params = params ? (params + '&' + serializedForm) : serializedForm;
  32120. }
  32121. return params;
  32122. },
  32123. /**
  32124. * Template method for overriding method
  32125. * @template
  32126. * @private
  32127. * @param {Object} options
  32128. * @param {String} method
  32129. * @return {String} The modified method
  32130. */
  32131. setupMethod: function(options, method) {
  32132. if (this.isFormUpload(options)) {
  32133. return 'POST';
  32134. }
  32135. return method;
  32136. },
  32137. /**
  32138. * Setup all the headers for the request
  32139. * @private
  32140. * @param {Object} xhr The xhr object
  32141. * @param {Object} options The options for the request
  32142. * @param {Object} data The data for the request
  32143. * @param {Object} params The params for the request
  32144. */
  32145. setupHeaders: function(xhr, options, data, params) {
  32146. var me = this,
  32147. headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
  32148. contentType = me.defaultPostHeader,
  32149. jsonData = options.jsonData,
  32150. xmlData = options.xmlData,
  32151. key,
  32152. header;
  32153. if (!headers['Content-Type'] && (data || params)) {
  32154. if (data) {
  32155. if (options.rawData) {
  32156. contentType = 'text/plain';
  32157. } else {
  32158. if (xmlData && Ext.isDefined(xmlData)) {
  32159. contentType = 'text/xml';
  32160. } else if (jsonData && Ext.isDefined(jsonData)) {
  32161. contentType = 'application/json';
  32162. }
  32163. }
  32164. }
  32165. headers['Content-Type'] = contentType;
  32166. }
  32167. if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
  32168. headers['X-Requested-With'] = me.defaultXhrHeader;
  32169. }
  32170. // set up all the request headers on the xhr object
  32171. try {
  32172. for (key in headers) {
  32173. if (headers.hasOwnProperty(key)) {
  32174. header = headers[key];
  32175. xhr.setRequestHeader(key, header);
  32176. }
  32177. }
  32178. } catch(e) {
  32179. me.fireEvent('exception', key, header);
  32180. }
  32181. return headers;
  32182. },
  32183. /**
  32184. * Creates the appropriate XHR transport for a given request on this browser. On IE
  32185. * this may be an `XDomainRequest` rather than an `XMLHttpRequest`.
  32186. * @private
  32187. */
  32188. newRequest: function (options) {
  32189. var xhr;
  32190. if ((options.cors || this.cors) && Ext.isIE && Ext.ieVersion >= 8) {
  32191. xhr = new XDomainRequest();
  32192. } else {
  32193. xhr = this.getXhrInstance();
  32194. }
  32195. return xhr;
  32196. },
  32197. /**
  32198. * Creates and opens an appropriate XHR transport for a given request on this browser.
  32199. * This logic is contained in an individual method to allow for overrides to process all
  32200. * of the parameters and options and return a suitable, open connection.
  32201. * @private
  32202. */
  32203. openRequest: function (options, requestOptions, async, username, password) {
  32204. var xhr = this.newRequest(options);
  32205. if (username) {
  32206. xhr.open(requestOptions.method, requestOptions.url, async, username, password);
  32207. } else {
  32208. xhr.open(requestOptions.method, requestOptions.url, async);
  32209. }
  32210. if (options.withCredentials || this.withCredentials) {
  32211. xhr.withCredentials = true;
  32212. }
  32213. return xhr;
  32214. },
  32215. /**
  32216. * Creates the appropriate XHR transport for this browser.
  32217. * @private
  32218. */
  32219. getXhrInstance: (function() {
  32220. var options = [function() {
  32221. return new XMLHttpRequest();
  32222. }, function() {
  32223. return new ActiveXObject('MSXML2.XMLHTTP.3.0');
  32224. }, function() {
  32225. return new ActiveXObject('MSXML2.XMLHTTP');
  32226. }, function() {
  32227. return new ActiveXObject('Microsoft.XMLHTTP');
  32228. }], i = 0,
  32229. len = options.length,
  32230. xhr;
  32231. for (; i < len; ++i) {
  32232. try {
  32233. xhr = options[i];
  32234. xhr();
  32235. break;
  32236. } catch(e) {
  32237. }
  32238. }
  32239. return xhr;
  32240. }()),
  32241. /**
  32242. * Determines whether this object has a request outstanding.
  32243. * @param {Object} [request] Defaults to the last transaction
  32244. * @return {Boolean} True if there is an outstanding request.
  32245. */
  32246. isLoading : function(request) {
  32247. if (!request) {
  32248. request = this.getLatest();
  32249. }
  32250. if (!(request && request.xhr)) {
  32251. return false;
  32252. }
  32253. // if there is a connection and readyState is not 0 or 4
  32254. var state = request.xhr.readyState;
  32255. return !(state === 0 || state == 4);
  32256. },
  32257. /**
  32258. * Aborts an active request.
  32259. * @param {Object} [request] Defaults to the last request
  32260. */
  32261. abort : function(request) {
  32262. var me = this,
  32263. xhr;
  32264. if (!request) {
  32265. request = me.getLatest();
  32266. }
  32267. if (request && me.isLoading(request)) {
  32268. /*
  32269. * Clear out the onreadystatechange here, this allows us
  32270. * greater control, the browser may/may not fire the function
  32271. * depending on a series of conditions.
  32272. */
  32273. xhr = request.xhr;
  32274. try {
  32275. xhr.onreadystatechange = null;
  32276. } catch (e) {
  32277. // Setting onreadystatechange to null can cause problems in IE, see
  32278. // http://www.quirksmode.org/blog/archives/2005/09/xmlhttp_notes_a_1.html
  32279. xhr = Ext.emptyFn;
  32280. }
  32281. xhr.abort();
  32282. me.clearTimeout(request);
  32283. if (!request.timedout) {
  32284. request.aborted = true;
  32285. }
  32286. me.onComplete(request);
  32287. me.cleanup(request);
  32288. }
  32289. },
  32290. /**
  32291. * Aborts all active requests
  32292. */
  32293. abortAll: function(){
  32294. var requests = this.requests,
  32295. id;
  32296. for (id in requests) {
  32297. if (requests.hasOwnProperty(id)) {
  32298. this.abort(requests[id]);
  32299. }
  32300. }
  32301. },
  32302. /**
  32303. * Gets the most recent request
  32304. * @private
  32305. * @return {Object} The request. Null if there is no recent request
  32306. */
  32307. getLatest: function(){
  32308. var id = this.latestId,
  32309. request;
  32310. if (id) {
  32311. request = this.requests[id];
  32312. }
  32313. return request || null;
  32314. },
  32315. /**
  32316. * Fires when the state of the xhr changes
  32317. * @private
  32318. * @param {Object} request The request
  32319. */
  32320. onStateChange : function(request) {
  32321. if (request.xhr.readyState == 4) {
  32322. this.clearTimeout(request);
  32323. this.onComplete(request);
  32324. this.cleanup(request);
  32325. }
  32326. },
  32327. /**
  32328. * Clears the timeout on the request
  32329. * @private
  32330. * @param {Object} The request
  32331. */
  32332. clearTimeout: function(request) {
  32333. clearTimeout(request.timeout);
  32334. delete request.timeout;
  32335. },
  32336. /**
  32337. * Cleans up any left over information from the request
  32338. * @private
  32339. * @param {Object} The request
  32340. */
  32341. cleanup: function(request) {
  32342. request.xhr = null;
  32343. delete request.xhr;
  32344. },
  32345. /**
  32346. * To be called when the request has come back from the server
  32347. * @private
  32348. * @param {Object} request
  32349. * @return {Object} The response
  32350. */
  32351. onComplete : function(request) {
  32352. var me = this,
  32353. options = request.options,
  32354. result,
  32355. success,
  32356. response;
  32357. try {
  32358. result = me.parseStatus(request.xhr.status);
  32359. } catch (e) {
  32360. // in some browsers we can't access the status if the readyState is not 4, so the request has failed
  32361. result = {
  32362. success : false,
  32363. isException : false
  32364. };
  32365. }
  32366. success = result.success;
  32367. if (success) {
  32368. response = me.createResponse(request);
  32369. me.fireEvent('requestcomplete', me, response, options);
  32370. Ext.callback(options.success, options.scope, [response, options]);
  32371. } else {
  32372. if (result.isException || request.aborted || request.timedout) {
  32373. response = me.createException(request);
  32374. } else {
  32375. response = me.createResponse(request);
  32376. }
  32377. me.fireEvent('requestexception', me, response, options);
  32378. Ext.callback(options.failure, options.scope, [response, options]);
  32379. }
  32380. Ext.callback(options.callback, options.scope, [options, success, response]);
  32381. delete me.requests[request.id];
  32382. return response;
  32383. },
  32384. /**
  32385. * Checks if the response status was successful
  32386. * @param {Number} status The status code
  32387. * @return {Object} An object containing success/status state
  32388. */
  32389. parseStatus: function(status) {
  32390. // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
  32391. status = status == 1223 ? 204 : status;
  32392. var success = (status >= 200 && status < 300) || status == 304,
  32393. isException = false;
  32394. if (!success) {
  32395. switch (status) {
  32396. case 12002:
  32397. case 12029:
  32398. case 12030:
  32399. case 12031:
  32400. case 12152:
  32401. case 13030:
  32402. isException = true;
  32403. break;
  32404. }
  32405. }
  32406. return {
  32407. success: success,
  32408. isException: isException
  32409. };
  32410. },
  32411. /**
  32412. * Creates the response object
  32413. * @private
  32414. * @param {Object} request
  32415. */
  32416. createResponse : function(request) {
  32417. var xhr = request.xhr,
  32418. headers = {},
  32419. lines = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n'),
  32420. count = lines.length,
  32421. line, index, key, value, response;
  32422. while (count--) {
  32423. line = lines[count];
  32424. index = line.indexOf(':');
  32425. if (index >= 0) {
  32426. key = line.substr(0, index).toLowerCase();
  32427. if (line.charAt(index + 1) == ' ') {
  32428. ++index;
  32429. }
  32430. headers[key] = line.substr(index + 1);
  32431. }
  32432. }
  32433. request.xhr = null;
  32434. delete request.xhr;
  32435. response = {
  32436. request: request,
  32437. requestId : request.id,
  32438. status : xhr.status,
  32439. statusText : xhr.statusText,
  32440. getResponseHeader : function(header) {
  32441. return headers[header.toLowerCase()];
  32442. },
  32443. getAllResponseHeaders : function() {
  32444. return headers;
  32445. },
  32446. responseText : xhr.responseText,
  32447. responseXML : xhr.responseXML
  32448. };
  32449. // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
  32450. // functions created with getResponseHeader/getAllResponseHeaders
  32451. xhr = null;
  32452. return response;
  32453. },
  32454. /**
  32455. * Creates the exception object
  32456. * @private
  32457. * @param {Object} request
  32458. */
  32459. createException : function(request) {
  32460. return {
  32461. request : request,
  32462. requestId : request.id,
  32463. status : request.aborted ? -1 : 0,
  32464. statusText : request.aborted ? 'transaction aborted' : 'communication failure',
  32465. aborted: request.aborted,
  32466. timedout: request.timedout
  32467. };
  32468. }
  32469. });
  32470. /**
  32471. * @class Ext.Ajax
  32472. * @singleton
  32473. * @markdown
  32474. A singleton instance of an {@link Ext.data.Connection}. This class
  32475. is used to communicate with your server side code. It can be used as follows:
  32476. Ext.Ajax.request({
  32477. url: 'page.php',
  32478. params: {
  32479. id: 1
  32480. },
  32481. success: function(response){
  32482. var text = response.responseText;
  32483. // process server response here
  32484. }
  32485. });
  32486. Default options for all requests can be set by changing a property on the Ext.Ajax class:
  32487. Ext.Ajax.timeout = 60000; // 60 seconds
  32488. Any options specified in the request method for the Ajax request will override any
  32489. defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
  32490. request will be 60 seconds.
  32491. Ext.Ajax.timeout = 120000; // 120 seconds
  32492. Ext.Ajax.request({
  32493. url: 'page.aspx',
  32494. timeout: 60000
  32495. });
  32496. In general, this class will be used for all Ajax requests in your application.
  32497. The main reason for creating a separate {@link Ext.data.Connection} is for a
  32498. series of requests that share common settings that are different to all other
  32499. requests in the application.
  32500. */
  32501. Ext.define('Ext.Ajax', {
  32502. extend: 'Ext.data.Connection',
  32503. singleton: true,
  32504. /**
  32505. * @cfg {Object} extraParams @hide
  32506. */
  32507. /**
  32508. * @cfg {Object} defaultHeaders @hide
  32509. */
  32510. /**
  32511. * @cfg {String} method @hide
  32512. */
  32513. /**
  32514. * @cfg {Number} timeout @hide
  32515. */
  32516. /**
  32517. * @cfg {Boolean} autoAbort @hide
  32518. */
  32519. /**
  32520. * @cfg {Boolean} disableCaching @hide
  32521. */
  32522. /**
  32523. * @property {Boolean} disableCaching
  32524. * True to add a unique cache-buster param to GET requests. Defaults to true.
  32525. */
  32526. /**
  32527. * @property {String} url
  32528. * The default URL to be used for requests to the server.
  32529. * If the server receives all requests through one URL, setting this once is easier than
  32530. * entering it on every request.
  32531. */
  32532. /**
  32533. * @property {Object} extraParams
  32534. * An object containing properties which are used as extra parameters to each request made
  32535. * by this object. Session information and other data that you need
  32536. * to pass with each request are commonly put here.
  32537. */
  32538. /**
  32539. * @property {Object} defaultHeaders
  32540. * An object containing request headers which are added to each request made by this object.
  32541. */
  32542. /**
  32543. * @property {String} method
  32544. * The default HTTP method to be used for requests. Note that this is case-sensitive and
  32545. * should be all caps (if not set but params are present will use
  32546. * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
  32547. */
  32548. /**
  32549. * @property {Number} timeout
  32550. * The timeout in milliseconds to be used for requests. Defaults to 30000.
  32551. */
  32552. /**
  32553. * @property {Boolean} autoAbort
  32554. * Whether a new request should abort any pending requests.
  32555. */
  32556. autoAbort : false
  32557. });
  32558. /**
  32559. * A mixin to add floating capability to a Component.
  32560. */
  32561. Ext.define('Ext.util.Floating', {
  32562. uses: ['Ext.Layer', 'Ext.window.Window'],
  32563. /**
  32564. * @cfg {Boolean} focusOnToFront
  32565. * Specifies whether the floated component should be automatically {@link Ext.Component#method-focus focused} when
  32566. * it is {@link #toFront brought to the front}.
  32567. */
  32568. focusOnToFront: true,
  32569. /**
  32570. * @cfg {String/Boolean} shadow
  32571. * Specifies whether the floating component should be given a shadow. Set to true to automatically create an
  32572. * {@link Ext.Shadow}, or a string indicating the shadow's display {@link Ext.Shadow#mode}. Set to false to
  32573. * disable the shadow.
  32574. */
  32575. shadow: 'sides',
  32576. /**
  32577. * @cfg {String/Boolean} shadowOffset
  32578. * Number of pixels to offset the shadow.
  32579. */
  32580. constructor: function (dom) {
  32581. var me = this;
  32582. me.el = new Ext.Layer(Ext.apply({
  32583. hideMode : me.hideMode,
  32584. hidden : me.hidden,
  32585. shadow : (typeof me.shadow != 'undefined') ? me.shadow : 'sides',
  32586. shadowOffset : me.shadowOffset,
  32587. constrain : false,
  32588. shim : (me.shim === false) ? false : undefined
  32589. }, me.floating), dom);
  32590. // release config object (if it was one)
  32591. me.floating = true;
  32592. // Register with the configured ownerCt.
  32593. // With this we acquire a floatParent for relative positioning, and a zIndexParent which is an
  32594. // ancestor floater which provides zIndex management.
  32595. me.registerWithOwnerCt();
  32596. },
  32597. registerWithOwnerCt: function() {
  32598. var me = this;
  32599. if (me.zIndexParent) {
  32600. me.zIndexParent.unregisterFloatingItem(me);
  32601. }
  32602. // Acquire a zIndexParent by traversing the ownerCt axis for the nearest floating ancestor
  32603. me.zIndexParent = me.up('[floating]');
  32604. me.setFloatParent(me.ownerCt);
  32605. delete me.ownerCt;
  32606. if (me.zIndexParent) {
  32607. me.zIndexParent.registerFloatingItem(me);
  32608. } else {
  32609. Ext.WindowManager.register(me);
  32610. }
  32611. },
  32612. setFloatParent: function(floatParent) {
  32613. var me = this;
  32614. // Remove listeners from previous floatParent
  32615. if (me.floatParent) {
  32616. me.mun(me.floatParent, {
  32617. hide: me.onFloatParentHide,
  32618. show: me.onFloatParentShow,
  32619. scope: me
  32620. });
  32621. }
  32622. me.floatParent = floatParent;
  32623. // Floating Components as children of Containers must hide when their parent hides.
  32624. if (floatParent) {
  32625. me.mon(me.floatParent, {
  32626. hide: me.onFloatParentHide,
  32627. show: me.onFloatParentShow,
  32628. scope: me
  32629. });
  32630. }
  32631. // If a floating Component is configured to be constrained, but has no configured
  32632. // constrainTo setting, set its constrainTo to be it's ownerCt before rendering.
  32633. if ((me.constrain || me.constrainHeader) && !me.constrainTo) {
  32634. me.constrainTo = floatParent ? floatParent.getTargetEl() : me.container;
  32635. }
  32636. },
  32637. onAfterFloatLayout: function(){
  32638. this.syncShadow();
  32639. },
  32640. onFloatParentHide: function() {
  32641. var me = this;
  32642. if (me.hideOnParentHide !== false && me.isVisible()) {
  32643. me.hide();
  32644. me.showOnParentShow = true;
  32645. }
  32646. },
  32647. onFloatParentShow: function() {
  32648. if (this.showOnParentShow) {
  32649. delete this.showOnParentShow;
  32650. this.show();
  32651. }
  32652. },
  32653. // private
  32654. // z-index is managed by the zIndexManager and may be overwritten at any time.
  32655. // Returns the next z-index to be used.
  32656. // If this is a Container, then it will have rebased any managed floating Components,
  32657. // and so the next available z-index will be approximately 10000 above that.
  32658. setZIndex: function(index) {
  32659. var me = this;
  32660. me.el.setZIndex(index);
  32661. // Next item goes 10 above;
  32662. index += 10;
  32663. // When a Container with floating descendants has its z-index set, it rebases any floating descendants it is managing.
  32664. // The returned value is a round number approximately 10000 above the last z-index used.
  32665. if (me.floatingDescendants) {
  32666. index = Math.floor(me.floatingDescendants.setBase(index) / 100) * 100 + 10000;
  32667. }
  32668. return index;
  32669. },
  32670. /**
  32671. * Moves this floating Component into a constrain region.
  32672. *
  32673. * By default, this Component is constrained to be within the container it was added to, or the element it was
  32674. * rendered to.
  32675. *
  32676. * An alternative constraint may be passed.
  32677. * @param {String/HTMLElement/Ext.Element/Ext.util.Region} [constrainTo] The Element or {@link Ext.util.Region Region}
  32678. * into which this Component is to be constrained. Defaults to the element into which this floating Component
  32679. * was rendered.
  32680. */
  32681. doConstrain: function(constrainTo) {
  32682. var me = this,
  32683. // Calculate the constrain vector to coerce our position to within our
  32684. // constrainTo setting. getConstrainVector will provide a default constraint
  32685. // region if there is no explicit constrainTo, *and* there is no floatParent owner Component.
  32686. vector = me.getConstrainVector(constrainTo),
  32687. xy;
  32688. if (vector) {
  32689. xy = me.getPosition(!!me.floatParent);
  32690. xy[0] += vector[0];
  32691. xy[1] += vector[1];
  32692. me.setPosition(xy);
  32693. }
  32694. },
  32695. /**
  32696. * Gets the x/y offsets to constrain this float
  32697. * @private
  32698. * @param {String/HTMLElement/Ext.Element/Ext.util.Region} [constrainTo] The Element or {@link Ext.util.Region Region}
  32699. * into which this Component is to be constrained.
  32700. * @return {Number[]} The x/y constraints
  32701. */
  32702. getConstrainVector: function(constrainTo){
  32703. var me = this;
  32704. if (me.constrain || me.constrainHeader) {
  32705. constrainTo = constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container || me.el.getScopeParent();
  32706. return (me.constrainHeader ? me.header.el : me.el).getConstrainVector(constrainTo);
  32707. }
  32708. },
  32709. /**
  32710. * Aligns this floating Component to the specified element
  32711. *
  32712. * @param {Ext.Component/Ext.Element/HTMLElement/String} element
  32713. * The element or {@link Ext.Component} to align to. If passing a component, it must be a
  32714. * component instance. If a string id is passed, it will be used as an element id.
  32715. * @param {String} [position="tl-bl?"] The position to align to
  32716. * (see {@link Ext.Element#alignTo} for more details).
  32717. * @param {Number[]} [offsets] Offset the positioning by [x, y]
  32718. * @return {Ext.Component} this
  32719. */
  32720. alignTo: function(element, position, offsets) {
  32721. // element may be a Component, so first attempt to use its el to align to.
  32722. // When aligning to an Element's X,Y position, we must use setPagePosition which disregards any floatParent
  32723. this.setPagePosition(this.el.getAlignToXY(element.el || element, position, offsets));
  32724. return this;
  32725. },
  32726. /**
  32727. * Brings this floating Component to the front of any other visible, floating Components managed by the same
  32728. * {@link Ext.ZIndexManager ZIndexManager}
  32729. *
  32730. * If this Component is modal, inserts the modal mask just below this Component in the z-index stack.
  32731. *
  32732. * @param {Boolean} [preventFocus=false] Specify `true` to prevent the Component from being focused.
  32733. * @return {Ext.Component} this
  32734. */
  32735. toFront: function(preventFocus) {
  32736. var me = this;
  32737. // Find the floating Component which provides the base for this Component's zIndexing.
  32738. // That must move to front to then be able to rebase its zIndex stack and move this to the front
  32739. if (me.zIndexParent && me.bringParentToFront !== false) {
  32740. me.zIndexParent.toFront(true);
  32741. }
  32742. if (!Ext.isDefined(preventFocus)) {
  32743. preventFocus = !me.focusOnToFront;
  32744. }
  32745. if (preventFocus) {
  32746. me.preventFocusOnActivate = true;
  32747. }
  32748. if (me.zIndexManager.bringToFront(me)) {
  32749. if (!preventFocus) {
  32750. // Kick off a delayed focus request.
  32751. // If another floating Component is toFronted before the delay expires
  32752. // this will not receive focus.
  32753. me.focus(false, true);
  32754. }
  32755. }
  32756. delete me.preventFocusOnActivate;
  32757. return me;
  32758. },
  32759. /**
  32760. * This method is called internally by {@link Ext.ZIndexManager} to signal that a floating Component has either been
  32761. * moved to the top of its zIndex stack, or pushed from the top of its zIndex stack.
  32762. *
  32763. * If a _Window_ is superceded by another Window, deactivating it hides its shadow.
  32764. *
  32765. * This method also fires the {@link Ext.Component#activate activate} or
  32766. * {@link Ext.Component#deactivate deactivate} event depending on which action occurred.
  32767. *
  32768. * @param {Boolean} [active=false] True to activate the Component, false to deactivate it.
  32769. * @param {Ext.Component} [newActive] The newly active Component which is taking over topmost zIndex position.
  32770. */
  32771. setActive: function(active, newActive) {
  32772. var me = this;
  32773. if (active) {
  32774. if (me.el.shadow && !me.maximized) {
  32775. me.el.enableShadow(true);
  32776. }
  32777. if (me.modal && !me.preventFocusOnActivate) {
  32778. me.focus(false, true);
  32779. }
  32780. me.fireEvent('activate', me);
  32781. } else {
  32782. // Only the *Windows* in a zIndex stack share a shadow. All other types of floaters
  32783. // can keep their shadows all the time
  32784. if (me.isWindow && (newActive && newActive.isWindow)) {
  32785. me.el.disableShadow();
  32786. }
  32787. me.fireEvent('deactivate', me);
  32788. }
  32789. },
  32790. /**
  32791. * Sends this Component to the back of (lower z-index than) any other visible windows
  32792. * @return {Ext.Component} this
  32793. */
  32794. toBack: function() {
  32795. this.zIndexManager.sendToBack(this);
  32796. return this;
  32797. },
  32798. /**
  32799. * Center this Component in its container.
  32800. * @return {Ext.Component} this
  32801. */
  32802. center: function() {
  32803. var me = this,
  32804. xy;
  32805. if (me.isVisible()) {
  32806. xy = me.el.getAlignToXY(me.container, 'c-c');
  32807. me.setPagePosition(xy);
  32808. } else {
  32809. me.needsCenter = true;
  32810. }
  32811. return me;
  32812. },
  32813. onFloatShow: function() {
  32814. if (this.needsCenter) {
  32815. this.center();
  32816. }
  32817. delete this.needsCenter;
  32818. },
  32819. // private
  32820. syncShadow : function() {
  32821. if (this.floating) {
  32822. this.el.sync(true);
  32823. }
  32824. },
  32825. // private
  32826. fitContainer: function() {
  32827. var me = this,
  32828. parent = me.floatParent,
  32829. container = parent ? parent.getTargetEl() : me.container;
  32830. me.setSize(container.getViewSize(false));
  32831. me.setPosition.apply(me, parent ? [0, 0] : container.getXY());
  32832. }
  32833. });
  32834. /**
  32835. * Base class for all Ext components.
  32836. *
  32837. * The Component base class has built-in support for basic hide/show and enable/disable and size control behavior.
  32838. *
  32839. * ## xtypes
  32840. *
  32841. * Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the xtype
  32842. * like {@link #getXType} and {@link #isXType}. See the [Component Guide][1] for more information on xtypes and the
  32843. * Component hierarchy.
  32844. *
  32845. * ## Finding components
  32846. *
  32847. * All Components are registered with the {@link Ext.ComponentManager} on construction so that they can be referenced at
  32848. * any time via {@link Ext#getCmp Ext.getCmp}, passing the {@link #id}.
  32849. *
  32850. * Additionally the {@link Ext.ComponentQuery} provides a CSS-selectors-like way to look up components by their xtype
  32851. * and many other attributes. For example the following code will find all textfield components inside component with
  32852. * `id: 'myform'`:
  32853. *
  32854. * Ext.ComponentQuery.query('#myform textfield');
  32855. *
  32856. * ## Extending Ext.Component
  32857. *
  32858. * All subclasses of Component may participate in the automated Ext component
  32859. * lifecycle of creation, rendering and destruction which is provided by the {@link Ext.container.Container Container}
  32860. * class. Components may be added to a Container through the {@link Ext.container.Container#cfg-items items} config option
  32861. * at the time the Container is created, or they may be added dynamically via the
  32862. * {@link Ext.container.Container#method-add add} method.
  32863. *
  32864. * All user-developed visual widgets that are required to participate in automated lifecycle and size management should
  32865. * subclass Component.
  32866. *
  32867. * See the Creating new UI controls chapter in [Component Guide][1] for details on how and to either extend
  32868. * or augment Ext JS base classes to create custom Components.
  32869. *
  32870. * ## The Ext.Component class by itself
  32871. *
  32872. * Usually one doesn't need to instantiate the Ext.Component class. There are subclasses which implement
  32873. * specialized use cases, covering most application needs. However it is possible to instantiate a base
  32874. * Component, and it can be rendered to document, or handled by layouts as the child item of a Container:
  32875. *
  32876. * @example
  32877. * Ext.create('Ext.Component', {
  32878. * html: 'Hello world!',
  32879. * width: 300,
  32880. * height: 200,
  32881. * padding: 20,
  32882. * style: {
  32883. * color: '#FFFFFF',
  32884. * backgroundColor:'#000000'
  32885. * },
  32886. * renderTo: Ext.getBody()
  32887. * });
  32888. *
  32889. * The Component above creates its encapsulating `div` upon render, and use the configured HTML as content. More complex
  32890. * internal structure may be created using the {@link #renderTpl} configuration, although to display database-derived
  32891. * mass data, it is recommended that an ExtJS data-backed Component such as a {@link Ext.view.View View},
  32892. * {@link Ext.grid.Panel GridPanel}, or {@link Ext.tree.Panel TreePanel} be used.
  32893. *
  32894. * [1]: #!/guide/components
  32895. */
  32896. Ext.define('Ext.Component', {
  32897. /* Begin Definitions */
  32898. alias: ['widget.component', 'widget.box'],
  32899. extend: 'Ext.AbstractComponent',
  32900. requires: [
  32901. 'Ext.util.DelayedTask'
  32902. ],
  32903. uses: [
  32904. 'Ext.Layer',
  32905. 'Ext.resizer.Resizer',
  32906. 'Ext.util.ComponentDragger'
  32907. ],
  32908. mixins: {
  32909. floating: 'Ext.util.Floating'
  32910. },
  32911. statics: {
  32912. // Collapse/expand directions
  32913. DIRECTION_TOP: 'top',
  32914. DIRECTION_RIGHT: 'right',
  32915. DIRECTION_BOTTOM: 'bottom',
  32916. DIRECTION_LEFT: 'left',
  32917. VERTICAL_DIRECTION_Re: /^(?:top|bottom)$/,
  32918. // RegExp whih specifies characters in an xtype which must be translated to '-' when generating auto IDs.
  32919. // This includes dot, comma and whitespace
  32920. INVALID_ID_CHARS_Re: /[\.,\s]/g
  32921. },
  32922. /* End Definitions */
  32923. /**
  32924. * @cfg {Boolean/Object} resizable
  32925. * Specify as `true` to apply a {@link Ext.resizer.Resizer Resizer} to this Component after rendering.
  32926. *
  32927. * May also be specified as a config object to be passed to the constructor of {@link Ext.resizer.Resizer Resizer}
  32928. * to override any defaults. By default the Component passes its minimum and maximum size, and uses
  32929. * `{@link Ext.resizer.Resizer#dynamic}: false`
  32930. */
  32931. /**
  32932. * @cfg {String} resizeHandles
  32933. * A valid {@link Ext.resizer.Resizer} handles config string. Only applies when resizable = true.
  32934. */
  32935. resizeHandles: 'all',
  32936. /**
  32937. * @cfg {Boolean} [autoScroll=false]
  32938. * `true` to use overflow:'auto' on the components layout element and show scroll bars automatically when necessary,
  32939. * `false` to clip any overflowing content.
  32940. * This should not be combined with {@link #overflowX} or {@link #overflowY}.
  32941. */
  32942. /**
  32943. * @cfg {String} overflowX
  32944. * Possible values are:
  32945. * * `'auto'` to enable automatic horizontal scrollbar (overflow-x: 'auto').
  32946. * * `'scroll'` to always enable horizontal scrollbar (overflow-x: 'scroll').
  32947. * The default is overflow-x: 'hidden'. This should not be combined with {@link #autoScroll}.
  32948. */
  32949. /**
  32950. * @cfg {String} overflowY
  32951. * Possible values are:
  32952. * * `'auto'` to enable automatic vertical scrollbar (overflow-y: 'auto').
  32953. * * `'scroll'` to always enable vertical scrollbar (overflow-y: 'scroll').
  32954. * The default is overflow-y: 'hidden'. This should not be combined with {@link #autoScroll}.
  32955. */
  32956. /**
  32957. * @cfg {Boolean} floating
  32958. * Specify as true to float the Component outside of the document flow using CSS absolute positioning.
  32959. *
  32960. * Components such as {@link Ext.window.Window Window}s and {@link Ext.menu.Menu Menu}s are floating by default.
  32961. *
  32962. * Floating Components that are programatically {@link Ext.Component#method-render rendered} will register
  32963. * themselves with the global {@link Ext.WindowManager ZIndexManager}
  32964. *
  32965. * ### Floating Components as child items of a Container
  32966. *
  32967. * A floating Component may be used as a child item of a Container. This just allows the floating Component to seek
  32968. * a ZIndexManager by examining the ownerCt chain.
  32969. *
  32970. * When configured as floating, Components acquire, at render time, a {@link Ext.ZIndexManager ZIndexManager} which
  32971. * manages a stack of related floating Components. The ZIndexManager brings a single floating Component to the top
  32972. * of its stack when the Component's {@link #toFront} method is called.
  32973. *
  32974. * The ZIndexManager is found by traversing up the {@link #ownerCt} chain to find an ancestor which itself is
  32975. * floating. This is so that descendant floating Components of floating _Containers_ (Such as a ComboBox dropdown
  32976. * within a Window) can have its zIndex managed relative to any siblings, but always **above** that floating
  32977. * ancestor Container.
  32978. *
  32979. * If no floating ancestor is found, a floating Component registers itself with the default {@link Ext.WindowManager
  32980. * ZIndexManager}.
  32981. *
  32982. * Floating components _do not participate in the Container's layout_. Because of this, they are not rendered until
  32983. * you explicitly {@link #method-show} them.
  32984. *
  32985. * After rendering, the ownerCt reference is deleted, and the {@link #floatParent} property is set to the found
  32986. * floating ancestor Container. If no floating ancestor Container was found the {@link #floatParent} property will
  32987. * not be set.
  32988. */
  32989. floating: false,
  32990. /**
  32991. * @cfg {Boolean} toFrontOnShow
  32992. * True to automatically call {@link #toFront} when the {@link #method-show} method is called on an already visible,
  32993. * floating component.
  32994. */
  32995. toFrontOnShow: true,
  32996. /**
  32997. * @property {Ext.ZIndexManager} zIndexManager
  32998. * Only present for {@link #floating} Components after they have been rendered.
  32999. *
  33000. * A reference to the ZIndexManager which is managing this Component's z-index.
  33001. *
  33002. * The {@link Ext.ZIndexManager ZIndexManager} maintains a stack of floating Component z-indices, and also provides
  33003. * a single modal mask which is insert just beneath the topmost visible modal floating Component.
  33004. *
  33005. * Floating Components may be {@link #toFront brought to the front} or {@link #toBack sent to the back} of the
  33006. * z-index stack.
  33007. *
  33008. * This defaults to the global {@link Ext.WindowManager ZIndexManager} for floating Components that are
  33009. * programatically {@link Ext.Component#method-render rendered}.
  33010. *
  33011. * For {@link #floating} Components which are added to a Container, the ZIndexManager is acquired from the first
  33012. * ancestor Container found which is floating. If no floating ancestor is found, the global {@link Ext.WindowManager ZIndexManager} is
  33013. * used.
  33014. *
  33015. * See {@link #floating} and {@link #zIndexParent}
  33016. * @readonly
  33017. */
  33018. /**
  33019. * @property {Ext.Container} floatParent
  33020. * Only present for {@link #floating} Components which were inserted as child items of Containers.
  33021. *
  33022. * Floating Components that are programatically {@link Ext.Component#method-render rendered} will not have a `floatParent`
  33023. * property.
  33024. *
  33025. * For {@link #floating} Components which are child items of a Container, the floatParent will be the owning Container.
  33026. *
  33027. * For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the
  33028. * Window as its `floatParent`
  33029. *
  33030. * See {@link #floating} and {@link #zIndexManager}
  33031. * @readonly
  33032. */
  33033. /**
  33034. * @property {Ext.Container} zIndexParent
  33035. * Only present for {@link #floating} Components which were inserted as child items of Containers, and which have a floating
  33036. * Container in their containment ancestry.
  33037. *
  33038. * For {@link #floating} Components which are child items of a Container, the zIndexParent will be a floating
  33039. * ancestor Container which is responsible for the base z-index value of all its floating descendants. It provides
  33040. * a {@link Ext.ZIndexManager ZIndexManager} which provides z-indexing services for all its descendant floating
  33041. * Components.
  33042. *
  33043. * Floating Components that are programatically {@link Ext.Component#method-render rendered} will not have a `zIndexParent`
  33044. * property.
  33045. *
  33046. * For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the
  33047. * Window as its `zIndexParent`, and will always show above that Window, wherever the Window is placed in the z-index stack.
  33048. *
  33049. * See {@link #floating} and {@link #zIndexManager}
  33050. * @readonly
  33051. */
  33052. /**
  33053. * @cfg {Boolean/Object} [draggable=false]
  33054. * Specify as true to make a {@link #floating} Component draggable using the Component's encapsulating element as
  33055. * the drag handle.
  33056. *
  33057. * This may also be specified as a config object for the {@link Ext.util.ComponentDragger ComponentDragger} which is
  33058. * instantiated to perform dragging.
  33059. *
  33060. * For example to create a Component which may only be dragged around using a certain internal element as the drag
  33061. * handle, use the delegate option:
  33062. *
  33063. * new Ext.Component({
  33064. * constrain: true,
  33065. * floating: true,
  33066. * style: {
  33067. * backgroundColor: '#fff',
  33068. * border: '1px solid black'
  33069. * },
  33070. * html: '<h1 style="cursor:move">The title</h1><p>The content</p>',
  33071. * draggable: {
  33072. * delegate: 'h1'
  33073. * }
  33074. * }).show();
  33075. */
  33076. /**
  33077. * @cfg {Boolean} [formBind=false]
  33078. * When inside FormPanel, any component configured with `formBind: true` will
  33079. * be enabled/disabled depending on the validity state of the form.
  33080. * See {@link Ext.form.Panel} for more information and example.
  33081. */
  33082. /**
  33083. * @cfg {Number/String} [columnWidth=undefined]
  33084. * Defines the column width inside {@link Ext.layout.container.Column column layout}.
  33085. *
  33086. * Can be specified as a number or as a percentage.
  33087. */
  33088. /**
  33089. * @cfg {String} [region=undefined]
  33090. * Defines the region inside {@link Ext.layout.container.Border border layout}.
  33091. *
  33092. * Possible values:
  33093. *
  33094. * - center
  33095. * - north
  33096. * - south
  33097. * - east
  33098. * - west
  33099. */
  33100. hideMode: 'display',
  33101. bubbleEvents: [],
  33102. monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
  33103. defaultComponentLayoutType: 'autocomponent',
  33104. //renderTpl: new Ext.XTemplate(
  33105. // '<div id="{id}" class="{baseCls} {cls} {cmpCls}<tpl if="typeof ui !== \'undefined\'"> {uiBase}-{ui}</tpl>"<tpl if="typeof style !== \'undefined\'"> style="{style}"</tpl>></div>', {
  33106. // compiled: true,
  33107. // disableFormats: true
  33108. // }
  33109. //),
  33110. /**
  33111. * Creates new Component.
  33112. * @param {Ext.Element/String/Object} config The configuration options may be specified as either:
  33113. *
  33114. * - **an element** : it is set as the internal element and its id used as the component id
  33115. * - **a string** : it is assumed to be the id of an existing element and is used as the component id
  33116. * - **anything else** : it is assumed to be a standard config object and is applied to the component
  33117. */
  33118. constructor: function(config) {
  33119. var me = this;
  33120. config = config || {};
  33121. if (config.initialConfig) {
  33122. // Being initialized from an Ext.Action instance...
  33123. if (config.isAction) {
  33124. me.baseAction = config;
  33125. }
  33126. config = config.initialConfig;
  33127. // component cloning / action set up
  33128. }
  33129. else if (config.tagName || config.dom || Ext.isString(config)) {
  33130. // element object
  33131. config = {
  33132. applyTo: config,
  33133. id: config.id || config
  33134. };
  33135. }
  33136. me.callParent([config]);
  33137. // If we were configured from an instance of Ext.Action, (or configured with a baseAction option),
  33138. // register this Component as one of its items
  33139. if (me.baseAction){
  33140. me.baseAction.addComponent(me);
  33141. }
  33142. },
  33143. /**
  33144. * The initComponent template method is an important initialization step for a Component. It is intended to be
  33145. * implemented by each subclass of Ext.Component to provide any needed constructor logic. The
  33146. * initComponent method of the class being created is called first, with each initComponent method
  33147. * up the hierarchy to Ext.Component being called thereafter. This makes it easy to implement and,
  33148. * if needed, override the constructor logic of the Component at any step in the hierarchy.
  33149. *
  33150. * The initComponent method **must** contain a call to {@link Ext.Base#callParent callParent} in order
  33151. * to ensure that the parent class' initComponent method is also called.
  33152. *
  33153. * All config options passed to the constructor are applied to `this` before initComponent is called,
  33154. * so you can simply access them with `this.someOption`.
  33155. *
  33156. * The following example demonstrates using a dynamic string for the text of a button at the time of
  33157. * instantiation of the class.
  33158. *
  33159. * Ext.define('DynamicButtonText', {
  33160. * extend: 'Ext.button.Button',
  33161. *
  33162. * initComponent: function() {
  33163. * this.text = new Date();
  33164. * this.renderTo = Ext.getBody();
  33165. * this.callParent();
  33166. * }
  33167. * });
  33168. *
  33169. * Ext.onReady(function() {
  33170. * Ext.create('DynamicButtonText');
  33171. * });
  33172. *
  33173. * @template
  33174. * @protected
  33175. */
  33176. initComponent: function() {
  33177. var me = this;
  33178. me.callParent();
  33179. if (me.listeners) {
  33180. me.on(me.listeners);
  33181. me.listeners = null; //change the value to remove any on prototype
  33182. }
  33183. me.enableBubble(me.bubbleEvents);
  33184. me.mons = [];
  33185. },
  33186. // private
  33187. afterRender: function() {
  33188. var me = this;
  33189. me.callParent();
  33190. if (!(me.x && me.y) && (me.pageX || me.pageY)) {
  33191. me.setPagePosition(me.pageX, me.pageY);
  33192. }
  33193. },
  33194. /**
  33195. * Sets the overflow on the content element of the component.
  33196. * @param {Boolean} scroll True to allow the Component to auto scroll.
  33197. * @return {Ext.Component} this
  33198. */
  33199. setAutoScroll : function(scroll) {
  33200. var me = this;
  33201. me.autoScroll = !!scroll;
  33202. // Scrolling styles must be applied to Component's main element.
  33203. // Layouts which use an innerCt (Box layout), shrinkwrap the innerCt round overflowing content,
  33204. // so the innerCt must be scrolled by the container, it does not scroll content.
  33205. if (me.rendered) {
  33206. me.getTargetEl().setStyle(me.getOverflowStyle());
  33207. }
  33208. me.updateLayout();
  33209. return me;
  33210. },
  33211. /**
  33212. * Sets the overflow x/y on the content element of the component. The x/y overflow
  33213. * values can be any valid CSS overflow (e.g., 'auto' or 'scroll'). By default, the
  33214. * value is 'hidden'. Passing null for one of the values will erase the inline style.
  33215. * Passing `undefined` will preserve the current value.
  33216. *
  33217. * @param {String} overflowX The overflow-x value.
  33218. * @param {String} overflowY The overflow-y value.
  33219. * @return {Ext.Component} this
  33220. */
  33221. setOverflowXY: function(overflowX, overflowY) {
  33222. var me = this,
  33223. argCount = arguments.length;
  33224. if (argCount) {
  33225. me.overflowX = overflowX || '';
  33226. if (argCount > 1) {
  33227. me.overflowY = overflowY || '';
  33228. }
  33229. }
  33230. // Scrolling styles must be applied to Component's main element.
  33231. // Layouts which use an innerCt (Box layout), shrinkwrap the innerCt round overflowing content,
  33232. // so the innerCt must be scrolled by the container, it does not scroll content.
  33233. if (me.rendered) {
  33234. me.getTargetEl().setStyle(me.getOverflowStyle());
  33235. }
  33236. me.updateLayout();
  33237. return me;
  33238. },
  33239. beforeRender: function () {
  33240. var me = this,
  33241. floating = me.floating,
  33242. cls;
  33243. if (floating) {
  33244. me.addCls(Ext.baseCSSPrefix + 'layer');
  33245. cls = floating.cls;
  33246. if (cls) {
  33247. me.addCls(cls);
  33248. }
  33249. }
  33250. return me.callParent();
  33251. },
  33252. afterComponentLayout: function(){
  33253. this.callParent(arguments);
  33254. if (this.floating) {
  33255. this.onAfterFloatLayout();
  33256. }
  33257. },
  33258. // private
  33259. makeFloating : function (dom) {
  33260. this.mixins.floating.constructor.call(this, dom);
  33261. },
  33262. wrapPrimaryEl: function (dom) {
  33263. if (this.floating) {
  33264. this.makeFloating(dom);
  33265. } else {
  33266. this.callParent(arguments);
  33267. }
  33268. },
  33269. initResizable: function(resizable) {
  33270. var me = this;
  33271. resizable = Ext.apply({
  33272. target: me,
  33273. dynamic: false,
  33274. constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : null),
  33275. handles: me.resizeHandles
  33276. }, resizable);
  33277. resizable.target = me;
  33278. me.resizer = new Ext.resizer.Resizer(resizable);
  33279. },
  33280. getDragEl: function() {
  33281. return this.el;
  33282. },
  33283. initDraggable: function() {
  33284. var me = this,
  33285. // If we are resizable, and the resizer had to wrap this Component's el (eg an Img)
  33286. // Then we have to create a pseudo-Component out of the resizer to drag that,
  33287. // otherwise, we just drag this Component
  33288. dragTarget = (me.resizer && me.resizer.el !== me.el) ? me.resizerComponent = new Ext.Component({
  33289. el: me.resizer.el,
  33290. rendered: true,
  33291. container: me.container
  33292. }) : me,
  33293. ddConfig = Ext.applyIf({
  33294. el: dragTarget.getDragEl(),
  33295. constrainTo: me.constrain ? (me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent())) : undefined
  33296. }, me.draggable);
  33297. // Add extra configs if Component is specified to be constrained
  33298. if (me.constrain || me.constrainDelegate) {
  33299. ddConfig.constrain = me.constrain;
  33300. ddConfig.constrainDelegate = me.constrainDelegate;
  33301. }
  33302. me.dd = new Ext.util.ComponentDragger(dragTarget, ddConfig);
  33303. },
  33304. /**
  33305. * Scrolls this Component's {@link #getTargetEl target element} by the passed delta values, optionally animating.
  33306. *
  33307. * All of the following are equivalent:
  33308. *
  33309. * comp.scrollBy(10, 10, true);
  33310. * comp.scrollBy([10, 10], true);
  33311. * comp.scrollBy({ x: 10, y: 10 }, true);
  33312. *
  33313. * @param {Number/Number[]/Object} deltaX Either the x delta, an Array specifying x and y deltas or
  33314. * an object with "x" and "y" properties.
  33315. * @param {Number/Boolean/Object} deltaY Either the y delta, or an animate flag or config object.
  33316. * @param {Boolean/Object} animate Animate flag/config object if the delta values were passed separately.
  33317. */
  33318. scrollBy: function(deltaX, deltaY, animate) {
  33319. var el;
  33320. if ((el = this.getTargetEl()) && el.dom) {
  33321. el.scrollBy.apply(el, arguments);
  33322. }
  33323. },
  33324. /**
  33325. * This method allows you to show or hide a LoadMask on top of this component.
  33326. *
  33327. * @param {Boolean/Object/String} load True to show the default LoadMask, a config object that will be passed to the
  33328. * LoadMask constructor, or a message String to show. False to hide the current LoadMask.
  33329. * @param {Boolean} [targetEl=false] True to mask the targetEl of this Component instead of the `this.el`. For example,
  33330. * setting this to true on a Panel will cause only the body to be masked.
  33331. * @return {Ext.LoadMask} The LoadMask instance that has just been shown.
  33332. */
  33333. setLoading : function(load, targetEl) {
  33334. var me = this,
  33335. config;
  33336. if (me.rendered) {
  33337. Ext.destroy(me.loadMask);
  33338. me.loadMask = null;
  33339. if (load !== false && !me.collapsed) {
  33340. if (Ext.isObject(load)) {
  33341. config = Ext.apply({}, load);
  33342. } else if (Ext.isString(load)) {
  33343. config = {msg: load};
  33344. } else {
  33345. config = {};
  33346. }
  33347. if (targetEl) {
  33348. Ext.applyIf(config, {
  33349. useTargetEl: true
  33350. });
  33351. }
  33352. me.loadMask = new Ext.LoadMask(me, config);
  33353. me.loadMask.show();
  33354. }
  33355. }
  33356. return me.loadMask;
  33357. },
  33358. beforeSetPosition: function () {
  33359. var me = this,
  33360. pos = me.callParent(arguments), // pass all args on for signature decoding
  33361. adj;
  33362. if (pos) {
  33363. adj = me.adjustPosition(pos.x, pos.y);
  33364. pos.x = adj.x;
  33365. pos.y = adj.y;
  33366. }
  33367. return pos || null;
  33368. },
  33369. afterSetPosition: function(ax, ay) {
  33370. this.onPosition(ax, ay);
  33371. this.fireEvent('move', this, ax, ay);
  33372. },
  33373. /**
  33374. * Displays component at specific xy position.
  33375. * A floating component (like a menu) is positioned relative to its ownerCt if any.
  33376. * Useful for popping up a context menu:
  33377. *
  33378. * listeners: {
  33379. * itemcontextmenu: function(view, record, item, index, event, options) {
  33380. * Ext.create('Ext.menu.Menu', {
  33381. * width: 100,
  33382. * height: 100,
  33383. * margin: '0 0 10 0',
  33384. * items: [{
  33385. * text: 'regular item 1'
  33386. * },{
  33387. * text: 'regular item 2'
  33388. * },{
  33389. * text: 'regular item 3'
  33390. * }]
  33391. * }).showAt(event.getXY());
  33392. * }
  33393. * }
  33394. *
  33395. * @param {Number} x The new x position
  33396. * @param {Number} y The new y position
  33397. * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
  33398. * animation configuration.
  33399. */
  33400. showAt: function(x, y, animate) {
  33401. var me = this;
  33402. if (!me.rendered && (me.autoRender || me.floating)) {
  33403. me.doAutoRender();
  33404. // forcibly set hidden here, since we still want the initial beforeshow/show event to fire
  33405. me.hidden = true;
  33406. }
  33407. if (me.floating) {
  33408. me.setPosition(x, y, animate);
  33409. } else {
  33410. me.setPagePosition(x, y, animate);
  33411. }
  33412. me.show();
  33413. },
  33414. /**
  33415. * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
  33416. * This method fires the {@link #event-move} event.
  33417. * @param {Number} x The new x position
  33418. * @param {Number} y The new y position
  33419. * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
  33420. * animation configuration.
  33421. * @return {Ext.Component} this
  33422. */
  33423. setPagePosition: function(x, y, animate) {
  33424. var me = this,
  33425. p,
  33426. floatParentBox;
  33427. if (Ext.isArray(x)) {
  33428. y = x[1];
  33429. x = x[0];
  33430. }
  33431. me.pageX = x;
  33432. me.pageY = y;
  33433. if (me.floating) {
  33434. // Floating Components which are registered with a Container have to have their x and y properties made relative
  33435. if (me.isContainedFloater()) {
  33436. floatParentBox = me.floatParent.getTargetEl().getViewRegion();
  33437. if (Ext.isNumber(x) && Ext.isNumber(floatParentBox.left)) {
  33438. x -= floatParentBox.left;
  33439. }
  33440. if (Ext.isNumber(y) && Ext.isNumber(floatParentBox.top)) {
  33441. y -= floatParentBox.top;
  33442. }
  33443. } else {
  33444. p = me.el.translatePoints(x, y);
  33445. x = p.left;
  33446. y = p.top;
  33447. }
  33448. me.setPosition(x, y, animate);
  33449. } else {
  33450. p = me.el.translatePoints(x, y);
  33451. me.setPosition(p.left, p.top, animate);
  33452. }
  33453. return me;
  33454. },
  33455. // Utility method to determine if a Component is floating, and has an owning Container whose coordinate system
  33456. // it must be positioned in when using setPosition.
  33457. isContainedFloater: function() {
  33458. return (this.floating && this.floatParent);
  33459. },
  33460. /**
  33461. * Gets the current box measurements of the component's underlying element.
  33462. * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
  33463. * @return {Object} box An object in the format {x, y, width, height}
  33464. */
  33465. getBox : function(local){
  33466. var pos = local ? this.getPosition(local) : this.el.getXY(),
  33467. size = this.getSize();
  33468. size.x = pos[0];
  33469. size.y = pos[1];
  33470. return size;
  33471. },
  33472. /**
  33473. * Sets the current box measurements of the component's underlying element.
  33474. * @param {Object} box An object in the format {x, y, width, height}
  33475. * @return {Ext.Component} this
  33476. */
  33477. updateBox : function(box){
  33478. this.setSize(box.width, box.height);
  33479. this.setPagePosition(box.x, box.y);
  33480. return this;
  33481. },
  33482. // Include margins
  33483. getOuterSize: function() {
  33484. var el = this.el;
  33485. return {
  33486. width: el.getWidth() + el.getMargin('lr'),
  33487. height: el.getHeight() + el.getMargin('tb')
  33488. };
  33489. },
  33490. // private
  33491. adjustPosition: function(x, y) {
  33492. var me = this,
  33493. floatParentBox;
  33494. // Floating Components being positioned in their ownerCt have to be made absolute.
  33495. if (me.isContainedFloater()) {
  33496. floatParentBox = me.floatParent.getTargetEl().getViewRegion();
  33497. x += floatParentBox.left;
  33498. y += floatParentBox.top;
  33499. }
  33500. return {
  33501. x: x,
  33502. y: y
  33503. };
  33504. },
  33505. /**
  33506. * Gets the current XY position of the component's underlying element.
  33507. * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
  33508. * @return {Number[]} The XY position of the element (e.g., [100, 200])
  33509. */
  33510. getPosition: function(local) {
  33511. var me = this,
  33512. el = me.el,
  33513. xy,
  33514. isContainedFloater = me.isContainedFloater(),
  33515. floatParentBox;
  33516. // Local position for non-floaters means element's local position
  33517. if ((local === true) && !isContainedFloater) {
  33518. return [el.getLocalX(), el.getLocalY()];
  33519. }
  33520. xy = me.el.getXY();
  33521. // Local position for floaters means position relative to the container's target element
  33522. if ((local === true) && isContainedFloater) {
  33523. floatParentBox = me.floatParent.getTargetEl().getViewRegion();
  33524. xy[0] -= floatParentBox.left;
  33525. xy[1] -= floatParentBox.top;
  33526. }
  33527. return xy;
  33528. },
  33529. getId: function() {
  33530. var me = this,
  33531. xtype;
  33532. if (!me.id) {
  33533. xtype = me.getXType();
  33534. if (xtype) {
  33535. xtype = xtype.replace(Ext.Component.INVALID_ID_CHARS_Re, '-');
  33536. } else {
  33537. xtype = Ext.name.toLowerCase() + '-comp';
  33538. }
  33539. me.id = xtype + '-' + me.getAutoId();
  33540. }
  33541. return me.id;
  33542. },
  33543. /**
  33544. * Shows this Component, rendering it first if {@link #autoRender} or {@link #floating} are `true`.
  33545. *
  33546. * After being shown, a {@link #floating} Component (such as a {@link Ext.window.Window}), is activated it and
  33547. * brought to the front of its {@link #zIndexManager z-index stack}.
  33548. *
  33549. * @param {String/Ext.Element} [animateTarget=null] **only valid for {@link #floating} Components such as {@link
  33550. * Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
  33551. * with `floating: true`.** The target from which the Component should animate from while opening.
  33552. * @param {Function} [callback] A callback function to call after the Component is displayed.
  33553. * Only necessary if animation was specified.
  33554. * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
  33555. * Defaults to this Component.
  33556. * @return {Ext.Component} this
  33557. */
  33558. show: function(animateTarget, cb, scope) {
  33559. var me = this,
  33560. rendered = me.rendered;
  33561. if (rendered && me.isVisible()) {
  33562. if (me.toFrontOnShow && me.floating) {
  33563. me.toFront();
  33564. }
  33565. } else {
  33566. if (me.fireEvent('beforeshow', me) !== false) {
  33567. // Render on first show if there is an autoRender config, or if this is a floater (Window, Menu, BoundList etc).
  33568. me.hidden = false;
  33569. if (!rendered && (me.autoRender || me.floating)) {
  33570. me.doAutoRender();
  33571. rendered = me.rendered;
  33572. }
  33573. if (rendered) {
  33574. me.beforeShow();
  33575. me.onShow.apply(me, arguments);
  33576. me.afterShow.apply(me, arguments);
  33577. }
  33578. } else {
  33579. me.onShowVeto();
  33580. }
  33581. }
  33582. return me;
  33583. },
  33584. onShowVeto: Ext.emptyFn,
  33585. /**
  33586. * Invoked before the Component is shown.
  33587. *
  33588. * @method
  33589. * @template
  33590. * @protected
  33591. */
  33592. beforeShow: Ext.emptyFn,
  33593. /**
  33594. * Allows addition of behavior to the show operation. After
  33595. * calling the superclass's onShow, the Component will be visible.
  33596. *
  33597. * Override in subclasses where more complex behaviour is needed.
  33598. *
  33599. * Gets passed the same parameters as #show.
  33600. *
  33601. * @param {String/Ext.Element} [animateTarget]
  33602. * @param {Function} [callback]
  33603. * @param {Object} [scope]
  33604. *
  33605. * @template
  33606. * @protected
  33607. */
  33608. onShow: function() {
  33609. var me = this;
  33610. me.el.show();
  33611. me.callParent(arguments);
  33612. // Constraining/containing element may have changed size while this Component was hidden
  33613. if (me.floating) {
  33614. if (me.maximized) {
  33615. me.fitContainer();
  33616. }
  33617. else if (me.constrain) {
  33618. me.doConstrain();
  33619. }
  33620. }
  33621. },
  33622. /**
  33623. * Invoked after the Component is shown (after #onShow is called).
  33624. *
  33625. * Gets passed the same parameters as #show.
  33626. *
  33627. * @param {String/Ext.Element} [animateTarget]
  33628. * @param {Function} [callback]
  33629. * @param {Object} [scope]
  33630. *
  33631. * @template
  33632. * @protected
  33633. */
  33634. afterShow: function(animateTarget, cb, scope) {
  33635. var me = this,
  33636. fromBox,
  33637. toBox,
  33638. ghostPanel;
  33639. // Default to configured animate target if none passed
  33640. animateTarget = animateTarget || me.animateTarget;
  33641. // Need to be able to ghost the Component
  33642. if (!me.ghost) {
  33643. animateTarget = null;
  33644. }
  33645. // If we're animating, kick of an animation of the ghost from the target to the *Element* current box
  33646. if (animateTarget) {
  33647. animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
  33648. toBox = me.el.getBox();
  33649. fromBox = animateTarget.getBox();
  33650. me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
  33651. ghostPanel = me.ghost();
  33652. ghostPanel.el.stopAnimation();
  33653. // Shunting it offscreen immediately, *before* the Animation class grabs it ensure no flicker.
  33654. ghostPanel.el.setX(-10000);
  33655. ghostPanel.el.animate({
  33656. from: fromBox,
  33657. to: toBox,
  33658. listeners: {
  33659. afteranimate: function() {
  33660. delete ghostPanel.componentLayout.lastComponentSize;
  33661. me.unghost();
  33662. me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
  33663. me.onShowComplete(cb, scope);
  33664. }
  33665. }
  33666. });
  33667. }
  33668. else {
  33669. me.onShowComplete(cb, scope);
  33670. }
  33671. },
  33672. /**
  33673. * Invoked after the #afterShow method is complete.
  33674. *
  33675. * Gets passed the same `callback` and `scope` parameters that #afterShow received.
  33676. *
  33677. * @param {Function} [callback]
  33678. * @param {Object} [scope]
  33679. *
  33680. * @template
  33681. * @protected
  33682. */
  33683. onShowComplete: function(cb, scope) {
  33684. var me = this;
  33685. if (me.floating) {
  33686. me.toFront();
  33687. me.onFloatShow();
  33688. }
  33689. Ext.callback(cb, scope || me);
  33690. me.fireEvent('show', me);
  33691. delete me.hiddenByLayout;
  33692. },
  33693. /**
  33694. * Hides this Component, setting it to invisible using the configured {@link #hideMode}.
  33695. * @param {String/Ext.Element/Ext.Component} [animateTarget=null] **only valid for {@link #floating} Components
  33696. * such as {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have
  33697. * been configured with `floating: true`.**. The target to which the Component should animate while hiding.
  33698. * @param {Function} [callback] A callback function to call after the Component is hidden.
  33699. * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
  33700. * Defaults to this Component.
  33701. * @return {Ext.Component} this
  33702. */
  33703. hide: function() {
  33704. var me = this;
  33705. // Clear the flag which is set if a floatParent was hidden while this is visible.
  33706. // If a hide operation was subsequently called, that pending show must be hidden.
  33707. me.showOnParentShow = false;
  33708. if (!(me.rendered && !me.isVisible()) && me.fireEvent('beforehide', me) !== false) {
  33709. me.hidden = true;
  33710. if (me.rendered) {
  33711. me.onHide.apply(me, arguments);
  33712. }
  33713. }
  33714. return me;
  33715. },
  33716. /**
  33717. * Possibly animates down to a target element.
  33718. *
  33719. * Allows addition of behavior to the hide operation. After
  33720. * calling the superclass’s onHide, the Component will be hidden.
  33721. *
  33722. * Gets passed the same parameters as #hide.
  33723. *
  33724. * @param {String/Ext.Element/Ext.Component} [animateTarget]
  33725. * @param {Function} [callback]
  33726. * @param {Object} [scope]
  33727. *
  33728. * @template
  33729. * @protected
  33730. */
  33731. onHide: function(animateTarget, cb, scope) {
  33732. var me = this,
  33733. ghostPanel,
  33734. toBox;
  33735. // Default to configured animate target if none passed
  33736. animateTarget = animateTarget || me.animateTarget;
  33737. // Need to be able to ghost the Component
  33738. if (!me.ghost) {
  33739. animateTarget = null;
  33740. }
  33741. // If we're animating, kick off an animation of the ghost down to the target
  33742. if (animateTarget) {
  33743. animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
  33744. ghostPanel = me.ghost();
  33745. ghostPanel.el.stopAnimation();
  33746. toBox = animateTarget.getBox();
  33747. toBox.width += 'px';
  33748. toBox.height += 'px';
  33749. ghostPanel.el.animate({
  33750. to: toBox,
  33751. listeners: {
  33752. afteranimate: function() {
  33753. delete ghostPanel.componentLayout.lastComponentSize;
  33754. ghostPanel.el.hide();
  33755. me.afterHide(cb, scope);
  33756. }
  33757. }
  33758. });
  33759. }
  33760. me.el.hide();
  33761. if (!animateTarget) {
  33762. me.afterHide(cb, scope);
  33763. }
  33764. },
  33765. /**
  33766. * Invoked after the Component has been hidden.
  33767. *
  33768. * Gets passed the same `callback` and `scope` parameters that #onHide received.
  33769. *
  33770. * @param {Function} [callback]
  33771. * @param {Object} [scope]
  33772. *
  33773. * @template
  33774. * @protected
  33775. */
  33776. afterHide: function(cb, scope) {
  33777. var me = this;
  33778. delete me.hiddenByLayout;
  33779. // we are the back-end method of onHide at this level, but our call to our parent
  33780. // may need to be async... so callParent won't quite work here...
  33781. Ext.AbstractComponent.prototype.onHide.call(this);
  33782. Ext.callback(cb, scope || me);
  33783. me.fireEvent('hide', me);
  33784. },
  33785. /**
  33786. * Allows addition of behavior to the destroy operation.
  33787. * After calling the superclass’s onDestroy, the Component will be destroyed.
  33788. *
  33789. * @template
  33790. * @protected
  33791. */
  33792. onDestroy: function() {
  33793. var me = this;
  33794. // Ensure that any ancillary components are destroyed.
  33795. if (me.rendered) {
  33796. Ext.destroy(
  33797. me.proxy,
  33798. me.proxyWrap,
  33799. me.resizer,
  33800. me.resizerComponent
  33801. );
  33802. }
  33803. delete me.focusTask;
  33804. me.callParent();
  33805. },
  33806. deleteMembers: function() {
  33807. var args = arguments,
  33808. len = args.length,
  33809. i = 0;
  33810. for (; i < len; ++i) {
  33811. delete this[args[i]];
  33812. }
  33813. },
  33814. /**
  33815. * Try to focus this component.
  33816. * @param {Boolean} [selectText] If applicable, true to also select the text in this component
  33817. * @param {Boolean/Number} [delay] Delay the focus this number of milliseconds (true for 10 milliseconds).
  33818. * @return {Ext.Component} The focused Component. Usually <code>this</code> Component. Some Containers may
  33819. * delegate focus to a descendant Component ({@link Ext.window.Window Window}s can do this through their
  33820. * {@link Ext.window.Window#defaultFocus defaultFocus} config option.
  33821. */
  33822. focus: function(selectText, delay) {
  33823. var me = this,
  33824. focusEl,
  33825. focusElDom,
  33826. containerScrollTop;
  33827. // If delay is wanted, queue a call to this function.
  33828. if (delay) {
  33829. if (!me.focusTask) {
  33830. me.focusTask = new Ext.util.DelayedTask(me.focus);
  33831. }
  33832. me.focusTask.delay(Ext.isNumber(delay) ? delay : 10, null, me, [selectText, false]);
  33833. return me;
  33834. }
  33835. if (me.rendered && !me.isDestroyed && me.isVisible(true) && (focusEl = me.getFocusEl())) {
  33836. // getFocusEl might return a Component if a Container wishes to delegate focus to a descendant.
  33837. // Window can do this via its defaultFocus configuration which can reference a Button.
  33838. if (focusEl.isComponent) {
  33839. return focusEl.focus(selectText, delay);
  33840. }
  33841. // If it was an Element with a dom property
  33842. if ((focusElDom = focusEl.dom)) {
  33843. // Not a natural focus holding element, add a tab index to make it programatically focusable.
  33844. if (focusEl.needsTabIndex()) {
  33845. focusElDom.tabIndex = -1;
  33846. }
  33847. if (me.floating) {
  33848. containerScrollTop = me.container.dom.scrollTop;
  33849. }
  33850. // Focus the element.
  33851. // The focusEl has a DOM focus listener on it which invokes the Component's onFocus method
  33852. // to perform Component-specific focus processing
  33853. focusEl.focus();
  33854. if (selectText === true) {
  33855. focusElDom.select();
  33856. }
  33857. }
  33858. // Focusing a floating Component brings it to the front of its stack.
  33859. // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion.
  33860. if (me.floating) {
  33861. me.toFront(true);
  33862. if (containerScrollTop !== undefined) {
  33863. me.container.dom.scrollTop = containerScrollTop;
  33864. }
  33865. }
  33866. }
  33867. return me;
  33868. },
  33869. /**
  33870. * Cancel any deferred focus on this component
  33871. * @protected
  33872. */
  33873. cancelFocus: function() {
  33874. var task = this.focusTask;
  33875. if (task) {
  33876. task.cancel();
  33877. }
  33878. },
  33879. // private
  33880. blur: function() {
  33881. var focusEl;
  33882. if (this.rendered && (focusEl = this.getFocusEl())) {
  33883. focusEl.blur();
  33884. }
  33885. return this;
  33886. },
  33887. getEl: function() {
  33888. return this.el;
  33889. },
  33890. // Deprecate 5.0
  33891. getResizeEl: function() {
  33892. return this.el;
  33893. },
  33894. // Deprecate 5.0
  33895. getPositionEl: function() {
  33896. return this.el;
  33897. },
  33898. // Deprecate 5.0
  33899. getActionEl: function() {
  33900. return this.el;
  33901. },
  33902. // Deprecate 5.0
  33903. getVisibilityEl: function() {
  33904. return this.el;
  33905. },
  33906. // Deprecate 5.0
  33907. onResize: Ext.emptyFn,
  33908. // private
  33909. // Implements an upward event bubbilng policy. By default a Component bubbles events up to its ownerCt
  33910. // Floating Components target the floatParent.
  33911. // Some Component subclasses (such as Menu) might implement a different ownership hierarchy.
  33912. // The up() method uses this to find the immediate owner.
  33913. getBubbleTarget: function() {
  33914. return this.ownerCt || this.floatParent;
  33915. },
  33916. // private
  33917. getContentTarget: function() {
  33918. return this.el;
  33919. },
  33920. /**
  33921. * Clone the current component using the original config values passed into this instance by default.
  33922. * @param {Object} overrides A new config containing any properties to override in the cloned version.
  33923. * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
  33924. * @return {Ext.Component} clone The cloned copy of this component
  33925. */
  33926. cloneConfig: function(overrides) {
  33927. overrides = overrides || {};
  33928. var id = overrides.id || Ext.id(),
  33929. cfg = Ext.applyIf(overrides, this.initialConfig),
  33930. self;
  33931. cfg.id = id;
  33932. self = Ext.getClass(this);
  33933. // prevent dup id
  33934. return new self(cfg);
  33935. },
  33936. /**
  33937. * Gets the xtype for this component as registered with {@link Ext.ComponentManager}. For a list of all available
  33938. * xtypes, see the {@link Ext.Component} header. Example usage:
  33939. *
  33940. * var t = new Ext.form.field.Text();
  33941. * alert(t.getXType()); // alerts 'textfield'
  33942. *
  33943. * @return {String} The xtype
  33944. */
  33945. getXType: function() {
  33946. return this.self.xtype;
  33947. },
  33948. /**
  33949. * Find a container above this component at any level by a custom function. If the passed function returns true, the
  33950. * container will be returned.
  33951. *
  33952. * See also the {@link Ext.Component#up up} method.
  33953. *
  33954. * @param {Function} fn The custom function to call with the arguments (container, this component).
  33955. * @return {Ext.container.Container} The first Container for which the custom function returns true
  33956. */
  33957. findParentBy: function(fn) {
  33958. var p;
  33959. // Iterate up the ownerCt chain until there's no ownerCt, or we find an ancestor which matches using the selector function.
  33960. for (p = this.getBubbleTarget(); p && !fn(p, this); p = p.getBubbleTarget()) {
  33961. // do nothing
  33962. }
  33963. return p || null;
  33964. },
  33965. /**
  33966. * Find a container above this component at any level by xtype or class
  33967. *
  33968. * See also the {@link Ext.Component#up up} method.
  33969. *
  33970. * @param {String/Ext.Class} xtype The xtype string for a component, or the class of the component directly
  33971. * @return {Ext.container.Container} The first Container which matches the given xtype or class
  33972. */
  33973. findParentByType: function(xtype) {
  33974. return Ext.isFunction(xtype) ?
  33975. this.findParentBy(function(p) {
  33976. return p.constructor === xtype;
  33977. })
  33978. :
  33979. this.up(xtype);
  33980. },
  33981. /**
  33982. * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope
  33983. * (*this*) of function call will be the scope provided or the current component. The arguments to the function will
  33984. * be the args provided or the current component. If the function returns false at any point, the bubble is stopped.
  33985. *
  33986. * @param {Function} fn The function to call
  33987. * @param {Object} [scope] The scope of the function. Defaults to current node.
  33988. * @param {Array} [args] The args to call the function with. Defaults to passing the current component.
  33989. * @return {Ext.Component} this
  33990. */
  33991. bubble: function(fn, scope, args) {
  33992. var p = this;
  33993. while (p) {
  33994. if (fn.apply(scope || p, args || [p]) === false) {
  33995. break;
  33996. }
  33997. p = p.getBubbleTarget();
  33998. }
  33999. return this;
  34000. },
  34001. getProxy: function() {
  34002. var me = this,
  34003. target;
  34004. if (!me.proxy) {
  34005. target = Ext.getBody();
  34006. if (Ext.scopeResetCSS) {
  34007. me.proxyWrap = target = Ext.getBody().createChild({
  34008. cls: Ext.resetCls
  34009. });
  34010. }
  34011. me.proxy = me.el.createProxy(Ext.baseCSSPrefix + 'proxy-el', target, true);
  34012. }
  34013. return me.proxy;
  34014. }
  34015. });
  34016. /**
  34017. * A class used to load remote content to an Element. Sample usage:
  34018. *
  34019. * Ext.get('el').load({
  34020. * url: 'myPage.php',
  34021. * scripts: true,
  34022. * params: {
  34023. * id: 1
  34024. * }
  34025. * });
  34026. *
  34027. * In general this class will not be instanced directly, rather the {@link Ext.Element#method-load} method
  34028. * will be used.
  34029. */
  34030. Ext.define('Ext.ElementLoader', {
  34031. /* Begin Definitions */
  34032. mixins: {
  34033. observable: 'Ext.util.Observable'
  34034. },
  34035. uses: [
  34036. 'Ext.data.Connection',
  34037. 'Ext.Ajax'
  34038. ],
  34039. statics: {
  34040. Renderer: {
  34041. Html: function(loader, response, active){
  34042. loader.getTarget().update(response.responseText, active.scripts === true);
  34043. return true;
  34044. }
  34045. }
  34046. },
  34047. /* End Definitions */
  34048. /**
  34049. * @cfg {String} url (required)
  34050. * The url to retrieve the content from.
  34051. */
  34052. url: null,
  34053. /**
  34054. * @cfg {Object} params
  34055. * Any params to be attached to the Ajax request. These parameters will
  34056. * be overridden by any params in the load options.
  34057. */
  34058. params: null,
  34059. /**
  34060. * @cfg {Object} baseParams Params that will be attached to every request. These parameters
  34061. * will not be overridden by any params in the load options.
  34062. */
  34063. baseParams: null,
  34064. /**
  34065. * @cfg {Boolean/Object} autoLoad
  34066. * True to have the loader make a request as soon as it is created.
  34067. * This argument can also be a set of options that will be passed to {@link #method-load} is called.
  34068. */
  34069. autoLoad: false,
  34070. /**
  34071. * @cfg {HTMLElement/Ext.Element/String} target
  34072. * The target element for the loader. It can be the DOM element, the id or an {@link Ext.Element}.
  34073. */
  34074. target: null,
  34075. /**
  34076. * @cfg {Boolean/String} loadMask
  34077. * True or a string to show when the element is loading.
  34078. */
  34079. loadMask: false,
  34080. /**
  34081. * @cfg {Object} ajaxOptions
  34082. * Any additional options to be passed to the request, for example timeout or headers.
  34083. */
  34084. ajaxOptions: null,
  34085. /**
  34086. * @cfg {Boolean} scripts
  34087. * True to parse any inline script tags in the response.
  34088. */
  34089. scripts: false,
  34090. /**
  34091. * @cfg {Function} success
  34092. * A function to be called when a load request is successful.
  34093. * Will be called with the following config parameters:
  34094. *
  34095. * - this - The ElementLoader instance.
  34096. * - response - The response object.
  34097. * - options - Ajax options.
  34098. */
  34099. /**
  34100. * @cfg {Function} failure A function to be called when a load request fails.
  34101. * Will be called with the following config parameters:
  34102. *
  34103. * - this - The ElementLoader instance.
  34104. * - response - The response object.
  34105. * - options - Ajax options.
  34106. */
  34107. /**
  34108. * @cfg {Function} callback A function to be called when a load request finishes.
  34109. * Will be called with the following config parameters:
  34110. *
  34111. * - this - The ElementLoader instance.
  34112. * - success - True if successful request.
  34113. * - response - The response object.
  34114. * - options - Ajax options.
  34115. */
  34116. /**
  34117. * @cfg {Object} scope
  34118. * The scope to execute the {@link #success} and {@link #failure} functions in.
  34119. */
  34120. /**
  34121. * @cfg {Function} renderer
  34122. * A custom function to render the content to the element. The function should
  34123. * return false if the renderer could not be applied. The passed parameters are:
  34124. *
  34125. * - The loader
  34126. * - The response
  34127. * - The active request
  34128. */
  34129. /**
  34130. * @property {Boolean} isLoader
  34131. * `true` in this class to identify an object as an instantiated ElementLoader, or subclass thereof.
  34132. */
  34133. isLoader: true,
  34134. constructor: function(config) {
  34135. var me = this,
  34136. autoLoad;
  34137. config = config || {};
  34138. Ext.apply(me, config);
  34139. me.setTarget(me.target);
  34140. me.addEvents(
  34141. /**
  34142. * @event beforeload
  34143. * Fires before a load request is made to the server.
  34144. * Returning false from an event listener can prevent the load
  34145. * from occurring.
  34146. * @param {Ext.ElementLoader} this
  34147. * @param {Object} options The options passed to the request
  34148. */
  34149. 'beforeload',
  34150. /**
  34151. * @event exception
  34152. * Fires after an unsuccessful load.
  34153. * @param {Ext.ElementLoader} this
  34154. * @param {Object} response The response from the server
  34155. * @param {Object} options The options passed to the request
  34156. */
  34157. 'exception',
  34158. /**
  34159. * @event load
  34160. * Fires after a successful load.
  34161. * @param {Ext.ElementLoader} this
  34162. * @param {Object} response The response from the server
  34163. * @param {Object} options The options passed to the request
  34164. */
  34165. 'load'
  34166. );
  34167. // don't pass config because we have already applied it.
  34168. me.mixins.observable.constructor.call(me);
  34169. if (me.autoLoad) {
  34170. autoLoad = me.autoLoad;
  34171. if (autoLoad === true) {
  34172. autoLoad = {};
  34173. }
  34174. me.load(autoLoad);
  34175. }
  34176. },
  34177. /**
  34178. * Sets an {@link Ext.Element} as the target of this loader.
  34179. * Note that if the target is changed, any active requests will be aborted.
  34180. * @param {String/HTMLElement/Ext.Element} target The element or its ID.
  34181. */
  34182. setTarget: function(target){
  34183. var me = this;
  34184. target = Ext.get(target);
  34185. if (me.target && me.target != target) {
  34186. me.abort();
  34187. }
  34188. me.target = target;
  34189. },
  34190. /**
  34191. * Returns the target of this loader.
  34192. * @return {Ext.Component} The target or null if none exists.
  34193. */
  34194. getTarget: function(){
  34195. return this.target || null;
  34196. },
  34197. /**
  34198. * Aborts the active load request
  34199. */
  34200. abort: function(){
  34201. var active = this.active;
  34202. if (active !== undefined) {
  34203. Ext.Ajax.abort(active.request);
  34204. if (active.mask) {
  34205. this.removeMask();
  34206. }
  34207. delete this.active;
  34208. }
  34209. },
  34210. /**
  34211. * Removes the mask on the target
  34212. * @private
  34213. */
  34214. removeMask: function(){
  34215. this.target.unmask();
  34216. },
  34217. /**
  34218. * Adds the mask on the target
  34219. * @private
  34220. * @param {Boolean/Object} mask The mask configuration
  34221. */
  34222. addMask: function(mask){
  34223. this.target.mask(mask === true ? null : mask);
  34224. },
  34225. /**
  34226. * Loads new data from the server.
  34227. * @param {Object} options The options for the request. They can be any configuration option that can be specified for
  34228. * the class, with the exception of the target option. Note that any options passed to the method will override any
  34229. * class defaults.
  34230. */
  34231. load: function(options) {
  34232. if (!this.target) {
  34233. Ext.Error.raise('A valid target is required when loading content');
  34234. }
  34235. options = Ext.apply({}, options);
  34236. var me = this,
  34237. target = me.target,
  34238. mask = Ext.isDefined(options.loadMask) ? options.loadMask : me.loadMask,
  34239. params = Ext.apply({}, options.params),
  34240. ajaxOptions = Ext.apply({}, options.ajaxOptions),
  34241. callback = options.callback || me.callback,
  34242. scope = options.scope || me.scope || me,
  34243. request;
  34244. Ext.applyIf(ajaxOptions, me.ajaxOptions);
  34245. Ext.applyIf(options, ajaxOptions);
  34246. Ext.applyIf(params, me.params);
  34247. Ext.apply(params, me.baseParams);
  34248. Ext.applyIf(options, {
  34249. url: me.url
  34250. });
  34251. if (!options.url) {
  34252. Ext.Error.raise('You must specify the URL from which content should be loaded');
  34253. }
  34254. Ext.apply(options, {
  34255. scope: me,
  34256. params: params,
  34257. callback: me.onComplete
  34258. });
  34259. if (me.fireEvent('beforeload', me, options) === false) {
  34260. return;
  34261. }
  34262. if (mask) {
  34263. me.addMask(mask);
  34264. }
  34265. request = Ext.Ajax.request(options);
  34266. me.active = {
  34267. request: request,
  34268. options: options,
  34269. mask: mask,
  34270. scope: scope,
  34271. callback: callback,
  34272. success: options.success || me.success,
  34273. failure: options.failure || me.failure,
  34274. renderer: options.renderer || me.renderer,
  34275. scripts: Ext.isDefined(options.scripts) ? options.scripts : me.scripts
  34276. };
  34277. me.setOptions(me.active, options);
  34278. },
  34279. /**
  34280. * Sets any additional options on the active request
  34281. * @private
  34282. * @param {Object} active The active request
  34283. * @param {Object} options The initial options
  34284. */
  34285. setOptions: Ext.emptyFn,
  34286. /**
  34287. * Parses the response after the request completes
  34288. * @private
  34289. * @param {Object} options Ajax options
  34290. * @param {Boolean} success Success status of the request
  34291. * @param {Object} response The response object
  34292. */
  34293. onComplete: function(options, success, response) {
  34294. var me = this,
  34295. active = me.active,
  34296. scope = active.scope,
  34297. renderer = me.getRenderer(active.renderer);
  34298. if (success) {
  34299. success = renderer.call(me, me, response, active) !== false;
  34300. }
  34301. if (success) {
  34302. Ext.callback(active.success, scope, [me, response, options]);
  34303. me.fireEvent('load', me, response, options);
  34304. } else {
  34305. Ext.callback(active.failure, scope, [me, response, options]);
  34306. me.fireEvent('exception', me, response, options);
  34307. }
  34308. Ext.callback(active.callback, scope, [me, success, response, options]);
  34309. if (active.mask) {
  34310. me.removeMask();
  34311. }
  34312. delete me.active;
  34313. },
  34314. /**
  34315. * Gets the renderer to use
  34316. * @private
  34317. * @param {String/Function} renderer The renderer to use
  34318. * @return {Function} A rendering function to use.
  34319. */
  34320. getRenderer: function(renderer){
  34321. if (Ext.isFunction(renderer)) {
  34322. return renderer;
  34323. }
  34324. return this.statics().Renderer.Html;
  34325. },
  34326. /**
  34327. * Automatically refreshes the content over a specified period.
  34328. * @param {Number} interval The interval to refresh in ms.
  34329. * @param {Object} options (optional) The options to pass to the load method. See {@link #method-load}
  34330. */
  34331. startAutoRefresh: function(interval, options){
  34332. var me = this;
  34333. me.stopAutoRefresh();
  34334. me.autoRefresh = setInterval(function(){
  34335. me.load(options);
  34336. }, interval);
  34337. },
  34338. /**
  34339. * Clears any auto refresh. See {@link #startAutoRefresh}.
  34340. */
  34341. stopAutoRefresh: function(){
  34342. clearInterval(this.autoRefresh);
  34343. delete this.autoRefresh;
  34344. },
  34345. /**
  34346. * Checks whether the loader is automatically refreshing. See {@link #startAutoRefresh}.
  34347. * @return {Boolean} True if the loader is automatically refreshing
  34348. */
  34349. isAutoRefreshing: function(){
  34350. return Ext.isDefined(this.autoRefresh);
  34351. },
  34352. /**
  34353. * Destroys the loader. Any active requests will be aborted.
  34354. */
  34355. destroy: function(){
  34356. var me = this;
  34357. me.stopAutoRefresh();
  34358. delete me.target;
  34359. me.abort();
  34360. me.clearListeners();
  34361. }
  34362. });
  34363. /**
  34364. * This class is used to load content via Ajax into a {@link Ext.Component}. In general
  34365. * this class will not be instanced directly, rather a loader configuration will be passed to the
  34366. * constructor of the {@link Ext.Component}.
  34367. *
  34368. * ## HTML Renderer
  34369. *
  34370. * By default, the content loaded will be processed as raw html. The response text
  34371. * from the request is taken and added to the component. This can be used in
  34372. * conjunction with the {@link #scripts} option to execute any inline scripts in
  34373. * the resulting content. Using this renderer has the same effect as passing the
  34374. * {@link Ext.Component#html} configuration option.
  34375. *
  34376. * ## Data Renderer
  34377. *
  34378. * This renderer allows content to be added by using JSON data and a {@link Ext.XTemplate}.
  34379. * The content received from the response is passed to the {@link Ext.Component#update} method.
  34380. * This content is run through the attached {@link Ext.Component#tpl} and the data is added to
  34381. * the Component. Using this renderer has the same effect as using the {@link Ext.Component#data}
  34382. * configuration in conjunction with a {@link Ext.Component#tpl}.
  34383. *
  34384. * ## Component Renderer
  34385. *
  34386. * This renderer can only be used with a {@link Ext.container.Container} and subclasses. It allows for
  34387. * Components to be loaded remotely into a Container. The response is expected to be a single/series of
  34388. * {@link Ext.Component} configuration objects. When the response is received, the data is decoded
  34389. * and then passed to {@link Ext.container.Container#method-add}. Using this renderer has the same effect as specifying
  34390. * the {@link Ext.container.Container#cfg-items} configuration on a Container.
  34391. *
  34392. * ## Custom Renderer
  34393. *
  34394. * A custom function can be passed to handle any other special case, see the {@link #renderer} option.
  34395. *
  34396. * ## Example Usage
  34397. *
  34398. * var cmp = Ext.create('Ext.Component', {
  34399. * renderTo: Ext.getBody(),
  34400. * tpl: '{firstName} - {lastName}',
  34401. * loader: {
  34402. * url: 'myPage.php',
  34403. * renderer: 'data',
  34404. * params: {
  34405. * userId: 1
  34406. * }
  34407. * }
  34408. * });
  34409. *
  34410. * // call the loader manually (or use autoLoad:true instead)
  34411. * cmp.getLoader().load();
  34412. */
  34413. Ext.define('Ext.ComponentLoader', {
  34414. /* Begin Definitions */
  34415. extend: 'Ext.ElementLoader',
  34416. statics: {
  34417. Renderer: {
  34418. Data: function(loader, response, active){
  34419. var success = true;
  34420. try {
  34421. loader.getTarget().update(Ext.decode(response.responseText));
  34422. } catch (e) {
  34423. success = false;
  34424. }
  34425. return success;
  34426. },
  34427. Component: function(loader, response, active){
  34428. var success = true,
  34429. target = loader.getTarget(),
  34430. items = [];
  34431. if (!target.isContainer) {
  34432. Ext.Error.raise({
  34433. target: target,
  34434. msg: 'Components can only be loaded into a container'
  34435. });
  34436. }
  34437. try {
  34438. items = Ext.decode(response.responseText);
  34439. } catch (e) {
  34440. success = false;
  34441. }
  34442. if (success) {
  34443. target.suspendLayouts();
  34444. if (active.removeAll) {
  34445. target.removeAll();
  34446. }
  34447. target.add(items);
  34448. target.resumeLayouts(true);
  34449. }
  34450. return success;
  34451. }
  34452. }
  34453. },
  34454. /* End Definitions */
  34455. /**
  34456. * @cfg {Ext.Component/String} target The target {@link Ext.Component} for the loader.
  34457. * If a string is passed it will be looked up via the id.
  34458. */
  34459. target: null,
  34460. /**
  34461. * @cfg {Boolean/Object} loadMask True or a {@link Ext.LoadMask} configuration to enable masking during loading.
  34462. */
  34463. loadMask: false,
  34464. /**
  34465. * @cfg {Boolean} scripts True to parse any inline script tags in the response. This only used when using the html
  34466. * {@link #renderer}.
  34467. */
  34468. /**
  34469. * @cfg {String/Function} renderer
  34470. The type of content that is to be loaded into, which can be one of 3 types:
  34471. + **html** : Loads raw html content, see {@link Ext.Component#html}
  34472. + **data** : Loads raw html content, see {@link Ext.Component#data}
  34473. + **component** : Loads child {Ext.Component} instances. This option is only valid when used with a Container.
  34474. Alternatively, you can pass a function which is called with the following parameters.
  34475. + loader - Loader instance
  34476. + response - The server response
  34477. + active - The active request
  34478. The function must return false is loading is not successful. Below is a sample of using a custom renderer:
  34479. new Ext.Component({
  34480. loader: {
  34481. url: 'myPage.php',
  34482. renderer: function(loader, response, active) {
  34483. var text = response.responseText;
  34484. loader.getTarget().update('The response is ' + text);
  34485. return true;
  34486. }
  34487. }
  34488. });
  34489. */
  34490. renderer: 'html',
  34491. /**
  34492. * Set a {Ext.Component} as the target of this loader. Note that if the target is changed,
  34493. * any active requests will be aborted.
  34494. * @param {String/Ext.Component} target The component to be the target of this loader. If a string is passed
  34495. * it will be looked up via its id.
  34496. */
  34497. setTarget: function(target){
  34498. var me = this;
  34499. if (Ext.isString(target)) {
  34500. target = Ext.getCmp(target);
  34501. }
  34502. if (me.target && me.target != target) {
  34503. me.abort();
  34504. }
  34505. me.target = target;
  34506. },
  34507. // inherit docs
  34508. removeMask: function(){
  34509. this.target.setLoading(false);
  34510. },
  34511. /**
  34512. * Add the mask on the target
  34513. * @private
  34514. * @param {Boolean/Object} mask The mask configuration
  34515. */
  34516. addMask: function(mask){
  34517. this.target.setLoading(mask);
  34518. },
  34519. setOptions: function(active, options){
  34520. active.removeAll = Ext.isDefined(options.removeAll) ? options.removeAll : this.removeAll;
  34521. },
  34522. /**
  34523. * Gets the renderer to use
  34524. * @private
  34525. * @param {String/Function} renderer The renderer to use
  34526. * @return {Function} A rendering function to use.
  34527. */
  34528. getRenderer: function(renderer){
  34529. if (Ext.isFunction(renderer)) {
  34530. return renderer;
  34531. }
  34532. var renderers = this.statics().Renderer;
  34533. switch (renderer) {
  34534. case 'component':
  34535. return renderers.Component;
  34536. case 'data':
  34537. return renderers.Data;
  34538. default:
  34539. return Ext.ElementLoader.Renderer.Html;
  34540. }
  34541. }
  34542. });
  34543. /**
  34544. * Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
  34545. *
  34546. * An instance of this class may be created by passing to the constructor either a single argument, or multiple
  34547. * arguments:
  34548. *
  34549. * # Single argument: String/Array
  34550. *
  34551. * The single argument may be either a String or an Array:
  34552. *
  34553. * - String:
  34554. *
  34555. * var t = new Ext.Template("<div>Hello {0}.</div>");
  34556. * t.{@link #append}('some-element', ['foo']);
  34557. *
  34558. * - Array:
  34559. *
  34560. * An Array will be combined with `join('')`.
  34561. *
  34562. * var t = new Ext.Template([
  34563. * '<div name="{id}">',
  34564. * '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
  34565. * '</div>',
  34566. * ]);
  34567. * t.{@link #compile}();
  34568. * t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
  34569. *
  34570. * # Multiple arguments: String, Object, Array, ...
  34571. *
  34572. * Multiple arguments will be combined with `join('')`.
  34573. *
  34574. * var t = new Ext.Template(
  34575. * '<div name="{id}">',
  34576. * '<span class="{cls}">{name} {value}</span>',
  34577. * '</div>',
  34578. * // a configuration object:
  34579. * {
  34580. * compiled: true, // {@link #compile} immediately
  34581. * }
  34582. * );
  34583. *
  34584. * # Notes
  34585. *
  34586. * - For a list of available format functions, see {@link Ext.util.Format}.
  34587. * - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
  34588. */
  34589. Ext.define('Ext.Template', {
  34590. /* Begin Definitions */
  34591. requires: ['Ext.dom.Helper', 'Ext.util.Format'],
  34592. inheritableStatics: {
  34593. /**
  34594. * Creates a template from the passed element's value (_display:none_ textarea, preferred) or innerHTML.
  34595. * @param {String/HTMLElement} el A DOM element or its id
  34596. * @param {Object} config (optional) Config object
  34597. * @return {Ext.Template} The created template
  34598. * @static
  34599. * @inheritable
  34600. */
  34601. from: function(el, config) {
  34602. el = Ext.getDom(el);
  34603. return new this(el.value || el.innerHTML, config || '');
  34604. }
  34605. },
  34606. /* End Definitions */
  34607. /**
  34608. * Creates new template.
  34609. *
  34610. * @param {String...} html List of strings to be concatenated into template.
  34611. * Alternatively an array of strings can be given, but then no config object may be passed.
  34612. * @param {Object} config (optional) Config object
  34613. */
  34614. constructor: function(html) {
  34615. var me = this,
  34616. args = arguments,
  34617. buffer = [],
  34618. i = 0,
  34619. length = args.length,
  34620. value;
  34621. me.initialConfig = {};
  34622. // Allow an array to be passed here so we can
  34623. // pass an array of strings and an object
  34624. // at the end
  34625. if (length === 1 && Ext.isArray(html)) {
  34626. args = html;
  34627. length = args.length;
  34628. }
  34629. if (length > 1) {
  34630. for (; i < length; i++) {
  34631. value = args[i];
  34632. if (typeof value == 'object') {
  34633. Ext.apply(me.initialConfig, value);
  34634. Ext.apply(me, value);
  34635. } else {
  34636. buffer.push(value);
  34637. }
  34638. }
  34639. } else {
  34640. buffer.push(html);
  34641. }
  34642. // @private
  34643. me.html = buffer.join('');
  34644. if (me.compiled) {
  34645. me.compile();
  34646. }
  34647. },
  34648. /**
  34649. * @property {Boolean} isTemplate
  34650. * `true` in this class to identify an object as an instantiated Template, or subclass thereof.
  34651. */
  34652. isTemplate: true,
  34653. /**
  34654. * @cfg {Boolean} compiled
  34655. * True to immediately compile the template. Defaults to false.
  34656. */
  34657. /**
  34658. * @cfg {Boolean} disableFormats
  34659. * True to disable format functions in the template. If the template doesn't contain
  34660. * format functions, setting disableFormats to true will reduce apply time. Defaults to false.
  34661. */
  34662. disableFormats: false,
  34663. re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
  34664. /**
  34665. * Returns an HTML fragment of this template with the specified values applied.
  34666. *
  34667. * @param {Object/Array} values The template values. Can be an array if your params are numeric:
  34668. *
  34669. * var tpl = new Ext.Template('Name: {0}, Age: {1}');
  34670. * tpl.apply(['John', 25]);
  34671. *
  34672. * or an object:
  34673. *
  34674. * var tpl = new Ext.Template('Name: {name}, Age: {age}');
  34675. * tpl.apply({name: 'John', age: 25});
  34676. *
  34677. * @return {String} The HTML fragment
  34678. */
  34679. apply: function(values) {
  34680. var me = this,
  34681. useFormat = me.disableFormats !== true,
  34682. fm = Ext.util.Format,
  34683. tpl = me,
  34684. ret;
  34685. if (me.compiled) {
  34686. return me.compiled(values).join('');
  34687. }
  34688. function fn(m, name, format, args) {
  34689. if (format && useFormat) {
  34690. if (args) {
  34691. args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
  34692. } else {
  34693. args = [values[name]];
  34694. }
  34695. if (format.substr(0, 5) == "this.") {
  34696. return tpl[format.substr(5)].apply(tpl, args);
  34697. }
  34698. else {
  34699. return fm[format].apply(fm, args);
  34700. }
  34701. }
  34702. else {
  34703. return values[name] !== undefined ? values[name] : "";
  34704. }
  34705. }
  34706. ret = me.html.replace(me.re, fn);
  34707. return ret;
  34708. },
  34709. /**
  34710. * Appends the result of this template to the provided output array.
  34711. * @param {Object/Array} values The template values. See {@link #apply}.
  34712. * @param {Array} out The array to which output is pushed.
  34713. * @return {Array} The given out array.
  34714. */
  34715. applyOut: function(values, out) {
  34716. var me = this;
  34717. if (me.compiled) {
  34718. out.push.apply(out, me.compiled(values));
  34719. } else {
  34720. out.push(me.apply(values));
  34721. }
  34722. return out;
  34723. },
  34724. /**
  34725. * @method applyTemplate
  34726. * @member Ext.Template
  34727. * Alias for {@link #apply}.
  34728. * @inheritdoc Ext.Template#apply
  34729. */
  34730. applyTemplate: function () {
  34731. return this.apply.apply(this, arguments);
  34732. },
  34733. /**
  34734. * Sets the HTML used as the template and optionally compiles it.
  34735. * @param {String} html
  34736. * @param {Boolean} compile (optional) True to compile the template.
  34737. * @return {Ext.Template} this
  34738. */
  34739. set: function(html, compile) {
  34740. var me = this;
  34741. me.html = html;
  34742. me.compiled = null;
  34743. return compile ? me.compile() : me;
  34744. },
  34745. compileARe: /\\/g,
  34746. compileBRe: /(\r\n|\n)/g,
  34747. compileCRe: /'/g,
  34748. /**
  34749. * Compiles the template into an internal function, eliminating the RegEx overhead.
  34750. * @return {Ext.Template} this
  34751. */
  34752. compile: function() {
  34753. var me = this,
  34754. fm = Ext.util.Format,
  34755. useFormat = me.disableFormats !== true,
  34756. body, bodyReturn;
  34757. function fn(m, name, format, args) {
  34758. if (format && useFormat) {
  34759. args = args ? ',' + args: "";
  34760. if (format.substr(0, 5) != "this.") {
  34761. format = "fm." + format + '(';
  34762. }
  34763. else {
  34764. format = 'this.' + format.substr(5) + '(';
  34765. }
  34766. }
  34767. else {
  34768. args = '';
  34769. format = "(values['" + name + "'] == undefined ? '' : ";
  34770. }
  34771. return "'," + format + "values['" + name + "']" + args + ") ,'";
  34772. }
  34773. bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
  34774. body = "this.compiled = function(values){ return ['" + bodyReturn + "'];};";
  34775. eval(body);
  34776. return me;
  34777. },
  34778. /**
  34779. * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
  34780. *
  34781. * @param {String/HTMLElement/Ext.Element} el The context element
  34782. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  34783. * @param {Boolean} returnElement (optional) true to return a Ext.Element.
  34784. * @return {HTMLElement/Ext.Element} The new node or Element
  34785. */
  34786. insertFirst: function(el, values, returnElement) {
  34787. return this.doInsert('afterBegin', el, values, returnElement);
  34788. },
  34789. /**
  34790. * Applies the supplied values to the template and inserts the new node(s) before el.
  34791. *
  34792. * @param {String/HTMLElement/Ext.Element} el The context element
  34793. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  34794. * @param {Boolean} returnElement (optional) true to return a Ext.Element.
  34795. * @return {HTMLElement/Ext.Element} The new node or Element
  34796. */
  34797. insertBefore: function(el, values, returnElement) {
  34798. return this.doInsert('beforeBegin', el, values, returnElement);
  34799. },
  34800. /**
  34801. * Applies the supplied values to the template and inserts the new node(s) after el.
  34802. *
  34803. * @param {String/HTMLElement/Ext.Element} el The context element
  34804. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  34805. * @param {Boolean} returnElement (optional) true to return a Ext.Element.
  34806. * @return {HTMLElement/Ext.Element} The new node or Element
  34807. */
  34808. insertAfter: function(el, values, returnElement) {
  34809. return this.doInsert('afterEnd', el, values, returnElement);
  34810. },
  34811. /**
  34812. * Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
  34813. *
  34814. * For example usage see {@link Ext.Template Ext.Template class docs}.
  34815. *
  34816. * @param {String/HTMLElement/Ext.Element} el The context element
  34817. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  34818. * @param {Boolean} returnElement (optional) true to return an Ext.Element.
  34819. * @return {HTMLElement/Ext.Element} The new node or Element
  34820. */
  34821. append: function(el, values, returnElement) {
  34822. return this.doInsert('beforeEnd', el, values, returnElement);
  34823. },
  34824. doInsert: function(where, el, values, returnElement) {
  34825. var newNode = Ext.DomHelper.insertHtml(where, Ext.getDom(el), this.apply(values));
  34826. return returnElement ? Ext.get(newNode) : newNode;
  34827. },
  34828. /**
  34829. * Applies the supplied values to the template and overwrites the content of el with the new node(s).
  34830. *
  34831. * @param {String/HTMLElement/Ext.Element} el The context element
  34832. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  34833. * @param {Boolean} returnElement (optional) true to return a Ext.Element.
  34834. * @return {HTMLElement/Ext.Element} The new node or Element
  34835. */
  34836. overwrite: function(el, values, returnElement) {
  34837. var newNode = Ext.DomHelper.overwrite(Ext.getDom(el), this.apply(values));
  34838. return returnElement ? Ext.get(newNode) : newNode;
  34839. }
  34840. });
  34841. /**
  34842. * This class parses the XTemplate syntax and calls abstract methods to process the parts.
  34843. * @private
  34844. */
  34845. Ext.define('Ext.XTemplateParser', {
  34846. constructor: function (config) {
  34847. Ext.apply(this, config);
  34848. },
  34849. /**
  34850. * @property {Number} level The 'for' loop context level. This is adjusted up by one
  34851. * prior to calling {@link #doFor} and down by one after calling the corresponding
  34852. * {@link #doEnd} that closes the loop. This will be 1 on the first {@link #doFor}
  34853. * call.
  34854. */
  34855. /**
  34856. * This method is called to process a piece of raw text from the tpl.
  34857. * @param {String} text
  34858. * @method doText
  34859. */
  34860. // doText: function (text)
  34861. /**
  34862. * This method is called to process expressions (like `{[expr]}`).
  34863. * @param {String} expr The body of the expression (inside "{[" and "]}").
  34864. * @method doExpr
  34865. */
  34866. // doExpr: function (expr)
  34867. /**
  34868. * This method is called to process simple tags (like `{tag}`).
  34869. * @method doTag
  34870. */
  34871. // doTag: function (tag)
  34872. /**
  34873. * This method is called to process `<tpl else>`.
  34874. * @method doElse
  34875. */
  34876. // doElse: function ()
  34877. /**
  34878. * This method is called to process `{% text %}`.
  34879. * @param {String} text
  34880. * @method doEval
  34881. */
  34882. // doEval: function (text)
  34883. /**
  34884. * This method is called to process `<tpl if="action">`. If there are other attributes,
  34885. * these are passed in the actions object.
  34886. * @param {String} action
  34887. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  34888. * @method doIf
  34889. */
  34890. // doIf: function (action, actions)
  34891. /**
  34892. * This method is called to process `<tpl elseif="action">`. If there are other attributes,
  34893. * these are passed in the actions object.
  34894. * @param {String} action
  34895. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  34896. * @method doElseIf
  34897. */
  34898. // doElseIf: function (action, actions)
  34899. /**
  34900. * This method is called to process `<tpl switch="action">`. If there are other attributes,
  34901. * these are passed in the actions object.
  34902. * @param {String} action
  34903. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  34904. * @method doSwitch
  34905. */
  34906. // doSwitch: function (action, actions)
  34907. /**
  34908. * This method is called to process `<tpl case="action">`. If there are other attributes,
  34909. * these are passed in the actions object.
  34910. * @param {String} action
  34911. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  34912. * @method doCase
  34913. */
  34914. // doCase: function (action, actions)
  34915. /**
  34916. * This method is called to process `<tpl default>`.
  34917. * @method doDefault
  34918. */
  34919. // doDefault: function ()
  34920. /**
  34921. * This method is called to process `</tpl>`. It is given the action type that started
  34922. * the tpl and the set of additional actions.
  34923. * @param {String} type The type of action that is being ended.
  34924. * @param {Object} actions The other actions keyed by the attribute name (such as 'exec').
  34925. * @method doEnd
  34926. */
  34927. // doEnd: function (type, actions)
  34928. /**
  34929. * This method is called to process `<tpl for="action">`. If there are other attributes,
  34930. * these are passed in the actions object.
  34931. * @param {String} action
  34932. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  34933. * @method doFor
  34934. */
  34935. // doFor: function (action, actions)
  34936. /**
  34937. * This method is called to process `<tpl exec="action">`. If there are other attributes,
  34938. * these are passed in the actions object.
  34939. * @param {String} action
  34940. * @param {Object} actions Other actions keyed by the attribute name.
  34941. * @method doExec
  34942. */
  34943. // doExec: function (action, actions)
  34944. /**
  34945. * This method is called to process an empty `<tpl>`. This is unlikely to need to be
  34946. * implemented, so a default (do nothing) version is provided.
  34947. * @method
  34948. */
  34949. doTpl: Ext.emptyFn,
  34950. parse: function (str) {
  34951. var me = this,
  34952. len = str.length,
  34953. aliases = { elseif: 'elif' },
  34954. topRe = me.topRe,
  34955. actionsRe = me.actionsRe,
  34956. index, stack, s, m, t, prev, frame, subMatch, begin, end, actions,
  34957. prop;
  34958. me.level = 0;
  34959. me.stack = stack = [];
  34960. for (index = 0; index < len; index = end) {
  34961. topRe.lastIndex = index;
  34962. m = topRe.exec(str);
  34963. if (!m) {
  34964. me.doText(str.substring(index, len));
  34965. break;
  34966. }
  34967. begin = m.index;
  34968. end = topRe.lastIndex;
  34969. if (index < begin) {
  34970. me.doText(str.substring(index, begin));
  34971. }
  34972. if (m[1]) {
  34973. end = str.indexOf('%}', begin+2);
  34974. me.doEval(str.substring(begin+2, end));
  34975. end += 2;
  34976. } else if (m[2]) {
  34977. end = str.indexOf(']}', begin+2);
  34978. me.doExpr(str.substring(begin+2, end));
  34979. end += 2;
  34980. } else if (m[3]) { // if ('{' token)
  34981. me.doTag(m[3]);
  34982. } else if (m[4]) { // content of a <tpl xxxxxx xxx> tag
  34983. actions = null;
  34984. while ((subMatch = actionsRe.exec(m[4])) !== null) {
  34985. s = subMatch[2] || subMatch[3];
  34986. if (s) {
  34987. s = Ext.String.htmlDecode(s); // decode attr value
  34988. t = subMatch[1];
  34989. t = aliases[t] || t;
  34990. actions = actions || {};
  34991. prev = actions[t];
  34992. if (typeof prev == 'string') {
  34993. actions[t] = [prev, s];
  34994. } else if (prev) {
  34995. actions[t].push(s);
  34996. } else {
  34997. actions[t] = s;
  34998. }
  34999. }
  35000. }
  35001. if (!actions) {
  35002. if (me.elseRe.test(m[4])) {
  35003. me.doElse();
  35004. } else if (me.defaultRe.test(m[4])) {
  35005. me.doDefault();
  35006. } else {
  35007. me.doTpl();
  35008. stack.push({ type: 'tpl' });
  35009. }
  35010. }
  35011. else if (actions['if']) {
  35012. me.doIf(actions['if'], actions);
  35013. stack.push({ type: 'if' });
  35014. }
  35015. else if (actions['switch']) {
  35016. me.doSwitch(actions['switch'], actions);
  35017. stack.push({ type: 'switch' });
  35018. }
  35019. else if (actions['case']) {
  35020. me.doCase(actions['case'], actions);
  35021. }
  35022. else if (actions['elif']) {
  35023. me.doElseIf(actions['elif'], actions);
  35024. }
  35025. else if (actions['for']) {
  35026. ++me.level;
  35027. // Extract property name to use from indexed item
  35028. if (prop = me.propRe.exec(m[4])) {
  35029. actions.propName = prop[1] || prop[2];
  35030. }
  35031. me.doFor(actions['for'], actions);
  35032. stack.push({ type: 'for', actions: actions });
  35033. }
  35034. else if (actions.exec) {
  35035. me.doExec(actions.exec, actions);
  35036. stack.push({ type: 'exec', actions: actions });
  35037. }
  35038. /*
  35039. else {
  35040. // todo - error
  35041. }
  35042. */
  35043. } else if (m[0].length === 5) {
  35044. // if the length of m[0] is 5, assume that we're dealing with an opening tpl tag with no attributes (e.g. <tpl>...</tpl>)
  35045. // in this case no action is needed other than pushing it on to the stack
  35046. stack.push({ type: 'tpl' });
  35047. } else {
  35048. frame = stack.pop();
  35049. me.doEnd(frame.type, frame.actions);
  35050. if (frame.type == 'for') {
  35051. --me.level;
  35052. }
  35053. }
  35054. }
  35055. },
  35056. // Internal regexes
  35057. topRe: /(?:(\{\%)|(\{\[)|\{([^{}]*)\})|(?:<tpl([^>]*)\>)|(?:<\/tpl>)/g,
  35058. actionsRe: /\s*(elif|elseif|if|for|exec|switch|case|eval)\s*\=\s*(?:(?:"([^"]*)")|(?:'([^']*)'))\s*/g,
  35059. propRe: /prop=(?:(?:"([^"]*)")|(?:'([^']*)'))/,
  35060. defaultRe: /^\s*default\s*$/,
  35061. elseRe: /^\s*else\s*$/
  35062. });
  35063. /**
  35064. * This class compiles the XTemplate syntax into a function object. The function is used
  35065. * like so:
  35066. *
  35067. * function (out, values, parent, xindex, xcount) {
  35068. * // out is the output array to store results
  35069. * // values, parent, xindex and xcount have their historical meaning
  35070. * }
  35071. *
  35072. * @markdown
  35073. * @private
  35074. */
  35075. Ext.define('Ext.XTemplateCompiler', {
  35076. extend: 'Ext.XTemplateParser',
  35077. // Chrome really likes "new Function" to realize the code block (as in it is
  35078. // 2x-3x faster to call it than using eval), but Firefox chokes on it badly.
  35079. // IE and Opera are also fine with the "new Function" technique.
  35080. useEval: Ext.isGecko,
  35081. // See http://jsperf.com/nige-array-append for quickest way to append to an array of unknown length
  35082. // (Due to arbitrary code execution inside a template, we cannot easily track the length in var)
  35083. // On IE6 and 7 myArray[myArray.length]='foo' is better. On other browsers myArray.push('foo') is better.
  35084. useIndex: Ext.isIE6 || Ext.isIE7,
  35085. useFormat: true,
  35086. propNameRe: /^[\w\d\$]*$/,
  35087. compile: function (tpl) {
  35088. var me = this,
  35089. code = me.generate(tpl);
  35090. // When using "new Function", we have to pass our "Ext" variable to it in order to
  35091. // support sandboxing. If we did not, the generated function would use the global
  35092. // "Ext", not the "Ext" from our sandbox (scope chain).
  35093. //
  35094. return me.useEval ? me.evalTpl(code) : (new Function('Ext', code))(Ext);
  35095. },
  35096. generate: function (tpl) {
  35097. var me = this,
  35098. // note: Ext here is properly sandboxed
  35099. definitions = 'var fm=Ext.util.Format,ts=Object.prototype.toString;',
  35100. code;
  35101. // Track how many levels we use, so that we only "var" each level's variables once
  35102. me.maxLevel = 0;
  35103. me.body = [
  35104. 'var c0=values, a0=' + me.createArrayTest(0) + ', p0=parent, n0=xcount, i0=xindex, v;\n'
  35105. ];
  35106. if (me.definitions) {
  35107. if (typeof me.definitions === 'string') {
  35108. me.definitions = [me.definitions, definitions ];
  35109. } else {
  35110. me.definitions.push(definitions);
  35111. }
  35112. } else {
  35113. me.definitions = [ definitions ];
  35114. }
  35115. me.switches = [];
  35116. me.parse(tpl);
  35117. me.definitions.push(
  35118. (me.useEval ? '$=' : 'return') + ' function (' + me.fnArgs + ') {',
  35119. me.body.join(''),
  35120. '}'
  35121. );
  35122. code = me.definitions.join('\n');
  35123. // Free up the arrays.
  35124. me.definitions.length = me.body.length = me.switches.length = 0;
  35125. delete me.definitions;
  35126. delete me.body;
  35127. delete me.switches;
  35128. return code;
  35129. },
  35130. //-----------------------------------
  35131. // XTemplateParser callouts
  35132. doText: function (text) {
  35133. var me = this,
  35134. out = me.body;
  35135. text = text.replace(me.aposRe, "\\'").replace(me.newLineRe, '\\n');
  35136. if (me.useIndex) {
  35137. out.push('out[out.length]=\'', text, '\'\n');
  35138. } else {
  35139. out.push('out.push(\'', text, '\')\n');
  35140. }
  35141. },
  35142. doExpr: function (expr) {
  35143. var out = this.body;
  35144. out.push('if ((v=' + expr + ')!==undefined) out');
  35145. // Coerce value to string using concatenation of an empty string literal.
  35146. // See http://jsperf.com/tostringvscoercion/5
  35147. if (this.useIndex) {
  35148. out.push('[out.length]=v+\'\'\n');
  35149. } else {
  35150. out.push('.push(v+\'\')\n');
  35151. }
  35152. },
  35153. doTag: function (tag) {
  35154. this.doExpr(this.parseTag(tag));
  35155. },
  35156. doElse: function () {
  35157. this.body.push('} else {\n');
  35158. },
  35159. doEval: function (text) {
  35160. this.body.push(text, '\n');
  35161. },
  35162. doIf: function (action, actions) {
  35163. var me = this;
  35164. // If it's just a propName, use it directly in the if
  35165. if (action === '.') {
  35166. me.body.push('if (values) {\n');
  35167. } else if (me.propNameRe.test(action)) {
  35168. me.body.push('if (', me.parseTag(action), ') {\n');
  35169. }
  35170. // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
  35171. else {
  35172. me.body.push('if (', me.addFn(action), me.callFn, ') {\n');
  35173. }
  35174. if (actions.exec) {
  35175. me.doExec(actions.exec);
  35176. }
  35177. },
  35178. doElseIf: function (action, actions) {
  35179. var me = this;
  35180. // If it's just a propName, use it directly in the else if
  35181. if (action === '.') {
  35182. me.body.push('else if (values) {\n');
  35183. } else if (me.propNameRe.test(action)) {
  35184. me.body.push('} else if (', me.parseTag(action), ') {\n');
  35185. }
  35186. // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
  35187. else {
  35188. me.body.push('} else if (', me.addFn(action), me.callFn, ') {\n');
  35189. }
  35190. if (actions.exec) {
  35191. me.doExec(actions.exec);
  35192. }
  35193. },
  35194. doSwitch: function (action) {
  35195. var me = this;
  35196. // If it's just a propName, use it directly in the switch
  35197. if (action === '.') {
  35198. me.body.push('switch (values) {\n');
  35199. } else if (me.propNameRe.test(action)) {
  35200. me.body.push('switch (', me.parseTag(action), ') {\n');
  35201. }
  35202. // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
  35203. else {
  35204. me.body.push('switch (', me.addFn(action), me.callFn, ') {\n');
  35205. }
  35206. me.switches.push(0);
  35207. },
  35208. doCase: function (action) {
  35209. var me = this,
  35210. cases = Ext.isArray(action) ? action : [action],
  35211. n = me.switches.length - 1,
  35212. match, i;
  35213. if (me.switches[n]) {
  35214. me.body.push('break;\n');
  35215. } else {
  35216. me.switches[n]++;
  35217. }
  35218. for (i = 0, n = cases.length; i < n; ++i) {
  35219. match = me.intRe.exec(cases[i]);
  35220. cases[i] = match ? match[1] : ("'" + cases[i].replace(me.aposRe,"\\'") + "'");
  35221. }
  35222. me.body.push('case ', cases.join(': case '), ':\n');
  35223. },
  35224. doDefault: function () {
  35225. var me = this,
  35226. n = me.switches.length - 1;
  35227. if (me.switches[n]) {
  35228. me.body.push('break;\n');
  35229. } else {
  35230. me.switches[n]++;
  35231. }
  35232. me.body.push('default:\n');
  35233. },
  35234. doEnd: function (type, actions) {
  35235. var me = this,
  35236. L = me.level-1;
  35237. if (type == 'for') {
  35238. /*
  35239. To exit a for loop we must restore the outer loop's context. The code looks
  35240. like this (which goes with that produced by doFor:
  35241. for (...) { // the part generated by doFor
  35242. ... // the body of the for loop
  35243. // ... any tpl for exec statement goes here...
  35244. }
  35245. parent = p1;
  35246. values = r2;
  35247. xcount = n1;
  35248. xindex = i1
  35249. */
  35250. if (actions.exec) {
  35251. me.doExec(actions.exec);
  35252. }
  35253. me.body.push('}\n');
  35254. me.body.push('parent=p',L,';values=r',L+1,';xcount=n',L,';xindex=i',L,'\n');
  35255. } else if (type == 'if' || type == 'switch') {
  35256. me.body.push('}\n');
  35257. }
  35258. },
  35259. doFor: function (action, actions) {
  35260. var me = this,
  35261. s,
  35262. L = me.level,
  35263. up = L-1,
  35264. pL = 'p' + L,
  35265. parentAssignment;
  35266. // If it's just a propName, use it directly in the switch
  35267. if (action === '.') {
  35268. s = 'values';
  35269. } else if (me.propNameRe.test(action)) {
  35270. s = me.parseTag(action);
  35271. }
  35272. // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
  35273. else {
  35274. s = me.addFn(action) + me.callFn;
  35275. }
  35276. /*
  35277. We are trying to produce a block of code that looks like below. We use the nesting
  35278. level to uniquely name the control variables.
  35279. // Omit "var " if we have already been through level 2
  35280. var i2 = 0,
  35281. n2 = 0,
  35282. c2 = values['propName'],
  35283. // c2 is the context object for the for loop
  35284. a2 = Array.isArray(c2);
  35285. p2 = c1,
  35286. // p2 is the parent context (of the outer for loop)
  35287. r2 = values
  35288. // r2 is the values object to
  35289. // If iterating over the current data, the parent is always set to c2
  35290. parent = c2;
  35291. // If iterating over a property in an object, set the parent to the object
  35292. parent = a1 ? c1[i1] : p2 // set parent
  35293. if (c2) {
  35294. if (a2) {
  35295. n2 = c2.length;
  35296. } else if (c2.isMixedCollection) {
  35297. c2 = c2.items;
  35298. n2 = c2.length;
  35299. } else if (c2.isStore) {
  35300. c2 = c2.data.items;
  35301. n2 = c2.length;
  35302. } else {
  35303. c2 = [ c2 ];
  35304. n2 = 1;
  35305. }
  35306. }
  35307. // i2 is the loop index and n2 is the number (xcount) of this for loop
  35308. for (xcount = n2; i2 < n2; ++i2) {
  35309. values = c2[i2] // adjust special vars to inner scope
  35310. xindex = i2 + 1 // xindex is 1-based
  35311. The body of the loop is whatever comes between the tpl and /tpl statements (which
  35312. is handled by doEnd).
  35313. */
  35314. // Declare the vars for a particular level only if we have not already declared them.
  35315. if (me.maxLevel < L) {
  35316. me.maxLevel = L;
  35317. me.body.push('var ');
  35318. }
  35319. if (action == '.') {
  35320. parentAssignment = 'c' + L;
  35321. } else {
  35322. parentAssignment = 'a' + up + '?c' + up + '[i' + up + ']:p' + L;
  35323. }
  35324. me.body.push('i',L,'=0,n', L, '=0,c',L,'=',s,',a',L,'=', me.createArrayTest(L), ',p',L,'=c',up,',r',L,'=values;\n',
  35325. 'parent=',parentAssignment,'\n',
  35326. 'if (c',L,'){if(a',L,'){n', L,'=c', L, '.length;}else if (c', L, '.isMixedCollection){c',L,'=c',L,'.items;n',L,'=c',L,'.length;}else if(c',L,'.isStore){c',L,'=c',L,'.data.items;n',L,'=c',L,'.length;}else{c',L,'=[c',L,'];n',L,'=1;}}\n',
  35327. 'for (xcount=n',L,';i',L,'<n'+L+';++i',L,'){\n',
  35328. 'values=c',L,'[i',L,']');
  35329. if (actions.propName) {
  35330. me.body.push('.', actions.propName);
  35331. }
  35332. me.body.push('\n',
  35333. 'xindex=i',L,'+1\n');
  35334. },
  35335. createArrayTest: ('isArray' in Array) ? function(L) {
  35336. return 'Array.isArray(c' + L + ')';
  35337. } : function(L) {
  35338. return 'ts.call(c' + L + ')==="[object Array]"';
  35339. },
  35340. doExec: function (action, actions) {
  35341. var me = this,
  35342. name = 'f' + me.definitions.length;
  35343. me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
  35344. ' try { with(values) {',
  35345. ' ' + action,
  35346. ' }} catch(e) {',
  35347. 'Ext.log("XTemplate Error: " + e.message);',
  35348. '}',
  35349. '}');
  35350. me.body.push(name + me.callFn + '\n');
  35351. },
  35352. //-----------------------------------
  35353. // Internal
  35354. addFn: function (body) {
  35355. var me = this,
  35356. name = 'f' + me.definitions.length;
  35357. if (body === '.') {
  35358. me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
  35359. ' return values',
  35360. '}');
  35361. } else if (body === '..') {
  35362. me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
  35363. ' return parent',
  35364. '}');
  35365. } else {
  35366. me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
  35367. ' try { with(values) {',
  35368. ' return(' + body + ')',
  35369. ' }} catch(e) {',
  35370. 'Ext.log("XTemplate Error: " + e.message);',
  35371. '}',
  35372. '}');
  35373. }
  35374. return name;
  35375. },
  35376. parseTag: function (tag) {
  35377. var me = this,
  35378. m = me.tagRe.exec(tag),
  35379. name = m[1],
  35380. format = m[2],
  35381. args = m[3],
  35382. math = m[4],
  35383. v;
  35384. // name = "." - Just use the values object.
  35385. if (name == '.') {
  35386. // filter to not include arrays/objects/nulls
  35387. if (!me.validTypes) {
  35388. me.definitions.push('var validTypes={string:1,number:1,boolean:1};');
  35389. me.validTypes = true;
  35390. }
  35391. v = 'validTypes[typeof values] || ts.call(values) === "[object Date]" ? values : ""';
  35392. }
  35393. // name = "#" - Use the xindex
  35394. else if (name == '#') {
  35395. v = 'xindex';
  35396. }
  35397. else if (name.substr(0, 7) == "parent.") {
  35398. v = name;
  35399. }
  35400. // compound Javascript property name (e.g., "foo.bar")
  35401. else if (isNaN(name) && name.indexOf('-') == -1 && name.indexOf('.') != -1) {
  35402. v = "values." + name;
  35403. }
  35404. // number or a '-' in it or a single word (maybe a keyword): use array notation
  35405. // (http://jsperf.com/string-property-access/4)
  35406. else {
  35407. v = "values['" + name + "']";
  35408. }
  35409. if (math) {
  35410. v = '(' + v + math + ')';
  35411. }
  35412. if (format && me.useFormat) {
  35413. args = args ? ',' + args : "";
  35414. if (format.substr(0, 5) != "this.") {
  35415. format = "fm." + format + '(';
  35416. } else {
  35417. format += '(';
  35418. }
  35419. } else {
  35420. return v;
  35421. }
  35422. return format + v + args + ')';
  35423. },
  35424. // @private
  35425. evalTpl: function ($) {
  35426. // We have to use eval to realize the code block and capture the inner func we also
  35427. // don't want a deep scope chain. We only do this in Firefox and it is also unhappy
  35428. // with eval containing a return statement, so instead we assign to "$" and return
  35429. // that. Because we use "eval", we are automatically sandboxed properly.
  35430. eval($);
  35431. return $;
  35432. },
  35433. newLineRe: /\r\n|\r|\n/g,
  35434. aposRe: /[']/g,
  35435. intRe: /^\s*(\d+)\s*$/,
  35436. tagRe: /([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?/
  35437. }, function () {
  35438. var proto = this.prototype;
  35439. proto.fnArgs = 'out,values,parent,xindex,xcount';
  35440. proto.callFn = '.call(this,' + proto.fnArgs + ')';
  35441. });
  35442. /**
  35443. * A template class that supports advanced functionality like:
  35444. *
  35445. * - Autofilling arrays using templates and sub-templates
  35446. * - Conditional processing with basic comparison operators
  35447. * - Basic math function support
  35448. * - Execute arbitrary inline code with special built-in template variables
  35449. * - Custom member functions
  35450. * - Many special tags and built-in operators that aren't defined as part of the API, but are supported in the templates that can be created
  35451. *
  35452. * XTemplate provides the templating mechanism built into {@link Ext.view.View}.
  35453. *
  35454. * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
  35455. * demonstrate all of the supported features.
  35456. *
  35457. * # Sample Data
  35458. *
  35459. * This is the data object used for reference in each code example:
  35460. *
  35461. * var data = {
  35462. * name: 'Don Griffin',
  35463. * title: 'Senior Technomage',
  35464. * company: 'Sencha Inc.',
  35465. * drinks: ['Coffee', 'Water', 'More Coffee'],
  35466. * kids: [
  35467. * { name: 'Aubrey', age: 17 },
  35468. * { name: 'Joshua', age: 13 },
  35469. * { name: 'Cale', age: 10 },
  35470. * { name: 'Nikol', age: 5 },
  35471. * { name: 'Solomon', age: 0 }
  35472. * ]
  35473. * };
  35474. *
  35475. * # Auto filling of arrays
  35476. *
  35477. * The **tpl** tag and the **for** operator are used to process the provided data object:
  35478. *
  35479. * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
  35480. * tag for each item in the array.
  35481. * - If for="." is specified, the data object provided is examined.
  35482. * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
  35483. *
  35484. * Examples:
  35485. *
  35486. * <tpl for=".">...</tpl> // loop through array at root node
  35487. * <tpl for="foo">...</tpl> // loop through array at foo node
  35488. * <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node
  35489. *
  35490. * Using the sample data above:
  35491. *
  35492. * var tpl = new Ext.XTemplate(
  35493. * '<p>Kids: ',
  35494. * '<tpl for=".">', // process the data.kids node
  35495. * '<p>{#}. {name}</p>', // use current array index to autonumber
  35496. * '</tpl></p>'
  35497. * );
  35498. * tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
  35499. *
  35500. * An example illustrating how the **for** property can be leveraged to access specified members of the provided data
  35501. * object to populate the template:
  35502. *
  35503. * var tpl = new Ext.XTemplate(
  35504. * '<p>Name: {name}</p>',
  35505. * '<p>Title: {title}</p>',
  35506. * '<p>Company: {company}</p>',
  35507. * '<p>Kids: ',
  35508. * '<tpl for="kids">', // interrogate the kids property within the data
  35509. * '<p>{name}</p>',
  35510. * '</tpl></p>'
  35511. * );
  35512. * tpl.overwrite(panel.body, data); // pass the root node of the data object
  35513. *
  35514. * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
  35515. * loop. This variable will represent the value of the array at the current index:
  35516. *
  35517. * var tpl = new Ext.XTemplate(
  35518. * '<p>{name}\'s favorite beverages:</p>',
  35519. * '<tpl for="drinks">',
  35520. * '<div> - {.}</div>',
  35521. * '</tpl>'
  35522. * );
  35523. * tpl.overwrite(panel.body, data);
  35524. *
  35525. * When processing a sub-template, for example while looping through a child array, you can access the parent object's
  35526. * members via the **parent** object:
  35527. *
  35528. * var tpl = new Ext.XTemplate(
  35529. * '<p>Name: {name}</p>',
  35530. * '<p>Kids: ',
  35531. * '<tpl for="kids">',
  35532. * '<tpl if="age &gt; 1">',
  35533. * '<p>{name}</p>',
  35534. * '<p>Dad: {parent.name}</p>',
  35535. * '</tpl>',
  35536. * '</tpl></p>'
  35537. * );
  35538. * tpl.overwrite(panel.body, data);
  35539. *
  35540. * # Conditional processing with basic comparison operators
  35541. *
  35542. * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
  35543. * specific parts of the template.
  35544. *
  35545. * Using the sample data above:
  35546. *
  35547. * var tpl = new Ext.XTemplate(
  35548. * '<p>Name: {name}</p>',
  35549. * '<p>Kids: ',
  35550. * '<tpl for="kids">',
  35551. * '<tpl if="age &gt; 1">',
  35552. * '<p>{name}</p>',
  35553. * '</tpl>',
  35554. * '</tpl></p>'
  35555. * );
  35556. * tpl.overwrite(panel.body, data);
  35557. *
  35558. * More advanced conditionals are also supported:
  35559. *
  35560. * var tpl = new Ext.XTemplate(
  35561. * '<p>Name: {name}</p>',
  35562. * '<p>Kids: ',
  35563. * '<tpl for="kids">',
  35564. * '<p>{name} is a ',
  35565. * '<tpl if="age &gt;= 13">',
  35566. * '<p>teenager</p>',
  35567. * '<tpl elseif="age &gt;= 2">',
  35568. * '<p>kid</p>',
  35569. * '<tpl else>',
  35570. * '<p>baby</p>',
  35571. * '</tpl>',
  35572. * '</tpl></p>'
  35573. * );
  35574. *
  35575. * var tpl = new Ext.XTemplate(
  35576. * '<p>Name: {name}</p>',
  35577. * '<p>Kids: ',
  35578. * '<tpl for="kids">',
  35579. * '<p>{name} is a ',
  35580. * '<tpl switch="name">',
  35581. * '<tpl case="Aubrey" case="Nikol">',
  35582. * '<p>girl</p>',
  35583. * '<tpl default>',
  35584. * '<p>boy</p>',
  35585. * '</tpl>',
  35586. * '</tpl></p>'
  35587. * );
  35588. *
  35589. * A `break` is implied between each case and default, however, multiple cases can be listed
  35590. * in a single &lt;tpl&gt; tag.
  35591. *
  35592. * # Using double quotes
  35593. *
  35594. * Examples:
  35595. *
  35596. * var tpl = new Ext.XTemplate(
  35597. * "<tpl if='age &gt; 1 && age &lt; 10'>Child</tpl>",
  35598. * "<tpl if='age &gt;= 10 && age &lt; 18'>Teenager</tpl>",
  35599. * "<tpl if='this.isGirl(name)'>...</tpl>",
  35600. * '<tpl if="id == \'download\'">...</tpl>',
  35601. * "<tpl if='needsIcon'><img src='{icon}' class='{iconCls}'/></tpl>",
  35602. * "<tpl if='name == \"Don\"'>Hello</tpl>"
  35603. * );
  35604. *
  35605. * # Basic math support
  35606. *
  35607. * The following basic math operators may be applied directly on numeric data values:
  35608. *
  35609. * + - * /
  35610. *
  35611. * For example:
  35612. *
  35613. * var tpl = new Ext.XTemplate(
  35614. * '<p>Name: {name}</p>',
  35615. * '<p>Kids: ',
  35616. * '<tpl for="kids">',
  35617. * '<tpl if="age &gt; 1">', // <-- Note that the > is encoded
  35618. * '<p>{#}: {name}</p>', // <-- Auto-number each item
  35619. * '<p>In 5 Years: {age+5}</p>', // <-- Basic math
  35620. * '<p>Dad: {parent.name}</p>',
  35621. * '</tpl>',
  35622. * '</tpl></p>'
  35623. * );
  35624. * tpl.overwrite(panel.body, data);
  35625. *
  35626. * # Execute arbitrary inline code with special built-in template variables
  35627. *
  35628. * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template.
  35629. * The expression is evaluated and the result is included in the generated result. There are
  35630. * some special variables available in that code:
  35631. *
  35632. * - **out**: The output array into which the template is being appended (using `push` to later
  35633. * `join`).
  35634. * - **values**: The values in the current scope. If you are using scope changing sub-templates,
  35635. * you can change what values is.
  35636. * - **parent**: The scope (values) of the ancestor template.
  35637. * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
  35638. * - **xcount**: If you are in a looping template, the total length of the array you are looping.
  35639. *
  35640. * This example demonstrates basic row striping using an inline code block and the xindex variable:
  35641. *
  35642. * var tpl = new Ext.XTemplate(
  35643. * '<p>Name: {name}</p>',
  35644. * '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
  35645. * '<p>Kids: ',
  35646. * '<tpl for="kids">',
  35647. * '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
  35648. * '{name}',
  35649. * '</div>',
  35650. * '</tpl></p>'
  35651. * );
  35652. *
  35653. * Any code contained in "verbatim" blocks (using "{% ... %}") will be inserted directly in
  35654. * the generated code for the template. These blocks are not included in the output. This
  35655. * can be used for simple things like break/continue in a loop, or control structures or
  35656. * method calls (when they don't produce output). The `this` references the template instance.
  35657. *
  35658. * var tpl = new Ext.XTemplate(
  35659. * '<p>Name: {name}</p>',
  35660. * '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
  35661. * '<p>Kids: ',
  35662. * '<tpl for="kids">',
  35663. * '{% if (xindex % 2 === 0) continue; %}',
  35664. * '{name}',
  35665. * '{% if (xindex > 100) break; %}',
  35666. * '</div>',
  35667. * '</tpl></p>'
  35668. * );
  35669. *
  35670. * # Template member functions
  35671. *
  35672. * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
  35673. * more complex processing:
  35674. *
  35675. * var tpl = new Ext.XTemplate(
  35676. * '<p>Name: {name}</p>',
  35677. * '<p>Kids: ',
  35678. * '<tpl for="kids">',
  35679. * '<tpl if="this.isGirl(name)">',
  35680. * '<p>Girl: {name} - {age}</p>',
  35681. * '<tpl else>',
  35682. * '<p>Boy: {name} - {age}</p>',
  35683. * '</tpl>',
  35684. * '<tpl if="this.isBaby(age)">',
  35685. * '<p>{name} is a baby!</p>',
  35686. * '</tpl>',
  35687. * '</tpl></p>',
  35688. * {
  35689. * // XTemplate configuration:
  35690. * disableFormats: true,
  35691. * // member functions:
  35692. * isGirl: function(name){
  35693. * return name == 'Aubrey' || name == 'Nikol';
  35694. * },
  35695. * isBaby: function(age){
  35696. * return age < 1;
  35697. * }
  35698. * }
  35699. * );
  35700. * tpl.overwrite(panel.body, data);
  35701. */
  35702. Ext.define('Ext.XTemplate', {
  35703. extend: 'Ext.Template',
  35704. requires: 'Ext.XTemplateCompiler',
  35705. /**
  35706. * @private
  35707. */
  35708. emptyObj: {},
  35709. /**
  35710. * @cfg {Boolean} compiled
  35711. * Only applies to {@link Ext.Template}, XTemplates are compiled automatically on the
  35712. * first call to {@link #apply} or {@link #applyOut}.
  35713. */
  35714. /**
  35715. * @cfg {String/Array} definitions
  35716. * Optional. A statement, or array of statements which set up `var`s which may then
  35717. * be accessed within the scope of the generated function.
  35718. */
  35719. apply: function(values, parent) {
  35720. return this.applyOut(values, [], parent).join('');
  35721. },
  35722. applyOut: function(values, out, parent) {
  35723. var me = this,
  35724. compiler;
  35725. if (!me.fn) {
  35726. compiler = new Ext.XTemplateCompiler({
  35727. useFormat: me.disableFormats !== true,
  35728. definitions: me.definitions
  35729. });
  35730. me.fn = compiler.compile(me.html);
  35731. }
  35732. try {
  35733. me.fn.call(me, out, values, parent || me.emptyObj, 1, 1);
  35734. } catch (e) {
  35735. Ext.log('Error: ' + e.message);
  35736. }
  35737. return out;
  35738. },
  35739. /**
  35740. * Does nothing. XTemplates are compiled automatically, so this function simply returns this.
  35741. * @return {Ext.XTemplate} this
  35742. */
  35743. compile: function() {
  35744. return this;
  35745. },
  35746. statics: {
  35747. /**
  35748. * Gets an `XTemplate` from an object (an instance of an {@link Ext#define}'d class).
  35749. * Many times, templates are configured high in the class hierarchy and are to be
  35750. * shared by all classes that derive from that base. To further complicate matters,
  35751. * these templates are seldom actual instances but are rather configurations. For
  35752. * example:
  35753. *
  35754. * Ext.define('MyApp.Class', {
  35755. * someTpl: [
  35756. * 'tpl text here'
  35757. * ]
  35758. * });
  35759. *
  35760. * The goal being to share that template definition with all instances and even
  35761. * instances of derived classes, until `someTpl` is overridden. This method will
  35762. * "upgrade" these configurations to be real `XTemplate` instances *in place* (to
  35763. * avoid creating one instance per object).
  35764. *
  35765. * @param {Object} instance The object from which to get the `XTemplate` (must be
  35766. * an instance of an {@link Ext#define}'d class).
  35767. * @param {String} name The name of the property by which to get the `XTemplate`.
  35768. * @return {Ext.XTemplate} The `XTemplate` instance or null if not found.
  35769. * @protected
  35770. */
  35771. getTpl: function (instance, name) {
  35772. var tpl = instance[name], // go for it! 99% of the time we will get it!
  35773. proto;
  35774. if (tpl && !tpl.isTemplate) { // tpl is just a configuration (not an instance)
  35775. // create the template instance from the configuration:
  35776. tpl = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
  35777. // and replace the reference with the new instance:
  35778. if (instance.hasOwnProperty(name)) { // the tpl is on the instance
  35779. instance[name] = tpl;
  35780. } else { // must be somewhere in the prototype chain
  35781. for (proto = instance.self.prototype; proto; proto = proto.superclass) {
  35782. if (proto.hasOwnProperty(name)) {
  35783. proto[name] = tpl;
  35784. break;
  35785. }
  35786. }
  35787. }
  35788. }
  35789. // else !tpl (no such tpl) or the tpl is an instance already... either way, tpl
  35790. // is ready to return
  35791. return tpl || null;
  35792. }
  35793. }
  35794. });
  35795. /**
  35796. * Base Layout class - extended by ComponentLayout and ContainerLayout
  35797. */
  35798. Ext.define('Ext.layout.Layout', {
  35799. requires: [
  35800. 'Ext.XTemplate'
  35801. ],
  35802. uses: [ 'Ext.layout.Context' ],
  35803. /**
  35804. * @property {Boolean} isLayout
  35805. * `true` in this class to identify an object as an instantiated Layout, or subclass thereof.
  35806. */
  35807. isLayout: true,
  35808. initialized: false,
  35809. running: false,
  35810. autoSizePolicy: {
  35811. setsWidth: 0,
  35812. setsHeight: 0
  35813. },
  35814. statics: {
  35815. layoutsByType: {},
  35816. create: function(layout, defaultType) {
  35817. var ClassManager = Ext.ClassManager,
  35818. layoutsByType = this.layoutsByType,
  35819. alias, className, config, layoutClass, type, load;
  35820. if (!layout || typeof layout === 'string') {
  35821. type = layout || defaultType;
  35822. config = {};
  35823. } else if (layout.isLayout) {
  35824. return layout;
  35825. } else {
  35826. config = layout;
  35827. type = layout.type || defaultType;
  35828. }
  35829. if (!(layoutClass = layoutsByType[type])) {
  35830. alias = 'layout.' + type;
  35831. className = ClassManager.getNameByAlias(alias);
  35832. // this is needed to support demand loading of the class
  35833. if (!className) {
  35834. load = true;
  35835. }
  35836. layoutClass = ClassManager.get(className);
  35837. if (load || !layoutClass) {
  35838. return ClassManager.instantiateByAlias(alias, config || {});
  35839. }
  35840. layoutsByType[type] = layoutClass;
  35841. }
  35842. return new layoutClass(config);
  35843. }
  35844. },
  35845. constructor : function(config) {
  35846. var me = this;
  35847. me.id = Ext.id(null, me.type + '-');
  35848. Ext.apply(me, config);
  35849. me.layoutCount = 0;
  35850. },
  35851. /**
  35852. * @property {Boolean} done Used only during a layout run, this value indicates that a
  35853. * layout has finished its calculations. This flag is set to true prior to the call to
  35854. * {@link #calculate} and should be set to false if this layout has more work to do.
  35855. */
  35856. /**
  35857. * Called before any calculation cycles to prepare for layout.
  35858. *
  35859. * This is a write phase and DOM reads should be strictly avoided when overridding
  35860. * this method.
  35861. *
  35862. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  35863. * component.
  35864. * @method beginLayout
  35865. */
  35866. beginLayout: Ext.emptyFn,
  35867. /**
  35868. * Called before any calculation cycles to reset DOM values and prepare for calculation.
  35869. *
  35870. * This is a write phase and DOM reads should be strictly avoided when overridding
  35871. * this method.
  35872. *
  35873. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  35874. * component.
  35875. * @method beginLayoutCycle
  35876. */
  35877. beginLayoutCycle: function (ownerContext) {
  35878. var me = this,
  35879. context = me.context,
  35880. changed;
  35881. if (me.lastWidthModel != ownerContext.widthModel) {
  35882. if (me.lastWidthModel) {
  35883. changed = true;
  35884. }
  35885. me.lastWidthModel = ownerContext.widthModel;
  35886. }
  35887. if (me.lastHeightModel != ownerContext.heightModel) {
  35888. if (me.lastWidthModel) {
  35889. changed = true;
  35890. }
  35891. me.lastHeightModel = ownerContext.heightModel;
  35892. }
  35893. if (changed) {
  35894. (context = ownerContext.context).clearTriggers(me, false);
  35895. context.clearTriggers(me, true);
  35896. me.triggerCount = 0;
  35897. }
  35898. },
  35899. /**
  35900. * Called to perform the calculations for this layout. This method will be called at
  35901. * least once and may be called repeatedly if the {@link #done} property is cleared
  35902. * before return to indicate that this layout is not yet done. The {@link #done} property
  35903. * is always set to `true` before entering this method.
  35904. *
  35905. * This is a read phase and DOM writes should be strictly avoided in derived classes.
  35906. * Instead, DOM writes need to be written to {@link Ext.layout.ContextItem} objects to
  35907. * be flushed at the next opportunity.
  35908. *
  35909. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  35910. * component.
  35911. * @method calculate
  35912. * @abstract
  35913. */
  35914. /**
  35915. * This method (if implemented) is called at the end of the cycle in which this layout
  35916. * completes (by not setting {@link #done} to `false` in {@link #calculate}). It is
  35917. * possible for the layout to complete and yet become invalid before the end of the cycle,
  35918. * in which case, this method will not be called. It is also possible for this method to
  35919. * be called and then later the layout becomes invalidated. This will result in
  35920. * {@link #calculate} being called again, followed by another call to this method.
  35921. *
  35922. * This is a read phase and DOM writes should be strictly avoided in derived classes.
  35923. * Instead, DOM writes need to be written to {@link Ext.layout.ContextItem} objects to
  35924. * be flushed at the next opportunity.
  35925. *
  35926. * This method need not be implemented by derived classes and, in fact, should only be
  35927. * implemented when needed.
  35928. *
  35929. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  35930. * component.
  35931. * @method completeLayout
  35932. */
  35933. /**
  35934. * This method (if implemented) is called after all layouts have completed. In most
  35935. * ways this is similar to {@link #completeLayout}. This call can cause this (or any
  35936. * layout) to be become invalid (see {@link Ext.layout.Context#invalidate}), but this
  35937. * is best avoided. This method is intended to be where final reads are made and so it
  35938. * is best to avoid invalidating layouts at this point whenever possible. Even so, this
  35939. * method can be used to perform final checks that may require all other layouts to be
  35940. * complete and then invalidate some results.
  35941. *
  35942. * This is a read phase and DOM writes should be strictly avoided in derived classes.
  35943. * Instead, DOM writes need to be written to {@link Ext.layout.ContextItem} objects to
  35944. * be flushed at the next opportunity.
  35945. *
  35946. * This method need not be implemented by derived classes and, in fact, should only be
  35947. * implemented when needed.
  35948. *
  35949. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  35950. * component.
  35951. * @method finalizeLayout
  35952. */
  35953. /**
  35954. * This method is called after all layouts are complete and their calculations flushed
  35955. * to the DOM. No further layouts will be run and this method is only called once per
  35956. * layout run. The base component layout caches `lastComponentSize`.
  35957. *
  35958. * This is a write phase and DOM reads should be avoided if possible when overridding
  35959. * this method.
  35960. *
  35961. * This method need not be implemented by derived classes and, in fact, should only be
  35962. * implemented when needed.
  35963. *
  35964. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  35965. * component.
  35966. */
  35967. finishedLayout: function () {
  35968. this.ownerContext = null;
  35969. },
  35970. /**
  35971. * This method (if implemented) is called after all layouts are finished, and all have
  35972. * a `lastComponentSize` cached. No further layouts will be run and this method is only
  35973. * called once per layout run. It is the bookend to {@link #beginLayout}.
  35974. *
  35975. * This is a write phase and DOM reads should be avoided if possible when overridding
  35976. * this method. This is the catch-all tail method to a layout and so the rules are more
  35977. * relaxed. Even so, for performance reasons, it is best to avoid reading the DOM. If
  35978. * a read is necessary, consider implementing a {@link #finalizeLayout} method to do the
  35979. * required reads.
  35980. *
  35981. * This method need not be implemented by derived classes and, in fact, should only be
  35982. * implemented when needed.
  35983. *
  35984. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  35985. * component.
  35986. * @method notifyOwner
  35987. */
  35988. redoLayout: Ext.emptyFn,
  35989. undoLayout: Ext.emptyFn,
  35990. getAnimatePolicy: function() {
  35991. return this.animatePolicy;
  35992. },
  35993. /**
  35994. * Returns an object describing how this layout manages the size of the given component.
  35995. * This method must be implemented by any layout that manages components.
  35996. *
  35997. * @param {Ext.Component} item
  35998. *
  35999. * @return {Object} An object describing the sizing done by the layout for this item or
  36000. * null if the layout mimics the size policy of its ownerCt (e.g., 'fit' and 'card').
  36001. * @return {Boolean} return.readsWidth True if the natural/auto width of this component
  36002. * is used by the ownerLayout.
  36003. * @return {Boolean} return.readsHeight True if the natural/auto height of this component
  36004. * is used by the ownerLayout.
  36005. * @return {Boolean} return.setsWidth True if the ownerLayout set this component's width.
  36006. * @return {Boolean} return.setsHeight True if the ownerLayout set this component's height.
  36007. *
  36008. * @protected
  36009. */
  36010. getItemSizePolicy: function (item) {
  36011. return this.autoSizePolicy;
  36012. },
  36013. isItemBoxParent: function (itemContext) {
  36014. return false;
  36015. },
  36016. isItemLayoutRoot: function (item) {
  36017. var sizeModel = item.getSizeModel(),
  36018. width = sizeModel.width,
  36019. height = sizeModel.height;
  36020. // If this component has never had a layout and some of its dimensions are set by
  36021. // its ownerLayout, we cannot be the layoutRoot...
  36022. if (!item.componentLayout.lastComponentSize && (width.calculated || height.calculated)) {
  36023. return false;
  36024. }
  36025. // otherwise an ownerCt whose size is not effected by its content is a root
  36026. return !width.shrinkWrap && !height.shrinkWrap;
  36027. },
  36028. isItemShrinkWrap: function (item) {
  36029. return item.shrinkWrap;
  36030. },
  36031. isRunning: function () {
  36032. return !!this.ownerContext;
  36033. },
  36034. //-----------------------------------------------------
  36035. /*
  36036. * Clears any styles which must be cleared before layout can take place.
  36037. * Only DOM WRITES must be performed at this stage.
  36038. *
  36039. * An entry for the owner's element ID must be created in the layoutContext containing
  36040. * a reference to the target which must be sized/positioned/styled by the layout at
  36041. * the flush stage:
  36042. *
  36043. * {
  36044. * target: me.owner
  36045. * }
  36046. *
  36047. * Component layouts should iterate through managed Elements,
  36048. * pushing an entry for each element:
  36049. *
  36050. * {
  36051. * target: childElement
  36052. * }
  36053. */
  36054. //-----------------------------------------------------
  36055. getItemsRenderTree: function (items, renderCfgs) {
  36056. var length = items.length,
  36057. i, item, itemConfig, result;
  36058. if (length) {
  36059. result = [];
  36060. for (i = 0; i < length; ++i) {
  36061. item = items[i];
  36062. // If we are being asked to move an already rendered Component, we must not recalculate its renderTree
  36063. // and rerun its render process. The Layout's isValidParent check will ensure that the DOM is moved into place.
  36064. if (!item.rendered) {
  36065. // If we've already calculated the item's element config, don't calculate it again.
  36066. // This may happen if the rendering process mutates the owning Container's items
  36067. // collection, and Ext.layout.Container#getRenderTree runs through the collection again.
  36068. // Note that the config may be null if a beforerender listener vetoed the operation, so
  36069. // we must compare to undefined.
  36070. if (renderCfgs && (renderCfgs[item.id] !== undefined)) {
  36071. itemConfig = renderCfgs[item.id];
  36072. } else {
  36073. // Perform layout preprocessing in the bulk render path
  36074. this.configureItem(item);
  36075. itemConfig = item.getRenderTree();
  36076. if (renderCfgs) {
  36077. renderCfgs[item.id] = itemConfig;
  36078. }
  36079. }
  36080. // itemConfig mey be null if a beforerender listener vetoed the operation.
  36081. if (itemConfig) {
  36082. result.push(itemConfig);
  36083. }
  36084. }
  36085. }
  36086. }
  36087. return result;
  36088. },
  36089. finishRender: Ext.emptyFn,
  36090. finishRenderItems: function (target, items) {
  36091. var length = items.length,
  36092. i, item;
  36093. for (i = 0; i < length; i++) {
  36094. item = items[i];
  36095. // Only postprocess items which are being rendered. deferredRender may mean that only one has been rendered.
  36096. if (item.rendering) {
  36097. // Tell the item at which index in the Container it is
  36098. item.finishRender(i);
  36099. this.afterRenderItem(item);
  36100. }
  36101. }
  36102. },
  36103. renderChildren: function () {
  36104. var me = this,
  36105. items = me.getLayoutItems(),
  36106. target = me.getRenderTarget();
  36107. me.renderItems(items, target);
  36108. },
  36109. /**
  36110. * Iterates over all passed items, ensuring they are rendered. If the items are already rendered,
  36111. * also determines if the items are in the proper place in the dom.
  36112. * @protected
  36113. */
  36114. renderItems : function(items, target) {
  36115. var me = this,
  36116. ln = items.length,
  36117. i = 0,
  36118. item;
  36119. if (ln) {
  36120. Ext.suspendLayouts();
  36121. for (; i < ln; i++) {
  36122. item = items[i];
  36123. if (item && !item.rendered) {
  36124. me.renderItem(item, target, i);
  36125. } else if (!me.isValidParent(item, target, i)) {
  36126. me.moveItem(item, target, i);
  36127. } else {
  36128. // still need to configure the item, it may have moved in the container.
  36129. me.configureItem(item);
  36130. }
  36131. }
  36132. Ext.resumeLayouts(true);
  36133. }
  36134. },
  36135. /**
  36136. * Validates item is in the proper place in the dom.
  36137. * @protected
  36138. */
  36139. isValidParent : function(item, target, position) {
  36140. var itemDom = item.el ? item.el.dom : Ext.getDom(item),
  36141. targetDom = (target && target.dom) || target;
  36142. // If it's resizable+wrapped, the position element is the wrapper.
  36143. if (itemDom.parentNode && itemDom.parentNode.className.indexOf(Ext.baseCSSPrefix + 'resizable-wrap') !== -1) {
  36144. itemDom = itemDom.parentNode;
  36145. }
  36146. // Test DOM nodes for equality using "===" : http://jsperf.com/dom-equality-test
  36147. if (itemDom && targetDom) {
  36148. if (typeof position == 'number') {
  36149. return itemDom === targetDom.childNodes[position];
  36150. }
  36151. return itemDom.parentNode === targetDom;
  36152. }
  36153. return false;
  36154. },
  36155. /**
  36156. * Called before an item is rendered to allow the layout to configure the item.
  36157. * @param {Ext.Component} item The item to be configured
  36158. * @protected
  36159. */
  36160. configureItem: function(item) {
  36161. item.ownerLayout = this;
  36162. },
  36163. /**
  36164. * Renders the given Component into the target Element.
  36165. * @param {Ext.Component} item The Component to render
  36166. * @param {Ext.dom.Element} target The target Element
  36167. * @param {Number} position The position within the target to render the item to
  36168. * @private
  36169. */
  36170. renderItem : function(item, target, position) {
  36171. var me = this;
  36172. if (!item.rendered) {
  36173. me.configureItem(item);
  36174. item.render(target, position);
  36175. me.afterRenderItem(item);
  36176. }
  36177. },
  36178. /**
  36179. * Moves Component to the provided target instead.
  36180. * @private
  36181. */
  36182. moveItem : function(item, target, position) {
  36183. target = target.dom || target;
  36184. if (typeof position == 'number') {
  36185. position = target.childNodes[position];
  36186. }
  36187. target.insertBefore(item.el.dom, position || null);
  36188. item.container = Ext.get(target);
  36189. this.configureItem(item);
  36190. },
  36191. /**
  36192. * This method is called when a child item changes in some way. By default this calls
  36193. * {@link Ext.AbstractComponent#updateLayout} on this layout's owner.
  36194. *
  36195. * @param {Ext.Component} child The child item that has changed.
  36196. * @return {Boolean} True if this layout has handled the content change.
  36197. */
  36198. onContentChange: function () {
  36199. this.owner.updateLayout();
  36200. return true;
  36201. },
  36202. /**
  36203. * A one-time initialization method called just before rendering.
  36204. * @protected
  36205. */
  36206. initLayout : function() {
  36207. this.initialized = true;
  36208. },
  36209. // @private Sets the layout owner
  36210. setOwner : function(owner) {
  36211. this.owner = owner;
  36212. },
  36213. /**
  36214. * Returns the set of items to layout (empty by default).
  36215. * @protected
  36216. */
  36217. getLayoutItems : function() {
  36218. return [];
  36219. },
  36220. // Placeholder empty functions for subclasses to extend
  36221. afterRenderItem: Ext.emptyFn,
  36222. onAdd : Ext.emptyFn,
  36223. onRemove : Ext.emptyFn,
  36224. onDestroy : Ext.emptyFn,
  36225. /**
  36226. * Removes layout's itemCls and owning Container's itemCls.
  36227. * Clears the managed dimensions flags
  36228. * @protected
  36229. */
  36230. afterRemove : function(item) {
  36231. var me = this,
  36232. el = item.el,
  36233. owner = me.owner,
  36234. removeClasses;
  36235. if (item.rendered) {
  36236. removeClasses = [].concat(me.itemCls || []);
  36237. if (owner.itemCls) {
  36238. removeClasses = Ext.Array.push(removeClasses, owner.itemCls);
  36239. }
  36240. if (removeClasses.length) {
  36241. el.removeCls(removeClasses);
  36242. }
  36243. }
  36244. delete item.ownerLayout;
  36245. },
  36246. /**
  36247. * Destroys this layout. This method removes a `targetCls` from the `target`
  36248. * element and calls `onDestroy`.
  36249. *
  36250. * A derived class can override either this method or `onDestroy` but in all
  36251. * cases must call the base class versions of these methods to allow the base class to
  36252. * perform its cleanup.
  36253. *
  36254. * This method (or `onDestroy`) are overridden by subclasses most often to purge
  36255. * event handlers or remove unmanged DOM nodes.
  36256. *
  36257. * @protected
  36258. */
  36259. destroy : function() {
  36260. var me = this,
  36261. target;
  36262. if (me.targetCls) {
  36263. target = me.getTarget();
  36264. if (target) {
  36265. target.removeCls(me.targetCls);
  36266. }
  36267. }
  36268. me.onDestroy();
  36269. },
  36270. sortWeightedItems: function (items, reverseProp) {
  36271. for (var i = 0, length = items.length; i < length; ++i) {
  36272. items[i].$i = i;
  36273. }
  36274. Ext.Array.sort(items, function (item1, item2) {
  36275. var ret = item2.weight - item1.weight;
  36276. if (!ret) {
  36277. ret = item1.$i - item2.$i;
  36278. if (item1[reverseProp]) {
  36279. ret = -ret;
  36280. }
  36281. }
  36282. return ret;
  36283. });
  36284. for (i = 0; i < length; ++i) {
  36285. delete items[i].$i;
  36286. }
  36287. }
  36288. }, function () {
  36289. var Layout = this,
  36290. sizeModels = {},
  36291. sizeModelsArray = [],
  36292. i, j, n, pairs, sizeModel;
  36293. Layout.prototype.sizeModels = Layout.sizeModels = sizeModels;
  36294. /**
  36295. * This class describes a size determination strategy or algorithm used by the layout
  36296. * system. There are special instances of this class stored as static properties to
  36297. * avoid needless object instantiation. These instances should be treated as readonly.
  36298. *
  36299. * * `calculated`
  36300. * * `configured`
  36301. * * `constrainedMax`
  36302. * * `constrainedMin`
  36303. * * `natural`
  36304. * * `shrinkWrap`
  36305. * * `calculatedFromConfigured`
  36306. * * `calculatedFromNatural`
  36307. * * `calculatedFromShrinkWrap`
  36308. *
  36309. * Using one of these instances is simply:
  36310. *
  36311. * var calculated = Ext.layout.SizeModel.calculated;
  36312. *
  36313. * @class Ext.layout.SizeModel
  36314. * @protected
  36315. */
  36316. var SizeModel = function (config) {
  36317. var me = this,
  36318. name = config.name;
  36319. Ext.apply(Ext.apply(me, defaults), config);
  36320. me[name] = true; // set the one special flag that matches our name
  36321. SizeModel[name] = sizeModels[name] = me;
  36322. me.fixed = !(me.auto = me.natural || me.shrinkWrap);
  36323. /**
  36324. * @prop {Number} ordinal
  36325. * The 0-based ordinal for this `SizeModel` instance.
  36326. * @readonly
  36327. */
  36328. me.ordinal = sizeModelsArray.length;
  36329. sizeModelsArray.push(me);
  36330. };
  36331. Ext.layout.SizeModel = SizeModel;
  36332. var defaults = {
  36333. /**
  36334. * @property {String} name
  36335. * The name of this size model (e.g., "calculated").
  36336. * @readonly
  36337. */
  36338. /**
  36339. * @property {Boolean} auto
  36340. * True if the size is either `natural` or `shrinkWrap`, otherwise false.
  36341. * @readonly
  36342. */
  36343. /**
  36344. * @property {Boolean} calculated
  36345. * True if the size is calculated by the `ownerLayout`.
  36346. * @readonly
  36347. */
  36348. calculated: false,
  36349. /**
  36350. * @property {Boolean} configured
  36351. * True if the size is configured (e.g., by a `width` or `minWidth`). The names of
  36352. * configuration properties can be found in the {@link #names} property.
  36353. * @readonly
  36354. */
  36355. configured: false,
  36356. /**
  36357. * @property {Boolean} constrainedMax
  36358. * True if the size is constrained by a `maxWidth` or `maxHeight` configuration. This
  36359. * is a flavor of `configured` (since `maxWidth` and `maxHeight` are config options).
  36360. * If true, the {@link #names} property will be defined as well.
  36361. * @readonly
  36362. */
  36363. constrainedMax: false,
  36364. /**
  36365. * @property {Boolean} constrainedMin
  36366. * True if the size is constrained by a `minWidth` or `minHeight` configuration. This
  36367. * is a flavor of `configured` (since `minWidth` and `minHeight` are config options).
  36368. * If true, the {@link #names} property will be defined as well.
  36369. * @readonly
  36370. */
  36371. constrainedMin: false,
  36372. /**
  36373. * @property {Boolean} fixed
  36374. * True if the size is either `calculated` or `configured`, otherwise false.
  36375. * @readonly
  36376. */
  36377. /**
  36378. * @property {Boolean} natural
  36379. * True if the size is determined by CSS and not by content. Such sizes are assumed to
  36380. * be dependent on the container box and measurement occurs on the outer-most element.
  36381. * @readonly
  36382. */
  36383. natural: false,
  36384. /**
  36385. * @property {Boolean} shrinkWrap
  36386. * True if the size is determined by content irrespective of the container box.
  36387. * @readonly
  36388. */
  36389. shrinkWrap: false,
  36390. /**
  36391. * @property {Boolean} calculatedFromConfigured
  36392. * True if the size is calculated by the `ownerLayout` based on a configured size.
  36393. * @readonly
  36394. */
  36395. calculatedFromConfigured: false,
  36396. /**
  36397. * @property {Boolean} calculatedFromNatural
  36398. * True if the size is calculated by the `ownerLayout` based on `natural` size model
  36399. * results.
  36400. * @readonly
  36401. */
  36402. calculatedFromNatural: false,
  36403. /**
  36404. * @property {Boolean} calculatedFromShrinkWrap
  36405. * True if the size is calculated by the `ownerLayout` based on `shrinkWrap` size model
  36406. * results.
  36407. * @readonly
  36408. */
  36409. calculatedFromShrinkWrap: false,
  36410. /**
  36411. * @property {Object} names An object with the config property names that determine the
  36412. * size.
  36413. * @property {String} names.width The width property name (e.g., 'width').
  36414. * @property {String} names.height The height property name (e.g., 'minHeight').
  36415. * @readonly
  36416. */
  36417. names: null
  36418. };
  36419. //-------------------------------------------------------------------------------
  36420. // These are the 4 fundamental size models.
  36421. new SizeModel({
  36422. name: 'calculated'
  36423. });
  36424. new SizeModel({
  36425. name: 'configured',
  36426. names: { width: 'width', height: 'height' }
  36427. });
  36428. new SizeModel({
  36429. name: 'natural'
  36430. });
  36431. new SizeModel({
  36432. name: 'shrinkWrap'
  36433. });
  36434. //-------------------------------------------------------------------------------
  36435. // These are the size models are flavors of the above but with some extra detail
  36436. // about their dynamic use.
  36437. new SizeModel({
  36438. name: 'calculatedFromConfigured',
  36439. configured: true,
  36440. names: { width: 'width', height: 'height' }
  36441. });
  36442. new SizeModel({
  36443. name: 'calculatedFromNatural',
  36444. natural: true
  36445. });
  36446. new SizeModel({
  36447. name: 'calculatedFromShrinkWrap',
  36448. shrinkWrap: true
  36449. });
  36450. new SizeModel({
  36451. name: 'constrainedMax',
  36452. configured: true,
  36453. constrained: true,
  36454. names: { width: 'maxWidth', height: 'maxHeight' }
  36455. });
  36456. new SizeModel({
  36457. name: 'constrainedMin',
  36458. configured: true,
  36459. constrained: true,
  36460. names: { width: 'minWidth', height: 'minHeight' }
  36461. });
  36462. for (i = 0, n = sizeModelsArray.length; i < n; ++i) {
  36463. sizeModel = sizeModelsArray[i];
  36464. /**
  36465. * An array of objects indexed by the {@link #ordinal} of a height `SizeModel` on
  36466. * a width `SizeModel` to yield an object describing both height and width size
  36467. * models.
  36468. *
  36469. * Used like this:
  36470. *
  36471. * widthModel.pairsByHeightOrdinal[heightModel.ordinal]
  36472. *
  36473. * This provides a reusable object equivalent to the following:
  36474. *
  36475. * {
  36476. * width: widthModel,
  36477. * height: heightModel
  36478. * }
  36479. *
  36480. * @property {Object[]} pairsByHeightOrdinal
  36481. * @property {Ext.layout.SizeModel} pairsByHeightOrdinal.width The `SizeModel` for
  36482. * the width.
  36483. * @property {Ext.layout.SizeModel} pairsByHeightOrdinal.height The `SizeModel` for
  36484. * the height.
  36485. */
  36486. sizeModel.pairsByHeightOrdinal = pairs = [];
  36487. for (j = 0; j < n; ++j) {
  36488. pairs.push({
  36489. width: sizeModel,
  36490. height: sizeModelsArray[j]
  36491. });
  36492. }
  36493. }
  36494. });
  36495. /**
  36496. * This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
  36497. * configuration property. See {@link Ext.container.Container#layout} for additional details.
  36498. */
  36499. Ext.define('Ext.layout.container.Container', {
  36500. /* Begin Definitions */
  36501. extend: 'Ext.layout.Layout',
  36502. alternateClassName: 'Ext.layout.ContainerLayout',
  36503. mixins: {
  36504. elementCt: 'Ext.util.ElementContainer'
  36505. },
  36506. requires: [
  36507. 'Ext.XTemplate'
  36508. ],
  36509. type: 'container',
  36510. /* End Definitions */
  36511. /**
  36512. * @cfg {String} itemCls
  36513. * An optional extra CSS class that will be added to the container. This can be useful for
  36514. * adding customized styles to the container or any of its children using standard CSS
  36515. * rules. See {@link Ext.Component}.{@link Ext.Component#componentCls componentCls} also.
  36516. */
  36517. /**
  36518. * @cfg {Number} [manageOverflow=0]
  36519. * One of the following values:
  36520. *
  36521. * - 0 if the layout should ignore overflow.
  36522. * - 1 if the layout should be rerun if scrollbars are needed.
  36523. * - 2 if the layout should also correct padding when overflowed.
  36524. */
  36525. manageOverflow: 0,
  36526. /**
  36527. * @private
  36528. * Called by an owning Panel before the Panel begins its collapse process.
  36529. * Most layouts will not need to override the default Ext.emptyFn implementation.
  36530. */
  36531. beginCollapse: Ext.emptyFn,
  36532. /**
  36533. * @private
  36534. * Called by an owning Panel before the Panel begins its expand process.
  36535. * Most layouts will not need to override the default Ext.emptyFn implementation.
  36536. */
  36537. beginExpand: Ext.emptyFn,
  36538. /**
  36539. * An object which contains boolean properties specifying which properties are to be
  36540. * animated upon flush of child Component ContextItems. For example, Accordion would
  36541. * have:
  36542. *
  36543. * {
  36544. * y: true,
  36545. * height: true
  36546. * }
  36547. *
  36548. * @private
  36549. */
  36550. animatePolicy: null,
  36551. childEls: [
  36552. /**
  36553. * @property {Ext.Element} overflowPadderEl
  36554. * The element used to correct body padding during overflow.
  36555. */
  36556. 'overflowPadderEl'
  36557. ],
  36558. renderTpl: [
  36559. '{%this.renderBody(out,values)%}'
  36560. ],
  36561. usesContainerHeight: true,
  36562. usesContainerWidth: true,
  36563. usesHeight: true,
  36564. usesWidth: true,
  36565. /**
  36566. * @cfg {Boolean} [reserveScrollbar=false]
  36567. * Set to `true` to leave space for a vertical scrollbar (if the OS shows space-consuming scrollbars) regardless
  36568. * of whether a scrollbar is needed.
  36569. *
  36570. * This is useful if content height changes during application usage, but you do not want the calculated width
  36571. * of child items to change when a scrollbar appears or disappears. The scrollbar will appear in the reserved space,
  36572. * and the calculated width of child Components will not change.
  36573. *
  36574. * @example
  36575. * Ext.define('Employee', {
  36576. * extend: 'Ext.data.Model',
  36577. * fields: [
  36578. * {name: 'rating', type: 'int'},
  36579. * {name: 'salary', type: 'float'},
  36580. * {name: 'name'}
  36581. * ]
  36582. * });
  36583. *
  36584. * function createFakeData(count) {
  36585. * var firstNames = ['Ed', 'Tommy', 'Aaron', 'Abe', 'Jamie', 'Adam', 'Dave', 'David', 'Jay', 'Nicolas', 'Nige'],
  36586. * lastNames = ['Spencer', 'Maintz', 'Conran', 'Elias', 'Avins', 'Mishcon', 'Kaneda', 'Davis', 'Robinson', 'Ferrero', 'White'],
  36587. * ratings = [1, 2, 3, 4, 5],
  36588. * salaries = [100, 400, 900, 1500, 1000000];
  36589. *
  36590. * var data = [];
  36591. * for (var i = 0; i < (count || 25); i++) {
  36592. * var ratingId = Math.floor(Math.random() * ratings.length),
  36593. * salaryId = Math.floor(Math.random() * salaries.length),
  36594. * firstNameId = Math.floor(Math.random() * firstNames.length),
  36595. * lastNameId = Math.floor(Math.random() * lastNames.length),
  36596. *
  36597. * rating = ratings[ratingId],
  36598. * salary = salaries[salaryId],
  36599. * name = Ext.String.format("{0} {1}", firstNames[firstNameId], lastNames[lastNameId]);
  36600. *
  36601. * data.push({
  36602. * rating: rating,
  36603. * salary: salary,
  36604. * name: name
  36605. * });
  36606. * }
  36607. * store.loadData(data);
  36608. * }
  36609. *
  36610. * // create the Data Store
  36611. * var store = Ext.create('Ext.data.Store', {
  36612. * id: 'store',
  36613. * model: 'Employee',
  36614. * proxy: {
  36615. * type: 'memory'
  36616. * }
  36617. * });
  36618. * createFakeData(10);
  36619. *
  36620. * var grid = Ext.create('Ext.grid.Panel', {
  36621. * title: 'Grid loaded with varying number of records',
  36622. * anchor: '100%',
  36623. * store: store,
  36624. * columns: [{
  36625. * xtype: 'rownumberer',
  36626. * width: 40,
  36627. * sortable: false
  36628. * },{
  36629. * text: 'Name',
  36630. * flex: 1,
  36631. * sortable: true,
  36632. * dataIndex: 'name'
  36633. * },{
  36634. * text: 'Rating',
  36635. * width: 125,
  36636. * sortable: true,
  36637. * dataIndex: 'rating'
  36638. * },{
  36639. * text: 'Salary',
  36640. * width: 125,
  36641. * sortable: true,
  36642. * dataIndex: 'salary',
  36643. * align: 'right',
  36644. * renderer: Ext.util.Format.usMoney
  36645. * }]
  36646. * });
  36647. *
  36648. * Ext.create('Ext.panel.Panel', {
  36649. * renderTo: document.body,
  36650. * width: 800,
  36651. * height: 600,
  36652. * layout: {
  36653. * type: 'anchor',
  36654. * reserveScrollbar: true // There will be a gap even when there's no scrollbar
  36655. * },
  36656. * autoScroll: true,
  36657. * items: grid,
  36658. * tbar: {
  36659. * defaults: {
  36660. * handler: function(b) {
  36661. * createFakeData(b.count);
  36662. * }
  36663. * },
  36664. * items: [{
  36665. * text: '10 Items',
  36666. * count: 10
  36667. * },{
  36668. * text: '100 Items',
  36669. * count: 100
  36670. * },{
  36671. * text: '300 Items',
  36672. * count: 300
  36673. * },{
  36674. * text: '1000 Items',
  36675. * count: 1000
  36676. * },{
  36677. * text: '5000 Items',
  36678. * count: 5000
  36679. * }]
  36680. * }
  36681. * });
  36682. *
  36683. */
  36684. reserveScrollbar: false,
  36685. // Begin with no previous adjustments
  36686. lastOverflowAdjust: {
  36687. width: 0,
  36688. height: 0
  36689. },
  36690. constructor: function () {
  36691. this.callParent(arguments);
  36692. this.mixins.elementCt.constructor.call(this);
  36693. },
  36694. destroy : function() {
  36695. this.callParent();
  36696. this.mixins.elementCt.destroy.call(this);
  36697. },
  36698. initLayout: function() {
  36699. var me = this,
  36700. scrollbarWidth = Ext.getScrollbarSize().width;
  36701. me.callParent();
  36702. // Create a default lastOverflowAdjust based upon scrolling configuration.
  36703. // If the Container is to overflow, or we *always* reserve space for a scrollbar
  36704. // then reserve space for a vertical scrollbar
  36705. if (scrollbarWidth && me.manageOverflow && !me.hasOwnProperty('lastOverflowAdjust')) {
  36706. if (me.owner.autoScroll || me.reserveScrollbar) {
  36707. me.lastOverflowAdjust = {
  36708. width: scrollbarWidth,
  36709. height: 0
  36710. };
  36711. }
  36712. }
  36713. },
  36714. /**
  36715. * In addition to work done by our base classes, containers benefit from some extra
  36716. * cached data. The following properties are added to the ownerContext:
  36717. *
  36718. * - visibleItems: the result of {@link #getVisibleItems}
  36719. * - childItems: the ContextItem[] for each visible item
  36720. * - targetContext: the ContextItem for the {@link #getTarget} element
  36721. */
  36722. beginLayout: function (ownerContext) {
  36723. this.callParent(arguments);
  36724. ownerContext.targetContext = ownerContext.getEl('getTarget', this);
  36725. this.cacheChildItems(ownerContext);
  36726. },
  36727. beginLayoutCycle: function (ownerContext, firstCycle) {
  36728. var me = this,
  36729. padEl = me.overflowPadderEl;
  36730. me.callParent(arguments);
  36731. // Begin with the scrollbar adjustment that we used last time - this is more likely to be correct
  36732. // than beginning with no adjustment at all
  36733. if (!ownerContext.state.overflowAdjust) {
  36734. ownerContext.state.overflowAdjust = me.lastOverflowAdjust;
  36735. }
  36736. if (firstCycle) {
  36737. if (me.usesContainerHeight) {
  36738. ++ownerContext.consumersContainerHeight;
  36739. }
  36740. if (me.usesContainerWidth) {
  36741. ++ownerContext.consumersContainerWidth;
  36742. }
  36743. }
  36744. if (padEl) {
  36745. padEl.setStyle('display', 'none');
  36746. }
  36747. },
  36748. completeLayout: function (ownerContext) {
  36749. // Cache the scrollbar adjustment
  36750. this.lastOverflowAdjust = ownerContext.state.overflowAdjust;
  36751. },
  36752. cacheChildItems: function (ownerContext) {
  36753. var context = ownerContext.context,
  36754. childItems = [],
  36755. items = this.getVisibleItems(),
  36756. length = items.length,
  36757. i;
  36758. ownerContext.childItems = childItems;
  36759. ownerContext.visibleItems = items;
  36760. for (i = 0; i < length; ++i) {
  36761. childItems.push(context.getCmp(items[i]));
  36762. }
  36763. },
  36764. cacheElements: function () {
  36765. var owner = this.owner;
  36766. this.applyChildEls(owner.el, owner.id); // from ElementContainer mixin
  36767. },
  36768. calculateContentSize: function (ownerContext, dimensions) {
  36769. var me = this,
  36770. containerDimensions = (dimensions || 0) | me.manageOverflow |
  36771. ((ownerContext.widthModel.shrinkWrap ? 1 : 0) |
  36772. (ownerContext.heightModel.shrinkWrap ? 2 : 0)),
  36773. calcWidth = (containerDimensions & 1) || undefined,
  36774. calcHeight = (containerDimensions & 2) || undefined,
  36775. childItems = ownerContext.childItems,
  36776. length = childItems.length,
  36777. contentHeight = 0,
  36778. contentWidth = 0,
  36779. needed = 0,
  36780. props = ownerContext.props,
  36781. targetXY, targetX, targetY, targetPadding,
  36782. borders, child, childContext, childX, childY, height, i, margins, width, xy;
  36783. if (calcWidth) {
  36784. if (isNaN(props.contentWidth)) {
  36785. ++needed;
  36786. } else {
  36787. calcWidth = undefined;
  36788. }
  36789. }
  36790. if (calcHeight) {
  36791. if (isNaN(props.contentHeight)) {
  36792. ++needed;
  36793. } else {
  36794. calcHeight = undefined;
  36795. }
  36796. }
  36797. if (needed) {
  36798. // TODO - this is rather brute force... maybe a wrapping el or clientHeight/Width
  36799. // trick might help. Whatever we do, it must either work for Absolute layout or
  36800. // at least be correctable by an overridden method in that derived class.
  36801. for (i = 0; i < length; ++i) {
  36802. childContext = childItems[i];
  36803. child = childContext.target;
  36804. height = calcHeight && childContext.getProp('height');
  36805. width = calcWidth && childContext.getProp('width');
  36806. margins = childContext.getMarginInfo();
  36807. // getXY is the root method here (meaning that we cannot avoid getting both
  36808. // even if we need only one), so dip into the DOM if something is needed
  36809. if ((calcWidth && isNaN(child.x)) || (calcHeight && isNaN(child.y))) {
  36810. xy = child.el.getXY();
  36811. if (!targetXY) {
  36812. targetXY = ownerContext.targetContext.el.getXY();
  36813. borders = ownerContext.targetContext.getBorderInfo();
  36814. targetX = targetXY[0] + borders.left;
  36815. targetY = targetXY[1] + borders.top;
  36816. }
  36817. // not worth avoiding the possibly useless calculation here:
  36818. childX = xy[0] - targetX;
  36819. childY = xy[1] - targetY;
  36820. } else {
  36821. // not worth avoiding these either:
  36822. childX = child.x;
  36823. childY = child.y;
  36824. }
  36825. // XY includes the top/left margin
  36826. height += margins.bottom;
  36827. width += margins.right;
  36828. contentHeight = Math.max(contentHeight, childY + height);
  36829. contentWidth = Math.max(contentWidth, childX + width);
  36830. if (isNaN(contentHeight) && isNaN(contentWidth)) {
  36831. me.done = false;
  36832. return;
  36833. }
  36834. }
  36835. if (calcWidth || calcHeight) {
  36836. targetPadding = ownerContext.targetContext.getPaddingInfo();
  36837. }
  36838. if (calcWidth && !ownerContext.setContentWidth(contentWidth + targetPadding.right)) {
  36839. me.done = false;
  36840. }
  36841. if (calcHeight && !ownerContext.setContentHeight(contentHeight + targetPadding.bottom)) {
  36842. me.done = false;
  36843. }
  36844. /* add a '/' to turn on this log ('//* enables, '/*' disables)
  36845. if (me.done) {
  36846. var el = ownerContext.targetContext.el.dom;
  36847. Ext.log(this.owner.id, '.contentSize: ', contentWidth, 'x', contentHeight,
  36848. ' => scrollSize: ', el.scrollWidth, 'x', el.scrollHeight);
  36849. }/**/
  36850. }
  36851. },
  36852. /**
  36853. * Handles overflow processing for a container. This should be called once the layout
  36854. * has determined contentWidth/Height. In addition to the ownerContext passed to the
  36855. * {@link #calculate} method, this method also needs the containerSize (the object
  36856. * returned by {@link #getContainerSize}).
  36857. *
  36858. * @param {Ext.layout.ContextItem} ownerContext
  36859. * @param {Object} containerSize
  36860. * @param {Number} dimensions A bit mask for the overflow managed dimensions. The 0-bit
  36861. * is for `width` and the 1-bit is for `height`. In other words, a value of 1 would be
  36862. * only `width`, 2 would be only `height` and 3 would be both.
  36863. */
  36864. calculateOverflow: function (ownerContext, containerSize, dimensions) {
  36865. var me = this,
  36866. owner = me.owner,
  36867. manageOverflow = me.manageOverflow,
  36868. state = ownerContext.state,
  36869. overflowAdjust = state.overflowAdjust,
  36870. padWidth, padHeight, padElContext, padding, scrollRangeFlags,
  36871. overflow, scrollbarSize, contentW, contentH, ownerW, ownerH, scrollbars,
  36872. xauto, yauto;
  36873. if (manageOverflow && !state.secondPass && !me.reserveScrollbar) {
  36874. // Determine the dimensions that have overflow:auto applied. If these come by
  36875. // way of component config, this does not require a DOM read:
  36876. if (owner.autoScroll) {
  36877. xauto = yauto = true;
  36878. } else {
  36879. if (owner.overflowX) {
  36880. xauto = owner.overflowX == 'auto';
  36881. } else {
  36882. overflow = ownerContext.targetContext.getStyle('overflow-x');
  36883. xauto = overflow && overflow != 'hidden' && overflow != 'scroll';
  36884. }
  36885. if (owner.overflowY) {
  36886. yauto = owner.overflowY == 'auto';
  36887. } else {
  36888. overflow = ownerContext.targetContext.getStyle('overflow-y');
  36889. yauto = overflow && overflow != 'hidden' && overflow != 'scroll';
  36890. }
  36891. }
  36892. // If the container layout is not using width, we don't need to adjust for the
  36893. // vscroll (likewise for height). Perhaps we don't even need to run the layout
  36894. // again if the adjustments won't have any effect on the result!
  36895. if (!containerSize.gotWidth) {
  36896. xauto = false;
  36897. }
  36898. if (!containerSize.gotHeight) {
  36899. yauto = false;
  36900. }
  36901. if (xauto || yauto) {
  36902. scrollbarSize = Ext.getScrollbarSize();
  36903. // as a container we calculate contentWidth/Height, so we don't want
  36904. // to use getProp and make it look like we are triggered by them...
  36905. contentW = ownerContext.peek('contentWidth');
  36906. contentH = ownerContext.peek('contentHeight');
  36907. ownerW = containerSize.width;
  36908. ownerH = containerSize.height;
  36909. scrollbars = me.getScrollbarsNeeded(ownerW, ownerH, contentW, contentH);
  36910. state.overflowState = scrollbars;
  36911. if (typeof dimensions == 'number') {
  36912. scrollbars &= ~dimensions; // ignore dimensions that have no effect
  36913. }
  36914. overflowAdjust = {
  36915. width: (xauto && (scrollbars & 2)) ? scrollbarSize.width : 0,
  36916. height: (yauto && (scrollbars & 1)) ? scrollbarSize.height : 0
  36917. };
  36918. // We can have 0-sized scrollbars (new Mac OS) and so don't invalidate
  36919. // the layout unless this will change something...
  36920. if (overflowAdjust.width !== me.lastOverflowAdjust.width || overflowAdjust.height !== me.lastOverflowAdjust.height) {
  36921. me.done = false;
  36922. // we pass overflowAdjust and overflowState in as state for the next
  36923. // cycle (these are discarded if one of our ownerCt's invalidates):
  36924. ownerContext.invalidate({
  36925. state: {
  36926. overflowAdjust: overflowAdjust,
  36927. overflowState: state.overflowState,
  36928. secondPass: true
  36929. }
  36930. });
  36931. }
  36932. }
  36933. }
  36934. if (!me.done) {
  36935. return;
  36936. }
  36937. padElContext = ownerContext.padElContext ||
  36938. (ownerContext.padElContext = ownerContext.getEl('overflowPadderEl', me));
  36939. // Even if overflow does not effect the layout, we still do need the padEl to be
  36940. // sized or hidden appropriately...
  36941. if (padElContext) {
  36942. scrollbars = state.overflowState; // the true overflow state
  36943. padWidth = containerSize.width;
  36944. padHeight = 0;// TODO me.padHeightAdj; // 0 or 1
  36945. if (scrollbars) {
  36946. padding = ownerContext.targetContext.getPaddingInfo();
  36947. scrollRangeFlags = me.scrollRangeFlags;
  36948. if ((scrollbars & 2) && (scrollRangeFlags & 1)) { // if (vscroll and loses bottom)
  36949. padHeight += padding.bottom;
  36950. }
  36951. if ((scrollbars & 1) && (scrollRangeFlags & 4)) { // if (hscroll and loses right)
  36952. padWidth += padding.right;
  36953. }
  36954. padElContext.setProp('display', '');
  36955. padElContext.setSize(padWidth, padHeight);
  36956. } else {
  36957. padElContext.setProp('display', 'none');
  36958. }
  36959. }
  36960. },
  36961. /**
  36962. * Adds layout's itemCls and owning Container's itemCls
  36963. * @protected
  36964. */
  36965. configureItem: function(item) {
  36966. var me = this,
  36967. ownerItemCls = me.owner.itemCls,
  36968. addClasses = [].concat(me.itemCls || []);
  36969. me.callParent(arguments);
  36970. if (ownerItemCls) {
  36971. addClasses = Ext.Array.push(addClasses, ownerItemCls);
  36972. }
  36973. item.addCls(addClasses);
  36974. },
  36975. doRenderBody: function (out, renderData) {
  36976. // Careful! This method is bolted on to the renderTpl so all we get for context is
  36977. // the renderData! The "this" pointer is the renderTpl instance!
  36978. this.renderItems(out, renderData);
  36979. this.renderContent(out, renderData);
  36980. },
  36981. doRenderContainer: function (out, renderData) {
  36982. // Careful! This method is bolted on to the renderTpl so all we get for context is
  36983. // the renderData! The "this" pointer is the renderTpl instance!
  36984. var me = renderData.$comp.layout,
  36985. tpl = me.getRenderTpl(),
  36986. data = me.getRenderData();
  36987. tpl.applyOut(data, out);
  36988. },
  36989. doRenderItems: function (out, renderData) {
  36990. // Careful! This method is bolted on to the renderTpl so all we get for context is
  36991. // the renderData! The "this" pointer is the renderTpl instance!
  36992. var me = renderData.$layout,
  36993. tree = me.getRenderTree();
  36994. if (tree) {
  36995. Ext.DomHelper.generateMarkup(tree, out);
  36996. }
  36997. },
  36998. /**
  36999. * Creates an element that makes bottom/right body padding consistent across browsers.
  37000. * This element is sized based on the need for scrollbars in {@link #calculateOverflow}.
  37001. * If the {@link #manageOverflow} option is false, this element is not created.
  37002. *
  37003. * See {@link #getScrollRangeFlags} for more details.
  37004. */
  37005. doRenderPadder: function (out, renderData) {
  37006. // Careful! This method is bolted on to the renderTpl so all we get for context is
  37007. // the renderData! The "this" pointer is the renderTpl instance!
  37008. var me = renderData.$layout,
  37009. owner = me.owner,
  37010. scrollRangeFlags = me.getScrollRangeFlags();
  37011. if (me.manageOverflow == 2) {
  37012. if (scrollRangeFlags & 5) { // if (loses parent bottom and/or right padding)
  37013. out.push('<div id="',owner.id,'-overflowPadderEl" ',
  37014. 'style="font-size: 1px; width:1px; height: 1px;');
  37015. // We won't want the height of the padder to cause problems when we only
  37016. // want to adjust for right padding, so we relatively position it up 1px so
  37017. // its height of 1px will have no vertical effect. This trick does not work
  37018. // on IE due to bugs (the effects are worse than the off-by-1px in scroll
  37019. // height).
  37020. //
  37021. /* turns out this does not work on FF (5) either... TODO
  37022. if (Ext.isIE || Ext.isGecko) {
  37023. me.padHeightAdj = 0;
  37024. } else {
  37025. me.padHeightAdj = 1;
  37026. out.push('position: relative; top: -1px;');
  37027. }/**/
  37028. out.push('"></div>');
  37029. me.scrollRangeFlags = scrollRangeFlags; // remember for calculateOverflow
  37030. }
  37031. }
  37032. },
  37033. finishRender: function () {
  37034. var me = this,
  37035. target, items;
  37036. me.callParent();
  37037. me.cacheElements();
  37038. target = me.getRenderTarget();
  37039. items = me.getLayoutItems();
  37040. if (me.targetCls) {
  37041. me.getTarget().addCls(me.targetCls);
  37042. }
  37043. me.finishRenderItems(target, items);
  37044. },
  37045. /**
  37046. * @private
  37047. * Called for every layout in the layout context after all the layouts have been finally flushed
  37048. */
  37049. notifyOwner: function() {
  37050. this.owner.afterLayout(this);
  37051. },
  37052. /**
  37053. * Returns the container size (that of the target). Only the fixed-sized dimensions can
  37054. * be returned because the shrinkWrap dimensions are based on the contentWidth/Height
  37055. * as determined by the container layout.
  37056. *
  37057. * If the {@link #calculateOverflow} method is used and if {@link #manageOverflow} is
  37058. * true, this may adjust the width/height by the size of scrollbars.
  37059. *
  37060. * @param {Ext.layout.ContextItem} ownerContext The owner's context item.
  37061. * @param {Boolean} [inDom=false] True if the container size must be in the DOM.
  37062. * @return {Object} The size
  37063. * @return {Number} return.width The width
  37064. * @return {Number} return.height The height
  37065. * @protected
  37066. */
  37067. getContainerSize : function(ownerContext, inDom) {
  37068. // Subtle But Important:
  37069. //
  37070. // We don't want to call getProp/hasProp et.al. unless we in fact need that value
  37071. // for our results! If we call it and don't need it, the layout manager will think
  37072. // we depend on it and will schedule us again should it change.
  37073. var targetContext = ownerContext.targetContext,
  37074. frameInfo = targetContext.getFrameInfo(),
  37075. padding = targetContext.getPaddingInfo(),
  37076. got = 0,
  37077. needed = 0,
  37078. overflowAdjust = ownerContext.state.overflowAdjust,
  37079. gotWidth, gotHeight, width, height;
  37080. // In an shrinkWrap width/height case, we must not ask for any of these dimensions
  37081. // because they will be determined by contentWidth/Height which is calculated by
  37082. // this layout...
  37083. // Fit/Card layouts are able to set just the width of children, allowing child's
  37084. // resulting height to autosize the Container.
  37085. // See examples/tabs/tabs.html for an example of this.
  37086. if (!ownerContext.widthModel.shrinkWrap) {
  37087. ++needed;
  37088. width = inDom ? targetContext.getDomProp('width') : targetContext.getProp('width');
  37089. gotWidth = (typeof width == 'number');
  37090. if (gotWidth) {
  37091. ++got;
  37092. width -= frameInfo.width + padding.width;
  37093. if (overflowAdjust) {
  37094. width -= overflowAdjust.width;
  37095. }
  37096. }
  37097. }
  37098. if (!ownerContext.heightModel.shrinkWrap) {
  37099. ++needed;
  37100. height = inDom ? targetContext.getDomProp('height') : targetContext.getProp('height');
  37101. gotHeight = (typeof height == 'number');
  37102. if (gotHeight) {
  37103. ++got;
  37104. height -= frameInfo.height + padding.height;
  37105. if (overflowAdjust) {
  37106. height -= overflowAdjust.height;
  37107. }
  37108. }
  37109. }
  37110. return {
  37111. width: width,
  37112. height: height,
  37113. needed: needed,
  37114. got: got,
  37115. gotAll: got == needed,
  37116. gotWidth: gotWidth,
  37117. gotHeight: gotHeight
  37118. };
  37119. },
  37120. /**
  37121. * Returns an array of child components either for a render phase (Performed in the beforeLayout
  37122. * method of the layout's base class), or the layout phase (onLayout).
  37123. * @return {Ext.Component[]} of child components
  37124. */
  37125. getLayoutItems: function() {
  37126. var owner = this.owner,
  37127. items = owner && owner.items;
  37128. return (items && items.items) || [];
  37129. },
  37130. getRenderData: function () {
  37131. var comp = this.owner;
  37132. return {
  37133. $comp: comp,
  37134. $layout: this,
  37135. ownerId: comp.id
  37136. };
  37137. },
  37138. /**
  37139. * @protected
  37140. * Returns all items that are rendered
  37141. * @return {Array} All matching items
  37142. */
  37143. getRenderedItems: function() {
  37144. var me = this,
  37145. target = me.getRenderTarget(),
  37146. items = me.getLayoutItems(),
  37147. ln = items.length,
  37148. renderedItems = [],
  37149. i, item;
  37150. for (i = 0; i < ln; i++) {
  37151. item = items[i];
  37152. if (item.rendered && me.isValidParent(item, target, i)) {
  37153. renderedItems.push(item);
  37154. }
  37155. }
  37156. return renderedItems;
  37157. },
  37158. /**
  37159. * Returns the element into which rendering must take place. Defaults to the owner Container's
  37160. * target element.
  37161. *
  37162. * May be overridden in layout managers which implement an inner element.
  37163. *
  37164. * @return {Ext.Element}
  37165. */
  37166. getRenderTarget: function() {
  37167. return this.owner.getTargetEl();
  37168. },
  37169. /**
  37170. * Returns the element into which extra functional DOM elements can be inserted. Defaults to the owner Component's encapsulating element.
  37171. *
  37172. * May be overridden in Component layout managers which implement a {@link #getRenderTarget component render target} which must only
  37173. * contain child components.
  37174. * @return {Ext.Element}
  37175. */
  37176. getElementTarget: function() {
  37177. return this.getRenderTarget();
  37178. },
  37179. getRenderTpl: function () {
  37180. var me = this,
  37181. renderTpl = Ext.XTemplate.getTpl(this, 'renderTpl');
  37182. // Make sure all standard callout methods for the owner component are placed on the
  37183. // XTemplate instance (but only once please):
  37184. if (!renderTpl.renderContent) {
  37185. me.owner.setupRenderTpl(renderTpl);
  37186. }
  37187. return renderTpl;
  37188. },
  37189. getRenderTree: function () {
  37190. var result,
  37191. items = this.owner.items,
  37192. itemsGen,
  37193. renderCfgs = {};
  37194. do {
  37195. itemsGen = items.generation;
  37196. result = this.getItemsRenderTree(this.getLayoutItems(), renderCfgs);
  37197. } while (items.generation !== itemsGen);
  37198. return result;
  37199. },
  37200. getScrollbarsNeeded: function (width, height, contentWidth, contentHeight) {
  37201. var scrollbarSize = Ext.getScrollbarSize(),
  37202. hasWidth = typeof width == 'number',
  37203. hasHeight = typeof height == 'number',
  37204. needHorz = 0,
  37205. needVert = 0;
  37206. // No space-consuming scrollbars.
  37207. if (!scrollbarSize.width) {
  37208. return 0;
  37209. }
  37210. if (hasHeight && height < contentHeight) {
  37211. needVert = 2;
  37212. width -= scrollbarSize.width;
  37213. }
  37214. if (hasWidth && width < contentWidth) {
  37215. needHorz = 1;
  37216. if (!needVert && hasHeight) {
  37217. height -= scrollbarSize.height;
  37218. if (height < contentHeight) {
  37219. needVert = 2;
  37220. }
  37221. }
  37222. }
  37223. return needVert + needHorz;
  37224. },
  37225. /**
  37226. * Returns flags indicating cross-browser handling of scrollHeight/Width. In particular,
  37227. * IE has issues with padding-bottom in a scrolling element (it does not include that
  37228. * padding in the scrollHeight). Also, margin-bottom on a child in a scrolling element
  37229. * can be lost.
  37230. *
  37231. * All browsers seem to ignore margin-right on children and padding-right on the parent
  37232. * element (the one with the overflow)
  37233. *
  37234. * This method returns a number with the follow bit positions set based on things not
  37235. * accounted for in scrollHeight and scrollWidth:
  37236. *
  37237. * - 1: Scrolling element's padding-bottom is not included in scrollHeight.
  37238. * - 2: Last child's margin-bottom is not included in scrollHeight.
  37239. * - 4: Scrolling element's padding-right is not included in scrollWidth.
  37240. * - 8: Child's margin-right is not included in scrollWidth.
  37241. *
  37242. * To work around the margin-bottom issue, it is sufficient to create a 0px tall last
  37243. * child that will "hide" the margin. This can also be handled by wrapping the children
  37244. * in an element, again "hiding" the margin. Wrapping the elements is about the only
  37245. * way to preserve their right margins. This is the strategy used by Column layout.
  37246. *
  37247. * To work around the padding-bottom problem, since it is comes from a style on the
  37248. * parent element, about the only simple fix is to create a last child with height
  37249. * equal to padding-bottom. To preserve the right padding, the sizing element needs to
  37250. * have a width that includes the right padding.
  37251. */
  37252. getScrollRangeFlags: (function () {
  37253. var flags = -1;
  37254. return function () {
  37255. if (flags < 0) {
  37256. var div = Ext.getBody().createChild({
  37257. //cls: 'x-border-box x-hide-offsets',
  37258. cls: Ext.baseCSSPrefix + 'border-box',
  37259. style: {
  37260. width: '100px', height: '100px', padding: '10px',
  37261. overflow: 'auto'
  37262. },
  37263. children: [{
  37264. style: {
  37265. border: '1px solid red',
  37266. width: '150px', height: '150px',
  37267. margin: '0 5px 5px 0' // TRBL
  37268. }
  37269. }]
  37270. }),
  37271. scrollHeight = div.dom.scrollHeight,
  37272. scrollWidth = div.dom.scrollWidth,
  37273. heightFlags = {
  37274. // right answer, nothing missing:
  37275. 175: 0,
  37276. // missing parent padding-bottom:
  37277. 165: 1,
  37278. // missing child margin-bottom:
  37279. 170: 2,
  37280. // missing both
  37281. 160: 3
  37282. },
  37283. widthFlags = {
  37284. // right answer, nothing missing:
  37285. 175: 0,
  37286. // missing parent padding-right:
  37287. 165: 4,
  37288. // missing child margin-right:
  37289. 170: 8,
  37290. // missing both
  37291. 160: 12
  37292. };
  37293. flags = (heightFlags[scrollHeight] || 0) | (widthFlags[scrollWidth] || 0);
  37294. //Ext.log('flags=',flags.toString(2));
  37295. div.remove();
  37296. }
  37297. return flags;
  37298. };
  37299. }()),
  37300. /**
  37301. * Returns the owner component's resize element.
  37302. * @return {Ext.Element}
  37303. */
  37304. getTarget: function() {
  37305. return this.owner.getTargetEl();
  37306. },
  37307. /**
  37308. * @protected
  37309. * Returns all items that are both rendered and visible
  37310. * @return {Array} All matching items
  37311. */
  37312. getVisibleItems: function() {
  37313. var target = this.getRenderTarget(),
  37314. items = this.getLayoutItems(),
  37315. ln = items.length,
  37316. visibleItems = [],
  37317. i, item;
  37318. for (i = 0; i < ln; i++) {
  37319. item = items[i];
  37320. if (item.rendered && this.isValidParent(item, target, i) && item.hidden !== true) {
  37321. visibleItems.push(item);
  37322. }
  37323. }
  37324. return visibleItems;
  37325. },
  37326. setupRenderTpl: function (renderTpl) {
  37327. var me = this;
  37328. renderTpl.renderBody = me.doRenderBody;
  37329. renderTpl.renderContainer = me.doRenderContainer;
  37330. renderTpl.renderItems = me.doRenderItems;
  37331. renderTpl.renderPadder = me.doRenderPadder;
  37332. }
  37333. });
  37334. /**
  37335. * Component layout for editors
  37336. * @private
  37337. */
  37338. Ext.define('Ext.layout.container.Editor', {
  37339. /* Begin Definitions */
  37340. alias: 'layout.editor',
  37341. extend: 'Ext.layout.container.Container',
  37342. /* End Definitions */
  37343. autoSizeDefault: {
  37344. width: 'field',
  37345. height: 'field'
  37346. },
  37347. getItemSizePolicy: function (item) {
  37348. var me = this,
  37349. autoSize = me.owner.autoSize;
  37350. return me.sizePolicy || (me.sizePolicy = {
  37351. setsWidth: autoSize && autoSize.width === 'boundEl' ? 1 : 0,
  37352. setsHeight: autoSize && autoSize.height === 'boundEl' ? 1 : 0
  37353. });
  37354. },
  37355. calculate: function(ownerContext) {
  37356. var me = this,
  37357. owner = me.owner,
  37358. autoSize = owner.autoSize,
  37359. fieldWidth,
  37360. fieldHeight;
  37361. if (autoSize === true) {
  37362. autoSize = me.autoSizeDefault;
  37363. }
  37364. // Calculate size of both Editor, and its owned Field
  37365. if (autoSize) {
  37366. fieldWidth = me.getDimension(owner, autoSize.width, 'getWidth', owner.width);
  37367. fieldHeight = me.getDimension(owner, autoSize.height, 'getHeight', owner.height);
  37368. }
  37369. // Set Field size
  37370. ownerContext.childItems[0].setSize(fieldWidth, fieldHeight);
  37371. // Bypass validity checking. Container layouts should not usually set their owner's size.
  37372. ownerContext.setWidth(fieldWidth);
  37373. ownerContext.setHeight(fieldHeight);
  37374. // This is a Container layout, so publish content size
  37375. ownerContext.setContentSize(fieldWidth || owner.field.getWidth(),
  37376. fieldHeight || owner.field.getHeight());
  37377. },
  37378. getDimension: function(owner, type, getMethod, ownerSize){
  37379. switch (type) {
  37380. // Size to boundEl's dimension
  37381. case 'boundEl':
  37382. return owner.boundEl[getMethod]();
  37383. // Auto size (shrink wrap the Field's size
  37384. case 'field':
  37385. return undefined;
  37386. // Size to the Editor's configured size
  37387. default:
  37388. return ownerSize;
  37389. }
  37390. }
  37391. });
  37392. /**
  37393. * This class is intended to be extended or created via the {@link Ext.Component#componentLayout layout}
  37394. * configuration property. See {@link Ext.Component#componentLayout} for additional details.
  37395. * @private
  37396. */
  37397. Ext.define('Ext.layout.component.Component', {
  37398. /* Begin Definitions */
  37399. extend: 'Ext.layout.Layout',
  37400. /* End Definitions */
  37401. type: 'component',
  37402. isComponentLayout: true,
  37403. nullBox: {},
  37404. usesContentHeight: true,
  37405. usesContentWidth: true,
  37406. usesHeight: true,
  37407. usesWidth: true,
  37408. beginLayoutCycle: function (ownerContext, firstCycle) {
  37409. var me = this,
  37410. owner = me.owner,
  37411. ownerCtContext = ownerContext.ownerCtContext,
  37412. heightModel = ownerContext.heightModel,
  37413. widthModel = ownerContext.widthModel,
  37414. body = owner.el.dom === document.body,
  37415. lastBox = owner.lastBox || me.nullBox,
  37416. lastSize = owner.el.lastBox || me.nullBox,
  37417. dirty = !body,
  37418. ownerLayout, v, widthName, heightName;
  37419. me.callParent(arguments);
  37420. if (firstCycle) {
  37421. if (me.usesContentWidth) {
  37422. ++ownerContext.consumersContentWidth;
  37423. }
  37424. if (me.usesContentHeight) {
  37425. ++ownerContext.consumersContentHeight;
  37426. }
  37427. if (me.usesWidth) {
  37428. ++ownerContext.consumersWidth;
  37429. }
  37430. if (me.usesHeight) {
  37431. ++ownerContext.consumersHeight;
  37432. }
  37433. if (ownerCtContext && !ownerCtContext.hasRawContent) {
  37434. ownerLayout = owner.ownerLayout;
  37435. if (ownerLayout.usesWidth) {
  37436. ++ownerContext.consumersWidth;
  37437. }
  37438. if (ownerLayout.usesHeight) {
  37439. ++ownerContext.consumersHeight;
  37440. }
  37441. }
  37442. }
  37443. // we want to publish configured dimensions as early as possible and since this is
  37444. // a write phase...
  37445. if (widthModel.configured) {
  37446. // If the owner.el is the body, owner.width is not dirty (we don't want to write
  37447. // it to the body el). For other el's, the width may already be correct in the
  37448. // DOM (e.g., it is rendered in the markup initially). If the width is not
  37449. // correct in the DOM, this is only going to be the case on the first cycle.
  37450. widthName = widthModel.names.width;
  37451. if (!body) {
  37452. dirty = firstCycle ? owner[widthName] !== lastSize.width
  37453. : widthModel.constrained;
  37454. }
  37455. ownerContext.setWidth(owner[widthName], dirty);
  37456. } else if (ownerContext.isTopLevel) {
  37457. if (widthModel.calculated) {
  37458. v = lastBox.width;
  37459. ownerContext.setWidth(v, /*dirty=*/v != lastSize.width);
  37460. }
  37461. v = lastBox.x;
  37462. ownerContext.setProp('x', v, /*dirty=*/v != lastSize.x);
  37463. }
  37464. if (heightModel.configured) {
  37465. heightName = heightModel.names.height;
  37466. if (!body) {
  37467. dirty = firstCycle ? owner[heightName] !== lastSize.height
  37468. : heightModel.constrained;
  37469. }
  37470. ownerContext.setHeight(owner[heightName], dirty);
  37471. } else if (ownerContext.isTopLevel) {
  37472. if (heightModel.calculated) {
  37473. v = lastBox.height;
  37474. ownerContext.setHeight(v, v != lastSize.height);
  37475. }
  37476. v = lastBox.y;
  37477. ownerContext.setProp('y', v, /*dirty=*/v != lastSize.y);
  37478. }
  37479. },
  37480. finishedLayout: function(ownerContext) {
  37481. var me = this,
  37482. elementChildren = ownerContext.children,
  37483. owner = me.owner,
  37484. len, i, elContext, lastBox, props, v;
  37485. // NOTE: In the code below we cannot use getProp because that will generate a layout dependency
  37486. // Set lastBox on managed child Elements.
  37487. // So that ContextItem.constructor can snag the lastBox for use by its undo method.
  37488. if (elementChildren) {
  37489. len = elementChildren.length;
  37490. for (i = 0; i < len; i++) {
  37491. elContext = elementChildren[i];
  37492. elContext.el.lastBox = elContext.props;
  37493. }
  37494. }
  37495. // Cache the size from which we are changing so that notifyOwner can notify the owningComponent with all essential information
  37496. ownerContext.previousSize = me.lastComponentSize;
  37497. // Cache the currently layed out size
  37498. me.lastComponentSize = owner.el.lastBox = props = ownerContext.props;
  37499. // lastBox is a copy of the defined props to allow save/restore of these (panel
  37500. // collapse needs this)
  37501. owner.lastBox = lastBox = {};
  37502. v = props.x;
  37503. if (v !== undefined) {
  37504. lastBox.x = v;
  37505. }
  37506. v = props.y;
  37507. if (v !== undefined) {
  37508. lastBox.y = v;
  37509. }
  37510. v = props.width;
  37511. if (v !== undefined) {
  37512. lastBox.width = v;
  37513. }
  37514. v = props.height;
  37515. if (v !== undefined) {
  37516. lastBox.height = v;
  37517. }
  37518. me.callParent(arguments);
  37519. },
  37520. notifyOwner: function(ownerContext) {
  37521. var me = this,
  37522. currentSize = me.lastComponentSize,
  37523. prevSize = ownerContext.previousSize,
  37524. args = [currentSize.width, currentSize.height];
  37525. if (prevSize) {
  37526. args.push(prevSize.width, prevSize.height);
  37527. }
  37528. // Call afterComponentLayout passing new size, and only passing old size if there *was* an old size.
  37529. me.owner.afterComponentLayout.apply(me.owner, args);
  37530. },
  37531. /**
  37532. * Returns the owner component's resize element.
  37533. * @return {Ext.Element}
  37534. */
  37535. getTarget : function() {
  37536. return this.owner.el;
  37537. },
  37538. /**
  37539. * Returns the element into which rendering must take place. Defaults to the owner Component's encapsulating element.
  37540. *
  37541. * May be overridden in Component layout managers which implement an inner element.
  37542. * @return {Ext.Element}
  37543. */
  37544. getRenderTarget : function() {
  37545. return this.owner.el;
  37546. },
  37547. cacheTargetInfo: function(ownerContext) {
  37548. var me = this,
  37549. targetInfo = me.targetInfo,
  37550. target;
  37551. if (!targetInfo) {
  37552. target = ownerContext.getEl('getTarget', me);
  37553. me.targetInfo = targetInfo = {
  37554. padding: target.getPaddingInfo(),
  37555. border: target.getBorderInfo()
  37556. };
  37557. }
  37558. return targetInfo;
  37559. },
  37560. measureAutoDimensions: function (ownerContext, dimensions) {
  37561. // Subtle But Important:
  37562. //
  37563. // We don't want to call getProp/hasProp et.al. unless we in fact need that value
  37564. // for our results! If we call it and don't need it, the layout manager will think
  37565. // we depend on it and will schedule us again should it change.
  37566. var me = this,
  37567. owner = me.owner,
  37568. containerLayout = owner.layout,
  37569. heightModel = ownerContext.heightModel,
  37570. widthModel = ownerContext.widthModel,
  37571. boxParent = ownerContext.boxParent,
  37572. isBoxParent = ownerContext.isBoxParent,
  37573. props = ownerContext.props,
  37574. isContainer,
  37575. ret = {
  37576. gotWidth: false,
  37577. gotHeight: false,
  37578. isContainer: (isContainer = !ownerContext.hasRawContent)
  37579. },
  37580. hv = dimensions || 3,
  37581. zeroWidth, zeroHeight,
  37582. needed = 0,
  37583. got = 0,
  37584. ready, size, temp;
  37585. // Note: this method is called *a lot*, so we have to be careful not to waste any
  37586. // time or make useless calls or, especially, read the DOM when we can avoid it.
  37587. //---------------------------------------------------------------------
  37588. // Width
  37589. if (widthModel.shrinkWrap && ownerContext.consumersContentWidth) {
  37590. ++needed;
  37591. zeroWidth = !(hv & 1);
  37592. if (isContainer) {
  37593. // as a componentLayout for a container, we rely on the container layout to
  37594. // produce contentWidth...
  37595. if (zeroWidth) {
  37596. ret.contentWidth = 0;
  37597. ret.gotWidth = true;
  37598. ++got;
  37599. } else if ((ret.contentWidth = ownerContext.getProp('contentWidth')) !== undefined) {
  37600. ret.gotWidth = true;
  37601. ++got;
  37602. }
  37603. } else {
  37604. size = props.contentWidth;
  37605. if (typeof size == 'number') { // if (already determined)
  37606. ret.contentWidth = size;
  37607. ret.gotWidth = true;
  37608. ++got;
  37609. } else {
  37610. if (zeroWidth) {
  37611. ready = true;
  37612. } else if (!ownerContext.hasDomProp('containerChildrenDone')) {
  37613. ready = false;
  37614. } else if (isBoxParent || !boxParent || boxParent.widthModel.shrinkWrap) {
  37615. // if we have no boxParent, we are ready, but a shrinkWrap boxParent
  37616. // artificially provides width early in the measurement process so
  37617. // we are ready to go in that case as well...
  37618. ready = true;
  37619. } else {
  37620. // lastly, we have a boxParent that will be given a width, so we
  37621. // can wait for that width to be set in order to properly measure
  37622. // whatever is inside...
  37623. ready = boxParent.hasDomProp('width');
  37624. }
  37625. if (ready) {
  37626. if (zeroWidth) {
  37627. temp = 0;
  37628. } else if (containerLayout && containerLayout.measureContentWidth) {
  37629. // Allow the container layout to do the measurement since it
  37630. // may have a better idea of how to do it even with no items:
  37631. temp = containerLayout.measureContentWidth(ownerContext);
  37632. } else {
  37633. temp = me.measureContentWidth(ownerContext);
  37634. }
  37635. if (!isNaN(ret.contentWidth = temp)) {
  37636. ownerContext.setContentWidth(temp, true);
  37637. ret.gotWidth = true;
  37638. ++got;
  37639. }
  37640. }
  37641. }
  37642. }
  37643. } else if (widthModel.natural && ownerContext.consumersWidth) {
  37644. ++needed;
  37645. size = props.width;
  37646. // zeroWidth does not apply
  37647. if (typeof size == 'number') { // if (already determined)
  37648. ret.width = size;
  37649. ret.gotWidth = true;
  37650. ++got;
  37651. } else {
  37652. if (isBoxParent || !boxParent) {
  37653. ready = true;
  37654. } else {
  37655. // lastly, we have a boxParent that will be given a width, so we
  37656. // can wait for that width to be set in order to properly measure
  37657. // whatever is inside...
  37658. ready = boxParent.hasDomProp('width');
  37659. }
  37660. if (ready) {
  37661. if (!isNaN(ret.width = me.measureOwnerWidth(ownerContext))) {
  37662. ownerContext.setWidth(ret.width, false);
  37663. ret.gotWidth = true;
  37664. ++got;
  37665. }
  37666. }
  37667. }
  37668. }
  37669. //---------------------------------------------------------------------
  37670. // Height
  37671. if (heightModel.shrinkWrap && ownerContext.consumersContentHeight) {
  37672. ++needed;
  37673. zeroHeight = !(hv & 2);
  37674. if (isContainer) {
  37675. // don't ask unless we need to know...
  37676. if (zeroHeight) {
  37677. ret.contentHeight = 0;
  37678. ret.gotHeight = true;
  37679. ++got;
  37680. } else if ((ret.contentHeight = ownerContext.getProp('contentHeight')) !== undefined) {
  37681. ret.gotHeight = true;
  37682. ++got;
  37683. }
  37684. } else {
  37685. size = props.contentHeight;
  37686. if (typeof size == 'number') { // if (already determined)
  37687. ret.contentHeight = size;
  37688. ret.gotHeight = true;
  37689. ++got;
  37690. } else {
  37691. if (zeroHeight) {
  37692. ready = true;
  37693. } else if (!ownerContext.hasDomProp('containerChildrenDone')) {
  37694. ready = false;
  37695. } else if (owner.noWrap) {
  37696. ready = true;
  37697. } else if (!widthModel.shrinkWrap) {
  37698. // fixed width, so we need the width to determine the height...
  37699. ready = (ownerContext.bodyContext || ownerContext).hasDomProp('width');// && (!ownerContext.bodyContext || ownerContext.bodyContext.hasDomProp('width'));
  37700. } else if (isBoxParent || !boxParent || boxParent.widthModel.shrinkWrap) {
  37701. // if we have no boxParent, we are ready, but an autoWidth boxParent
  37702. // artificially provides width early in the measurement process so
  37703. // we are ready to go in that case as well...
  37704. ready = true;
  37705. } else {
  37706. // lastly, we have a boxParent that will be given a width, so we
  37707. // can wait for that width to be set in order to properly measure
  37708. // whatever is inside...
  37709. ready = boxParent.hasDomProp('width');
  37710. }
  37711. if (ready) {
  37712. if (zeroHeight) {
  37713. temp = 0;
  37714. } else if (containerLayout && containerLayout.measureContentHeight) {
  37715. // Allow the container layout to do the measurement since it
  37716. // may have a better idea of how to do it even with no items:
  37717. temp = containerLayout.measureContentHeight(ownerContext);
  37718. } else {
  37719. temp = me.measureContentHeight(ownerContext);
  37720. }
  37721. if (!isNaN(ret.contentHeight = temp)) {
  37722. ownerContext.setContentHeight(temp, true);
  37723. ret.gotHeight = true;
  37724. ++got;
  37725. }
  37726. }
  37727. }
  37728. }
  37729. } else if (heightModel.natural && ownerContext.consumersHeight) {
  37730. ++needed;
  37731. size = props.height;
  37732. // zeroHeight does not apply
  37733. if (typeof size == 'number') { // if (already determined)
  37734. ret.height = size;
  37735. ret.gotHeight = true;
  37736. ++got;
  37737. } else {
  37738. if (isBoxParent || !boxParent) {
  37739. ready = true;
  37740. } else {
  37741. // lastly, we have a boxParent that will be given a width, so we
  37742. // can wait for that width to be set in order to properly measure
  37743. // whatever is inside...
  37744. ready = boxParent.hasDomProp('width');
  37745. }
  37746. if (ready) {
  37747. if (!isNaN(ret.height = me.measureOwnerHeight(ownerContext))) {
  37748. ownerContext.setHeight(ret.height, false);
  37749. ret.gotHeight = true;
  37750. ++got;
  37751. }
  37752. }
  37753. }
  37754. }
  37755. if (boxParent) {
  37756. ownerContext.onBoxMeasured();
  37757. }
  37758. ret.gotAll = got == needed;
  37759. // see if we can avoid calling this method by storing something on ownerContext.
  37760. return ret;
  37761. },
  37762. measureContentWidth: function (ownerContext) {
  37763. // contentWidth includes padding, but not border, framing or margins
  37764. return ownerContext.el.getWidth() - ownerContext.getFrameInfo().width;
  37765. },
  37766. measureContentHeight: function (ownerContext) {
  37767. // contentHeight includes padding, but not border, framing or margins
  37768. return ownerContext.el.getHeight() - ownerContext.getFrameInfo().height;
  37769. },
  37770. measureOwnerHeight: function (ownerContext) {
  37771. return ownerContext.el.getHeight();
  37772. },
  37773. measureOwnerWidth: function (ownerContext) {
  37774. return ownerContext.el.getWidth();
  37775. }
  37776. });
  37777. /**
  37778. * The class is the default component layout for {@link Ext.Component} when no explicit
  37779. * `{@link Ext.Component#componentLayout componentLayout}` is configured.
  37780. *
  37781. * This class uses template methods to perform the individual aspects of measurement,
  37782. * calculation and publication of results. The methods called depend on the component's
  37783. * {@link Ext.AbstractComponent#getSizeModel size model}.
  37784. *
  37785. * ## configured / calculated
  37786. *
  37787. * In either of these size models, the dimension of the outer element is of a known size.
  37788. * The size is found in the `ownerContext` (the {@link Ext.layout.ContextItem} for the owner
  37789. * component) as either "width" or "height". This value, if available, is passed to the
  37790. * `publishInnerWidth` or `publishInnerHeight` method, respectively.
  37791. *
  37792. * ## shrinkWrap
  37793. *
  37794. * When a dimension uses the `shrinkWrap` size model, that means the content is measured,
  37795. * then the outer (owner) size is calculated and published.
  37796. *
  37797. * For example, for a shrinkWrap width, the following sequence of calls are made:
  37798. *
  37799. * - `Ext.layout.component.Component#measureContentWidth`
  37800. * - `publishOwnerWidth`
  37801. * - `calculateOwnerWidthFromContentWidth`
  37802. * - `publishInnerWidth` (in the event of hitting a min/maxWidth constraint)
  37803. *
  37804. * ## natural
  37805. *
  37806. * When a dimension uses the `natural` size model, the measurement is made on the outer
  37807. * (owner) element. This size is then used to determine the content area in much the same
  37808. * way as if the outer element had a `configured` or `calculated` size model.
  37809. *
  37810. * - `Ext.layout.component.Component#measureOwnerWidth`
  37811. * - `publishInnerWidth`
  37812. *
  37813. * @protected
  37814. */
  37815. Ext.define('Ext.layout.component.Auto', {
  37816. /* Begin Definitions */
  37817. alias: 'layout.autocomponent',
  37818. extend: 'Ext.layout.component.Component',
  37819. /* End Definitions */
  37820. type: 'autocomponent',
  37821. /**
  37822. * @cfg {Boolean} [setHeightInDom=false]
  37823. * @protected
  37824. * When publishing height of an auto Component, it is usually not written to the DOM.
  37825. * Setting this to `true` overrides this behaviour.
  37826. */
  37827. setHeightInDom: false,
  37828. /**
  37829. * @cfg {Boolean} [setWidthInDom=false]
  37830. * @protected
  37831. * When publishing width of an auto Component, it is usually not written to the DOM.
  37832. * Setting this to `true` overrides this behaviour.
  37833. */
  37834. setWidthInDom: false,
  37835. waitForOuterHeightInDom: false,
  37836. waitForOuterWidthInDom: false,
  37837. beginLayoutCycle: function(ownerContext, firstCycle){
  37838. var me = this,
  37839. lastWidthModel = me.lastWidthModel,
  37840. lastHeightModel = me.lastHeightModel,
  37841. owner = me.owner;
  37842. me.callParent(arguments);
  37843. if (lastWidthModel && lastWidthModel.fixed && ownerContext.widthModel.shrinkWrap) {
  37844. owner.el.setWidth(null);
  37845. }
  37846. if (lastHeightModel && lastHeightModel.fixed && ownerContext.heightModel.shrinkWrap) {
  37847. owner.el.setHeight(null);
  37848. }
  37849. },
  37850. calculate: function(ownerContext) {
  37851. var me = this,
  37852. measurement = me.measureAutoDimensions(ownerContext),
  37853. heightModel = ownerContext.heightModel,
  37854. widthModel = ownerContext.widthModel,
  37855. width, height;
  37856. // It is generally important to process widths before heights, since widths can
  37857. // often effect heights...
  37858. if (measurement.gotWidth) {
  37859. if (widthModel.shrinkWrap) {
  37860. me.publishOwnerWidth(ownerContext, measurement.contentWidth);
  37861. } else if (me.publishInnerWidth) {
  37862. me.publishInnerWidth(ownerContext, measurement.width);
  37863. }
  37864. } else if (!widthModel.auto && me.publishInnerWidth) {
  37865. width = me.waitForOuterWidthInDom ? ownerContext.getDomProp('width')
  37866. : ownerContext.getProp('width');
  37867. if (width === undefined) {
  37868. me.done = false;
  37869. } else {
  37870. me.publishInnerWidth(ownerContext, width);
  37871. }
  37872. }
  37873. if (measurement.gotHeight) {
  37874. if (heightModel.shrinkWrap) {
  37875. me.publishOwnerHeight(ownerContext, measurement.contentHeight);
  37876. } else if (me.publishInnerHeight) {
  37877. me.publishInnerHeight(ownerContext, measurement.height);
  37878. }
  37879. } else if (!heightModel.auto && me.publishInnerHeight) {
  37880. height = me.waitForOuterHeightInDom ? ownerContext.getDomProp('height')
  37881. : ownerContext.getProp('height');
  37882. if (height === undefined) {
  37883. me.done = false;
  37884. } else {
  37885. me.publishInnerHeight(ownerContext, height);
  37886. }
  37887. }
  37888. if (!measurement.gotAll) {
  37889. me.done = false;
  37890. }
  37891. },
  37892. calculateOwnerHeightFromContentHeight: function (ownerContext, contentHeight) {
  37893. return contentHeight + ownerContext.getFrameInfo().height;
  37894. },
  37895. calculateOwnerWidthFromContentWidth: function (ownerContext, contentWidth) {
  37896. return contentWidth + ownerContext.getFrameInfo().width;
  37897. },
  37898. publishOwnerHeight: function (ownerContext, contentHeight) {
  37899. var me = this,
  37900. owner = me.owner,
  37901. height = me.calculateOwnerHeightFromContentHeight(ownerContext, contentHeight),
  37902. constrainedHeight, dirty, heightModel;
  37903. if (isNaN(height)) {
  37904. me.done = false;
  37905. } else {
  37906. constrainedHeight = Ext.Number.constrain(height, owner.minHeight, owner.maxHeight);
  37907. if (constrainedHeight == height) {
  37908. dirty = me.setHeightInDom;
  37909. } else {
  37910. heightModel = me.sizeModels[
  37911. (constrainedHeight < height) ? 'constrainedMax' : 'constrainedMin'];
  37912. height = constrainedHeight;
  37913. if (ownerContext.heightModel.calculatedFromShrinkWrap) {
  37914. // Don't bother to invalidate since that will come soon... but we need
  37915. // to signal our ownerLayout that we need an invalidate to actually
  37916. // make good on the determined (constrained) size!
  37917. ownerContext.heightModel = heightModel;
  37918. } else {
  37919. ownerContext.invalidate({ heightModel: heightModel });
  37920. }
  37921. }
  37922. ownerContext.setHeight(height, dirty);
  37923. }
  37924. },
  37925. publishOwnerWidth: function (ownerContext, contentWidth) {
  37926. var me = this,
  37927. owner = me.owner,
  37928. width = me.calculateOwnerWidthFromContentWidth(ownerContext, contentWidth),
  37929. constrainedWidth, dirty, widthModel;
  37930. if (isNaN(width)) {
  37931. me.done = false;
  37932. } else {
  37933. constrainedWidth = Ext.Number.constrain(width, owner.minWidth, owner.maxWidth);
  37934. if (constrainedWidth == width) {
  37935. dirty = me.setWidthInDom;
  37936. } else {
  37937. widthModel = me.sizeModels[
  37938. (constrainedWidth < width) ? 'constrainedMax' : 'constrainedMin'];
  37939. width = constrainedWidth;
  37940. if (ownerContext.widthModel.calculatedFromShrinkWrap) {
  37941. // Don't bother to invalidate since that will come soon... but we need
  37942. // to signal our ownerLayout that we need an invalidate to actually
  37943. // make good on the determined (constrained) size!
  37944. ownerContext.widthModel = widthModel;
  37945. } else {
  37946. ownerContext.invalidate({ widthModel: widthModel });
  37947. }
  37948. }
  37949. ownerContext.setWidth(width, dirty);
  37950. }
  37951. }
  37952. });
  37953. /**
  37954. * @class Ext.layout.container.Auto
  37955. *
  37956. * The AutoLayout is the default layout manager delegated by {@link Ext.container.Container} to
  37957. * render any child Components when no `{@link Ext.container.Container#layout layout}` is configured into
  37958. * a `{@link Ext.container.Container Container}.` AutoLayout provides only a passthrough of any layout calls
  37959. * to any child containers.
  37960. *
  37961. * @example
  37962. * Ext.create('Ext.Panel', {
  37963. * width: 500,
  37964. * height: 280,
  37965. * title: "AutoLayout Panel",
  37966. * layout: 'auto',
  37967. * renderTo: document.body,
  37968. * items: [{
  37969. * xtype: 'panel',
  37970. * title: 'Top Inner Panel',
  37971. * width: '75%',
  37972. * height: 90
  37973. * },
  37974. * {
  37975. * xtype: 'panel',
  37976. * title: 'Bottom Inner Panel',
  37977. * width: '75%',
  37978. * height: 90
  37979. * }]
  37980. * });
  37981. */
  37982. Ext.define('Ext.layout.container.Auto', {
  37983. /* Begin Definitions */
  37984. alias: ['layout.auto', 'layout.autocontainer'],
  37985. extend: 'Ext.layout.container.Container',
  37986. /* End Definitions */
  37987. type: 'autocontainer',
  37988. childEls: [
  37989. 'clearEl'
  37990. ],
  37991. renderTpl: [
  37992. '{%this.renderBody(out,values)%}',
  37993. // clear element is needed to prevent the bottom margins of the last child element from collapsing
  37994. '<div id="{ownerId}-clearEl" class="', Ext.baseCSSPrefix, 'clear" role="presentation"></div>'
  37995. ],
  37996. // TODO - do we need to clear sizes in beginLayout?
  37997. calculate: function(ownerContext) {
  37998. var me = this,
  37999. containerSize;
  38000. if (!ownerContext.hasDomProp('containerChildrenDone')) {
  38001. me.done = false;
  38002. } else {
  38003. // Once the child layouts are done we can determine the content sizes...
  38004. containerSize = me.getContainerSize(ownerContext);
  38005. if (!containerSize.gotAll) {
  38006. me.done = false;
  38007. }
  38008. me.calculateContentSize(ownerContext);
  38009. }
  38010. }
  38011. });
  38012. /**
  38013. * Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
  38014. * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the
  38015. * context of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching
  38016. * on their records. Example usage:
  38017. *
  38018. * //set up a fictional MixedCollection containing a few people to filter on
  38019. * var allNames = new Ext.util.MixedCollection();
  38020. * allNames.addAll([
  38021. * {id: 1, name: 'Ed', age: 25},
  38022. * {id: 2, name: 'Jamie', age: 37},
  38023. * {id: 3, name: 'Abe', age: 32},
  38024. * {id: 4, name: 'Aaron', age: 26},
  38025. * {id: 5, name: 'David', age: 32}
  38026. * ]);
  38027. *
  38028. * var ageFilter = new Ext.util.Filter({
  38029. * property: 'age',
  38030. * value : 32
  38031. * });
  38032. *
  38033. * var longNameFilter = new Ext.util.Filter({
  38034. * filterFn: function(item) {
  38035. * return item.name.length > 4;
  38036. * }
  38037. * });
  38038. *
  38039. * //a new MixedCollection with the 3 names longer than 4 characters
  38040. * var longNames = allNames.filter(longNameFilter);
  38041. *
  38042. * //a new MixedCollection with the 2 people of age 32:
  38043. * var youngFolk = allNames.filter(ageFilter);
  38044. *
  38045. */
  38046. Ext.define('Ext.util.Filter', {
  38047. /* Begin Definitions */
  38048. /* End Definitions */
  38049. /**
  38050. * @cfg {String} property
  38051. * The property to filter on. Required unless a {@link #filterFn} is passed
  38052. */
  38053. /**
  38054. * @cfg {Function} filterFn
  38055. * A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} in turn. Should return
  38056. * true to accept each item or false to reject it
  38057. */
  38058. /**
  38059. * @cfg {Boolean} anyMatch
  38060. * True to allow any match - no regex start/end line anchors will be added.
  38061. */
  38062. anyMatch: false,
  38063. /**
  38064. * @cfg {Boolean} exactMatch
  38065. * True to force exact match (^ and $ characters added to the regex). Ignored if anyMatch is true.
  38066. */
  38067. exactMatch: false,
  38068. /**
  38069. * @cfg {Boolean} caseSensitive
  38070. * True to make the regex case sensitive (adds 'i' switch to regex).
  38071. */
  38072. caseSensitive: false,
  38073. /**
  38074. * @cfg {String} root
  38075. * Optional root property. This is mostly useful when filtering a Store, in which case we set the root to 'data' to
  38076. * make the filter pull the {@link #property} out of the data object of each item
  38077. */
  38078. /**
  38079. * Creates new Filter.
  38080. * @param {Object} [config] Config object
  38081. */
  38082. constructor: function(config) {
  38083. var me = this;
  38084. Ext.apply(me, config);
  38085. //we're aliasing filter to filterFn mostly for API cleanliness reasons, despite the fact it dirties the code here.
  38086. //Ext.util.Sorter takes a sorterFn property but allows .sort to be called - we do the same here
  38087. me.filter = me.filter || me.filterFn;
  38088. if (me.filter === undefined) {
  38089. if (me.property === undefined || me.value === undefined) {
  38090. // Commented this out temporarily because it stops us using string ids in models. TODO: Remove this once
  38091. // Model has been updated to allow string ids
  38092. // Ext.Error.raise("A Filter requires either a property or a filterFn to be set");
  38093. } else {
  38094. me.filter = me.createFilterFn();
  38095. }
  38096. me.filterFn = me.filter;
  38097. }
  38098. },
  38099. /**
  38100. * @private
  38101. * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
  38102. */
  38103. createFilterFn: function() {
  38104. var me = this,
  38105. matcher = me.createValueMatcher(),
  38106. property = me.property;
  38107. return function(item) {
  38108. var value = me.getRoot.call(me, item)[property];
  38109. return matcher === null ? value === null : matcher.test(value);
  38110. };
  38111. },
  38112. /**
  38113. * @private
  38114. * Returns the root property of the given item, based on the configured {@link #root} property
  38115. * @param {Object} item The item
  38116. * @return {Object} The root property of the object
  38117. */
  38118. getRoot: function(item) {
  38119. var root = this.root;
  38120. return root === undefined ? item : item[root];
  38121. },
  38122. /**
  38123. * @private
  38124. * Returns a regular expression based on the given value and matching options
  38125. */
  38126. createValueMatcher : function() {
  38127. var me = this,
  38128. value = me.value,
  38129. anyMatch = me.anyMatch,
  38130. exactMatch = me.exactMatch,
  38131. caseSensitive = me.caseSensitive,
  38132. escapeRe = Ext.String.escapeRegex;
  38133. if (value === null) {
  38134. return value;
  38135. }
  38136. if (!value.exec) { // not a regex
  38137. value = String(value);
  38138. if (anyMatch === true) {
  38139. value = escapeRe(value);
  38140. } else {
  38141. value = '^' + escapeRe(value);
  38142. if (exactMatch === true) {
  38143. value += '$';
  38144. }
  38145. }
  38146. value = new RegExp(value, caseSensitive ? '' : 'i');
  38147. }
  38148. return value;
  38149. }
  38150. });
  38151. /**
  38152. * @class Ext.util.AbstractMixedCollection
  38153. * @private
  38154. */
  38155. Ext.define('Ext.util.AbstractMixedCollection', {
  38156. requires: ['Ext.util.Filter'],
  38157. mixins: {
  38158. observable: 'Ext.util.Observable'
  38159. },
  38160. /**
  38161. * @property {Boolean} isMixedCollection
  38162. * `true` in this class to identify an object as an instantiated MixedCollection, or subclass thereof.
  38163. */
  38164. isMixedCollection: true,
  38165. /**
  38166. * @private Mutation counter which is incremented upon add and remove.
  38167. */
  38168. generation: 0,
  38169. constructor: function(allowFunctions, keyFn) {
  38170. var me = this;
  38171. me.items = [];
  38172. me.map = {};
  38173. me.keys = [];
  38174. me.length = 0;
  38175. /**
  38176. * @event clear
  38177. * Fires when the collection is cleared.
  38178. */
  38179. /**
  38180. * @event add
  38181. * Fires when an item is added to the collection.
  38182. * @param {Number} index The index at which the item was added.
  38183. * @param {Object} o The item added.
  38184. * @param {String} key The key associated with the added item.
  38185. */
  38186. /**
  38187. * @event replace
  38188. * Fires when an item is replaced in the collection.
  38189. * @param {String} key he key associated with the new added.
  38190. * @param {Object} old The item being replaced.
  38191. * @param {Object} new The new item.
  38192. */
  38193. /**
  38194. * @event remove
  38195. * Fires when an item is removed from the collection.
  38196. * @param {Object} o The item being removed.
  38197. * @param {String} key (optional) The key associated with the removed item.
  38198. */
  38199. me.allowFunctions = allowFunctions === true;
  38200. if (keyFn) {
  38201. me.getKey = keyFn;
  38202. }
  38203. me.mixins.observable.constructor.call(me);
  38204. },
  38205. /**
  38206. * @cfg {Boolean} allowFunctions Specify <code>true</code> if the {@link #addAll}
  38207. * function should add function references to the collection. Defaults to
  38208. * <code>false</code>.
  38209. */
  38210. allowFunctions : false,
  38211. /**
  38212. * Adds an item to the collection. Fires the {@link #event-add} event when complete.
  38213. *
  38214. * @param {String/Object} key The key to associate with the item, or the new item.
  38215. *
  38216. * If a {@link #getKey} implementation was specified for this MixedCollection,
  38217. * or if the key of the stored items is in a property called `id`,
  38218. * the MixedCollection will be able to *derive* the key for the new item.
  38219. * In this case just pass the new item in this parameter.
  38220. *
  38221. * @param {Object} [o] The item to add.
  38222. *
  38223. * @return {Object} The item added.
  38224. */
  38225. add : function(key, obj){
  38226. var me = this,
  38227. myObj = obj,
  38228. myKey = key,
  38229. old;
  38230. if (arguments.length == 1) {
  38231. myObj = myKey;
  38232. myKey = me.getKey(myObj);
  38233. }
  38234. if (typeof myKey != 'undefined' && myKey !== null) {
  38235. old = me.map[myKey];
  38236. if (typeof old != 'undefined') {
  38237. return me.replace(myKey, myObj);
  38238. }
  38239. me.map[myKey] = myObj;
  38240. }
  38241. me.generation++;
  38242. me.length++;
  38243. me.items.push(myObj);
  38244. me.keys.push(myKey);
  38245. if (me.hasListeners.add) {
  38246. me.fireEvent('add', me.length - 1, myObj, myKey);
  38247. }
  38248. return myObj;
  38249. },
  38250. /**
  38251. * MixedCollection has a generic way to fetch keys if you implement getKey. The default implementation
  38252. * simply returns <b><code>item.id</code></b> but you can provide your own implementation
  38253. * to return a different value as in the following examples:<pre><code>
  38254. // normal way
  38255. var mc = new Ext.util.MixedCollection();
  38256. mc.add(someEl.dom.id, someEl);
  38257. mc.add(otherEl.dom.id, otherEl);
  38258. //and so on
  38259. // using getKey
  38260. var mc = new Ext.util.MixedCollection();
  38261. mc.getKey = function(el){
  38262. return el.dom.id;
  38263. };
  38264. mc.add(someEl);
  38265. mc.add(otherEl);
  38266. // or via the constructor
  38267. var mc = new Ext.util.MixedCollection(false, function(el){
  38268. return el.dom.id;
  38269. });
  38270. mc.add(someEl);
  38271. mc.add(otherEl);
  38272. * </code></pre>
  38273. * @param {Object} item The item for which to find the key.
  38274. * @return {Object} The key for the passed item.
  38275. */
  38276. getKey : function(o){
  38277. return o.id;
  38278. },
  38279. /**
  38280. * Replaces an item in the collection. Fires the {@link #event-replace} event when complete.
  38281. * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
  38282. * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
  38283. * of your stored items is in a property called <code><b>id</b></code>, then the MixedCollection
  38284. * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
  38285. * with one having the same key value, then just pass the replacement item in this parameter.</p>
  38286. * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
  38287. * with that key.
  38288. * @return {Object} The new item.
  38289. */
  38290. replace : function(key, o){
  38291. var me = this,
  38292. old,
  38293. index;
  38294. if (arguments.length == 1) {
  38295. o = arguments[0];
  38296. key = me.getKey(o);
  38297. }
  38298. old = me.map[key];
  38299. if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
  38300. return me.add(key, o);
  38301. }
  38302. me.generation++;
  38303. index = me.indexOfKey(key);
  38304. me.items[index] = o;
  38305. me.map[key] = o;
  38306. if (me.hasListeners.replace) {
  38307. me.fireEvent('replace', key, old, o);
  38308. }
  38309. return o;
  38310. },
  38311. /**
  38312. * Adds all elements of an Array or an Object to the collection.
  38313. * @param {Object/Array} objs An Object containing properties which will be added
  38314. * to the collection, or an Array of values, each of which are added to the collection.
  38315. * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
  38316. * has been set to <code>true</code>.
  38317. */
  38318. addAll : function(objs){
  38319. var me = this,
  38320. i = 0,
  38321. args,
  38322. len,
  38323. key;
  38324. if (arguments.length > 1 || Ext.isArray(objs)) {
  38325. args = arguments.length > 1 ? arguments : objs;
  38326. for (len = args.length; i < len; i++) {
  38327. me.add(args[i]);
  38328. }
  38329. } else {
  38330. for (key in objs) {
  38331. if (objs.hasOwnProperty(key)) {
  38332. if (me.allowFunctions || typeof objs[key] != 'function') {
  38333. me.add(key, objs[key]);
  38334. }
  38335. }
  38336. }
  38337. }
  38338. },
  38339. /**
  38340. * Executes the specified function once for every item in the collection.
  38341. * The function should return a boolean value.
  38342. * Returning false from the function will stop the iteration.
  38343. *
  38344. * @param {Function} fn The function to execute for each item.
  38345. * @param {Mixed} fn.item The collection item.
  38346. * @param {Number} fn.index The index of item.
  38347. * @param {Number} fn.len Total length of collection.
  38348. * @param {Object} scope (optional) The scope (<code>this</code> reference)
  38349. * in which the function is executed. Defaults to the current item in the iteration.
  38350. */
  38351. each : function(fn, scope){
  38352. var items = [].concat(this.items), // each safe for removal
  38353. i = 0,
  38354. len = items.length,
  38355. item;
  38356. for (; i < len; i++) {
  38357. item = items[i];
  38358. if (fn.call(scope || item, item, i, len) === false) {
  38359. break;
  38360. }
  38361. }
  38362. },
  38363. /**
  38364. * Executes the specified function once for every key in the collection, passing each
  38365. * key, and its associated item as the first two parameters.
  38366. * @param {Function} fn The function to execute for each item.
  38367. * @param {String} fn.key The key of collection item.
  38368. * @param {Mixed} fn.item The collection item.
  38369. * @param {Number} fn.index The index of item.
  38370. * @param {Number} fn.len Total length of collection.
  38371. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the
  38372. * function is executed. Defaults to the browser window.
  38373. */
  38374. eachKey : function(fn, scope){
  38375. var keys = this.keys,
  38376. items = this.items,
  38377. i = 0,
  38378. len = keys.length;
  38379. for (; i < len; i++) {
  38380. fn.call(scope || window, keys[i], items[i], i, len);
  38381. }
  38382. },
  38383. /**
  38384. * Returns the first item in the collection which elicits a true return value from the
  38385. * passed selection function.
  38386. * @param {Function} fn The selection function to execute for each item.
  38387. * @param {Mixed} fn.item The collection item.
  38388. * @param {String} fn.key The key of collection item.
  38389. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the
  38390. * function is executed. Defaults to the browser window.
  38391. * @return {Object} The first item in the collection which returned true from the selection
  38392. * function, or null if none was found.
  38393. */
  38394. findBy : function(fn, scope) {
  38395. var keys = this.keys,
  38396. items = this.items,
  38397. i = 0,
  38398. len = items.length;
  38399. for (; i < len; i++) {
  38400. if (fn.call(scope || window, items[i], keys[i])) {
  38401. return items[i];
  38402. }
  38403. }
  38404. return null;
  38405. },
  38406. find : function() {
  38407. if (Ext.isDefined(Ext.global.console)) {
  38408. Ext.global.console.warn('Ext.util.MixedCollection: find has been deprecated. Use findBy instead.');
  38409. }
  38410. return this.findBy.apply(this, arguments);
  38411. },
  38412. /**
  38413. * Inserts an item at the specified index in the collection. Fires the {@link #event-add} event when complete.
  38414. * @param {Number} index The index to insert the item at.
  38415. * @param {String} key The key to associate with the new item, or the item itself.
  38416. * @param {Object} o (optional) If the second parameter was a key, the new item.
  38417. * @return {Object} The item inserted.
  38418. */
  38419. insert : function(index, key, obj){
  38420. var me = this,
  38421. myKey = key,
  38422. myObj = obj;
  38423. if (arguments.length == 2) {
  38424. myObj = myKey;
  38425. myKey = me.getKey(myObj);
  38426. }
  38427. if (me.containsKey(myKey)) {
  38428. me.suspendEvents();
  38429. me.removeAtKey(myKey);
  38430. me.resumeEvents();
  38431. }
  38432. if (index >= me.length) {
  38433. return me.add(myKey, myObj);
  38434. }
  38435. me.generation++;
  38436. me.length++;
  38437. Ext.Array.splice(me.items, index, 0, myObj);
  38438. if (typeof myKey != 'undefined' && myKey !== null) {
  38439. me.map[myKey] = myObj;
  38440. }
  38441. Ext.Array.splice(me.keys, index, 0, myKey);
  38442. if (me.hasListeners.add) {
  38443. me.fireEvent('add', index, myObj, myKey);
  38444. }
  38445. return myObj;
  38446. },
  38447. /**
  38448. * Remove an item from the collection.
  38449. * @param {Object} o The item to remove.
  38450. * @return {Object} The item removed or false if no item was removed.
  38451. */
  38452. remove : function(o) {
  38453. this.generation++;
  38454. return this.removeAt(this.indexOf(o));
  38455. },
  38456. /**
  38457. * Remove all items in the passed array from the collection.
  38458. * @param {Array} items An array of items to be removed.
  38459. * @return {Ext.util.MixedCollection} this object
  38460. */
  38461. removeAll : function(items) {
  38462. items = [].concat(items);
  38463. var i, iLen = items.length;
  38464. for (i = 0; i < iLen; i++) {
  38465. this.remove(items[i]);
  38466. }
  38467. return this;
  38468. },
  38469. /**
  38470. * Remove an item from a specified index in the collection. Fires the {@link #event-remove} event when complete.
  38471. * @param {Number} index The index within the collection of the item to remove.
  38472. * @return {Object} The item removed or false if no item was removed.
  38473. */
  38474. removeAt : function(index) {
  38475. var me = this,
  38476. o,
  38477. key;
  38478. if (index < me.length && index >= 0) {
  38479. me.length--;
  38480. o = me.items[index];
  38481. Ext.Array.erase(me.items, index, 1);
  38482. key = me.keys[index];
  38483. if (typeof key != 'undefined') {
  38484. delete me.map[key];
  38485. }
  38486. Ext.Array.erase(me.keys, index, 1);
  38487. if (me.hasListeners.remove) {
  38488. me.fireEvent('remove', o, key);
  38489. }
  38490. me.generation++;
  38491. return o;
  38492. }
  38493. return false;
  38494. },
  38495. /**
  38496. * Removed an item associated with the passed key fom the collection.
  38497. * @param {String} key The key of the item to remove.
  38498. * @return {Object} The item removed or false if no item was removed.
  38499. */
  38500. removeAtKey : function(key){
  38501. return this.removeAt(this.indexOfKey(key));
  38502. },
  38503. /**
  38504. * Returns the number of items in the collection.
  38505. * @return {Number} the number of items in the collection.
  38506. */
  38507. getCount : function(){
  38508. return this.length;
  38509. },
  38510. /**
  38511. * Returns index within the collection of the passed Object.
  38512. * @param {Object} o The item to find the index of.
  38513. * @return {Number} index of the item. Returns -1 if not found.
  38514. */
  38515. indexOf : function(o){
  38516. return Ext.Array.indexOf(this.items, o);
  38517. },
  38518. /**
  38519. * Returns index within the collection of the passed key.
  38520. * @param {String} key The key to find the index of.
  38521. * @return {Number} index of the key.
  38522. */
  38523. indexOfKey : function(key){
  38524. return Ext.Array.indexOf(this.keys, key);
  38525. },
  38526. /**
  38527. * Returns the item associated with the passed key OR index.
  38528. * Key has priority over index. This is the equivalent
  38529. * of calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
  38530. * @param {String/Number} key The key or index of the item.
  38531. * @return {Object} If the item is found, returns the item. If the item was not found, returns <code>undefined</code>.
  38532. * If an item was found, but is a Class, returns <code>null</code>.
  38533. */
  38534. get : function(key) {
  38535. var me = this,
  38536. mk = me.map[key],
  38537. item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
  38538. return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
  38539. },
  38540. /**
  38541. * Returns the item at the specified index.
  38542. * @param {Number} index The index of the item.
  38543. * @return {Object} The item at the specified index.
  38544. */
  38545. getAt : function(index) {
  38546. return this.items[index];
  38547. },
  38548. /**
  38549. * Returns the item associated with the passed key.
  38550. * @param {String/Number} key The key of the item.
  38551. * @return {Object} The item associated with the passed key.
  38552. */
  38553. getByKey : function(key) {
  38554. return this.map[key];
  38555. },
  38556. /**
  38557. * Returns true if the collection contains the passed Object as an item.
  38558. * @param {Object} o The Object to look for in the collection.
  38559. * @return {Boolean} True if the collection contains the Object as an item.
  38560. */
  38561. contains : function(o){
  38562. return typeof this.map[this.getKey(o)] != 'undefined';
  38563. },
  38564. /**
  38565. * Returns true if the collection contains the passed Object as a key.
  38566. * @param {String} key The key to look for in the collection.
  38567. * @return {Boolean} True if the collection contains the Object as a key.
  38568. */
  38569. containsKey : function(key){
  38570. return typeof this.map[key] != 'undefined';
  38571. },
  38572. /**
  38573. * Removes all items from the collection. Fires the {@link #event-clear} event when complete.
  38574. */
  38575. clear : function(){
  38576. var me = this;
  38577. me.length = 0;
  38578. me.items = [];
  38579. me.keys = [];
  38580. me.map = {};
  38581. me.generation++;
  38582. if (me.hasListeners.clear) {
  38583. me.fireEvent('clear');
  38584. }
  38585. },
  38586. /**
  38587. * Returns the first item in the collection.
  38588. * @return {Object} the first item in the collection..
  38589. */
  38590. first : function() {
  38591. return this.items[0];
  38592. },
  38593. /**
  38594. * Returns the last item in the collection.
  38595. * @return {Object} the last item in the collection..
  38596. */
  38597. last : function() {
  38598. return this.items[this.length - 1];
  38599. },
  38600. /**
  38601. * Collects all of the values of the given property and returns their sum
  38602. * @param {String} property The property to sum by
  38603. * @param {String} [root] 'root' property to extract the first argument from. This is used mainly when
  38604. * summing fields in records, where the fields are all stored inside the 'data' object
  38605. * @param {Number} [start=0] The record index to start at
  38606. * @param {Number} [end=-1] The record index to end at
  38607. * @return {Number} The total
  38608. */
  38609. sum: function(property, root, start, end) {
  38610. var values = this.extractValues(property, root),
  38611. length = values.length,
  38612. sum = 0,
  38613. i;
  38614. start = start || 0;
  38615. end = (end || end === 0) ? end : length - 1;
  38616. for (i = start; i <= end; i++) {
  38617. sum += values[i];
  38618. }
  38619. return sum;
  38620. },
  38621. /**
  38622. * Collects unique values of a particular property in this MixedCollection
  38623. * @param {String} property The property to collect on
  38624. * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
  38625. * summing fields in records, where the fields are all stored inside the 'data' object
  38626. * @param {Boolean} allowBlank (optional) Pass true to allow null, undefined or empty string values
  38627. * @return {Array} The unique values
  38628. */
  38629. collect: function(property, root, allowNull) {
  38630. var values = this.extractValues(property, root),
  38631. length = values.length,
  38632. hits = {},
  38633. unique = [],
  38634. value, strValue, i;
  38635. for (i = 0; i < length; i++) {
  38636. value = values[i];
  38637. strValue = String(value);
  38638. if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
  38639. hits[strValue] = true;
  38640. unique.push(value);
  38641. }
  38642. }
  38643. return unique;
  38644. },
  38645. /**
  38646. * @private
  38647. * Extracts all of the given property values from the items in the MC. Mainly used as a supporting method for
  38648. * functions like sum and collect.
  38649. * @param {String} property The property to extract
  38650. * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
  38651. * extracting field data from Model instances, where the fields are stored inside the 'data' object
  38652. * @return {Array} The extracted values
  38653. */
  38654. extractValues: function(property, root) {
  38655. var values = this.items;
  38656. if (root) {
  38657. values = Ext.Array.pluck(values, root);
  38658. }
  38659. return Ext.Array.pluck(values, property);
  38660. },
  38661. /**
  38662. * Returns a range of items in this collection
  38663. * @param {Number} startIndex (optional) The starting index. Defaults to 0.
  38664. * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
  38665. * @return {Array} An array of items
  38666. */
  38667. getRange : function(start, end){
  38668. var me = this,
  38669. items = me.items,
  38670. range = [],
  38671. i;
  38672. if (items.length < 1) {
  38673. return range;
  38674. }
  38675. start = start || 0;
  38676. end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
  38677. if (start <= end) {
  38678. for (i = start; i <= end; i++) {
  38679. range[range.length] = items[i];
  38680. }
  38681. } else {
  38682. for (i = start; i >= end; i--) {
  38683. range[range.length] = items[i];
  38684. }
  38685. }
  38686. return range;
  38687. },
  38688. /**
  38689. * <p>Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
  38690. * property/value pair with optional parameters for substring matching and case sensitivity. See
  38691. * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
  38692. * MixedCollection can be easily filtered by property like this:</p>
  38693. <pre><code>
  38694. //create a simple store with a few people defined
  38695. var people = new Ext.util.MixedCollection();
  38696. people.addAll([
  38697. {id: 1, age: 25, name: 'Ed'},
  38698. {id: 2, age: 24, name: 'Tommy'},
  38699. {id: 3, age: 24, name: 'Arne'},
  38700. {id: 4, age: 26, name: 'Aaron'}
  38701. ]);
  38702. //a new MixedCollection containing only the items where age == 24
  38703. var middleAged = people.filter('age', 24);
  38704. </code></pre>
  38705. *
  38706. *
  38707. * @param {Ext.util.Filter[]/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
  38708. * @param {String/RegExp} value Either string that the property values
  38709. * should start with or a RegExp to test against the property
  38710. * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
  38711. * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
  38712. * @return {Ext.util.MixedCollection} The new filtered collection
  38713. */
  38714. filter : function(property, value, anyMatch, caseSensitive) {
  38715. var filters = [],
  38716. filterFn;
  38717. //support for the simple case of filtering by property/value
  38718. if (Ext.isString(property)) {
  38719. filters.push(new Ext.util.Filter({
  38720. property : property,
  38721. value : value,
  38722. anyMatch : anyMatch,
  38723. caseSensitive: caseSensitive
  38724. }));
  38725. } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
  38726. filters = filters.concat(property);
  38727. }
  38728. //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
  38729. //so here we construct a function that combines these filters by ANDing them together
  38730. filterFn = function(record) {
  38731. var isMatch = true,
  38732. length = filters.length,
  38733. i,
  38734. filter,
  38735. fn,
  38736. scope;
  38737. for (i = 0; i < length; i++) {
  38738. filter = filters[i];
  38739. fn = filter.filterFn;
  38740. scope = filter.scope;
  38741. isMatch = isMatch && fn.call(scope, record);
  38742. }
  38743. return isMatch;
  38744. };
  38745. return this.filterBy(filterFn);
  38746. },
  38747. /**
  38748. * Filter by a function. Returns a <i>new</i> collection that has been filtered.
  38749. * The passed function will be called with each object in the collection.
  38750. * If the function returns true, the value is included otherwise it is filtered.
  38751. * @param {Function} fn The function to be called.
  38752. * @param {Mixed} fn.item The collection item.
  38753. * @param {String} fn.key The key of collection item.
  38754. * @param {Object} scope (optional) The scope (<code>this</code> reference) in
  38755. * which the function is executed. Defaults to this MixedCollection.
  38756. * @return {Ext.util.MixedCollection} The new filtered collection
  38757. */
  38758. filterBy : function(fn, scope) {
  38759. var me = this,
  38760. newMC = new this.self(),
  38761. keys = me.keys,
  38762. items = me.items,
  38763. length = items.length,
  38764. i;
  38765. newMC.getKey = me.getKey;
  38766. for (i = 0; i < length; i++) {
  38767. if (fn.call(scope || me, items[i], keys[i])) {
  38768. newMC.add(keys[i], items[i]);
  38769. }
  38770. }
  38771. return newMC;
  38772. },
  38773. /**
  38774. * Finds the index of the first matching object in this collection by a specific property/value.
  38775. * @param {String} property The name of a property on your objects.
  38776. * @param {String/RegExp} value A string that the property values
  38777. * should start with or a RegExp to test against the property.
  38778. * @param {Number} [start=0] The index to start searching at.
  38779. * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning.
  38780. * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
  38781. * @return {Number} The matched index or -1
  38782. */
  38783. findIndex : function(property, value, start, anyMatch, caseSensitive){
  38784. if(Ext.isEmpty(value, false)){
  38785. return -1;
  38786. }
  38787. value = this.createValueMatcher(value, anyMatch, caseSensitive);
  38788. return this.findIndexBy(function(o){
  38789. return o && value.test(o[property]);
  38790. }, null, start);
  38791. },
  38792. /**
  38793. * Find the index of the first matching object in this collection by a function.
  38794. * If the function returns <i>true</i> it is considered a match.
  38795. * @param {Function} fn The function to be called.
  38796. * @param {Mixed} fn.item The collection item.
  38797. * @param {String} fn.key The key of collection item.
  38798. * @param {Object} [scope] The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
  38799. * @param {Number} [start=0] The index to start searching at.
  38800. * @return {Number} The matched index or -1
  38801. */
  38802. findIndexBy : function(fn, scope, start){
  38803. var me = this,
  38804. keys = me.keys,
  38805. items = me.items,
  38806. i = start || 0,
  38807. len = items.length;
  38808. for (; i < len; i++) {
  38809. if (fn.call(scope || me, items[i], keys[i])) {
  38810. return i;
  38811. }
  38812. }
  38813. return -1;
  38814. },
  38815. /**
  38816. * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
  38817. * and by Ext.data.Store#filter
  38818. * @private
  38819. * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
  38820. * @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
  38821. * @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
  38822. * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
  38823. */
  38824. createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
  38825. if (!value.exec) { // not a regex
  38826. var er = Ext.String.escapeRegex;
  38827. value = String(value);
  38828. if (anyMatch === true) {
  38829. value = er(value);
  38830. } else {
  38831. value = '^' + er(value);
  38832. if (exactMatch === true) {
  38833. value += '$';
  38834. }
  38835. }
  38836. value = new RegExp(value, caseSensitive ? '' : 'i');
  38837. }
  38838. return value;
  38839. },
  38840. /**
  38841. * Creates a shallow copy of this collection
  38842. * @return {Ext.util.MixedCollection}
  38843. */
  38844. clone : function() {
  38845. var me = this,
  38846. copy = new this.self(),
  38847. keys = me.keys,
  38848. items = me.items,
  38849. i = 0,
  38850. len = items.length;
  38851. for(; i < len; i++){
  38852. copy.add(keys[i], items[i]);
  38853. }
  38854. copy.getKey = me.getKey;
  38855. return copy;
  38856. }
  38857. });
  38858. /**
  38859. * Represents a single sorter that can be applied to a Store. The sorter is used
  38860. * to compare two values against each other for the purpose of ordering them. Ordering
  38861. * is achieved by specifying either:
  38862. *
  38863. * - {@link #property A sorting property}
  38864. * - {@link #sorterFn A sorting function}
  38865. *
  38866. * As a contrived example, we can specify a custom sorter that sorts by rank:
  38867. *
  38868. * Ext.define('Person', {
  38869. * extend: 'Ext.data.Model',
  38870. * fields: ['name', 'rank']
  38871. * });
  38872. *
  38873. * Ext.create('Ext.data.Store', {
  38874. * model: 'Person',
  38875. * proxy: 'memory',
  38876. * sorters: [{
  38877. * sorterFn: function(o1, o2){
  38878. * var getRank = function(o){
  38879. * var name = o.get('rank');
  38880. * if (name === 'first') {
  38881. * return 1;
  38882. * } else if (name === 'second') {
  38883. * return 2;
  38884. * } else {
  38885. * return 3;
  38886. * }
  38887. * },
  38888. * rank1 = getRank(o1),
  38889. * rank2 = getRank(o2);
  38890. *
  38891. * if (rank1 === rank2) {
  38892. * return 0;
  38893. * }
  38894. *
  38895. * return rank1 < rank2 ? -1 : 1;
  38896. * }
  38897. * }],
  38898. * data: [{
  38899. * name: 'Person1',
  38900. * rank: 'second'
  38901. * }, {
  38902. * name: 'Person2',
  38903. * rank: 'third'
  38904. * }, {
  38905. * name: 'Person3',
  38906. * rank: 'first'
  38907. * }]
  38908. * });
  38909. */
  38910. Ext.define('Ext.util.Sorter', {
  38911. /**
  38912. * @cfg {String} property
  38913. * The property to sort by. Required unless {@link #sorterFn} is provided. The property is extracted from the object
  38914. * directly and compared for sorting using the built in comparison operators.
  38915. */
  38916. /**
  38917. * @cfg {Function} sorterFn
  38918. * A specific sorter function to execute. Can be passed instead of {@link #property}. This sorter function allows
  38919. * for any kind of custom/complex comparisons. The sorterFn receives two arguments, the objects being compared. The
  38920. * function should return:
  38921. *
  38922. * - -1 if o1 is "less than" o2
  38923. * - 0 if o1 is "equal" to o2
  38924. * - 1 if o1 is "greater than" o2
  38925. */
  38926. /**
  38927. * @cfg {String} root
  38928. * Optional root property. This is mostly useful when sorting a Store, in which case we set the root to 'data' to
  38929. * make the filter pull the {@link #property} out of the data object of each item
  38930. */
  38931. /**
  38932. * @cfg {Function} transform
  38933. * A function that will be run on each value before it is compared in the sorter. The function will receive a single
  38934. * argument, the value.
  38935. */
  38936. /**
  38937. * @cfg {String} direction
  38938. * The direction to sort by.
  38939. */
  38940. direction: "ASC",
  38941. constructor: function(config) {
  38942. var me = this;
  38943. Ext.apply(me, config);
  38944. if (me.property === undefined && me.sorterFn === undefined) {
  38945. Ext.Error.raise("A Sorter requires either a property or a sorter function");
  38946. }
  38947. me.updateSortFunction();
  38948. },
  38949. /**
  38950. * @private
  38951. * Creates and returns a function which sorts an array by the given property and direction
  38952. * @return {Function} A function which sorts by the property/direction combination provided
  38953. */
  38954. createSortFunction: function(sorterFn) {
  38955. var me = this,
  38956. property = me.property,
  38957. direction = me.direction || "ASC",
  38958. modifier = direction.toUpperCase() == "DESC" ? -1 : 1;
  38959. //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
  38960. //-1 if object 2 is greater or 0 if they are equal
  38961. return function(o1, o2) {
  38962. return modifier * sorterFn.call(me, o1, o2);
  38963. };
  38964. },
  38965. /**
  38966. * @private
  38967. * Basic default sorter function that just compares the defined property of each object
  38968. */
  38969. defaultSorterFn: function(o1, o2) {
  38970. var me = this,
  38971. transform = me.transform,
  38972. v1 = me.getRoot(o1)[me.property],
  38973. v2 = me.getRoot(o2)[me.property];
  38974. if (transform) {
  38975. v1 = transform(v1);
  38976. v2 = transform(v2);
  38977. }
  38978. return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
  38979. },
  38980. /**
  38981. * @private
  38982. * Returns the root property of the given item, based on the configured {@link #root} property
  38983. * @param {Object} item The item
  38984. * @return {Object} The root property of the object
  38985. */
  38986. getRoot: function(item) {
  38987. return this.root === undefined ? item : item[this.root];
  38988. },
  38989. /**
  38990. * Set the sorting direction for this sorter.
  38991. * @param {String} direction The direction to sort in. Should be either 'ASC' or 'DESC'.
  38992. */
  38993. setDirection: function(direction) {
  38994. var me = this;
  38995. me.direction = direction ? direction.toUpperCase() : direction;
  38996. me.updateSortFunction();
  38997. },
  38998. /**
  38999. * Toggles the sorting direction for this sorter.
  39000. */
  39001. toggle: function() {
  39002. var me = this;
  39003. me.direction = Ext.String.toggle(me.direction, "ASC", "DESC");
  39004. me.updateSortFunction();
  39005. },
  39006. /**
  39007. * Update the sort function for this sorter.
  39008. * @param {Function} [fn] A new sorter function for this sorter. If not specified it will use the default
  39009. * sorting function.
  39010. */
  39011. updateSortFunction: function(fn) {
  39012. var me = this;
  39013. fn = fn || me.sorterFn || me.defaultSorterFn;
  39014. me.sort = me.createSortFunction(fn);
  39015. }
  39016. });
  39017. /**
  39018. * @docauthor Tommy Maintz <tommy@sencha.com>
  39019. *
  39020. * A mixin which allows a data component to be sorted. This is used by e.g. {@link Ext.data.Store} and {@link Ext.data.TreeStore}.
  39021. *
  39022. * **NOTE**: This mixin is mainly for internal use and most users should not need to use it directly. It
  39023. * is more likely you will want to use one of the component classes that import this mixin, such as
  39024. * {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
  39025. */
  39026. Ext.define("Ext.util.Sortable", {
  39027. /**
  39028. * @property {Boolean} isSortable
  39029. * `true` in this class to identify an object as an instantiated Sortable, or subclass thereof.
  39030. */
  39031. isSortable: true,
  39032. /**
  39033. * @property {String} defaultSortDirection
  39034. * The default sort direction to use if one is not specified.
  39035. */
  39036. defaultSortDirection: "ASC",
  39037. requires: [
  39038. 'Ext.util.Sorter'
  39039. ],
  39040. /**
  39041. * @property {String} sortRoot
  39042. * The property in each item that contains the data to sort.
  39043. */
  39044. /**
  39045. * Performs initialization of this mixin. Component classes using this mixin should call this method during their
  39046. * own initialization.
  39047. */
  39048. initSortable: function() {
  39049. var me = this,
  39050. sorters = me.sorters;
  39051. /**
  39052. * @property {Ext.util.MixedCollection} sorters
  39053. * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
  39054. */
  39055. me.sorters = new Ext.util.AbstractMixedCollection(false, function(item) {
  39056. return item.id || item.property;
  39057. });
  39058. if (sorters) {
  39059. me.sorters.addAll(me.decodeSorters(sorters));
  39060. }
  39061. },
  39062. /**
  39063. * Sorts the data in the Store by one or more of its properties. Example usage:
  39064. *
  39065. * //sort by a single field
  39066. * myStore.sort('myField', 'DESC');
  39067. *
  39068. * //sorting by multiple fields
  39069. * myStore.sort([
  39070. * {
  39071. * property : 'age',
  39072. * direction: 'ASC'
  39073. * },
  39074. * {
  39075. * property : 'name',
  39076. * direction: 'DESC'
  39077. * }
  39078. * ]);
  39079. *
  39080. * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
  39081. * the actual sorting to its internal {@link Ext.util.MixedCollection}.
  39082. *
  39083. * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
  39084. *
  39085. * store.sort('myField');
  39086. * store.sort('myField');
  39087. *
  39088. * Is equivalent to this code, because Store handles the toggling automatically:
  39089. *
  39090. * store.sort('myField', 'ASC');
  39091. * store.sort('myField', 'DESC');
  39092. *
  39093. * @param {String/Ext.util.Sorter[]} [sorters] Either a string name of one of the fields in this Store's configured
  39094. * {@link Ext.data.Model Model}, or an array of sorter configurations.
  39095. * @param {String} [direction="ASC"] The overall direction to sort the data by.
  39096. * @return {Ext.util.Sorter[]}
  39097. */
  39098. sort: function(sorters, direction, where, doSort) {
  39099. var me = this,
  39100. sorter, sorterFn,
  39101. newSorters;
  39102. if (Ext.isArray(sorters)) {
  39103. doSort = where;
  39104. where = direction;
  39105. newSorters = sorters;
  39106. }
  39107. else if (Ext.isObject(sorters)) {
  39108. doSort = where;
  39109. where = direction;
  39110. newSorters = [sorters];
  39111. }
  39112. else if (Ext.isString(sorters)) {
  39113. sorter = me.sorters.get(sorters);
  39114. if (!sorter) {
  39115. sorter = {
  39116. property : sorters,
  39117. direction: direction
  39118. };
  39119. newSorters = [sorter];
  39120. }
  39121. else if (direction === undefined) {
  39122. sorter.toggle();
  39123. }
  39124. else {
  39125. sorter.setDirection(direction);
  39126. }
  39127. }
  39128. if (newSorters && newSorters.length) {
  39129. newSorters = me.decodeSorters(newSorters);
  39130. if (Ext.isString(where)) {
  39131. if (where === 'prepend') {
  39132. sorters = me.sorters.clone().items;
  39133. me.sorters.clear();
  39134. me.sorters.addAll(newSorters);
  39135. me.sorters.addAll(sorters);
  39136. }
  39137. else {
  39138. me.sorters.addAll(newSorters);
  39139. }
  39140. }
  39141. else {
  39142. me.sorters.clear();
  39143. me.sorters.addAll(newSorters);
  39144. }
  39145. }
  39146. if (doSort !== false) {
  39147. me.onBeforeSort(newSorters);
  39148. sorters = me.sorters.items;
  39149. if (sorters.length) {
  39150. // Sort using a generated sorter function which combines all of the Sorters passed
  39151. me.doSort(me.generateComparator());
  39152. }
  39153. }
  39154. return sorters;
  39155. },
  39156. /**
  39157. * <p>Returns a comparator function which compares two items and returns -1, 0, or 1 depending
  39158. * on the currently defined set of {@link #sorters}.</p>
  39159. * <p>If there are no {@link #sorters} defined, it returns a function which returns <code>0</code> meaning that no sorting will occur.</p>
  39160. */
  39161. generateComparator: function() {
  39162. var sorters = this.sorters.getRange();
  39163. return sorters.length ? this.createComparator(sorters) : this.emptyComparator;
  39164. },
  39165. createComparator: function(sorters) {
  39166. return function(r1, r2) {
  39167. var result = sorters[0].sort(r1, r2),
  39168. length = sorters.length,
  39169. i = 1;
  39170. // if we have more than one sorter, OR any additional sorter functions together
  39171. for (; i < length; i++) {
  39172. result = result || sorters[i].sort.call(this, r1, r2);
  39173. }
  39174. return result;
  39175. };
  39176. },
  39177. emptyComparator: function(){
  39178. return 0;
  39179. },
  39180. onBeforeSort: Ext.emptyFn,
  39181. /**
  39182. * @private
  39183. * Normalizes an array of sorter objects, ensuring that they are all Ext.util.Sorter instances
  39184. * @param {Object[]} sorters The sorters array
  39185. * @return {Ext.util.Sorter[]} Array of Ext.util.Sorter objects
  39186. */
  39187. decodeSorters: function(sorters) {
  39188. if (!Ext.isArray(sorters)) {
  39189. if (sorters === undefined) {
  39190. sorters = [];
  39191. } else {
  39192. sorters = [sorters];
  39193. }
  39194. }
  39195. var length = sorters.length,
  39196. Sorter = Ext.util.Sorter,
  39197. fields = this.model ? this.model.prototype.fields : null,
  39198. field,
  39199. config, i;
  39200. for (i = 0; i < length; i++) {
  39201. config = sorters[i];
  39202. if (!(config instanceof Sorter)) {
  39203. if (Ext.isString(config)) {
  39204. config = {
  39205. property: config
  39206. };
  39207. }
  39208. Ext.applyIf(config, {
  39209. root : this.sortRoot,
  39210. direction: "ASC"
  39211. });
  39212. //support for 3.x style sorters where a function can be defined as 'fn'
  39213. if (config.fn) {
  39214. config.sorterFn = config.fn;
  39215. }
  39216. //support a function to be passed as a sorter definition
  39217. if (typeof config == 'function') {
  39218. config = {
  39219. sorterFn: config
  39220. };
  39221. }
  39222. // ensure sortType gets pushed on if necessary
  39223. if (fields && !config.transform) {
  39224. field = fields.get(config.property);
  39225. config.transform = field ? field.sortType : undefined;
  39226. }
  39227. sorters[i] = new Ext.util.Sorter(config);
  39228. }
  39229. }
  39230. return sorters;
  39231. },
  39232. getSorters: function() {
  39233. return this.sorters.items;
  39234. },
  39235. /**
  39236. * Gets the first sorter from the sorters collection, excluding
  39237. * any groupers that may be in place
  39238. * @protected
  39239. * @return {Ext.util.Sorter} The sorter, null if none exist
  39240. */
  39241. getFirstSorter: function(){
  39242. var sorters = this.sorters.items,
  39243. len = sorters.length,
  39244. i = 0,
  39245. sorter;
  39246. for (; i < len; ++i) {
  39247. sorter = sorters[i];
  39248. if (!sorter.isGrouper) {
  39249. return sorter;
  39250. }
  39251. }
  39252. return null;
  39253. }
  39254. });
  39255. /**
  39256. * @class Ext.util.MixedCollection
  39257. * <p>
  39258. * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
  39259. * must be unique, the same key cannot exist twice. This collection is ordered, items in the
  39260. * collection can be accessed by index or via the key. Newly added items are added to
  39261. * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
  39262. * is heavier and provides more functionality. Sample usage:
  39263. * <pre><code>
  39264. var coll = new Ext.util.MixedCollection();
  39265. coll.add('key1', 'val1');
  39266. coll.add('key2', 'val2');
  39267. coll.add('key3', 'val3');
  39268. console.log(coll.get('key1')); // prints 'val1'
  39269. console.log(coll.indexOfKey('key3')); // prints 2
  39270. * </code></pre>
  39271. *
  39272. * <p>
  39273. * The MixedCollection also has support for sorting and filtering of the values in the collection.
  39274. * <pre><code>
  39275. var coll = new Ext.util.MixedCollection();
  39276. coll.add('key1', 100);
  39277. coll.add('key2', -100);
  39278. coll.add('key3', 17);
  39279. coll.add('key4', 0);
  39280. var biggerThanZero = coll.filterBy(function(value){
  39281. return value > 0;
  39282. });
  39283. console.log(biggerThanZero.getCount()); // prints 2
  39284. * </code></pre>
  39285. * </p>
  39286. */
  39287. Ext.define('Ext.util.MixedCollection', {
  39288. extend: 'Ext.util.AbstractMixedCollection',
  39289. mixins: {
  39290. sortable: 'Ext.util.Sortable'
  39291. },
  39292. /**
  39293. * Creates new MixedCollection.
  39294. * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
  39295. * function should add function references to the collection. Defaults to
  39296. * <tt>false</tt>.
  39297. * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
  39298. * and return the key value for that item. This is used when available to look up the key on items that
  39299. * were passed without an explicit key parameter to a MixedCollection method. Passing this parameter is
  39300. * equivalent to providing an implementation for the {@link #getKey} method.
  39301. */
  39302. constructor: function() {
  39303. var me = this;
  39304. me.callParent(arguments);
  39305. me.addEvents('sort');
  39306. me.mixins.sortable.initSortable.call(me);
  39307. },
  39308. doSort: function(sorterFn) {
  39309. this.sortBy(sorterFn);
  39310. },
  39311. /**
  39312. * @private
  39313. * Performs the actual sorting based on a direction and a sorting function. Internally,
  39314. * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
  39315. * the sorted array data back into this.items and this.keys
  39316. * @param {String} property Property to sort by ('key', 'value', or 'index')
  39317. * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
  39318. * @param {Function} fn (optional) Comparison function that defines the sort order.
  39319. * Defaults to sorting by numeric value.
  39320. */
  39321. _sort : function(property, dir, fn){
  39322. var me = this,
  39323. i, len,
  39324. dsc = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
  39325. //this is a temporary array used to apply the sorting function
  39326. c = [],
  39327. keys = me.keys,
  39328. items = me.items;
  39329. //default to a simple sorter function if one is not provided
  39330. fn = fn || function(a, b) {
  39331. return a - b;
  39332. };
  39333. //copy all the items into a temporary array, which we will sort
  39334. for(i = 0, len = items.length; i < len; i++){
  39335. c[c.length] = {
  39336. key : keys[i],
  39337. value: items[i],
  39338. index: i
  39339. };
  39340. }
  39341. //sort the temporary array
  39342. Ext.Array.sort(c, function(a, b){
  39343. var v = fn(a[property], b[property]) * dsc;
  39344. if(v === 0){
  39345. v = (a.index < b.index ? -1 : 1);
  39346. }
  39347. return v;
  39348. });
  39349. //copy the temporary array back into the main this.items and this.keys objects
  39350. for(i = 0, len = c.length; i < len; i++){
  39351. items[i] = c[i].value;
  39352. keys[i] = c[i].key;
  39353. }
  39354. me.fireEvent('sort', me);
  39355. },
  39356. /**
  39357. * Sorts the collection by a single sorter function
  39358. * @param {Function} sorterFn The function to sort by
  39359. */
  39360. sortBy: function(sorterFn) {
  39361. var me = this,
  39362. items = me.items,
  39363. keys = me.keys,
  39364. length = items.length,
  39365. temp = [],
  39366. i;
  39367. //first we create a copy of the items array so that we can sort it
  39368. for (i = 0; i < length; i++) {
  39369. temp[i] = {
  39370. key : keys[i],
  39371. value: items[i],
  39372. index: i
  39373. };
  39374. }
  39375. Ext.Array.sort(temp, function(a, b) {
  39376. var v = sorterFn(a.value, b.value);
  39377. if (v === 0) {
  39378. v = (a.index < b.index ? -1 : 1);
  39379. }
  39380. return v;
  39381. });
  39382. //copy the temporary array back into the main this.items and this.keys objects
  39383. for (i = 0; i < length; i++) {
  39384. items[i] = temp[i].value;
  39385. keys[i] = temp[i].key;
  39386. }
  39387. me.fireEvent('sort', me, items, keys);
  39388. },
  39389. /**
  39390. * Calculates the insertion index of the new item based upon the comparison function passed, or the current sort order.
  39391. * @param {Object} newItem The new object to find the insertion position of.
  39392. * @param {Function} [sorterFn] The function to sort by. This is the same as the sorting function
  39393. * passed to {@link #sortBy}. It accepts 2 items from this MixedCollection, and returns -1 0, or 1
  39394. * depending on the relative sort positions of the 2 compared items.
  39395. *
  39396. * If omitted, a function {@link #generateComparator generated} from the currently defined set of
  39397. * {@link #sorters} will be used.
  39398. *
  39399. * @return {Number} The insertion point to add the new item into this MixedCollection at using {@link #insert}
  39400. */
  39401. findInsertionIndex: function(newItem, sorterFn) {
  39402. var me = this,
  39403. items = me.items,
  39404. start = 0,
  39405. end = items.length - 1,
  39406. middle,
  39407. comparison;
  39408. if (!sorterFn) {
  39409. sorterFn = me.generateComparator();
  39410. }
  39411. while (start <= end) {
  39412. middle = (start + end) >> 1;
  39413. comparison = sorterFn(newItem, items[middle]);
  39414. if (comparison >= 0) {
  39415. start = middle + 1;
  39416. } else if (comparison < 0) {
  39417. end = middle - 1;
  39418. }
  39419. }
  39420. return start;
  39421. },
  39422. /**
  39423. * Reorders each of the items based on a mapping from old index to new index. Internally this
  39424. * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
  39425. * @param {Object} mapping Mapping from old item index to new item index
  39426. */
  39427. reorder: function(mapping) {
  39428. var me = this,
  39429. items = me.items,
  39430. index = 0,
  39431. length = items.length,
  39432. order = [],
  39433. remaining = [],
  39434. oldIndex;
  39435. me.suspendEvents();
  39436. //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
  39437. for (oldIndex in mapping) {
  39438. order[mapping[oldIndex]] = items[oldIndex];
  39439. }
  39440. for (index = 0; index < length; index++) {
  39441. if (mapping[index] == undefined) {
  39442. remaining.push(items[index]);
  39443. }
  39444. }
  39445. for (index = 0; index < length; index++) {
  39446. if (order[index] == undefined) {
  39447. order[index] = remaining.shift();
  39448. }
  39449. }
  39450. me.clear();
  39451. me.addAll(order);
  39452. me.resumeEvents();
  39453. me.fireEvent('sort', me);
  39454. },
  39455. /**
  39456. * Sorts this collection by <b>key</b>s.
  39457. * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
  39458. * @param {Function} fn (optional) Comparison function that defines the sort order.
  39459. * Defaults to sorting by case insensitive string.
  39460. */
  39461. sortByKey : function(dir, fn){
  39462. this._sort('key', dir, fn || function(a, b){
  39463. var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
  39464. return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
  39465. });
  39466. }
  39467. });
  39468. /**
  39469. * A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
  39470. * and Component activation behavior, including masking below the active (topmost) Component.
  39471. *
  39472. * {@link Ext.Component#floating Floating} Components which are rendered directly into the document (such as
  39473. * {@link Ext.window.Window Window}s) which are {@link Ext.Component#method-show show}n are managed by a
  39474. * {@link Ext.WindowManager global instance}.
  39475. *
  39476. * {@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating}
  39477. * *Containers* (for example a {@link Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window},
  39478. * or a {@link Ext.menu.Menu Menu}), are managed by a ZIndexManager owned by that floating Container. Therefore
  39479. * ComboBox dropdowns within Windows will have managed z-indices guaranteed to be correct, relative to the Window.
  39480. */
  39481. Ext.define('Ext.ZIndexManager', {
  39482. alternateClassName: 'Ext.WindowGroup',
  39483. statics: {
  39484. zBase : 9000
  39485. },
  39486. constructor: function(container) {
  39487. var me = this;
  39488. me.list = {};
  39489. me.zIndexStack = [];
  39490. me.front = null;
  39491. if (container) {
  39492. // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
  39493. if (container.isContainer) {
  39494. container.on('resize', me._onContainerResize, me);
  39495. me.zseed = Ext.Number.from(me.rendered ? container.getEl().getStyle('zIndex') : undefined, me.getNextZSeed());
  39496. // The containing element we will be dealing with (eg masking) is the content target
  39497. me.targetEl = container.getTargetEl();
  39498. me.container = container;
  39499. }
  39500. // This is the ZIndexManager for a DOM element
  39501. else {
  39502. Ext.EventManager.onWindowResize(me._onContainerResize, me);
  39503. me.zseed = me.getNextZSeed();
  39504. me.targetEl = Ext.get(container);
  39505. }
  39506. }
  39507. // No container passed means we are the global WindowManager. Our target is the doc body.
  39508. // DOM must be ready to collect that ref.
  39509. else {
  39510. Ext.EventManager.onWindowResize(me._onContainerResize, me);
  39511. me.zseed = me.getNextZSeed();
  39512. Ext.onDocumentReady(function() {
  39513. me.targetEl = Ext.getBody();
  39514. });
  39515. }
  39516. },
  39517. getNextZSeed: function() {
  39518. return (Ext.ZIndexManager.zBase += 10000);
  39519. },
  39520. setBase: function(baseZIndex) {
  39521. this.zseed = baseZIndex;
  39522. var result = this.assignZIndices();
  39523. this._activateLast();
  39524. return result;
  39525. },
  39526. // private
  39527. assignZIndices: function() {
  39528. var a = this.zIndexStack,
  39529. len = a.length,
  39530. i = 0,
  39531. zIndex = this.zseed,
  39532. comp;
  39533. for (; i < len; i++) {
  39534. comp = a[i];
  39535. if (comp && !comp.hidden) {
  39536. // Setting the zIndex of a Component returns the topmost zIndex consumed by
  39537. // that Component.
  39538. // If it's just a plain floating Component such as a BoundList, then the
  39539. // return value is the passed value plus 10, ready for the next item.
  39540. // If a floating *Container* has its zIndex set, it re-orders its managed
  39541. // floating children, starting from that new base, and returns a value 10000 above
  39542. // the highest zIndex which it allocates.
  39543. zIndex = comp.setZIndex(zIndex);
  39544. }
  39545. }
  39546. // Activate new topmost
  39547. this._activateLast();
  39548. return zIndex;
  39549. },
  39550. // private
  39551. _setActiveChild: function(comp, oldFront) {
  39552. var front = this.front;
  39553. if (comp !== front) {
  39554. if (front && !front.destroying) {
  39555. front.setActive(false, comp);
  39556. }
  39557. this.front = comp;
  39558. if (comp && comp != oldFront) {
  39559. comp.setActive(true);
  39560. if (comp.modal) {
  39561. this._showModalMask(comp);
  39562. }
  39563. }
  39564. }
  39565. },
  39566. onComponentHide: function(comp){
  39567. comp.setActive(false);
  39568. this._activateLast();
  39569. },
  39570. // private
  39571. _activateLast: function() {
  39572. var me = this,
  39573. stack = me.zIndexStack,
  39574. i = stack.length - 1,
  39575. oldFront = me.front,
  39576. comp;
  39577. // There may be no visible floater to activate
  39578. me.front = undefined;
  39579. // Go down through the z-index stack.
  39580. // Activate the next visible one down.
  39581. // If that was modal, then we're done
  39582. for (; i >= 0 && stack[i].hidden; --i);
  39583. if ((comp = stack[i])) {
  39584. me._setActiveChild(comp, oldFront);
  39585. if (comp.modal) {
  39586. return;
  39587. }
  39588. }
  39589. // If the new top one was not modal, keep going down to find the next visible
  39590. // modal one to shift the modal mask down under
  39591. for (; i >= 0; --i) {
  39592. comp = stack[i];
  39593. // If we find a visible modal further down the zIndex stack, move the mask to just under it.
  39594. if (comp.isVisible() && comp.modal) {
  39595. me._showModalMask(comp);
  39596. return;
  39597. }
  39598. }
  39599. // No visible modal Component was found in the run down the stack.
  39600. // So hide the modal mask
  39601. me._hideModalMask();
  39602. },
  39603. _showModalMask: function(comp) {
  39604. var me = this,
  39605. zIndex = comp.el.getStyle('zIndex') - 4,
  39606. maskTarget = comp.floatParent ? comp.floatParent.getTargetEl() : comp.container,
  39607. viewSize = maskTarget.getBox();
  39608. if (maskTarget.dom === document.body) {
  39609. viewSize.height = Math.max(document.body.scrollHeight, Ext.dom.Element.getDocumentHeight());
  39610. viewSize.width = Math.max(document.body.scrollWidth, viewSize.width);
  39611. }
  39612. if (!me.mask) {
  39613. me.mask = Ext.getBody().createChild({
  39614. cls: Ext.baseCSSPrefix + 'mask'
  39615. });
  39616. me.mask.setVisibilityMode(Ext.Element.DISPLAY);
  39617. me.mask.on('click', me._onMaskClick, me);
  39618. }
  39619. me.mask.maskTarget = maskTarget;
  39620. maskTarget.addCls(Ext.baseCSSPrefix + 'body-masked');
  39621. me.mask.setStyle('zIndex', zIndex);
  39622. // setting mask box before showing it in an IE7 strict iframe within a quirks page
  39623. // can cause body scrolling [EXTJSIV-6219]
  39624. me.mask.show();
  39625. me.mask.setBox(viewSize);
  39626. },
  39627. _hideModalMask: function() {
  39628. var mask = this.mask;
  39629. if (mask && mask.isVisible()) {
  39630. mask.maskTarget.removeCls(Ext.baseCSSPrefix + 'body-masked');
  39631. mask.maskTarget = undefined;
  39632. mask.hide();
  39633. }
  39634. },
  39635. _onMaskClick: function() {
  39636. if (this.front) {
  39637. this.front.focus();
  39638. }
  39639. },
  39640. _onContainerResize: function() {
  39641. var mask = this.mask,
  39642. maskTarget,
  39643. viewSize;
  39644. if (mask && mask.isVisible()) {
  39645. // At the new container size, the mask might be *causing* the scrollbar, so to find the valid
  39646. // client size to mask, we must temporarily unmask the parent node.
  39647. mask.hide();
  39648. maskTarget = mask.maskTarget;
  39649. if (maskTarget.dom === document.body) {
  39650. viewSize = {
  39651. height: Math.max(document.body.scrollHeight, Ext.dom.Element.getDocumentHeight()),
  39652. width: Math.max(document.body.scrollWidth, document.documentElement.clientWidth)
  39653. };
  39654. } else {
  39655. viewSize = maskTarget.getViewSize(true);
  39656. }
  39657. mask.setSize(viewSize);
  39658. mask.show();
  39659. }
  39660. },
  39661. /**
  39662. * Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
  39663. * need to be called under normal circumstances. Floating Components (such as Windows,
  39664. * BoundLists and Menus) are automatically registered with a
  39665. * {@link Ext.Component#zIndexManager zIndexManager} at render time.
  39666. *
  39667. * Where this may be useful is moving Windows between two ZIndexManagers. For example,
  39668. * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
  39669. * ZIndexManager in the desktop sample app:
  39670. *
  39671. * MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
  39672. *
  39673. * @param {Ext.Component} comp The Component to register.
  39674. */
  39675. register : function(comp) {
  39676. var me = this;
  39677. if (comp.zIndexManager) {
  39678. comp.zIndexManager.unregister(comp);
  39679. }
  39680. comp.zIndexManager = me;
  39681. me.list[comp.id] = comp;
  39682. me.zIndexStack.push(comp);
  39683. comp.on('hide', me.onComponentHide, me);
  39684. },
  39685. /**
  39686. * Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
  39687. * need to be called. Components are automatically unregistered upon destruction.
  39688. * See {@link #register}.
  39689. * @param {Ext.Component} comp The Component to unregister.
  39690. */
  39691. unregister : function(comp) {
  39692. var me = this,
  39693. list = me.list;
  39694. delete comp.zIndexManager;
  39695. if (list && list[comp.id]) {
  39696. delete list[comp.id];
  39697. comp.un('hide', me.onComponentHide);
  39698. Ext.Array.remove(me.zIndexStack, comp);
  39699. // Destruction requires that the topmost visible floater be activated. Same as hiding.
  39700. me._activateLast();
  39701. }
  39702. },
  39703. /**
  39704. * Gets a registered Component by id.
  39705. * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
  39706. * @return {Ext.Component}
  39707. */
  39708. get : function(id) {
  39709. return id.isComponent ? id : this.list[id];
  39710. },
  39711. /**
  39712. * Brings the specified Component to the front of any other active Components in this ZIndexManager.
  39713. * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
  39714. * @return {Boolean} True if the dialog was brought to the front, else false
  39715. * if it was already in front
  39716. */
  39717. bringToFront : function(comp) {
  39718. var me = this,
  39719. result = false,
  39720. zIndexStack = me.zIndexStack;
  39721. comp = me.get(comp);
  39722. if (comp !== me.front) {
  39723. Ext.Array.remove(zIndexStack, comp);
  39724. if (comp.preventBringToFront) {
  39725. // this takes care of cases where a load mask should be displayed under a floated component
  39726. zIndexStack.unshift(comp);
  39727. } else {
  39728. // the default behavior is to push onto the stack
  39729. zIndexStack.push(comp);
  39730. }
  39731. me.assignZIndices();
  39732. result = true;
  39733. this.front = comp;
  39734. }
  39735. if (result && comp.modal) {
  39736. me._showModalMask(comp);
  39737. }
  39738. return result;
  39739. },
  39740. /**
  39741. * Sends the specified Component to the back of other active Components in this ZIndexManager.
  39742. * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
  39743. * @return {Ext.Component} The Component
  39744. */
  39745. sendToBack : function(comp) {
  39746. var me = this;
  39747. comp = me.get(comp);
  39748. Ext.Array.remove(me.zIndexStack, comp);
  39749. me.zIndexStack.unshift(comp);
  39750. me.assignZIndices();
  39751. this._activateLast();
  39752. return comp;
  39753. },
  39754. /**
  39755. * Hides all Components managed by this ZIndexManager.
  39756. */
  39757. hideAll : function() {
  39758. var list = this.list,
  39759. item,
  39760. id;
  39761. for (id in list) {
  39762. if (list.hasOwnProperty(id)) {
  39763. item = list[id];
  39764. if (item.isComponent && item.isVisible()) {
  39765. item.hide();
  39766. }
  39767. }
  39768. }
  39769. },
  39770. /**
  39771. * @private
  39772. * Temporarily hides all currently visible managed Components. This is for when
  39773. * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
  39774. * they should all be hidden just for the duration of the drag.
  39775. */
  39776. hide: function() {
  39777. var me = this,
  39778. mask = me.mask,
  39779. i = 0,
  39780. stack = me.zIndexStack,
  39781. len = stack.length,
  39782. comp;
  39783. me.tempHidden = me.tempHidden||[];
  39784. for (; i < len; i++) {
  39785. comp = stack[i];
  39786. if (comp.isVisible()) {
  39787. me.tempHidden.push(comp);
  39788. comp.el.hide();
  39789. }
  39790. }
  39791. // Also hide modal mask during hidden state
  39792. if (mask) {
  39793. mask.hide();
  39794. }
  39795. },
  39796. /**
  39797. * @private
  39798. * Restores temporarily hidden managed Components to visibility.
  39799. */
  39800. show: function() {
  39801. var me = this,
  39802. mask = me.mask,
  39803. i = 0,
  39804. tempHidden = me.tempHidden,
  39805. len = tempHidden ? tempHidden.length : 0,
  39806. comp;
  39807. for (; i < len; i++) {
  39808. comp = tempHidden[i];
  39809. comp.el.show();
  39810. comp.setPosition(comp.x, comp.y);
  39811. }
  39812. me.tempHidden.length = 0;
  39813. // Also restore mask to visibility and ensure it is aligned with its target element
  39814. if (mask) {
  39815. mask.show();
  39816. mask.alignTo(mask.maskTarget, 'tl-tl');
  39817. }
  39818. },
  39819. /**
  39820. * Gets the currently-active Component in this ZIndexManager.
  39821. * @return {Ext.Component} The active Component
  39822. */
  39823. getActive : function() {
  39824. return this.front;
  39825. },
  39826. /**
  39827. * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
  39828. * The function should accept a single {@link Ext.Component} reference as its only argument and should
  39829. * return true if the Component matches the search criteria, otherwise it should return false.
  39830. * @param {Function} fn The search function
  39831. * @param {Object} [scope] The scope (this reference) in which the function is executed.
  39832. * Defaults to the Component being tested. That gets passed to the function if not specified.
  39833. * @return {Array} An array of zero or more matching windows
  39834. */
  39835. getBy : function(fn, scope) {
  39836. var r = [],
  39837. i = 0,
  39838. stack = this.zIndexStack,
  39839. len = stack.length,
  39840. comp;
  39841. for (; i < len; i++) {
  39842. comp = stack[i];
  39843. if (fn.call(scope||comp, comp) !== false) {
  39844. r.push(comp);
  39845. }
  39846. }
  39847. return r;
  39848. },
  39849. /**
  39850. * Executes the specified function once for every Component in this ZIndexManager, passing each
  39851. * Component as the only parameter. Returning false from the function will stop the iteration.
  39852. * @param {Function} fn The function to execute for each item
  39853. * @param {Object} [scope] The scope (this reference) in which the function
  39854. * is executed. Defaults to the current Component in the iteration.
  39855. */
  39856. each : function(fn, scope) {
  39857. var list = this.list,
  39858. id,
  39859. comp;
  39860. for (id in list) {
  39861. if (list.hasOwnProperty(id)) {
  39862. comp = list[id];
  39863. if (comp.isComponent && fn.call(scope || comp, comp) === false) {
  39864. return;
  39865. }
  39866. }
  39867. }
  39868. },
  39869. /**
  39870. * Executes the specified function once for every Component in this ZIndexManager, passing each
  39871. * Component as the only parameter. Returning false from the function will stop the iteration.
  39872. * The components are passed to the function starting at the bottom and proceeding to the top.
  39873. * @param {Function} fn The function to execute for each item
  39874. * @param {Object} scope (optional) The scope (this reference) in which the function
  39875. * is executed. Defaults to the current Component in the iteration.
  39876. */
  39877. eachBottomUp: function (fn, scope) {
  39878. var stack = this.zIndexStack,
  39879. i = 0,
  39880. len = stack.length,
  39881. comp;
  39882. for (; i < len; i++) {
  39883. comp = stack[i];
  39884. if (comp.isComponent && fn.call(scope || comp, comp) === false) {
  39885. return;
  39886. }
  39887. }
  39888. },
  39889. /**
  39890. * Executes the specified function once for every Component in this ZIndexManager, passing each
  39891. * Component as the only parameter. Returning false from the function will stop the iteration.
  39892. * The components are passed to the function starting at the top and proceeding to the bottom.
  39893. * @param {Function} fn The function to execute for each item
  39894. * @param {Object} [scope] The scope (this reference) in which the function
  39895. * is executed. Defaults to the current Component in the iteration.
  39896. */
  39897. eachTopDown: function (fn, scope) {
  39898. var stack = this.zIndexStack,
  39899. i = stack.length,
  39900. comp;
  39901. for (; i-- > 0; ) {
  39902. comp = stack[i];
  39903. if (comp.isComponent && fn.call(scope || comp, comp) === false) {
  39904. return;
  39905. }
  39906. }
  39907. },
  39908. destroy: function() {
  39909. var me = this,
  39910. list = me.list,
  39911. comp,
  39912. id;
  39913. for (id in list) {
  39914. if (list.hasOwnProperty(id)) {
  39915. comp = list[id];
  39916. if (comp.isComponent) {
  39917. comp.destroy();
  39918. }
  39919. }
  39920. }
  39921. delete me.zIndexStack;
  39922. delete me.list;
  39923. delete me.container;
  39924. delete me.targetEl;
  39925. }
  39926. }, function() {
  39927. /**
  39928. * @class Ext.WindowManager
  39929. * @extends Ext.ZIndexManager
  39930. *
  39931. * The default global floating Component group that is available automatically.
  39932. *
  39933. * This manages instances of floating Components which were rendered programatically without
  39934. * being added to a {@link Ext.container.Container Container}, and for floating Components
  39935. * which were added into non-floating Containers.
  39936. *
  39937. * *Floating* Containers create their own instance of ZIndexManager, and floating Components
  39938. * added at any depth below there are managed by that ZIndexManager.
  39939. *
  39940. * @singleton
  39941. */
  39942. Ext.WindowManager = Ext.WindowMgr = new this();
  39943. });
  39944. /**
  39945. * An abstract base class which provides shared methods for Containers across the Sencha product line.
  39946. *
  39947. * Please refer to sub class's documentation
  39948. *
  39949. * @private
  39950. */
  39951. Ext.define('Ext.container.AbstractContainer', {
  39952. /* Begin Definitions */
  39953. extend: 'Ext.Component',
  39954. requires: [
  39955. 'Ext.util.MixedCollection',
  39956. 'Ext.layout.container.Auto',
  39957. 'Ext.ZIndexManager'
  39958. ],
  39959. /* End Definitions */
  39960. renderTpl: '{%this.renderContainer(out,values)%}',
  39961. /**
  39962. * @cfg {String/Object} layout
  39963. * **Important**: In order for child items to be correctly sized and
  39964. * positioned, typically a layout manager **must** be specified through
  39965. * the `layout` configuration option.
  39966. *
  39967. * The sizing and positioning of child {@link #cfg-items} is the responsibility of
  39968. * the Container's layout manager which creates and manages the type of layout
  39969. * you have in mind. For example:
  39970. *
  39971. * If the {@link #layout} configuration is not explicitly specified for
  39972. * a general purpose container (e.g. Container or Panel) the
  39973. * {@link Ext.layout.container.Auto default layout manager} will be used
  39974. * which does nothing but render child components sequentially into the
  39975. * Container (no sizing or positioning will be performed in this situation).
  39976. *
  39977. * **layout** may be specified as either as an Object or as a String:
  39978. *
  39979. * # Specify as an Object
  39980. *
  39981. * Example usage:
  39982. *
  39983. * layout: {
  39984. * type: 'vbox',
  39985. * align: 'left'
  39986. * }
  39987. *
  39988. * - **type**
  39989. *
  39990. * The layout type to be used for this container. If not specified,
  39991. * a default {@link Ext.layout.container.Auto} will be created and used.
  39992. *
  39993. * Valid layout <code>type</code> values are:
  39994. *
  39995. * - {@link Ext.layout.container.Auto Auto} - **Default**
  39996. * - {@link Ext.layout.container.Card card}
  39997. * - {@link Ext.layout.container.Fit fit}
  39998. * - {@link Ext.layout.container.HBox hbox}
  39999. * - {@link Ext.layout.container.VBox vbox}
  40000. * - {@link Ext.layout.container.Anchor anchor}
  40001. * - {@link Ext.layout.container.Table table}
  40002. *
  40003. * - Layout specific configuration properties
  40004. *
  40005. * Additional layout specific configuration properties may also be
  40006. * specified. For complete details regarding the valid config options for
  40007. * each layout type, see the layout class corresponding to the `type`
  40008. * specified.
  40009. *
  40010. * # Specify as a String
  40011. *
  40012. * Example usage:
  40013. *
  40014. * layout: 'vbox'
  40015. *
  40016. * - **layout**
  40017. *
  40018. * The layout `type` to be used for this container (see list
  40019. * of valid layout type values above).
  40020. *
  40021. * Additional layout specific configuration properties. For complete
  40022. * details regarding the valid config options for each layout type, see the
  40023. * layout class corresponding to the `layout` specified.
  40024. *
  40025. * # Configuring the default layout type
  40026. *
  40027. * If a certain Container class has a default layout (For example a {@link Ext.toolbar.Toolbar Toolbar}
  40028. * with a default `Box` layout), then to simply configure the default layout,
  40029. * use an object, but without the `type` property:
  40030. *
  40031. *
  40032. * xtype: 'toolbar',
  40033. * layout: {
  40034. * pack: 'center'
  40035. * }
  40036. */
  40037. /**
  40038. * @cfg {String/Number} activeItem
  40039. * A string component id or the numeric index of the component that should be
  40040. * initially activated within the container's layout on render. For example,
  40041. * activeItem: 'item-1' or activeItem: 0 (index 0 = the first item in the
  40042. * container's collection). activeItem only applies to layout styles that can
  40043. * display items one at a time (like {@link Ext.layout.container.Card} and
  40044. * {@link Ext.layout.container.Fit}).
  40045. */
  40046. /**
  40047. * @cfg {Object/Object[]} items
  40048. * A single item, or an array of child Components to be added to this container
  40049. *
  40050. * **Unless configured with a {@link #layout}, a Container simply renders child
  40051. * Components serially into its encapsulating element and performs no sizing or
  40052. * positioning upon them.**
  40053. *
  40054. * Example:
  40055. *
  40056. * // specifying a single item
  40057. * items: {...},
  40058. * layout: 'fit', // The single items is sized to fit
  40059. *
  40060. * // specifying multiple items
  40061. * items: [{...}, {...}],
  40062. * layout: 'hbox', // The items are arranged horizontally
  40063. *
  40064. * Each item may be:
  40065. *
  40066. * - A {@link Ext.Component Component}
  40067. * - A Component configuration object
  40068. *
  40069. * If a configuration object is specified, the actual type of Component to be
  40070. * instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.
  40071. *
  40072. * Every Component class has its own {@link Ext.Component#xtype xtype}.
  40073. *
  40074. * If an {@link Ext.Component#xtype xtype} is not explicitly specified, the
  40075. * {@link #defaultType} for the Container is used, which by default is usually `panel`.
  40076. *
  40077. * # Notes:
  40078. *
  40079. * Ext uses lazy rendering. Child Components will only be rendered
  40080. * should it become necessary. Items are automatically laid out when they are first
  40081. * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.
  40082. *
  40083. * Do not specify {@link Ext.panel.Panel#contentEl contentEl} or
  40084. * {@link Ext.panel.Panel#html html} with `items`.
  40085. */
  40086. /**
  40087. * @cfg {Object/Function} defaults
  40088. * This option is a means of applying default settings to all added items whether added
  40089. * through the {@link #cfg-items} config or via the {@link #method-add} or {@link #insert} methods.
  40090. *
  40091. * Defaults are applied to both config objects and instantiated components conditionally
  40092. * so as not to override existing properties in the item (see {@link Ext#applyIf}).
  40093. *
  40094. * If the defaults option is specified as a function, then the function will be called
  40095. * using this Container as the scope (`this` reference) and passing the added item as
  40096. * the first parameter. Any resulting object from that call is then applied to the item
  40097. * as default properties.
  40098. *
  40099. * For example, to automatically apply padding to the body of each of a set of
  40100. * contained {@link Ext.panel.Panel} items, you could pass:
  40101. * `defaults: {bodyStyle:'padding:15px'}`.
  40102. *
  40103. * Usage:
  40104. *
  40105. * defaults: { // defaults are applied to items, not the container
  40106. * autoScroll: true
  40107. * },
  40108. * items: [
  40109. * // default will not be applied here, panel1 will be autoScroll: false
  40110. * {
  40111. * xtype: 'panel',
  40112. * id: 'panel1',
  40113. * autoScroll: false
  40114. * },
  40115. * // this component will have autoScroll: true
  40116. * new Ext.panel.Panel({
  40117. * id: 'panel2'
  40118. * })
  40119. * ]
  40120. */
  40121. /**
  40122. * @cfg {Boolean} suspendLayout
  40123. * If true, suspend calls to doLayout. Useful when batching multiple adds to a container
  40124. * and not passing them as multiple arguments or an array.
  40125. */
  40126. suspendLayout : false,
  40127. /**
  40128. * @cfg {Boolean} [autoDestroy=true]
  40129. * If true the container will automatically destroy any contained component that is removed
  40130. * from it, else destruction must be handled manually.
  40131. */
  40132. autoDestroy : true,
  40133. /**
  40134. * @cfg {String} [defaultType="panel"]
  40135. * The default {@link Ext.Component xtype} of child Components to create in this Container when
  40136. * a child item is specified as a raw configuration object, rather than as an instantiated Component.
  40137. */
  40138. defaultType: 'panel',
  40139. /**
  40140. * @cfg {Boolean} [detachOnRemove=true]
  40141. * True to move any component to the {@link Ext#getDetachedBody detachedBody} when the component is
  40142. * removed from this container. This option is only applicable when the component is not destroyed while
  40143. * being removed, see {@link #autoDestroy} and {@link #method-remove}. If this option is set to false, the DOM
  40144. * of the component will remain in the current place until it is explicitly moved.
  40145. */
  40146. detachOnRemove: true,
  40147. /*
  40148. * @property {Boolean} isContainer
  40149. * `true` in this class to identify an object as an instantiated Container, or subclass thereof.
  40150. */
  40151. isContainer : true,
  40152. /**
  40153. * @property {Number} layoutCounter
  40154. * The number of container layout calls made on this object.
  40155. * @private
  40156. */
  40157. layoutCounter : 0,
  40158. baseCls: Ext.baseCSSPrefix + 'container',
  40159. /**
  40160. * @cfg {String[]} bubbleEvents
  40161. * An array of events that, when fired, should be bubbled to any parent container.
  40162. * See {@link Ext.util.Observable#enableBubble}.
  40163. */
  40164. bubbleEvents: ['add', 'remove'],
  40165. defaultLayoutType: 'auto',
  40166. // @private
  40167. initComponent : function(){
  40168. var me = this;
  40169. me.addEvents(
  40170. /**
  40171. * @event afterlayout
  40172. * Fires when the components in this container are arranged by the associated layout manager.
  40173. * @param {Ext.container.Container} this
  40174. * @param {Ext.layout.container.Container} layout The ContainerLayout implementation for this container
  40175. */
  40176. 'afterlayout',
  40177. /**
  40178. * @event beforeadd
  40179. * Fires before any {@link Ext.Component} is added or inserted into the container.
  40180. * A handler can return false to cancel the add.
  40181. * @param {Ext.container.Container} this
  40182. * @param {Ext.Component} component The component being added
  40183. * @param {Number} index The index at which the component will be added to the container's items collection
  40184. */
  40185. 'beforeadd',
  40186. /**
  40187. * @event beforeremove
  40188. * Fires before any {@link Ext.Component} is removed from the container. A handler can return
  40189. * false to cancel the remove.
  40190. * @param {Ext.container.Container} this
  40191. * @param {Ext.Component} component The component being removed
  40192. */
  40193. 'beforeremove',
  40194. /**
  40195. * @event add
  40196. * Fires after any {@link Ext.Component} is added or inserted into the container.
  40197. *
  40198. * **This event bubbles:** 'add' will also be fired when Component is added to any of
  40199. * the child containers or their childern or ...
  40200. * @param {Ext.container.Container} this
  40201. * @param {Ext.Component} component The component that was added
  40202. * @param {Number} index The index at which the component was added to the container's items collection
  40203. */
  40204. 'add',
  40205. /**
  40206. * @event remove
  40207. * Fires after any {@link Ext.Component} is removed from the container.
  40208. *
  40209. * **This event bubbles:** 'remove' will also be fired when Component is removed from any of
  40210. * the child containers or their children or ...
  40211. * @param {Ext.container.Container} this
  40212. * @param {Ext.Component} component The component that was removed
  40213. */
  40214. 'remove'
  40215. );
  40216. me.callParent();
  40217. me.getLayout();
  40218. me.initItems();
  40219. },
  40220. // @private
  40221. initItems : function() {
  40222. var me = this,
  40223. items = me.items;
  40224. /**
  40225. * The MixedCollection containing all the child items of this container.
  40226. * @property items
  40227. * @type Ext.util.AbstractMixedCollection
  40228. */
  40229. me.items = new Ext.util.AbstractMixedCollection(false, me.getComponentId);
  40230. if (items) {
  40231. if (!Ext.isArray(items)) {
  40232. items = [items];
  40233. }
  40234. me.add(items);
  40235. }
  40236. },
  40237. /**
  40238. * @private
  40239. * Returns the focus holder element associated with this Container. By default, this is the Container's target
  40240. * element. Subclasses which use embedded focusable elements (such as Window and Button) should override this for use
  40241. * by the {@link #method-focus} method.
  40242. * @returns {Ext.Element} the focus holding element.
  40243. */
  40244. getFocusEl: function() {
  40245. return this.getTargetEl();
  40246. },
  40247. finishRenderChildren: function () {
  40248. this.callParent();
  40249. var layout = this.getLayout();
  40250. if (layout) {
  40251. layout.finishRender();
  40252. }
  40253. },
  40254. beforeRender: function () {
  40255. var me = this,
  40256. layout = me.getLayout();
  40257. me.callParent();
  40258. if (!layout.initialized) {
  40259. layout.initLayout();
  40260. }
  40261. },
  40262. setupRenderTpl: function (renderTpl) {
  40263. var layout = this.getLayout();
  40264. this.callParent(arguments);
  40265. layout.setupRenderTpl(renderTpl);
  40266. },
  40267. // @private
  40268. setLayout : function(layout) {
  40269. var currentLayout = this.layout;
  40270. if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
  40271. currentLayout.setOwner(null);
  40272. }
  40273. this.layout = layout;
  40274. layout.setOwner(this);
  40275. },
  40276. /**
  40277. * Returns the {@link Ext.layout.container.Container layout} instance currently associated with this Container.
  40278. * If a layout has not been instantiated yet, that is done first
  40279. * @return {Ext.layout.container.Container} The layout
  40280. */
  40281. getLayout : function() {
  40282. var me = this;
  40283. if (!me.layout || !me.layout.isLayout) {
  40284. // Pass any configured in layout property, defaulting to the prototype's layout property, falling back to Auto.
  40285. me.setLayout(Ext.layout.Layout.create(me.layout, me.self.prototype.layout || 'autocontainer'));
  40286. }
  40287. return me.layout;
  40288. },
  40289. /**
  40290. * Manually force this container's layout to be recalculated. The framework uses this internally to refresh layouts
  40291. * form most cases.
  40292. * @return {Ext.container.Container} this
  40293. */
  40294. doLayout : function() {
  40295. this.updateLayout();
  40296. return this;
  40297. },
  40298. /**
  40299. * Invoked after the Container has laid out (and rendered if necessary)
  40300. * its child Components.
  40301. *
  40302. * @param {Ext.layout.container.Container} layout
  40303. *
  40304. * @template
  40305. * @protected
  40306. */
  40307. afterLayout : function(layout) {
  40308. var me = this;
  40309. ++me.layoutCounter;
  40310. if (me.hasListeners.afterlayout) {
  40311. me.fireEvent('afterlayout', me, layout);
  40312. }
  40313. },
  40314. // @private
  40315. prepareItems : function(items, applyDefaults) {
  40316. // Create an Array which does not refer to the passed array.
  40317. // The passed array is a reference to a user's config object and MUST NOT be mutated.
  40318. if (Ext.isArray(items)) {
  40319. items = items.slice();
  40320. } else {
  40321. items = [items];
  40322. }
  40323. // Make sure defaults are applied and item is initialized
  40324. var me = this,
  40325. i = 0,
  40326. len = items.length,
  40327. item;
  40328. for (; i < len; i++) {
  40329. item = items[i];
  40330. if (item == null) {
  40331. Ext.Array.erase(items, i, 1);
  40332. --i;
  40333. --len;
  40334. } else {
  40335. if (applyDefaults) {
  40336. item = this.applyDefaults(item);
  40337. }
  40338. // Tell the item we're in a container during construction
  40339. item.isContained = me;
  40340. items[i] = me.lookupComponent(item);
  40341. delete item.isContained;
  40342. }
  40343. }
  40344. return items;
  40345. },
  40346. // @private
  40347. applyDefaults : function(config) {
  40348. var defaults = this.defaults;
  40349. if (defaults) {
  40350. if (Ext.isFunction(defaults)) {
  40351. defaults = defaults.call(this, config);
  40352. }
  40353. if (Ext.isString(config)) {
  40354. config = Ext.ComponentManager.get(config);
  40355. }
  40356. Ext.applyIf(config, defaults);
  40357. }
  40358. return config;
  40359. },
  40360. // @private
  40361. lookupComponent : function(comp) {
  40362. return (typeof comp == 'string') ? Ext.ComponentManager.get(comp)
  40363. : Ext.ComponentManager.create(comp, this.defaultType);
  40364. },
  40365. // @private - used as the key lookup function for the items collection
  40366. getComponentId : function(comp) {
  40367. return comp.getItemId();
  40368. },
  40369. /**
  40370. * Adds {@link Ext.Component Component}(s) to this Container.
  40371. *
  40372. * ## Description:
  40373. *
  40374. * - Fires the {@link #beforeadd} event before adding.
  40375. * - The Container's {@link #defaults default config values} will be applied
  40376. * accordingly (see `{@link #defaults}` for details).
  40377. * - Fires the `{@link #event-add}` event after the component has been added.
  40378. *
  40379. * ## Notes:
  40380. *
  40381. * If the Container is __already rendered__ when `add`
  40382. * is called, it will render the newly added Component into its content area.
  40383. *
  40384. * **If** the Container was configured with a size-managing {@link #layout} manager,
  40385. * the Container will recalculate its internal layout at this time too.
  40386. *
  40387. * Note that the default layout manager simply renders child Components sequentially
  40388. * into the content area and thereafter performs no sizing.
  40389. *
  40390. * If adding multiple new child Components, pass them as an array to the `add` method,
  40391. * so that only one layout recalculation is performed.
  40392. *
  40393. * tb = new {@link Ext.toolbar.Toolbar}({
  40394. * renderTo: document.body
  40395. * }); // toolbar is rendered
  40396. * // add multiple items.
  40397. * // ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button')
  40398. * tb.add([{text:'Button 1'}, {text:'Button 2'}]);
  40399. *
  40400. * To inject components between existing ones, use the {@link #insert} method.
  40401. *
  40402. * ## Warning:
  40403. *
  40404. * Components directly managed by the BorderLayout layout manager may not be removed
  40405. * or added. See the Notes for {@link Ext.layout.container.Border BorderLayout} for
  40406. * more details.
  40407. *
  40408. * @param {Ext.Component[]/Ext.Component...} component
  40409. * Either one or more Components to add or an Array of Components to add.
  40410. * See `{@link #cfg-items}` for additional information.
  40411. *
  40412. * @return {Ext.Component[]/Ext.Component} The Components that were added.
  40413. */
  40414. add : function() {
  40415. var me = this,
  40416. args = Ext.Array.slice(arguments),
  40417. index = (typeof args[0] == 'number') ? args.shift() : -1,
  40418. layout = me.getLayout(),
  40419. addingArray, items, i, length, item, pos, ret;
  40420. if (args.length == 1 && Ext.isArray(args[0])) {
  40421. items = args[0];
  40422. addingArray = true;
  40423. } else {
  40424. items = args;
  40425. }
  40426. ret = items = me.prepareItems(items, true);
  40427. length = items.length;
  40428. if (me.rendered) {
  40429. Ext.suspendLayouts(); // suspend layouts while adding items...
  40430. }
  40431. if (!addingArray && length == 1) { // an array of 1 should still return an array...
  40432. ret = items[0];
  40433. }
  40434. // loop
  40435. for (i = 0; i < length; i++) {
  40436. item = items[i];
  40437. if (!item) {
  40438. Ext.Error.raise("Cannot add null item to Container with itemId/id: " + me.getItemId());
  40439. }
  40440. pos = (index < 0) ? me.items.length : (index + i);
  40441. // Floating Components are not added into the items collection, but to a separate floatingItems collection
  40442. if (item.floating) {
  40443. me.floatingItems = me.floatingItems || new Ext.util.MixedCollection();
  40444. me.floatingItems.add(item);
  40445. item.onAdded(me, pos);
  40446. } else if ((!me.hasListeners.beforeadd || me.fireEvent('beforeadd', me, item, pos) !== false) && me.onBeforeAdd(item) !== false) {
  40447. me.items.insert(pos, item);
  40448. item.onAdded(me, pos);
  40449. me.onAdd(item, pos);
  40450. layout.onAdd(item, pos);
  40451. if (me.hasListeners.add) {
  40452. me.fireEvent('add', me, item, pos);
  40453. }
  40454. }
  40455. }
  40456. // We need to update our layout after adding all passed items
  40457. me.updateLayout();
  40458. if (me.rendered) {
  40459. Ext.resumeLayouts(true);
  40460. }
  40461. return ret;
  40462. },
  40463. /**
  40464. * This method is invoked after a new Component has been added. It
  40465. * is passed the Component which has been added. This method may
  40466. * be used to update any internal structure which may depend upon
  40467. * the state of the child items.
  40468. *
  40469. * @param {Ext.Component} component
  40470. * @param {Number} position
  40471. *
  40472. * @template
  40473. * @protected
  40474. */
  40475. onAdd : Ext.emptyFn,
  40476. /**
  40477. * This method is invoked after a new Component has been
  40478. * removed. It is passed the Component which has been
  40479. * removed. This method may be used to update any internal
  40480. * structure which may depend upon the state of the child items.
  40481. *
  40482. * @param {Ext.Component} component
  40483. * @param {Boolean} autoDestroy
  40484. *
  40485. * @template
  40486. * @protected
  40487. */
  40488. onRemove : Ext.emptyFn,
  40489. /**
  40490. * Inserts a Component into this Container at a specified index. Fires the
  40491. * {@link #beforeadd} event before inserting, then fires the {@link #event-add}
  40492. * event after the Component has been inserted.
  40493. *
  40494. * @param {Number} index The index at which the Component will be inserted
  40495. * into the Container's items collection
  40496. *
  40497. * @param {Ext.Component} component The child Component to insert.
  40498. *
  40499. * Ext uses lazy rendering, and will only render the inserted Component should
  40500. * it become necessary.
  40501. *
  40502. * A Component config object may be passed in order to avoid the overhead of
  40503. * constructing a real Component object if lazy rendering might mean that the
  40504. * inserted Component will not be rendered immediately. To take advantage of
  40505. * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
  40506. * property to the registered type of the Component wanted.
  40507. *
  40508. * For a list of all available xtypes, see {@link Ext.Component}.
  40509. *
  40510. * @return {Ext.Component} component The Component (or config object) that was
  40511. * inserted with the Container's default config values applied.
  40512. */
  40513. insert : function(index, comp) {
  40514. return this.add(index, comp);
  40515. },
  40516. /**
  40517. * Moves a Component within the Container
  40518. * @param {Number} fromIdx The index the Component you wish to move is currently at.
  40519. * @param {Number} toIdx The new index for the Component.
  40520. * @return {Ext.Component} component The Component (or config object) that was moved.
  40521. */
  40522. move : function(fromIdx, toIdx) {
  40523. var items = this.items,
  40524. item;
  40525. item = items.removeAt(fromIdx);
  40526. if (item === false) {
  40527. return false;
  40528. }
  40529. items.insert(toIdx, item);
  40530. this.doLayout();
  40531. return item;
  40532. },
  40533. /**
  40534. * This method is invoked before adding a new child Component. It
  40535. * is passed the new Component, and may be used to modify the
  40536. * Component, or prepare the Container in some way. Returning
  40537. * false aborts the add operation.
  40538. *
  40539. * @param {Ext.Component} item
  40540. *
  40541. * @template
  40542. * @protected
  40543. */
  40544. onBeforeAdd : function(item) {
  40545. var me = this,
  40546. border = item.border;
  40547. // Remove from current container if it's not us.
  40548. if (item.ownerCt && item.ownerCt !== me) {
  40549. item.ownerCt.remove(item, false);
  40550. }
  40551. if (me.border === false || me.border === 0) {
  40552. // If the parent has no border, only use an explicitly defined border
  40553. item.border = Ext.isDefined(border) && border !== false && border !== 0;
  40554. }
  40555. },
  40556. /**
  40557. * Removes a component from this container. Fires the {@link #beforeremove} event
  40558. * before removing, then fires the {@link #event-remove} event after the component has
  40559. * been removed.
  40560. *
  40561. * @param {Ext.Component/String} component The component reference or id to remove.
  40562. *
  40563. * @param {Boolean} [autoDestroy] True to automatically invoke the removed Component's
  40564. * {@link Ext.Component#method-destroy} function.
  40565. *
  40566. * Defaults to the value of this Container's {@link #autoDestroy} config.
  40567. *
  40568. * @return {Ext.Component} component The Component that was removed.
  40569. */
  40570. remove : function(comp, autoDestroy) {
  40571. var me = this,
  40572. c = me.getComponent(comp);
  40573. if (Ext.isDefined(Ext.global.console) && !c) {
  40574. Ext.global.console.warn("Attempted to remove a component that does not exist. Ext.container.Container: remove takes an argument of the component to remove. cmp.remove() is incorrect usage.");
  40575. }
  40576. if (c && (!me.hasListeners.beforeremove || me.fireEvent('beforeremove', me, c) !== false)) {
  40577. me.doRemove(c, autoDestroy);
  40578. if (me.hasListeners.remove) {
  40579. me.fireEvent('remove', me, c);
  40580. }
  40581. if (!me.destroying) {
  40582. me.doLayout();
  40583. }
  40584. }
  40585. return c;
  40586. },
  40587. // @private
  40588. doRemove : function(component, autoDestroy) {
  40589. var me = this,
  40590. layout = me.layout,
  40591. hasLayout = layout && me.rendered,
  40592. destroying = autoDestroy === true || (autoDestroy !== false && me.autoDestroy);
  40593. autoDestroy = autoDestroy === true || (autoDestroy !== false && me.autoDestroy);
  40594. me.items.remove(component);
  40595. // Inform ownerLayout of removal before deleting the ownerLayout & ownerCt references in the onRemoved call
  40596. if (hasLayout) {
  40597. // Removing a component from a running layout has to cancel the layout
  40598. if (layout.running) {
  40599. Ext.AbstractComponent.cancelLayout(component, destroying);
  40600. }
  40601. layout.onRemove(component, destroying);
  40602. }
  40603. component.onRemoved(destroying);
  40604. me.onRemove(component, destroying);
  40605. // Destroy if we were explicitly told to, or we're defaulting to our autoDestroy configuration
  40606. if (destroying) {
  40607. component.destroy();
  40608. }
  40609. // Only have the layout perform remove postprocessing if the Component is not being destroyed
  40610. else {
  40611. if (hasLayout) {
  40612. layout.afterRemove(component);
  40613. }
  40614. if (me.detachOnRemove && component.rendered) {
  40615. Ext.getDetachedBody().appendChild(component.getEl());
  40616. }
  40617. }
  40618. },
  40619. /**
  40620. * Removes all components from this container.
  40621. * @param {Boolean} [autoDestroy] True to automatically invoke the removed
  40622. * Component's {@link Ext.Component#method-destroy} function.
  40623. * Defaults to the value of this Container's {@link #autoDestroy} config.
  40624. * @return {Ext.Component[]} Array of the removed components
  40625. */
  40626. removeAll : function(autoDestroy) {
  40627. var me = this,
  40628. removeItems = me.items.items.slice(),
  40629. items = [],
  40630. i = 0,
  40631. len = removeItems.length,
  40632. item;
  40633. // Suspend Layouts while we remove multiple items from the container
  40634. me.suspendLayouts();
  40635. for (; i < len; i++) {
  40636. item = removeItems[i];
  40637. me.remove(item, autoDestroy);
  40638. if (item.ownerCt !== me) {
  40639. items.push(item);
  40640. }
  40641. }
  40642. // Resume Layouts now that all items have been removed and do a single layout (if we removed anything!)
  40643. me.resumeLayouts(!!len);
  40644. return items;
  40645. },
  40646. // Used by ComponentQuery to retrieve all of the items
  40647. // which can potentially be considered a child of this Container.
  40648. // This should be overriden by components which have child items
  40649. // that are not contained in items. For example dockedItems, menu, etc
  40650. // IMPORTANT note for maintainers:
  40651. // Items are returned in tree traversal order. Each item is appended to the result array
  40652. // followed by the results of that child's getRefItems call.
  40653. // Floating child items are appended after internal child items.
  40654. getRefItems : function(deep) {
  40655. var me = this,
  40656. items = me.items.items,
  40657. len = items.length,
  40658. i = 0,
  40659. item,
  40660. result = [];
  40661. for (; i < len; i++) {
  40662. item = items[i];
  40663. result.push(item);
  40664. if (deep && item.getRefItems) {
  40665. result.push.apply(result, item.getRefItems(true));
  40666. }
  40667. }
  40668. // Append floating items to the list.
  40669. if (me.floatingItems) {
  40670. result.push.apply(result, me.floatingItems.items);
  40671. }
  40672. return result;
  40673. },
  40674. /**
  40675. * Cascades down the component/container heirarchy from this component (passed in
  40676. * the first call), calling the specified function with each component. The scope
  40677. * (this reference) of the function call will be the scope provided or the current
  40678. * component. The arguments to the function will be the args provided or the current
  40679. * component. If the function returns false at any point, the cascade is stopped on
  40680. * that branch.
  40681. * @param {Function} fn The function to call
  40682. * @param {Object} [scope] The scope of the function (defaults to current component)
  40683. * @param {Array} [args] The args to call the function with. The current component
  40684. * always passed as the last argument.
  40685. * @return {Ext.Container} this
  40686. */
  40687. cascade : function(fn, scope, origArgs){
  40688. var me = this,
  40689. cs = me.items ? me.items.items : [],
  40690. len = cs.length,
  40691. i = 0,
  40692. c,
  40693. args = origArgs ? origArgs.concat(me) : [me],
  40694. componentIndex = args.length - 1;
  40695. if (fn.apply(scope || me, args) !== false) {
  40696. for (; i < len; i++){
  40697. c = cs[i];
  40698. if (c.cascade) {
  40699. c.cascade(fn, scope, origArgs);
  40700. } else {
  40701. args[componentIndex] = c;
  40702. fn.apply(scope || cs, args);
  40703. }
  40704. }
  40705. }
  40706. return this;
  40707. },
  40708. /**
  40709. * Determines whether **this Container** is an ancestor of the passed Component.
  40710. * This will return `true` if the passed Component is anywhere within the subtree
  40711. * beneath this Container.
  40712. * @param {Ext.Component} possibleDescendant The Component to test for presence
  40713. * within this Container's subtree.
  40714. */
  40715. isAncestor: function(possibleDescendant) {
  40716. while (possibleDescendant) {
  40717. if (possibleDescendant.ownerCt === this) {
  40718. return true;
  40719. }
  40720. possibleDescendant = possibleDescendant.ownerCt;
  40721. }
  40722. },
  40723. /**
  40724. * Examines this container's {@link #property-items} **property** and gets a direct child
  40725. * component of this container.
  40726. *
  40727. * @param {String/Number} comp This parameter may be any of the following:
  40728. *
  40729. * - a **String** : representing the {@link Ext.Component#itemId itemId}
  40730. * or {@link Ext.Component#id id} of the child component.
  40731. * - a **Number** : representing the position of the child component
  40732. * within the {@link #property-items} **property**
  40733. *
  40734. * For additional information see {@link Ext.util.MixedCollection#get}.
  40735. *
  40736. * @return {Ext.Component} The component (if found).
  40737. */
  40738. getComponent : function(comp) {
  40739. if (Ext.isObject(comp)) {
  40740. comp = comp.getItemId();
  40741. }
  40742. return this.items.get(comp);
  40743. },
  40744. /**
  40745. * Retrieves all descendant components which match the passed selector.
  40746. * Executes an Ext.ComponentQuery.query using this container as its root.
  40747. * @param {String} [selector] Selector complying to an Ext.ComponentQuery selector.
  40748. * If no selector is specified all items will be returned.
  40749. * @return {Ext.Component[]} Components which matched the selector
  40750. */
  40751. query : function(selector) {
  40752. selector = selector || '*';
  40753. return Ext.ComponentQuery.query(selector, this);
  40754. },
  40755. /**
  40756. * Retrieves all descendant components which match the passed function.
  40757. * The function should return false for components that are to be
  40758. * excluded from the selection.
  40759. * @param {Function} fn The matcher function. It will be called with a single argument,
  40760. * the component being tested.
  40761. * @param {Object} [scope] The scope in which to run the function. If not specified,
  40762. * it will default to the active component.
  40763. * @return {Ext.Component[]} Components matched by the passed function
  40764. */
  40765. queryBy: function(fn, scope) {
  40766. var out = [],
  40767. items = this.getRefItems(true),
  40768. i = 0,
  40769. len = items.length,
  40770. item;
  40771. for (; i < len; ++i) {
  40772. item = items[i];
  40773. if (fn.call(scope || item, item) !== false) {
  40774. out.push(item);
  40775. }
  40776. }
  40777. return out;
  40778. },
  40779. /**
  40780. * Finds a component at any level under this container matching the id/itemId.
  40781. * This is a shorthand for calling ct.down('#' + id);
  40782. * @param {String} id The id to find
  40783. * @return {Ext.Component} The matching id, null if not found
  40784. */
  40785. queryById: function(id){
  40786. return this.down('#' + id);
  40787. },
  40788. /**
  40789. * Retrieves the first direct child of this container which matches the passed selector.
  40790. * The passed in selector must comply with an Ext.ComponentQuery selector.
  40791. * @param {String} [selector] An Ext.ComponentQuery selector. If no selector is
  40792. * specified, the first child will be returned.
  40793. * @return Ext.Component
  40794. */
  40795. child : function(selector) {
  40796. selector = selector || '';
  40797. return this.query('> ' + selector)[0] || null;
  40798. },
  40799. nextChild: function(child, selector) {
  40800. var me = this,
  40801. result,
  40802. childIndex = me.items.indexOf(child);
  40803. if (childIndex !== -1) {
  40804. result = selector ? Ext.ComponentQuery(selector, me.items.items.slice(childIndex + 1)) : me.items.getAt(childIndex + 1);
  40805. if (!result && me.ownerCt) {
  40806. result = me.ownerCt.nextChild(me, selector);
  40807. }
  40808. }
  40809. return result;
  40810. },
  40811. prevChild: function(child, selector) {
  40812. var me = this,
  40813. result,
  40814. childIndex = me.items.indexOf(child);
  40815. if (childIndex !== -1) {
  40816. result = selector ? Ext.ComponentQuery(selector, me.items.items.slice(childIndex + 1)) : me.items.getAt(childIndex + 1);
  40817. if (!result && me.ownerCt) {
  40818. result = me.ownerCt.nextChild(me, selector);
  40819. }
  40820. }
  40821. return result;
  40822. },
  40823. /**
  40824. * Retrieves the first descendant of this container which matches the passed selector.
  40825. * The passed in selector must comply with an Ext.ComponentQuery selector.
  40826. * @param {String} [selector] An Ext.ComponentQuery selector. If no selector is
  40827. * specified, the first child will be returned.
  40828. * @return Ext.Component
  40829. */
  40830. down : function(selector) {
  40831. return this.query(selector)[0] || null;
  40832. },
  40833. // @private
  40834. // Enable all immediate children that was previously disabled
  40835. // Override enable because onEnable only gets called when rendered
  40836. enable: function() {
  40837. this.callParent(arguments);
  40838. var itemsToDisable = this.getChildItemsToDisable(),
  40839. length = itemsToDisable.length,
  40840. item, i;
  40841. for (i = 0; i < length; i++) {
  40842. item = itemsToDisable[i];
  40843. if (item.resetDisable) {
  40844. item.enable();
  40845. }
  40846. }
  40847. return this;
  40848. },
  40849. // Inherit docs
  40850. // Disable all immediate children that was previously disabled
  40851. // Override disable because onDisable only gets called when rendered
  40852. disable: function() {
  40853. this.callParent(arguments);
  40854. var itemsToDisable = this.getChildItemsToDisable(),
  40855. length = itemsToDisable.length,
  40856. item, i;
  40857. for (i = 0; i < length; i++) {
  40858. item = itemsToDisable[i];
  40859. if (item.resetDisable !== false && !item.disabled) {
  40860. item.disable();
  40861. item.resetDisable = true;
  40862. }
  40863. }
  40864. return this;
  40865. },
  40866. /**
  40867. * Gets a list of child components to enable/disable when the container is
  40868. * enabled/disabled
  40869. * @private
  40870. * @return {Ext.Component[]} Items to be enabled/disabled
  40871. */
  40872. getChildItemsToDisable: function(){
  40873. return this.query('[isFormField],button');
  40874. },
  40875. /**
  40876. * Occurs before componentLayout is run. Returning false from this method
  40877. * will prevent the containerLayout from being executed.
  40878. *
  40879. * @template
  40880. * @protected
  40881. */
  40882. beforeLayout: function() {
  40883. return true;
  40884. },
  40885. // @private
  40886. beforeDestroy : function() {
  40887. var me = this,
  40888. items = me.items,
  40889. c;
  40890. if (items) {
  40891. while ((c = items.first())) {
  40892. me.doRemove(c, true);
  40893. }
  40894. }
  40895. Ext.destroy(
  40896. me.layout
  40897. );
  40898. me.callParent();
  40899. }
  40900. });
  40901. /**
  40902. * Base class for any Ext.Component that may contain other Components. Containers handle the basic behavior of
  40903. * containing items, namely adding, inserting and removing items.
  40904. *
  40905. * The most commonly used Container classes are Ext.panel.Panel, Ext.window.Window and
  40906. * Ext.tab.Panel. If you do not need the capabilities offered by the aforementioned classes you can create a
  40907. * lightweight Container to be encapsulated by an HTML element to your specifications by using the
  40908. * {@link Ext.Component#autoEl autoEl} config option.
  40909. *
  40910. * The code below illustrates how to explicitly create a Container:
  40911. *
  40912. * @example
  40913. * // Explicitly create a Container
  40914. * Ext.create('Ext.container.Container', {
  40915. * layout: {
  40916. * type: 'hbox'
  40917. * },
  40918. * width: 400,
  40919. * renderTo: Ext.getBody(),
  40920. * border: 1,
  40921. * style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
  40922. * defaults: {
  40923. * labelWidth: 80,
  40924. * // implicitly create Container by specifying xtype
  40925. * xtype: 'datefield',
  40926. * flex: 1,
  40927. * style: {
  40928. * padding: '10px'
  40929. * }
  40930. * },
  40931. * items: [{
  40932. * xtype: 'datefield',
  40933. * name: 'startDate',
  40934. * fieldLabel: 'Start date'
  40935. * },{
  40936. * xtype: 'datefield',
  40937. * name: 'endDate',
  40938. * fieldLabel: 'End date'
  40939. * }]
  40940. * });
  40941. *
  40942. * ## Layout
  40943. *
  40944. * Container classes delegate the rendering of child Components to a layout manager class which must be configured into
  40945. * the Container using the `{@link #layout}` configuration property.
  40946. *
  40947. * When either specifying child `{@link #cfg-items}` of a Container, or dynamically {@link #method-add adding} Components to a
  40948. * Container, remember to consider how you wish the Container to arrange those child elements, and whether those child
  40949. * elements need to be sized using one of Ext's built-in `{@link #layout}` schemes. By default, Containers use the
  40950. * {@link Ext.layout.container.Auto Auto} scheme which only renders child components, appending them one after the other
  40951. * inside the Container, and **does not apply any sizing** at all.
  40952. *
  40953. * A common mistake is when a developer neglects to specify a `{@link #layout}` (e.g. widgets like GridPanels or
  40954. * TreePanels are added to Containers for which no `{@link #layout}` has been specified). If a Container is left to
  40955. * use the default {@link Ext.layout.container.Auto Auto} scheme, none of its child components will be resized, or changed in
  40956. * any way when the Container is resized.
  40957. *
  40958. * Certain layout managers allow dynamic addition of child components. Those that do include
  40959. * Ext.layout.container.Card, Ext.layout.container.Anchor, Ext.layout.container.VBox,
  40960. * Ext.layout.container.HBox, and Ext.layout.container.Table. For example:
  40961. *
  40962. * // Create the GridPanel.
  40963. * var myNewGrid = Ext.create('Ext.grid.Panel', {
  40964. * store: myStore,
  40965. * headers: myHeaders,
  40966. * title: 'Results', // the title becomes the title of the tab
  40967. * });
  40968. *
  40969. * myTabPanel.add(myNewGrid); // {@link Ext.tab.Panel} implicitly uses {@link Ext.layout.container.Card Card}
  40970. * myTabPanel.{@link Ext.tab.Panel#setActiveTab setActiveTab}(myNewGrid);
  40971. *
  40972. * The example above adds a newly created GridPanel to a TabPanel. Note that a TabPanel uses {@link
  40973. * Ext.layout.container.Card} as its layout manager which means all its child items are sized to {@link
  40974. * Ext.layout.container.Fit fit} exactly into its client area.
  40975. *
  40976. * **_Overnesting is a common problem_**. An example of overnesting occurs when a GridPanel is added to a TabPanel by
  40977. * wrapping the GridPanel _inside_ a wrapping Panel (that has no `{@link #layout}` specified) and then add that
  40978. * wrapping Panel to the TabPanel. The point to realize is that a GridPanel **is** a Component which can be added
  40979. * directly to a Container. If the wrapping Panel has no `{@link #layout}` configuration, then the overnested
  40980. * GridPanel will not be sized as expected.
  40981. *
  40982. * ## Adding via remote configuration
  40983. *
  40984. * A server side script can be used to add Components which are generated dynamically on the server. An example of
  40985. * adding a GridPanel to a TabPanel where the GridPanel is generated by the server based on certain parameters:
  40986. *
  40987. * // execute an Ajax request to invoke server side script:
  40988. * Ext.Ajax.request({
  40989. * url: 'gen-invoice-grid.php',
  40990. * // send additional parameters to instruct server script
  40991. * params: {
  40992. * startDate: Ext.getCmp('start-date').getValue(),
  40993. * endDate: Ext.getCmp('end-date').getValue()
  40994. * },
  40995. * // process the response object to add it to the TabPanel:
  40996. * success: function(xhr) {
  40997. * var newComponent = eval(xhr.responseText); // see discussion below
  40998. * myTabPanel.add(newComponent); // add the component to the TabPanel
  40999. * myTabPanel.setActiveTab(newComponent);
  41000. * },
  41001. * failure: function() {
  41002. * Ext.Msg.alert("Grid create failed", "Server communication failure");
  41003. * }
  41004. * });
  41005. *
  41006. * The server script needs to return a JSON representation of a configuration object, which, when decoded will return a
  41007. * config object with an {@link Ext.Component#xtype xtype}. The server might return the following JSON:
  41008. *
  41009. * {
  41010. * "xtype": 'grid',
  41011. * "title": 'Invoice Report',
  41012. * "store": {
  41013. * "model": 'Invoice',
  41014. * "proxy": {
  41015. * "type": 'ajax',
  41016. * "url": 'get-invoice-data.php',
  41017. * "reader": {
  41018. * "type": 'json'
  41019. * "record": 'transaction',
  41020. * "idProperty": 'id',
  41021. * "totalRecords": 'total'
  41022. * })
  41023. * },
  41024. * "autoLoad": {
  41025. * "params": {
  41026. * "startDate": '01/01/2008',
  41027. * "endDate": '01/31/2008'
  41028. * }
  41029. * }
  41030. * },
  41031. * "headers": [
  41032. * {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
  41033. * {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
  41034. * {"header": "Invoice Date", "width": 100, "dataIndex": 'date', "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
  41035. * {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney', "sortable": true}
  41036. * ]
  41037. * }
  41038. *
  41039. * When the above code fragment is passed through the `eval` function in the success handler of the Ajax request, the
  41040. * result will be a config object which, when added to a Container, will cause instantiation of a GridPanel. **Be sure
  41041. * that the Container is configured with a layout which sizes and positions the child items to your requirements.**
  41042. *
  41043. * **Note:** since the code above is _generated_ by a server script, the `autoLoad` params for the Store, the user's
  41044. * preferred date format, the metadata to allow generation of the Model layout, and the ColumnModel can all be generated
  41045. * into the code since these are all known on the server.
  41046. */
  41047. Ext.define('Ext.container.Container', {
  41048. extend: 'Ext.container.AbstractContainer',
  41049. alias: 'widget.container',
  41050. alternateClassName: 'Ext.Container',
  41051. /*
  41052. * For more information on the following methods, see the note for the
  41053. * hierarchyEventSource observer defined in the class' callback
  41054. */
  41055. fireHierarchyEvent: function (ename) {
  41056. this.hierarchyEventSource.fireEvent(ename, this);
  41057. },
  41058. // note that the collapse and expand events are fired explicitly from Panel.js
  41059. afterHide: function() {
  41060. this.callParent(arguments);
  41061. this.fireHierarchyEvent('hide');
  41062. },
  41063. afterShow: function(){
  41064. this.callParent(arguments);
  41065. this.fireHierarchyEvent('show');
  41066. },
  41067. onAdded: function() {
  41068. this.callParent(arguments);
  41069. if (this.hierarchyEventSource.hasListeners.added) {
  41070. this.fireHierarchyEvent('added');
  41071. }
  41072. },
  41073. /**
  41074. * Return the immediate child Component in which the passed element is located.
  41075. * @param {Ext.Element/HTMLElement/String} el The element to test (or ID of element).
  41076. * @param {Boolean} deep If `true`, returns the deepest descendant Component which contains the passed element.
  41077. * @return {Ext.Component} The child item which contains the passed element.
  41078. */
  41079. getChildByElement: function(el, deep) {
  41080. var item,
  41081. itemEl,
  41082. i = 0,
  41083. it = this.getRefItems(),
  41084. ln = it.length;
  41085. el = Ext.getDom(el);
  41086. for (; i < ln; i++) {
  41087. item = it[i];
  41088. itemEl = item.getEl();
  41089. if (itemEl && ((itemEl.dom === el) || itemEl.contains(el))) {
  41090. return (deep && item.getChildByElement) ? item.getChildByElement(el, deep) : item;
  41091. }
  41092. }
  41093. return null;
  41094. }
  41095. }, function () {
  41096. /*
  41097. * The observer below is used to be able to detect showing/hiding at various levels
  41098. * in the hierarchy. While it's not particularly expensive to bubble an event up,
  41099. * cascading an event down can be quite costly.
  41100. *
  41101. * The main usage for this is to do with floating components. For example, the load mask
  41102. * is a floating component. The component it is masking may be inside several containers.
  41103. * As such, we need to know when component is hidden, either directly, or via a parent
  41104. * container being hidden. We can subscribe to these events and filter out the appropriate
  41105. * container.
  41106. */
  41107. this.hierarchyEventSource = this.prototype.hierarchyEventSource = new Ext.util.Observable({ events: {
  41108. hide: true,
  41109. show: true,
  41110. collapse: true,
  41111. expand: true,
  41112. added: true
  41113. }});
  41114. });
  41115. /**
  41116. * The Editor class is used to provide inline editing for elements on the page. The editor
  41117. * is backed by a {@link Ext.form.field.Field} that will be displayed to edit the underlying content.
  41118. * The editor is a floating Component, when the editor is shown it is automatically aligned to
  41119. * display over the top of the bound element it is editing. The Editor contains several options
  41120. * for how to handle key presses:
  41121. *
  41122. * - {@link #completeOnEnter}
  41123. * - {@link #cancelOnEsc}
  41124. * - {@link #swallowKeys}
  41125. *
  41126. * It also has options for how to use the value once the editor has been activated:
  41127. *
  41128. * - {@link #revertInvalid}
  41129. * - {@link #ignoreNoChange}
  41130. * - {@link #updateEl}
  41131. *
  41132. * Sample usage:
  41133. *
  41134. * var editor = new Ext.Editor({
  41135. * updateEl: true, // update the innerHTML of the bound element when editing completes
  41136. * field: {
  41137. * xtype: 'textfield'
  41138. * }
  41139. * });
  41140. * var el = Ext.get('my-text'); // The element to 'edit'
  41141. * editor.startEdit(el); // The value of the field will be taken as the innerHTML of the element.
  41142. *
  41143. * {@img Ext.Editor/Ext.Editor.png Ext.Editor component}
  41144. *
  41145. */
  41146. Ext.define('Ext.Editor', {
  41147. /* Begin Definitions */
  41148. extend: 'Ext.container.Container',
  41149. alias: 'widget.editor',
  41150. requires: ['Ext.layout.container.Editor'],
  41151. /* End Definitions */
  41152. layout: 'editor',
  41153. /**
  41154. * @cfg {Ext.form.field.Field} field
  41155. * The Field object (or descendant) or config object for field
  41156. */
  41157. /**
  41158. * @cfg {Boolean} allowBlur
  41159. * True to {@link #completeEdit complete the editing process} if in edit mode when the
  41160. * field is blurred.
  41161. */
  41162. allowBlur: true,
  41163. /**
  41164. * @cfg {Boolean/Object} autoSize
  41165. * True for the editor to automatically adopt the size of the underlying field. Otherwise, an object
  41166. * can be passed to indicate where to get each dimension. The available properties are 'boundEl' and
  41167. * 'field'. If a dimension is not specified, it will use the underlying height/width specified on
  41168. * the editor object.
  41169. * Examples:
  41170. *
  41171. * autoSize: true // The editor will be sized to the height/width of the field
  41172. *
  41173. * height: 21,
  41174. * autoSize: {
  41175. * width: 'boundEl' // The width will be determined by the width of the boundEl, the height from the editor (21)
  41176. * }
  41177. *
  41178. * autoSize: {
  41179. * width: 'field', // Width from the field
  41180. * height: 'boundEl' // Height from the boundEl
  41181. * }
  41182. */
  41183. /**
  41184. * @cfg {Boolean} revertInvalid
  41185. * True to automatically revert the field value and cancel the edit when the user completes an edit and the field
  41186. * validation fails
  41187. */
  41188. revertInvalid: true,
  41189. /**
  41190. * @cfg {Boolean} [ignoreNoChange=false]
  41191. * True to skip the edit completion process (no save, no events fired) if the user completes an edit and
  41192. * the value has not changed. Applies only to string values - edits for other data types
  41193. * will never be ignored.
  41194. */
  41195. /**
  41196. * @cfg {Boolean} [hideEl=true]
  41197. * False to keep the bound element visible while the editor is displayed
  41198. */
  41199. /**
  41200. * @cfg {Object} value
  41201. * The data value of the underlying field
  41202. */
  41203. value : '',
  41204. /**
  41205. * @cfg {String} alignment
  41206. * The position to align to (see {@link Ext.Element#alignTo} for more details).
  41207. */
  41208. alignment: 'c-c?',
  41209. /**
  41210. * @cfg {Number[]} offsets
  41211. * The offsets to use when aligning (see {@link Ext.Element#alignTo} for more details.
  41212. */
  41213. offsets: [0, 0],
  41214. /**
  41215. * @cfg {Boolean/String} shadow
  41216. * "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop" for bottom-right shadow.
  41217. */
  41218. shadow : 'frame',
  41219. /**
  41220. * @cfg {Boolean} constrain
  41221. * True to constrain the editor to the viewport
  41222. */
  41223. constrain : false,
  41224. /**
  41225. * @cfg {Boolean} swallowKeys
  41226. * Handle the keydown/keypress events so they don't propagate
  41227. */
  41228. swallowKeys : true,
  41229. /**
  41230. * @cfg {Boolean} completeOnEnter
  41231. * True to complete the edit when the enter key is pressed.
  41232. */
  41233. completeOnEnter : true,
  41234. /**
  41235. * @cfg {Boolean} cancelOnEsc
  41236. * True to cancel the edit when the escape key is pressed.
  41237. */
  41238. cancelOnEsc : true,
  41239. /**
  41240. * @cfg {Boolean} updateEl
  41241. * True to update the innerHTML of the bound element when the update completes
  41242. */
  41243. updateEl : false,
  41244. /**
  41245. * @cfg {String/HTMLElement/Ext.Element} [parentEl=document.body]
  41246. * An element to render to.
  41247. */
  41248. // private overrides
  41249. hidden: true,
  41250. baseCls: Ext.baseCSSPrefix + 'editor',
  41251. initComponent : function() {
  41252. var me = this,
  41253. field = me.field = Ext.ComponentManager.create(me.field, 'textfield');
  41254. Ext.apply(field, {
  41255. inEditor: true,
  41256. msgTarget: field.msgTarget == 'title' ? 'title' : 'qtip'
  41257. });
  41258. me.mon(field, {
  41259. scope: me,
  41260. blur: {
  41261. fn: me.onFieldBlur,
  41262. // slight delay to avoid race condition with startEdits (e.g. grid view refresh)
  41263. delay: 1
  41264. },
  41265. specialkey: me.onSpecialKey
  41266. });
  41267. if (field.grow) {
  41268. me.mon(field, 'autosize', me.onFieldAutosize, me, {delay: 1});
  41269. }
  41270. me.floating = {
  41271. constrain: me.constrain
  41272. };
  41273. me.items = field;
  41274. me.callParent(arguments);
  41275. me.addEvents(
  41276. /**
  41277. * @event beforestartedit
  41278. * Fires when editing is initiated, but before the value changes. Editing can be canceled by returning
  41279. * false from the handler of this event.
  41280. * @param {Ext.Editor} this
  41281. * @param {Ext.Element} boundEl The underlying element bound to this editor
  41282. * @param {Object} value The field value being set
  41283. */
  41284. 'beforestartedit',
  41285. /**
  41286. * @event startedit
  41287. * Fires when this editor is displayed
  41288. * @param {Ext.Editor} this
  41289. * @param {Ext.Element} boundEl The underlying element bound to this editor
  41290. * @param {Object} value The starting field value
  41291. */
  41292. 'startedit',
  41293. /**
  41294. * @event beforecomplete
  41295. * Fires after a change has been made to the field, but before the change is reflected in the underlying
  41296. * field. Saving the change to the field can be canceled by returning false from the handler of this event.
  41297. * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
  41298. * event will not fire since no edit actually occurred.
  41299. * @param {Ext.Editor} this
  41300. * @param {Object} value The current field value
  41301. * @param {Object} startValue The original field value
  41302. */
  41303. 'beforecomplete',
  41304. /**
  41305. * @event complete
  41306. * Fires after editing is complete and any changed value has been written to the underlying field.
  41307. * @param {Ext.Editor} this
  41308. * @param {Object} value The current field value
  41309. * @param {Object} startValue The original field value
  41310. */
  41311. 'complete',
  41312. /**
  41313. * @event canceledit
  41314. * Fires after editing has been canceled and the editor's value has been reset.
  41315. * @param {Ext.Editor} this
  41316. * @param {Object} value The user-entered field value that was discarded
  41317. * @param {Object} startValue The original field value that was set back into the editor after cancel
  41318. */
  41319. 'canceledit',
  41320. /**
  41321. * @event specialkey
  41322. * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. You can check
  41323. * {@link Ext.EventObject#getKey} to determine which key was pressed.
  41324. * @param {Ext.Editor} this
  41325. * @param {Ext.form.field.Field} field The field attached to this editor
  41326. * @param {Ext.EventObject} event The event object
  41327. */
  41328. 'specialkey'
  41329. );
  41330. },
  41331. // private
  41332. onFieldAutosize: function(){
  41333. this.updateLayout();
  41334. },
  41335. // private
  41336. afterRender : function(ct, position) {
  41337. var me = this,
  41338. field = me.field,
  41339. inputEl = field.inputEl;
  41340. me.callParent(arguments);
  41341. // Ensure the field doesn't get submitted as part of any form
  41342. if (inputEl) {
  41343. inputEl.dom.name = '';
  41344. if (me.swallowKeys) {
  41345. inputEl.swallowEvent([
  41346. 'keypress', // *** Opera
  41347. 'keydown' // *** all other browsers
  41348. ]);
  41349. }
  41350. }
  41351. },
  41352. // private
  41353. onSpecialKey : function(field, event) {
  41354. var me = this,
  41355. key = event.getKey(),
  41356. complete = me.completeOnEnter && key == event.ENTER,
  41357. cancel = me.cancelOnEsc && key == event.ESC;
  41358. if (complete || cancel) {
  41359. event.stopEvent();
  41360. // Must defer this slightly to prevent exiting edit mode before the field's own
  41361. // key nav can handle the enter key, e.g. selecting an item in a combobox list
  41362. Ext.defer(function() {
  41363. if (complete) {
  41364. me.completeEdit();
  41365. } else {
  41366. me.cancelEdit();
  41367. }
  41368. if (field.triggerBlur) {
  41369. field.triggerBlur(event);
  41370. }
  41371. }, 10);
  41372. }
  41373. me.fireEvent('specialkey', me, field, event);
  41374. },
  41375. /**
  41376. * Starts the editing process and shows the editor.
  41377. * @param {String/HTMLElement/Ext.Element} el The element to edit
  41378. * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
  41379. * to the innerHTML of el.
  41380. */
  41381. startEdit : function(el, value) {
  41382. var me = this,
  41383. field = me.field;
  41384. me.completeEdit();
  41385. me.boundEl = Ext.get(el);
  41386. value = Ext.isDefined(value) ? value : Ext.String.trim(me.boundEl.dom.innerText || me.boundEl.dom.innerHTML);
  41387. if (!me.rendered) {
  41388. me.render(me.parentEl || document.body);
  41389. }
  41390. if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
  41391. me.startValue = value;
  41392. me.show();
  41393. // temporarily suspend events on field to prevent the "change" event from firing when reset() and setValue() are called
  41394. field.suspendEvents();
  41395. field.reset();
  41396. field.setValue(value);
  41397. field.resumeEvents();
  41398. me.realign(true);
  41399. field.focus(false, 10);
  41400. if (field.autoSize) {
  41401. field.autoSize();
  41402. }
  41403. me.editing = true;
  41404. }
  41405. },
  41406. /**
  41407. * Realigns the editor to the bound field based on the current alignment config value.
  41408. * @param {Boolean} autoSize (optional) True to size the field to the dimensions of the bound element.
  41409. */
  41410. realign : function(autoSize) {
  41411. var me = this;
  41412. if (autoSize === true) {
  41413. me.updateLayout();
  41414. }
  41415. me.alignTo(me.boundEl, me.alignment, me.offsets);
  41416. },
  41417. /**
  41418. * Ends the editing process, persists the changed value to the underlying field, and hides the editor.
  41419. * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after edit
  41420. */
  41421. completeEdit : function(remainVisible) {
  41422. var me = this,
  41423. field = me.field,
  41424. value;
  41425. if (!me.editing) {
  41426. return;
  41427. }
  41428. // Assert combo values first
  41429. if (field.assertValue) {
  41430. field.assertValue();
  41431. }
  41432. value = me.getValue();
  41433. if (!field.isValid()) {
  41434. if (me.revertInvalid !== false) {
  41435. me.cancelEdit(remainVisible);
  41436. }
  41437. return;
  41438. }
  41439. if (String(value) === String(me.startValue) && me.ignoreNoChange) {
  41440. me.hideEdit(remainVisible);
  41441. return;
  41442. }
  41443. if (me.fireEvent('beforecomplete', me, value, me.startValue) !== false) {
  41444. // Grab the value again, may have changed in beforecomplete
  41445. value = me.getValue();
  41446. if (me.updateEl && me.boundEl) {
  41447. me.boundEl.update(value);
  41448. }
  41449. me.hideEdit(remainVisible);
  41450. me.fireEvent('complete', me, value, me.startValue);
  41451. }
  41452. },
  41453. // private
  41454. onShow : function() {
  41455. var me = this;
  41456. me.callParent(arguments);
  41457. if (me.hideEl !== false) {
  41458. me.boundEl.hide();
  41459. }
  41460. me.fireEvent('startedit', me, me.boundEl, me.startValue);
  41461. },
  41462. /**
  41463. * Cancels the editing process and hides the editor without persisting any changes. The field value will be
  41464. * reverted to the original starting value.
  41465. * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after cancel
  41466. */
  41467. cancelEdit : function(remainVisible) {
  41468. var me = this,
  41469. startValue = me.startValue,
  41470. field = me.field,
  41471. value;
  41472. if (me.editing) {
  41473. value = me.getValue();
  41474. // temporarily suspend events on field to prevent the "change" event from firing when setValue() is called
  41475. field.suspendEvents();
  41476. me.setValue(startValue);
  41477. field.resumeEvents();
  41478. me.hideEdit(remainVisible);
  41479. me.fireEvent('canceledit', me, value, startValue);
  41480. }
  41481. },
  41482. // private
  41483. hideEdit: function(remainVisible) {
  41484. if (remainVisible !== true) {
  41485. this.editing = false;
  41486. this.hide();
  41487. }
  41488. },
  41489. // private
  41490. onFieldBlur : function(field, e) {
  41491. var me = this,
  41492. target;
  41493. // selectSameEditor flag allows the same editor to be started without onFieldBlur firing on itself
  41494. if(me.allowBlur === true && me.editing && me.selectSameEditor !== true) {
  41495. me.completeEdit();
  41496. }
  41497. // If the target of the event was focusable, prevent reacquisition of focus by editor owner
  41498. if (e && Ext.fly(target = e.getTarget()).focusable()) {
  41499. target.focus();
  41500. }
  41501. },
  41502. // private
  41503. onHide : function() {
  41504. var me = this,
  41505. field = me.field;
  41506. if (me.editing) {
  41507. me.completeEdit();
  41508. return;
  41509. }
  41510. if (field.hasFocus) {
  41511. field.blur();
  41512. }
  41513. if (field.collapse) {
  41514. field.collapse();
  41515. }
  41516. //field.hide();
  41517. if (me.hideEl !== false) {
  41518. me.boundEl.show();
  41519. }
  41520. me.callParent(arguments);
  41521. },
  41522. /**
  41523. * Sets the data value of the editor
  41524. * @param {Object} value Any valid value supported by the underlying field
  41525. */
  41526. setValue : function(value) {
  41527. this.field.setValue(value);
  41528. },
  41529. /**
  41530. * Gets the data value of the editor
  41531. * @return {Object} The data value
  41532. */
  41533. getValue : function() {
  41534. return this.field.getValue();
  41535. },
  41536. beforeDestroy : function() {
  41537. var me = this;
  41538. Ext.destroy(me.field);
  41539. delete me.field;
  41540. delete me.parentEl;
  41541. delete me.boundEl;
  41542. me.callParent(arguments);
  41543. }
  41544. });
  41545. /**
  41546. * Handles mapping key events to handling functions for an element or a Component. One KeyMap can be used for multiple
  41547. * actions.
  41548. *
  41549. * A KeyMap must be configured with a {@link #target} as an event source which may be an Element or a Component.
  41550. *
  41551. * If the target is an element, then the `keydown` event will trigger the invocation of {@link #binding}s.
  41552. *
  41553. * It is possible to configure the KeyMap with a custom {@link #eventName} to listen for. This may be useful when the
  41554. * {@link #target} is a Component.
  41555. *
  41556. * The KeyMap's event handling requires that the first parameter passed is a key event. So if the Component's event
  41557. * signature is different, specify a {@link #processEvent} configuration which accepts the event's parameters and
  41558. * returns a key event.
  41559. *
  41560. * Functions specified in {@link #binding}s are called with this signature : `(String key, Ext.EventObject e)` (if the
  41561. * match is a multi-key combination the callback will still be called only once). A KeyMap can also handle a string
  41562. * representation of keys. By default KeyMap starts enabled.
  41563. *
  41564. * Usage:
  41565. *
  41566. * // map one key by key code
  41567. * var map = new Ext.util.KeyMap({
  41568. * target: "my-element",
  41569. * key: 13, // or Ext.EventObject.ENTER
  41570. * fn: myHandler,
  41571. * scope: myObject
  41572. * });
  41573. *
  41574. * // map multiple keys to one action by string
  41575. * var map = new Ext.util.KeyMap({
  41576. * target: "my-element",
  41577. * key: "a\r\n\t",
  41578. * fn: myHandler,
  41579. * scope: myObject
  41580. * });
  41581. *
  41582. * // map multiple keys to multiple actions by strings and array of codes
  41583. * var map = new Ext.util.KeyMap({
  41584. * target: "my-element",
  41585. * binding: [{
  41586. * key: [10,13],
  41587. * fn: function(){ alert("Return was pressed"); }
  41588. * }, {
  41589. * key: "abc",
  41590. * fn: function(){ alert('a, b or c was pressed'); }
  41591. * }, {
  41592. * key: "\t",
  41593. * ctrl:true,
  41594. * shift:true,
  41595. * fn: function(){ alert('Control + shift + tab was pressed.'); }
  41596. * }]
  41597. * });
  41598. *
  41599. * Since 4.1.0, KeyMaps can bind to Components and process key-based events fired by Components.
  41600. *
  41601. * To bind to a Component, use the single parameter form of constructor and include the Component event name
  41602. * to listen for, and a `processEvent` implementation which returns the key event for further processing by
  41603. * the KeyMap:
  41604. *
  41605. * var map = new Ext.util.KeyMap({
  41606. * target: myGridView,
  41607. * eventName: 'itemkeydown',
  41608. * processEvent: function(view, record, node, index, event) {
  41609. *
  41610. * // Load the event with the extra information needed by the mappings
  41611. * event.view = view;
  41612. * event.store = view.getStore();
  41613. * event.record = record;
  41614. * event.index = index;
  41615. * return event;
  41616. * },
  41617. * binding: {
  41618. * key: Ext.EventObject.DELETE,
  41619. * fn: function(keyCode, e) {
  41620. * e.store.remove(e.record);
  41621. *
  41622. * // Attempt to select the record that's now in its place
  41623. * e.view.getSelectionModel().select(e.index);
  41624. * e.view.el.focus();
  41625. * }
  41626. * }
  41627. * });
  41628. */
  41629. Ext.define('Ext.util.KeyMap', {
  41630. alternateClassName: 'Ext.KeyMap',
  41631. /**
  41632. * @cfg {Ext.Component/Ext.Element/HTMLElement/String} target
  41633. * The object on which to listen for the event specified by the {@link #eventName} config option.
  41634. */
  41635. /**
  41636. * @cfg {Object/Object[][]} binding
  41637. * Either a single object describing a handling function for s specified key (or set of keys), or
  41638. * an array of such objects.
  41639. * @cfg {String/String[]} binding.key A single keycode or an array of keycodes to handle
  41640. * @cfg {Boolean} binding.shift True to handle key only when shift is pressed, False to handle the
  41641. * key only when shift is not pressed (defaults to undefined)
  41642. * @cfg {Boolean} binding.ctrl True to handle key only when ctrl is pressed, False to handle the
  41643. * key only when ctrl is not pressed (defaults to undefined)
  41644. * @cfg {Boolean} binding.alt True to handle key only when alt is pressed, False to handle the key
  41645. * only when alt is not pressed (defaults to undefined)
  41646. * @cfg {Function} binding.handler The function to call when KeyMap finds the expected key combination
  41647. * @cfg {Function} binding.fn Alias of handler (for backwards-compatibility)
  41648. * @cfg {Object} binding.scope The scope of the callback function
  41649. * @cfg {String} binding.defaultEventAction A default action to apply to the event. Possible values
  41650. * are: stopEvent, stopPropagation, preventDefault. If no value is set no action is performed.
  41651. */
  41652. /**
  41653. * @cfg {Object} [processEventScope=this]
  41654. * The scope (`this` context) in which the {@link #processEvent} method is executed.
  41655. */
  41656. /**
  41657. * @cfg {Boolean} [ignoreInputFields=false]
  41658. * Configure this as `true` if there are any input fields within the {@link #target}, and this KeyNav
  41659. * should not process events from input fields, (`&lt;input>, &lt;textarea> and elements with `contentEditable="true"`)
  41660. */
  41661. /**
  41662. * @cfg {String} eventName
  41663. * The event to listen for to pick up key events.
  41664. */
  41665. eventName: 'keydown',
  41666. constructor: function(config) {
  41667. var me = this;
  41668. // Handle legacy arg list in which the first argument is the target.
  41669. // TODO: Deprecate in V5
  41670. if ((arguments.length !== 1) || (typeof config === 'string') || config.dom || config.tagName || config === document || config.isComponent) {
  41671. me.legacyConstructor.apply(me, arguments);
  41672. return;
  41673. }
  41674. Ext.apply(me, config);
  41675. me.bindings = [];
  41676. if (!me.target.isComponent) {
  41677. me.target = Ext.get(me.target);
  41678. }
  41679. if (me.binding) {
  41680. me.addBinding(me.binding);
  41681. } else if (config.key) {
  41682. me.addBinding(config);
  41683. }
  41684. me.enable();
  41685. },
  41686. /**
  41687. * @private
  41688. * Old constructor signature
  41689. * @param {String/HTMLElement/Ext.Element/Ext.Component} el The element or its ID, or Component to bind to
  41690. * @param {Object} binding The binding (see {@link #addBinding})
  41691. * @param {String} [eventName="keydown"] The event to bind to
  41692. */
  41693. legacyConstructor: function(el, binding, eventName){
  41694. var me = this;
  41695. Ext.apply(me, {
  41696. target: Ext.get(el),
  41697. eventName: eventName || me.eventName,
  41698. bindings: []
  41699. });
  41700. if (binding) {
  41701. me.addBinding(binding);
  41702. }
  41703. me.enable();
  41704. },
  41705. /**
  41706. * Add a new binding to this KeyMap.
  41707. *
  41708. * Usage:
  41709. *
  41710. * // Create a KeyMap
  41711. * var map = new Ext.util.KeyMap(document, {
  41712. * key: Ext.EventObject.ENTER,
  41713. * fn: handleKey,
  41714. * scope: this
  41715. * });
  41716. *
  41717. * //Add a new binding to the existing KeyMap later
  41718. * map.addBinding({
  41719. * key: 'abc',
  41720. * shift: true,
  41721. * fn: handleKey,
  41722. * scope: this
  41723. * });
  41724. *
  41725. * @param {Object/Object[]} binding A single KeyMap config or an array of configs.
  41726. * The following config object properties are supported:
  41727. * @param {String/Array} binding.key A single keycode or an array of keycodes to handle.
  41728. * @param {Boolean} binding.shift True to handle key only when shift is pressed,
  41729. * False to handle the keyonly when shift is not pressed (defaults to undefined).
  41730. * @param {Boolean} binding.ctrl True to handle key only when ctrl is pressed,
  41731. * False to handle the key only when ctrl is not pressed (defaults to undefined).
  41732. * @param {Boolean} binding.alt True to handle key only when alt is pressed,
  41733. * False to handle the key only when alt is not pressed (defaults to undefined).
  41734. * @param {Function} binding.handler The function to call when KeyMap finds the
  41735. * expected key combination.
  41736. * @param {Function} binding.fn Alias of handler (for backwards-compatibility).
  41737. * @param {Object} binding.scope The scope of the callback function.
  41738. * @param {String} binding.defaultEventAction A default action to apply to the event.
  41739. * Possible values are: stopEvent, stopPropagation, preventDefault. If no value is
  41740. * set no action is performed..
  41741. */
  41742. addBinding : function(binding){
  41743. var keyCode = binding.key,
  41744. processed = false,
  41745. key,
  41746. keys,
  41747. keyString,
  41748. i,
  41749. len;
  41750. if (Ext.isArray(binding)) {
  41751. for (i = 0, len = binding.length; i < len; i++) {
  41752. this.addBinding(binding[i]);
  41753. }
  41754. return;
  41755. }
  41756. if (Ext.isString(keyCode)) {
  41757. keys = [];
  41758. keyString = keyCode.toUpperCase();
  41759. for (i = 0, len = keyString.length; i < len; ++i){
  41760. keys.push(keyString.charCodeAt(i));
  41761. }
  41762. keyCode = keys;
  41763. processed = true;
  41764. }
  41765. if (!Ext.isArray(keyCode)) {
  41766. keyCode = [keyCode];
  41767. }
  41768. if (!processed) {
  41769. for (i = 0, len = keyCode.length; i < len; ++i) {
  41770. key = keyCode[i];
  41771. if (Ext.isString(key)) {
  41772. keyCode[i] = key.toUpperCase().charCodeAt(0);
  41773. }
  41774. }
  41775. }
  41776. this.bindings.push(Ext.apply({
  41777. keyCode: keyCode
  41778. }, binding));
  41779. },
  41780. /**
  41781. * Process the {@link #eventName event} from the {@link #target}.
  41782. * @private
  41783. * @param {Ext.EventObject} event
  41784. */
  41785. handleTargetEvent: (function() {
  41786. var tagRe = /input|textarea/i;
  41787. return function(event) {
  41788. var me = this,
  41789. bindings, i, len,
  41790. target, contentEditable;
  41791. if (this.enabled) { //just in case
  41792. bindings = this.bindings;
  41793. i = 0;
  41794. len = bindings.length;
  41795. // Process the event
  41796. event = me.processEvent.apply(me||me.processEventScope, arguments);
  41797. // Ignore events from input fields if configured to do so
  41798. if (me.ignoreInputFields) {
  41799. target = event.target;
  41800. contentEditable = target.contentEditable;
  41801. // contentEditable will default to inherit if not specified, only check if the
  41802. // attribute has been set or explicitly set to true
  41803. // http://html5doctor.com/the-contenteditable-attribute/
  41804. if (tagRe.test(target.tagName) || (contentEditable === '' || contentEditable === 'true')) {
  41805. return;
  41806. }
  41807. }
  41808. // If the processor does not return a keyEvent, we can't process it.
  41809. // Allow them to return false to cancel processing of the event
  41810. if (!event.getKey) {
  41811. return event;
  41812. }
  41813. for(; i < len; ++i){
  41814. this.processBinding(bindings[i], event);
  41815. }
  41816. }
  41817. }
  41818. }()),
  41819. /**
  41820. * @cfg {Function} processEvent
  41821. * An optional event processor function which accepts the argument list provided by the
  41822. * {@link #eventName configured event} of the {@link #target}, and returns a keyEvent for processing by the KeyMap.
  41823. *
  41824. * This may be useful when the {@link #target} is a Component with s complex event signature, where the event is not
  41825. * the first parameter. Extra information from the event arguments may be injected into the event for use by the handler
  41826. * functions before returning it.
  41827. */
  41828. processEvent: function(event){
  41829. return event;
  41830. },
  41831. /**
  41832. * Process a particular binding and fire the handler if necessary.
  41833. * @private
  41834. * @param {Object} binding The binding information
  41835. * @param {Ext.EventObject} event
  41836. */
  41837. processBinding: function(binding, event){
  41838. if (this.checkModifiers(binding, event)) {
  41839. var key = event.getKey(),
  41840. handler = binding.fn || binding.handler,
  41841. scope = binding.scope || this,
  41842. keyCode = binding.keyCode,
  41843. defaultEventAction = binding.defaultEventAction,
  41844. i,
  41845. len,
  41846. keydownEvent = new Ext.EventObjectImpl(event);
  41847. for (i = 0, len = keyCode.length; i < len; ++i) {
  41848. if (key === keyCode[i]) {
  41849. if (handler.call(scope, key, event) !== true && defaultEventAction) {
  41850. keydownEvent[defaultEventAction]();
  41851. }
  41852. break;
  41853. }
  41854. }
  41855. }
  41856. },
  41857. /**
  41858. * Check if the modifiers on the event match those on the binding
  41859. * @private
  41860. * @param {Object} binding
  41861. * @param {Ext.EventObject} event
  41862. * @return {Boolean} True if the event matches the binding
  41863. */
  41864. checkModifiers: function(binding, e) {
  41865. var keys = ['shift', 'ctrl', 'alt'],
  41866. i = 0,
  41867. len = keys.length,
  41868. val, key;
  41869. for (; i < len; ++i){
  41870. key = keys[i];
  41871. val = binding[key];
  41872. if (!(val === undefined || (val === e[key + 'Key']))) {
  41873. return false;
  41874. }
  41875. }
  41876. return true;
  41877. },
  41878. /**
  41879. * Shorthand for adding a single key listener.
  41880. *
  41881. * @param {Number/Number[]/Object} key Either the numeric key code, array of key codes or an object with the
  41882. * following options: `{key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}`
  41883. * @param {Function} fn The function to call
  41884. * @param {Object} [scope] The scope (`this` reference) in which the function is executed.
  41885. * Defaults to the browser window.
  41886. */
  41887. on: function(key, fn, scope) {
  41888. var keyCode, shift, ctrl, alt;
  41889. if (Ext.isObject(key) && !Ext.isArray(key)) {
  41890. keyCode = key.key;
  41891. shift = key.shift;
  41892. ctrl = key.ctrl;
  41893. alt = key.alt;
  41894. } else {
  41895. keyCode = key;
  41896. }
  41897. this.addBinding({
  41898. key: keyCode,
  41899. shift: shift,
  41900. ctrl: ctrl,
  41901. alt: alt,
  41902. fn: fn,
  41903. scope: scope
  41904. });
  41905. },
  41906. /**
  41907. * Returns true if this KeyMap is enabled
  41908. * @return {Boolean}
  41909. */
  41910. isEnabled : function() {
  41911. return this.enabled;
  41912. },
  41913. /**
  41914. * Enables this KeyMap
  41915. */
  41916. enable: function() {
  41917. var me = this;
  41918. if (!me.enabled) {
  41919. me.target.on(me.eventName, me.handleTargetEvent, me);
  41920. me.enabled = true;
  41921. }
  41922. },
  41923. /**
  41924. * Disable this KeyMap
  41925. */
  41926. disable: function() {
  41927. var me = this;
  41928. if (me.enabled) {
  41929. me.target.removeListener(me.eventName, me.handleTargetEvent, me);
  41930. me.enabled = false;
  41931. }
  41932. },
  41933. /**
  41934. * Convenience function for setting disabled/enabled by boolean.
  41935. * @param {Boolean} disabled
  41936. */
  41937. setDisabled : function(disabled) {
  41938. if (disabled) {
  41939. this.disable();
  41940. } else {
  41941. this.enable();
  41942. }
  41943. },
  41944. /**
  41945. * Destroys the KeyMap instance and removes all handlers.
  41946. * @param {Boolean} removeTarget True to also remove the {@link #target}
  41947. */
  41948. destroy: function(removeTarget) {
  41949. var me = this,
  41950. target = me.target;
  41951. me.bindings = [];
  41952. me.disable();
  41953. if (removeTarget === true) {
  41954. if (target.isComponent) {
  41955. target.destroy();
  41956. } else {
  41957. target.remove();
  41958. }
  41959. }
  41960. delete me.target;
  41961. }
  41962. });
  41963. /**
  41964. * Provides a convenient wrapper for normalized keyboard navigation. KeyNav allows you to bind navigation keys to
  41965. * function calls that will get called when the keys are pressed, providing an easy way to implement custom navigation
  41966. * schemes for any UI component.
  41967. *
  41968. * The following are all of the possible keys that can be implemented: enter, space, left, right, up, down, tab, esc,
  41969. * pageUp, pageDown, del, backspace, home, end.
  41970. *
  41971. * Usage:
  41972. *
  41973. * var nav = new Ext.util.KeyNav({
  41974. * target : "my-element",
  41975. * left : function(e){
  41976. * this.moveLeft(e.ctrlKey);
  41977. * },
  41978. * right : function(e){
  41979. * this.moveRight(e.ctrlKey);
  41980. * },
  41981. * enter : function(e){
  41982. * this.save();
  41983. * },
  41984. *
  41985. * // Binding may be a function specifiying fn, scope and defaultAction
  41986. * esc: {
  41987. * fn: this.onEsc,
  41988. * defaultEventAction: false
  41989. * },
  41990. * scope : this
  41991. * });
  41992. */
  41993. Ext.define('Ext.util.KeyNav', {
  41994. alternateClassName: 'Ext.KeyNav',
  41995. requires: ['Ext.util.KeyMap'],
  41996. statics: {
  41997. keyOptions: {
  41998. left: 37,
  41999. right: 39,
  42000. up: 38,
  42001. down: 40,
  42002. space: 32,
  42003. pageUp: 33,
  42004. pageDown: 34,
  42005. del: 46,
  42006. backspace: 8,
  42007. home: 36,
  42008. end: 35,
  42009. enter: 13,
  42010. esc: 27,
  42011. tab: 9
  42012. }
  42013. },
  42014. constructor: function(config) {
  42015. var me = this;
  42016. if (arguments.length === 2) {
  42017. me.legacyConstructor.apply(me, arguments);
  42018. return;
  42019. }
  42020. me.setConfig(config);
  42021. },
  42022. /**
  42023. * @private
  42024. * Old constructor signature.
  42025. * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
  42026. * @param {Object} config The config
  42027. */
  42028. legacyConstructor: function(el, config) {
  42029. this.setConfig(Ext.apply({
  42030. target: el
  42031. }, config));
  42032. },
  42033. /**
  42034. * Sets up a configuration for the KeyNav.
  42035. * @private
  42036. * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
  42037. * @param {Object} config A configuration object as specified in the constructor.
  42038. */
  42039. setConfig: function(config) {
  42040. var me = this,
  42041. keymapCfg = {
  42042. target: config.target,
  42043. ignoreInputFields: config.ignoreInputFields,
  42044. eventName: me.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : me.forceKeyDown, config.eventName)
  42045. },
  42046. map, keyCodes, defaultScope, keyName, binding;
  42047. if (me.map) {
  42048. me.map.destroy();
  42049. }
  42050. if (config.processEvent) {
  42051. keymapCfg.processEvent = config.processEvent;
  42052. keymapCfg.processEventScope = config.processEventScope||me;
  42053. }
  42054. map = me.map = new Ext.util.KeyMap(keymapCfg);
  42055. keyCodes = Ext.util.KeyNav.keyOptions;
  42056. defaultScope = config.scope || me;
  42057. for (keyName in keyCodes) {
  42058. if (keyCodes.hasOwnProperty(keyName)) {
  42059. // There is a property named after a key name.
  42060. // It may be a function or an binding spec containing handler, scope and defaultAction configs
  42061. if (binding = config[keyName]) {
  42062. if (typeof binding === 'function') {
  42063. binding = {
  42064. handler: binding,
  42065. defaultAction: (config.defaultEventAction !== undefined) ? config.defaultEventAction : me.defaultEventAction
  42066. };
  42067. }
  42068. map.addBinding({
  42069. key: keyCodes[keyName],
  42070. handler: Ext.Function.bind(me.handleEvent, binding.scope||defaultScope, binding.handler||binding.fn, true),
  42071. defaultEventAction: (binding.defaultEventAction !== undefined) ? binding.defaultAction : me.defaultEventAction
  42072. });
  42073. }
  42074. }
  42075. }
  42076. map.disable();
  42077. if (!config.disabled) {
  42078. map.enable();
  42079. }
  42080. },
  42081. /**
  42082. * Method for filtering out the map argument
  42083. * @private
  42084. * @param {Number} keyCode
  42085. * @param {Ext.EventObject} event
  42086. * @param {Object} options Contains the handler to call
  42087. */
  42088. handleEvent: function(keyCode, event, handler){
  42089. return handler.call(this, event);
  42090. },
  42091. /**
  42092. * @cfg {Boolean} disabled
  42093. * True to disable this KeyNav instance.
  42094. */
  42095. disabled: false,
  42096. /**
  42097. * @cfg {String} defaultEventAction
  42098. * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key. Valid values are {@link
  42099. * Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and {@link Ext.EventObject#stopPropagation}.
  42100. *
  42101. * If a falsy value is specified, no method is called on the key event.
  42102. */
  42103. defaultEventAction: "stopEvent",
  42104. /**
  42105. * @cfg {Boolean} forceKeyDown
  42106. * Handle the keydown event instead of keypress. KeyNav automatically does this for IE since IE does not propagate
  42107. * special keys on keypress, but setting this to true will force other browsers to also handle keydown instead of
  42108. * keypress.
  42109. */
  42110. forceKeyDown: false,
  42111. /**
  42112. * @cfg {Ext.Component/Ext.Element/HTMLElement/String} target
  42113. * The object on which to listen for the event specified by the {@link #eventName} config option.
  42114. */
  42115. /**
  42116. * @cfg {String} eventName
  42117. * The event to listen for to pick up key events.
  42118. */
  42119. eventName: 'keypress',
  42120. /**
  42121. * @cfg {Function} processEvent
  42122. * An optional event processor function which accepts the argument list provided by the {@link #eventName configured
  42123. * event} of the {@link #target}, and returns a keyEvent for processing by the KeyMap.
  42124. *
  42125. * This may be useful when the {@link #target} is a Component with s complex event signature. Extra information from
  42126. * the event arguments may be injected into the event for use by the handler functions before returning it.
  42127. */
  42128. /**
  42129. * @cfg {Object} [processEventScope=this]
  42130. * The scope (`this` context) in which the {@link #processEvent} method is executed.
  42131. */
  42132. /**
  42133. * @cfg {Boolean} [ignoreInputFields=false]
  42134. * Configure this as `true` if there are any input fields within the {@link #target}, and this KeyNav
  42135. * should not process events from input fields, (`&lt;input>, &lt;textarea> and elements with `contentEditable="true"`)
  42136. */
  42137. /**
  42138. * Destroy this KeyNav (this is the same as calling disable).
  42139. * @param {Boolean} removeEl True to remove the element associated with this KeyNav.
  42140. */
  42141. destroy: function(removeEl) {
  42142. this.map.destroy(removeEl);
  42143. delete this.map;
  42144. },
  42145. /**
  42146. * Enables this KeyNav.
  42147. */
  42148. enable: function() {
  42149. this.map.enable();
  42150. this.disabled = false;
  42151. },
  42152. /**
  42153. * Disables this KeyNav.
  42154. */
  42155. disable: function() {
  42156. this.map.disable();
  42157. this.disabled = true;
  42158. },
  42159. /**
  42160. * Convenience function for setting disabled/enabled by boolean.
  42161. * @param {Boolean} disabled
  42162. */
  42163. setDisabled : function(disabled) {
  42164. this.map.setDisabled(disabled);
  42165. this.disabled = disabled;
  42166. },
  42167. /**
  42168. * @private
  42169. * Determines the event to bind to listen for keys. Defaults to the {@link #eventName} value, but
  42170. * may be overridden the {@link #forceKeyDown} setting.
  42171. *
  42172. * The useKeyDown option on the EventManager modifies the default {@link #eventName} to be `keydown`,
  42173. * but a configured {@link #eventName} takes priority over this.
  42174. *
  42175. * @return {String} The type of event to listen for.
  42176. */
  42177. getKeyEvent: function(forceKeyDown, configuredEventName) {
  42178. if (forceKeyDown || (Ext.EventManager.useKeyDown && !configuredEventName)) {
  42179. return 'keydown';
  42180. } else {
  42181. return configuredEventName||this.eventName;
  42182. }
  42183. }
  42184. });
  42185. /**
  42186. * The FocusManager is responsible for globally:
  42187. *
  42188. * 1. Managing component focus
  42189. * 2. Providing basic keyboard navigation
  42190. * 3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
  42191. *
  42192. * To activate the FocusManager, simply call `Ext.FocusManager.enable();`. In turn, you may
  42193. * deactivate the FocusManager by subsequently calling `Ext.FocusManager.disable();`. The
  42194. * FocusManager is disabled by default.
  42195. *
  42196. * To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #method-enable}.
  42197. *
  42198. * Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
  42199. * that would like to have navigation between its child {@link Ext.Component}'s.
  42200. *
  42201. * @author Jarred Nicholls <jarred@sencha.com>
  42202. * @docauthor Jarred Nicholls <jarred@sencha.com>
  42203. */
  42204. Ext.define('Ext.FocusManager', {
  42205. singleton: true,
  42206. alternateClassName: ['Ext.FocusMgr' ],
  42207. mixins: {
  42208. observable: 'Ext.util.Observable'
  42209. },
  42210. requires: [
  42211. 'Ext.AbstractComponent',
  42212. 'Ext.Component',
  42213. 'Ext.ComponentManager',
  42214. 'Ext.ComponentQuery',
  42215. 'Ext.util.HashMap',
  42216. 'Ext.util.KeyNav'
  42217. ],
  42218. /**
  42219. * @property {Boolean} enabled
  42220. * Whether or not the FocusManager is currently enabled
  42221. */
  42222. enabled: false,
  42223. /**
  42224. * @property {Ext.Component} focusedCmp
  42225. * The currently focused component.
  42226. */
  42227. focusElementCls: Ext.baseCSSPrefix + 'focus-element',
  42228. focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
  42229. /**
  42230. * @property {String[]} whitelist
  42231. * A list of xtypes that should ignore certain navigation input keys and
  42232. * allow for the default browser event/behavior. These input keys include:
  42233. *
  42234. * 1. Backspace
  42235. * 2. Delete
  42236. * 3. Left
  42237. * 4. Right
  42238. * 5. Up
  42239. * 6. Down
  42240. *
  42241. * The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
  42242. * that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
  42243. * the user to move the input cursor left and right, and to delete characters, etc.
  42244. */
  42245. whitelist: [
  42246. 'textfield'
  42247. ],
  42248. constructor: function(config) {
  42249. var me = this,
  42250. CQ = Ext.ComponentQuery;
  42251. me.mixins.observable.constructor.call(me, config);
  42252. me.addEvents(
  42253. /**
  42254. * @event beforecomponentfocus
  42255. * Fires before a component becomes focused. Return `false` to prevent
  42256. * the component from gaining focus.
  42257. * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
  42258. * @param {Ext.Component} cmp The component that is being focused
  42259. * @param {Ext.Component} previousCmp The component that was previously focused,
  42260. * or `undefined` if there was no previously focused component.
  42261. */
  42262. 'beforecomponentfocus',
  42263. /**
  42264. * @event componentfocus
  42265. * Fires after a component becomes focused.
  42266. * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
  42267. * @param {Ext.Component} cmp The component that has been focused
  42268. * @param {Ext.Component} previousCmp The component that was previously focused,
  42269. * or `undefined` if there was no previously focused component.
  42270. */
  42271. 'componentfocus',
  42272. /**
  42273. * @event disable
  42274. * Fires when the FocusManager is disabled
  42275. * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
  42276. */
  42277. 'disable',
  42278. /**
  42279. * @event enable
  42280. * Fires when the FocusManager is enabled
  42281. * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
  42282. */
  42283. 'enable'
  42284. );
  42285. me.focusTask = new Ext.util.DelayedTask(me.handleComponentFocus, me);
  42286. // Gain control on Component focus, blur, hide and destroy
  42287. Ext.override(Ext.AbstractComponent, {
  42288. onFocus: function() {
  42289. this.callParent(arguments);
  42290. if (me.enabled && this.hasFocus) {
  42291. Array.prototype.unshift.call(arguments, this);
  42292. me.onComponentFocus.apply(me, arguments);
  42293. }
  42294. },
  42295. onBlur: function() {
  42296. this.callParent(arguments);
  42297. if (me.enabled && !this.hasFocus) {
  42298. Array.prototype.unshift.call(arguments, this);
  42299. me.onComponentBlur.apply(me, arguments);
  42300. }
  42301. },
  42302. onDestroy: function() {
  42303. this.callParent(arguments);
  42304. if (me.enabled) {
  42305. Array.prototype.unshift.call(arguments, this);
  42306. me.onComponentDestroy.apply(me, arguments);
  42307. }
  42308. }
  42309. });
  42310. Ext.override(Ext.Component, {
  42311. afterHide: function() {
  42312. this.callParent(arguments);
  42313. if (me.enabled) {
  42314. Array.prototype.unshift.call(arguments, this);
  42315. me.onComponentHide.apply(me, arguments);
  42316. }
  42317. }
  42318. });
  42319. // Setup KeyNav that's bound to document to catch all
  42320. // unhandled/bubbled key events for navigation
  42321. me.keyNav = new Ext.util.KeyNav(Ext.getDoc(), {
  42322. disabled: true,
  42323. scope: me,
  42324. backspace: me.focusLast,
  42325. enter: me.navigateIn,
  42326. esc: me.navigateOut,
  42327. tab: me.navigateSiblings,
  42328. space: me.navigateIn,
  42329. del: me.focusLast,
  42330. left: me.navigateSiblings,
  42331. right: me.navigateSiblings,
  42332. down: me.navigateSiblings,
  42333. up: me.navigateSiblings
  42334. });
  42335. me.focusData = {};
  42336. me.subscribers = new Ext.util.HashMap();
  42337. me.focusChain = {};
  42338. // Setup some ComponentQuery pseudos
  42339. Ext.apply(CQ.pseudos, {
  42340. focusable: function(cmps) {
  42341. var len = cmps.length,
  42342. results = [],
  42343. i = 0,
  42344. c;
  42345. for (; i < len; i++) {
  42346. c = cmps[i];
  42347. if (c.isFocusable()) {
  42348. results.push(c);
  42349. }
  42350. }
  42351. return results;
  42352. },
  42353. // Return the single next focusable sibling from the current idx in either direction (step -1 or 1)
  42354. nextFocus: function(cmps, idx, step) {
  42355. step = step || 1;
  42356. idx = parseInt(idx, 10);
  42357. var len = cmps.length,
  42358. i = idx, c;
  42359. for (;;) {
  42360. // Increment index, and loop round if off either end
  42361. if ((i += step) >= len) {
  42362. i = 0;
  42363. } else if (i < 0) {
  42364. i = len - 1;
  42365. }
  42366. // As soon as we loop back to the starting index, give up, there are no focusable siblings.
  42367. if (i === idx) {
  42368. return [];
  42369. }
  42370. // If we have found a focusable sibling, return it
  42371. if ((c = cmps[i]).isFocusable()) {
  42372. return [c];
  42373. }
  42374. }
  42375. return [];
  42376. },
  42377. prevFocus: function(cmps, idx) {
  42378. return this.nextFocus(cmps, idx, -1);
  42379. },
  42380. root: function(cmps) {
  42381. var len = cmps.length,
  42382. results = [],
  42383. i = 0,
  42384. c;
  42385. for (; i < len; i++) {
  42386. c = cmps[i];
  42387. if (!c.ownerCt) {
  42388. results.push(c);
  42389. }
  42390. }
  42391. return results;
  42392. }
  42393. });
  42394. },
  42395. /**
  42396. * Adds the specified xtype to the {@link #whitelist}.
  42397. * @param {String/String[]} xtype Adds the xtype(s) to the {@link #whitelist}.
  42398. */
  42399. addXTypeToWhitelist: function(xtype) {
  42400. var me = this;
  42401. if (Ext.isArray(xtype)) {
  42402. Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
  42403. return;
  42404. }
  42405. if (!Ext.Array.contains(me.whitelist, xtype)) {
  42406. me.whitelist.push(xtype);
  42407. }
  42408. },
  42409. clearComponent: function(cmp) {
  42410. clearTimeout(this.cmpFocusDelay);
  42411. if (!cmp.isDestroyed) {
  42412. cmp.blur();
  42413. }
  42414. },
  42415. /**
  42416. * Disables the FocusManager by turning of all automatic focus management and keyboard navigation
  42417. */
  42418. disable: function() {
  42419. var me = this;
  42420. if (!me.enabled) {
  42421. return;
  42422. }
  42423. delete me.options;
  42424. me.enabled = false;
  42425. me.removeDOM();
  42426. // Stop handling key navigation
  42427. me.keyNav.disable();
  42428. me.fireEvent('disable', me);
  42429. },
  42430. /**
  42431. * Enables the FocusManager by turning on all automatic focus management and keyboard navigation
  42432. * @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object
  42433. * with the following options:
  42434. * @param {Boolean} [focusFrame=false] `true` to show the focus frame around a component when it is focused.
  42435. */
  42436. enable: function(options) {
  42437. var me = this;
  42438. if (options === true) {
  42439. options = { focusFrame: true };
  42440. }
  42441. me.options = options = options || {};
  42442. if (me.enabled) {
  42443. return;
  42444. }
  42445. // When calling addFocusListener on Containers, the FocusManager must be enabled, otherwise it won't do it.
  42446. me.enabled = true;
  42447. me.initDOM(options);
  42448. // Start handling key navigation
  42449. me.keyNav.enable();
  42450. // Finally, let's focus our global focus el so we start fresh
  42451. me.focusEl.focus();
  42452. delete me.focusedCmp;
  42453. me.fireEvent('enable', me);
  42454. },
  42455. focusLast: function(e) {
  42456. var me = this;
  42457. if (me.isWhitelisted(me.focusedCmp)) {
  42458. return true;
  42459. }
  42460. // Go back to last focused item
  42461. if (me.previousFocusedCmp) {
  42462. me.previousFocusedCmp.focus();
  42463. }
  42464. },
  42465. getRootComponents: function() {
  42466. var me = this,
  42467. CQ = Ext.ComponentQuery,
  42468. inline = CQ.query(':focusable:root:not([floating])'),
  42469. floating = CQ.query(':focusable:root[floating]');
  42470. // Floating items should go to the top of our root stack, and be ordered
  42471. // by their z-index (highest first)
  42472. floating.sort(function(a, b) {
  42473. return a.el.getZIndex() > b.el.getZIndex();
  42474. });
  42475. return floating.concat(inline);
  42476. },
  42477. initDOM: function(options) {
  42478. var me = this,
  42479. cls = me.focusFrameCls,
  42480. needListeners = Ext.ComponentQuery.query('{getFocusEl()}:not([focusListenerAdded])'),
  42481. i = 0, len = needListeners.length;
  42482. if (!Ext.isReady) {
  42483. return Ext.onReady(me.initDOM, me);
  42484. }
  42485. // When we are enabled, we must ensure that all Components which return a focusEl that is *not naturally focusable*
  42486. // have focus/blur listeners enabled to then trigger onFocus/onBlur handling so that we get to know about their focus action.
  42487. // These listeners are not added at initialization unless the FocusManager is enabled at that time.
  42488. for (; i < len; i++) {
  42489. needListeners[i].addFocusListener();
  42490. }
  42491. // Make the document body the global focus element
  42492. if (!me.focusEl) {
  42493. me.focusEl = Ext.getBody();
  42494. me.focusEl.dom.tabIndex = -1;
  42495. }
  42496. // Create global focus frame
  42497. if (!me.focusFrame && options.focusFrame) {
  42498. me.focusFrame = Ext.getBody().createChild({
  42499. cls: cls,
  42500. children: [
  42501. { cls: cls + '-top' },
  42502. { cls: cls + '-bottom' },
  42503. { cls: cls + '-left' },
  42504. { cls: cls + '-right' }
  42505. ],
  42506. style: 'top: -100px; left: -100px;'
  42507. });
  42508. me.focusFrame.setVisibilityMode(Ext.Element.DISPLAY);
  42509. me.focusFrame.hide().setLeftTop(0, 0);
  42510. }
  42511. },
  42512. isWhitelisted: function(cmp) {
  42513. return cmp && Ext.Array.some(this.whitelist, function(x) {
  42514. return cmp.isXType(x);
  42515. });
  42516. },
  42517. navigateIn: function(e) {
  42518. var me = this,
  42519. focusedCmp = me.focusedCmp,
  42520. defaultRoot,
  42521. firstChild;
  42522. if (me.isWhitelisted(focusedCmp)) {
  42523. return true;
  42524. }
  42525. if (!focusedCmp) {
  42526. // No focus yet, so focus the first root cmp on the page
  42527. defaultRoot = me.getRootComponents()[0];
  42528. if (defaultRoot) {
  42529. // If the default root is based upon the body, then it will already be focused, and will not fire a focus event to
  42530. // trigger its own onFocus processing, so we have to programatically blur it first.
  42531. if (defaultRoot.getFocusEl() === me.focusEl) {
  42532. me.focusEl.blur();
  42533. }
  42534. defaultRoot.focus();
  42535. }
  42536. } else {
  42537. // Drill into child ref items of the focused cmp, if applicable.
  42538. // This works for any Component with a getRefItems implementation.
  42539. firstChild = focusedCmp.hasFocus ? Ext.ComponentQuery.query('>:focusable', focusedCmp)[0] : focusedCmp;
  42540. if (firstChild) {
  42541. firstChild.focus();
  42542. } else {
  42543. // Let's try to fire a click event, as if it came from the mouse
  42544. if (Ext.isFunction(focusedCmp.onClick)) {
  42545. e.button = 0;
  42546. focusedCmp.onClick(e);
  42547. if (focusedCmp.isVisible(true)) {
  42548. focusedCmp.focus();
  42549. } else {
  42550. me.navigateOut();
  42551. }
  42552. }
  42553. }
  42554. }
  42555. },
  42556. navigateOut: function(e) {
  42557. var me = this,
  42558. parent;
  42559. if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
  42560. me.focusEl.focus();
  42561. } else {
  42562. parent.focus();
  42563. }
  42564. // In some browsers (Chrome) FocusManager can handle this before other
  42565. // handlers. Ext Windows have their own Esc key handling, so we need to
  42566. // return true here to allow the event to bubble.
  42567. return true;
  42568. },
  42569. navigateSiblings: function(e, source, parent) {
  42570. var me = this,
  42571. src = source || me,
  42572. key = e.getKey(),
  42573. EO = Ext.EventObject,
  42574. goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
  42575. checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
  42576. nextSelector = goBack ? 'prev' : 'next',
  42577. idx, next, focusedCmp, siblings;
  42578. focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;
  42579. if (!focusedCmp && !parent) {
  42580. return true;
  42581. }
  42582. if (checkWhitelist && me.isWhitelisted(focusedCmp)) {
  42583. return true;
  42584. }
  42585. // If no focused Component, or a root level one was focused, then siblings are root components.
  42586. if (!focusedCmp || focusedCmp.is(':root')) {
  42587. siblings = me.getRootComponents();
  42588. } else {
  42589. // Else if the focused component has a parent, get siblings from there
  42590. parent = parent || focusedCmp.up();
  42591. if (parent) {
  42592. siblings = parent.getRefItems();
  42593. }
  42594. }
  42595. // Navigate if we have found siblings.
  42596. if (siblings) {
  42597. idx = focusedCmp ? Ext.Array.indexOf(siblings, focusedCmp) : -1;
  42598. next = Ext.ComponentQuery.query(':' + nextSelector + 'Focus(' + idx + ')', siblings)[0];
  42599. if (next && focusedCmp !== next) {
  42600. next.focus();
  42601. return next;
  42602. }
  42603. }
  42604. },
  42605. onComponentBlur: function(cmp, e) {
  42606. var me = this;
  42607. if (me.focusedCmp === cmp) {
  42608. me.previousFocusedCmp = cmp;
  42609. delete me.focusedCmp;
  42610. }
  42611. if (me.focusFrame) {
  42612. me.focusFrame.hide();
  42613. }
  42614. },
  42615. onComponentFocus: function(cmp, e) {
  42616. var me = this,
  42617. chain = me.focusChain,
  42618. parent;
  42619. if (!cmp.isFocusable()) {
  42620. me.clearComponent(cmp);
  42621. // Check our focus chain, so we don't run into a never ending recursion
  42622. // If we've attempted (unsuccessfully) to focus this component before,
  42623. // then we're caught in a loop of child->parent->...->child and we
  42624. // need to cut the loop off rather than feed into it.
  42625. if (chain[cmp.id]) {
  42626. return;
  42627. }
  42628. // Try to focus the parent instead
  42629. parent = cmp.up();
  42630. if (parent) {
  42631. // Add component to our focus chain to detect infinite focus loop
  42632. // before we fire off an attempt to focus our parent.
  42633. // See the comments above.
  42634. chain[cmp.id] = true;
  42635. parent.focus();
  42636. }
  42637. return;
  42638. }
  42639. // Clear our focus chain when we have a focusable component
  42640. me.focusChain = {};
  42641. // Capture the focusEl to frame now.
  42642. // Button returns its encapsulating element during the focus phase
  42643. // So that element gets styled and framed.
  42644. me.focusTask.delay(10, null, null, [cmp, cmp.getFocusEl()]);
  42645. },
  42646. handleComponentFocus: function(cmp, focusEl) {
  42647. var me = this,
  42648. cls,
  42649. ff,
  42650. fw,
  42651. box,
  42652. bt,
  42653. bl,
  42654. bw,
  42655. bh,
  42656. ft,
  42657. fb,
  42658. fl,
  42659. fr;
  42660. if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
  42661. me.clearComponent(cmp);
  42662. return;
  42663. }
  42664. me.focusedCmp = cmp;
  42665. // If we have a focus frame, show it around the focused component
  42666. if (me.shouldShowFocusFrame(cmp)) {
  42667. cls = '.' + me.focusFrameCls + '-';
  42668. ff = me.focusFrame;
  42669. box = focusEl.getPageBox();
  42670. // Size the focus frame's t/b/l/r according to the box
  42671. // This leaves a hole in the middle of the frame so user
  42672. // interaction w/ the mouse can continue
  42673. bt = box.top;
  42674. bl = box.left;
  42675. bw = box.width;
  42676. bh = box.height;
  42677. ft = ff.child(cls + 'top');
  42678. fb = ff.child(cls + 'bottom');
  42679. fl = ff.child(cls + 'left');
  42680. fr = ff.child(cls + 'right');
  42681. ft.setWidth(bw).setLeftTop(bl, bt);
  42682. fb.setWidth(bw).setLeftTop(bl, bt + bh - 2);
  42683. fl.setHeight(bh - 2).setLeftTop(bl, bt + 2);
  42684. fr.setHeight(bh - 2).setLeftTop(bl + bw - 2, bt + 2);
  42685. ff.show();
  42686. }
  42687. me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
  42688. },
  42689. onComponentHide: function(cmp) {
  42690. var me = this,
  42691. cmpHadFocus = false,
  42692. focusedCmp = me.focusedCmp,
  42693. parent;
  42694. if (focusedCmp) {
  42695. // See if the Component being hidden was the focused Component, or owns the focused Component
  42696. // In these cases, focus needs to be removed from the focused Component to the nearest focusable ancestor
  42697. cmpHadFocus = cmp.hasFocus || (cmp.isContainer && cmp.isAncestor(me.focusedCmp));
  42698. }
  42699. me.clearComponent(cmp);
  42700. // Move focus onto the nearest focusable ancestor, or this is there is none
  42701. if (cmpHadFocus && (parent = cmp.up(':focusable'))) {
  42702. parent.focus();
  42703. } else {
  42704. me.focusEl.focus();
  42705. }
  42706. },
  42707. onComponentDestroy: function() {
  42708. },
  42709. removeDOM: function() {
  42710. var me = this;
  42711. // If we are still enabled globally, or there are still subscribers
  42712. // then we will halt here, since our DOM stuff is still being used
  42713. if (me.enabled || me.subscribers.length) {
  42714. return;
  42715. }
  42716. Ext.destroy(
  42717. me.focusFrame
  42718. );
  42719. delete me.focusEl;
  42720. delete me.focusFrame;
  42721. },
  42722. /**
  42723. * Removes the specified xtype from the {@link #whitelist}.
  42724. * @param {String/String[]} xtype Removes the xtype(s) from the {@link #whitelist}.
  42725. */
  42726. removeXTypeFromWhitelist: function(xtype) {
  42727. var me = this;
  42728. if (Ext.isArray(xtype)) {
  42729. Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
  42730. return;
  42731. }
  42732. Ext.Array.remove(me.whitelist, xtype);
  42733. },
  42734. setupSubscriberKeys: function(container, keys) {
  42735. var me = this,
  42736. el = container.getFocusEl(),
  42737. scope = keys.scope,
  42738. handlers = {
  42739. backspace: me.focusLast,
  42740. enter: me.navigateIn,
  42741. esc: me.navigateOut,
  42742. scope: me
  42743. },
  42744. navSiblings = function(e) {
  42745. if (me.focusedCmp === container) {
  42746. // Root the sibling navigation to this container, so that we
  42747. // can automatically dive into the container, rather than forcing
  42748. // the user to hit the enter key to dive in.
  42749. return me.navigateSiblings(e, me, container);
  42750. } else {
  42751. return me.navigateSiblings(e);
  42752. }
  42753. };
  42754. Ext.iterate(keys, function(key, cb) {
  42755. handlers[key] = function(e) {
  42756. var ret = navSiblings(e);
  42757. if (Ext.isFunction(cb) && cb.call(scope || container, e, ret) === true) {
  42758. return true;
  42759. }
  42760. return ret;
  42761. };
  42762. }, me);
  42763. return new Ext.util.KeyNav(el, handlers);
  42764. },
  42765. shouldShowFocusFrame: function(cmp) {
  42766. var me = this,
  42767. opts = me.options || {},
  42768. cmpFocusEl = cmp.getFocusEl(),
  42769. cmpFocusElTag = Ext.getDom(cmpFocusEl).tagName;
  42770. // Do not show a focus frame if
  42771. // 1. We are configured not to.
  42772. // 2. No Component was passed
  42773. if (!me.focusFrame || !cmp) {
  42774. return false;
  42775. }
  42776. // Global trumps
  42777. if (opts.focusFrame) {
  42778. return true;
  42779. }
  42780. if (me.focusData[cmp.id].focusFrame) {
  42781. return true;
  42782. }
  42783. return false;
  42784. }
  42785. });
  42786. /**
  42787. * Simple helper class for easily creating image components. This renders an image tag to
  42788. * the DOM with the configured src.
  42789. *
  42790. * {@img Ext.Img/Ext.Img.png Ext.Img component}
  42791. *
  42792. * ## Example usage:
  42793. *
  42794. * var changingImage = Ext.create('Ext.Img', {
  42795. * src: 'http://www.sencha.com/img/20110215-feat-html5.png',
  42796. * renderTo: Ext.getBody()
  42797. * });
  42798. *
  42799. * // change the src of the image programmatically
  42800. * changingImage.setSrc('http://www.sencha.com/img/20110215-feat-perf.png');
  42801. *
  42802. * By default, only an img element is rendered and that is this component's primary
  42803. * {@link Ext.AbstractComponent#getEl element}. If the {@link Ext.AbstractComponent#autoEl} property
  42804. * is other than 'img' (the default), the a child img element will be added to the primary
  42805. * element. This can be used to create a wrapper element around the img.
  42806. *
  42807. * ## Wrapping the img in a div:
  42808. *
  42809. * var wrappedImage = Ext.create('Ext.Img', {
  42810. * src: 'http://www.sencha.com/img/20110215-feat-html5.png',
  42811. * autoEl: 'div', // wrap in a div
  42812. * renderTo: Ext.getBody()
  42813. * });
  42814. */
  42815. Ext.define('Ext.Img', {
  42816. extend: 'Ext.Component',
  42817. alias: ['widget.image', 'widget.imagecomponent'],
  42818. autoEl: 'img',
  42819. /**
  42820. * @cfg {String} src
  42821. * The image src.
  42822. */
  42823. src: '',
  42824. /**
  42825. * @cfg {String} alt
  42826. * The descriptive text for non-visual UI description.
  42827. */
  42828. alt: '',
  42829. /**
  42830. * @cfg {String} imgCls
  42831. * Optional CSS classes to add to the img element.
  42832. */
  42833. imgCls: '',
  42834. getElConfig: function() {
  42835. var me = this,
  42836. config = me.callParent(),
  42837. img;
  42838. // It is sometimes helpful (like in a panel header icon) to have the img wrapped
  42839. // by a div. If our autoEl is not 'img' then we just add an img child to the el.
  42840. if (me.autoEl == 'img') {
  42841. img = config;
  42842. } else {
  42843. config.cn = [img = {
  42844. tag: 'img',
  42845. id: me.id + '-img'
  42846. }];
  42847. }
  42848. if (me.imgCls) {
  42849. img.cls = (img.cls ? img.cls + ' ' : '') + me.imgCls;
  42850. }
  42851. img.src = me.src || Ext.BLANK_IMAGE_URL;
  42852. if (me.alt) {
  42853. img.alt = me.alt;
  42854. }
  42855. return config;
  42856. },
  42857. onRender: function () {
  42858. var me = this,
  42859. el;
  42860. me.callParent(arguments);
  42861. el = me.el;
  42862. me.imgEl = (me.autoEl == 'img') ? el : el.getById(me.id + '-img');
  42863. },
  42864. onDestroy: function () {
  42865. Ext.destroy(this.imgEl);
  42866. this.imgEl = null;
  42867. this.callParent();
  42868. },
  42869. /**
  42870. * Updates the {@link #src} of the image.
  42871. * @param {String} src
  42872. */
  42873. setSrc: function(src) {
  42874. var me = this,
  42875. imgEl = me.imgEl;
  42876. me.src = src;
  42877. if (imgEl) {
  42878. imgEl.dom.src = src || Ext.BLANK_IMAGE_URL;
  42879. }
  42880. }
  42881. });
  42882. /**
  42883. * An extended {@link Ext.Element} object that supports a shadow and shim, constrain to viewport and
  42884. * automatic maintaining of shadow/shim positions.
  42885. */
  42886. Ext.define('Ext.Layer', {
  42887. extend: 'Ext.Element',
  42888. uses: ['Ext.Shadow'],
  42889. /**
  42890. * @cfg {Boolean} [shim=true]
  42891. * False to disable the iframe shim in browsers which need one.
  42892. */
  42893. /**
  42894. * @cfg {String/Boolean} [shadow=false]
  42895. * True to automatically create an {@link Ext.Shadow}, or a string indicating the
  42896. * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow.
  42897. */
  42898. /**
  42899. * @cfg {Object} [dh={tag: 'div', cls: 'x-layer'}]
  42900. * DomHelper object config to create element with.
  42901. */
  42902. /**
  42903. * @cfg {Boolean} [constrain=true]
  42904. * False to disable constrain to viewport.
  42905. */
  42906. /**
  42907. * @cfg {String} cls
  42908. * CSS class to add to the element
  42909. */
  42910. /**
  42911. * @cfg {Number} [zindex=11000]
  42912. * Starting z-index.
  42913. */
  42914. /**
  42915. * @cfg {Number} [shadowOffset=4]
  42916. * Number of pixels to offset the shadow
  42917. */
  42918. /**
  42919. * @cfg {Boolean} [useDisplay=false]
  42920. * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
  42921. * to use css style <tt>'display:none;'</tt> to hide the Layer.
  42922. */
  42923. /**
  42924. * @cfg {String} visibilityCls
  42925. * The CSS class name to add in order to hide this Layer if this layer
  42926. * is configured with <code>{@link #hideMode}: 'asclass'</code>
  42927. */
  42928. /**
  42929. * @cfg {String} hideMode
  42930. * A String which specifies how this Layer will be hidden.
  42931. * Values may be:
  42932. *
  42933. * - `'display'` : The Component will be hidden using the `display: none` style.
  42934. * - `'visibility'` : The Component will be hidden using the `visibility: hidden` style.
  42935. * - `'offsets'` : The Component will be hidden by absolutely positioning it out of the visible area
  42936. * of the document. This is useful when a hidden Component must maintain measurable dimensions.
  42937. * Hiding using `display` results in a Component having zero dimensions.
  42938. */
  42939. // shims are shared among layer to keep from having 100 iframes
  42940. statics: {
  42941. shims: []
  42942. },
  42943. isLayer: true,
  42944. /**
  42945. * Creates new Layer.
  42946. * @param {Object} [config] An object with config options.
  42947. * @param {String/HTMLElement} [existingEl] Uses an existing DOM element.
  42948. * If the element is not found it creates it.
  42949. */
  42950. constructor: function(config, existingEl) {
  42951. config = config || {};
  42952. var me = this,
  42953. dh = Ext.DomHelper,
  42954. cp = config.parentEl,
  42955. pel = cp ? Ext.getDom(cp) : document.body,
  42956. hm = config.hideMode;
  42957. if (existingEl) {
  42958. me.dom = Ext.getDom(existingEl);
  42959. }
  42960. if (!me.dom) {
  42961. me.dom = dh.append(pel, config.dh || {
  42962. tag: 'div',
  42963. cls: Ext.baseCSSPrefix + 'layer' // primarily to give el 'position:absolute'
  42964. });
  42965. } else {
  42966. me.addCls(Ext.baseCSSPrefix + 'layer');
  42967. if (!me.dom.parentNode) {
  42968. pel.appendChild(me.dom);
  42969. }
  42970. }
  42971. if (config.id) {
  42972. me.id = me.dom.id = config.id;
  42973. } else {
  42974. me.id = Ext.id(me.dom);
  42975. }
  42976. Ext.Element.addToCache(me);
  42977. if (config.cls) {
  42978. me.addCls(config.cls);
  42979. }
  42980. me.constrain = config.constrain !== false;
  42981. // Allow Components to pass their hide mode down to the Layer if they are floating.
  42982. // Otherwise, allow useDisplay to override the default hiding method which is visibility.
  42983. // TODO: Have ExtJS's Element implement visibilityMode by using classes as in Mobile.
  42984. if (hm) {
  42985. me.setVisibilityMode(Ext.Element[hm.toUpperCase()]);
  42986. if (me.visibilityMode == Ext.Element.ASCLASS) {
  42987. me.visibilityCls = config.visibilityCls;
  42988. }
  42989. } else if (config.useDisplay) {
  42990. me.setVisibilityMode(Ext.Element.DISPLAY);
  42991. } else {
  42992. me.setVisibilityMode(Ext.Element.VISIBILITY);
  42993. }
  42994. if (config.shadow) {
  42995. me.shadowOffset = config.shadowOffset || 4;
  42996. me.shadow = new Ext.Shadow({
  42997. offset: me.shadowOffset,
  42998. mode: config.shadow
  42999. });
  43000. me.disableShadow();
  43001. } else {
  43002. me.shadowOffset = 0;
  43003. }
  43004. me.useShim = config.shim !== false && Ext.useShims;
  43005. if (config.hidden === true) {
  43006. me.hide();
  43007. } else {
  43008. me.show();
  43009. }
  43010. },
  43011. getZIndex: function() {
  43012. return parseInt((this.getShim() || this).getStyle('z-index'), 10);
  43013. },
  43014. getShim: function() {
  43015. var me = this,
  43016. shim, pn;
  43017. if (!me.useShim) {
  43018. return null;
  43019. }
  43020. if (!me.shim) {
  43021. shim = me.self.shims.shift();
  43022. if (!shim) {
  43023. shim = me.createShim();
  43024. shim.enableDisplayMode('block');
  43025. shim.hide();
  43026. }
  43027. pn = me.dom.parentNode;
  43028. if (shim.dom.parentNode != pn) {
  43029. pn.insertBefore(shim.dom, me.dom);
  43030. }
  43031. me.shim = shim;
  43032. }
  43033. return me.shim;
  43034. },
  43035. hideShim: function() {
  43036. var me = this;
  43037. if (me.shim) {
  43038. me.shim.setDisplayed(false);
  43039. me.self.shims.push(me.shim);
  43040. delete me.shim;
  43041. }
  43042. },
  43043. disableShadow: function() {
  43044. var me = this;
  43045. if (me.shadow && !me.shadowDisabled) {
  43046. me.shadowDisabled = true;
  43047. me.shadow.hide();
  43048. me.lastShadowOffset = me.shadowOffset;
  43049. me.shadowOffset = 0;
  43050. }
  43051. },
  43052. enableShadow: function(show) {
  43053. var me = this;
  43054. if (me.shadow && me.shadowDisabled) {
  43055. me.shadowDisabled = false;
  43056. me.shadowOffset = me.lastShadowOffset;
  43057. delete me.lastShadowOffset;
  43058. if (show) {
  43059. me.sync(true);
  43060. }
  43061. }
  43062. },
  43063. /**
  43064. * @private
  43065. * Synchronize this Layer's associated elements, the shadow, and possibly the shim.
  43066. *
  43067. * This code can execute repeatedly in milliseconds,
  43068. * eg: dragging a Component configured liveDrag: true, or which has no ghost method
  43069. * so code size was sacrificed for efficiency (e.g. no getBox/setBox, no XY calls)
  43070. *
  43071. * @param {Boolean} doShow Pass true to ensure that the shadow is shown.
  43072. */
  43073. sync: function(doShow) {
  43074. var me = this,
  43075. shadow = me.shadow,
  43076. shadowPos, shimStyle, shadowSize,
  43077. shim, l, t, w, h, shimIndex;
  43078. if (!me.updating && me.isVisible() && (shadow || me.useShim)) {
  43079. shim = me.getShim();
  43080. l = me.getLocalX();
  43081. t = me.getLocalY();
  43082. w = me.dom.offsetWidth;
  43083. h = me.dom.offsetHeight;
  43084. if (shadow && !me.shadowDisabled) {
  43085. if (doShow && !shadow.isVisible()) {
  43086. shadow.show(me);
  43087. } else {
  43088. shadow.realign(l, t, w, h);
  43089. }
  43090. if (shim) {
  43091. // TODO: Determine how the shims zIndex is above the layer zIndex at this point
  43092. shimIndex = shim.getStyle('z-index');
  43093. if (shimIndex > me.zindex) {
  43094. me.shim.setStyle('z-index', me.zindex - 2);
  43095. }
  43096. shim.show();
  43097. // fit the shim behind the shadow, so it is shimmed too
  43098. if (shadow.isVisible()) {
  43099. shadowPos = shadow.el.getXY();
  43100. shimStyle = shim.dom.style;
  43101. shadowSize = shadow.el.getSize();
  43102. if (Ext.supports.CSS3BoxShadow) {
  43103. shadowSize.height += 6;
  43104. shadowSize.width += 4;
  43105. shadowPos[0] -= 2;
  43106. shadowPos[1] -= 4;
  43107. }
  43108. shimStyle.left = (shadowPos[0]) + 'px';
  43109. shimStyle.top = (shadowPos[1]) + 'px';
  43110. shimStyle.width = (shadowSize.width) + 'px';
  43111. shimStyle.height = (shadowSize.height) + 'px';
  43112. } else {
  43113. shim.setSize(w, h);
  43114. shim.setLeftTop(l, t);
  43115. }
  43116. }
  43117. } else if (shim) {
  43118. // TODO: Determine how the shims zIndex is above the layer zIndex at this point
  43119. shimIndex = shim.getStyle('z-index');
  43120. if (shimIndex > me.zindex) {
  43121. me.shim.setStyle('z-index', me.zindex - 2);
  43122. }
  43123. shim.show();
  43124. shim.setSize(w, h);
  43125. shim.setLeftTop(l, t);
  43126. }
  43127. }
  43128. return me;
  43129. },
  43130. remove: function() {
  43131. this.hideUnders();
  43132. this.callParent();
  43133. },
  43134. // private
  43135. beginUpdate: function() {
  43136. this.updating = true;
  43137. },
  43138. // private
  43139. endUpdate: function() {
  43140. this.updating = false;
  43141. this.sync(true);
  43142. },
  43143. // private
  43144. hideUnders: function() {
  43145. if (this.shadow) {
  43146. this.shadow.hide();
  43147. }
  43148. this.hideShim();
  43149. },
  43150. // private
  43151. constrainXY: function() {
  43152. if (this.constrain) {
  43153. var vw = Ext.Element.getViewWidth(),
  43154. vh = Ext.Element.getViewHeight(),
  43155. s = Ext.getDoc().getScroll(),
  43156. xy = this.getXY(),
  43157. x = xy[0],
  43158. y = xy[1],
  43159. so = this.shadowOffset,
  43160. w = this.dom.offsetWidth + so,
  43161. h = this.dom.offsetHeight + so,
  43162. moved = false; // only move it if it needs it
  43163. // first validate right/bottom
  43164. if ((x + w) > vw + s.left) {
  43165. x = vw - w - so;
  43166. moved = true;
  43167. }
  43168. if ((y + h) > vh + s.top) {
  43169. y = vh - h - so;
  43170. moved = true;
  43171. }
  43172. // then make sure top/left isn't negative
  43173. if (x < s.left) {
  43174. x = s.left;
  43175. moved = true;
  43176. }
  43177. if (y < s.top) {
  43178. y = s.top;
  43179. moved = true;
  43180. }
  43181. if (moved) {
  43182. Ext.Layer.superclass.setXY.call(this, [x, y]);
  43183. this.sync();
  43184. }
  43185. }
  43186. return this;
  43187. },
  43188. getConstrainOffset: function() {
  43189. return this.shadowOffset;
  43190. },
  43191. // overridden Element method
  43192. setVisible: function(visible, animate, duration, callback, easing) {
  43193. var me = this,
  43194. cb;
  43195. // post operation processing
  43196. cb = function() {
  43197. if (visible) {
  43198. me.sync(true);
  43199. }
  43200. if (callback) {
  43201. callback();
  43202. }
  43203. };
  43204. // Hide shadow and shim if hiding
  43205. if (!visible) {
  43206. me.hideUnders(true);
  43207. }
  43208. me.callParent([visible, animate, duration, callback, easing]);
  43209. if (!animate) {
  43210. cb();
  43211. }
  43212. return me;
  43213. },
  43214. // private
  43215. beforeFx: function() {
  43216. this.beforeAction();
  43217. return this.callParent(arguments);
  43218. },
  43219. // private
  43220. afterFx: function() {
  43221. this.callParent(arguments);
  43222. this.sync(this.isVisible());
  43223. },
  43224. // private
  43225. beforeAction: function() {
  43226. if (!this.updating && this.shadow) {
  43227. this.shadow.hide();
  43228. }
  43229. },
  43230. // overridden Element method
  43231. setLeft: function(left) {
  43232. this.callParent(arguments);
  43233. return this.sync();
  43234. },
  43235. setTop: function(top) {
  43236. this.callParent(arguments);
  43237. return this.sync();
  43238. },
  43239. setLeftTop: function(left, top) {
  43240. this.callParent(arguments);
  43241. return this.sync();
  43242. },
  43243. setXY: function(xy, animate, duration, callback, easing) {
  43244. var me = this;
  43245. // Callback will restore shadow state and call the passed callback
  43246. callback = me.createCB(callback);
  43247. me.fixDisplay();
  43248. me.beforeAction();
  43249. me.callParent([xy, animate, duration, callback, easing]);
  43250. if (!animate) {
  43251. callback();
  43252. }
  43253. return me;
  43254. },
  43255. // private
  43256. createCB: function(callback) {
  43257. var me = this,
  43258. showShadow = me.shadow && me.shadow.isVisible();
  43259. return function() {
  43260. me.constrainXY();
  43261. me.sync(showShadow);
  43262. if (callback) {
  43263. callback();
  43264. }
  43265. };
  43266. },
  43267. // overridden Element method
  43268. setX: function(x, animate, duration, callback, easing) {
  43269. this.setXY([x, this.getY()], animate, duration, callback, easing);
  43270. return this;
  43271. },
  43272. // overridden Element method
  43273. setY: function(y, animate, duration, callback, easing) {
  43274. this.setXY([this.getX(), y], animate, duration, callback, easing);
  43275. return this;
  43276. },
  43277. // overridden Element method
  43278. setSize: function(w, h, animate, duration, callback, easing) {
  43279. var me = this;
  43280. // Callback will restore shadow state and call the passed callback
  43281. callback = me.createCB(callback);
  43282. me.beforeAction();
  43283. me.callParent([w, h, animate, duration, callback, easing]);
  43284. if (!animate) {
  43285. callback();
  43286. }
  43287. return me;
  43288. },
  43289. // overridden Element method
  43290. setWidth: function(w, animate, duration, callback, easing) {
  43291. var me = this;
  43292. // Callback will restore shadow state and call the passed callback
  43293. callback = me.createCB(callback);
  43294. me.beforeAction();
  43295. me.callParent([w, animate, duration, callback, easing]);
  43296. if (!animate) {
  43297. callback();
  43298. }
  43299. return me;
  43300. },
  43301. // overridden Element method
  43302. setHeight: function(h, animate, duration, callback, easing) {
  43303. var me = this;
  43304. // Callback will restore shadow state and call the passed callback
  43305. callback = me.createCB(callback);
  43306. me.beforeAction();
  43307. me.callParent([h, animate, duration, callback, easing]);
  43308. if (!animate) {
  43309. callback();
  43310. }
  43311. return me;
  43312. },
  43313. // overridden Element method
  43314. setBounds: function(x, y, width, height, animate, duration, callback, easing) {
  43315. var me = this;
  43316. // Callback will restore shadow state and call the passed callback
  43317. callback = me.createCB(callback);
  43318. me.beforeAction();
  43319. if (!animate) {
  43320. Ext.Layer.superclass.setXY.call(me, [x, y]);
  43321. Ext.Layer.superclass.setSize.call(me, width, height);
  43322. callback();
  43323. } else {
  43324. me.callParent([x, y, width, height, animate, duration, callback, easing]);
  43325. }
  43326. return me;
  43327. },
  43328. /**
  43329. * Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer
  43330. * z-index is automatically incremented depending upon the presence of a shim or a
  43331. * shadow in so that it always shows above those two associated elements.
  43332. *
  43333. * Any shim, will be assigned the passed z-index. A shadow will be assigned the next
  43334. * highet z-index, and the Layer's element will receive the highest z-index.
  43335. *
  43336. * @param {Number} zindex The new z-index to set
  43337. * @return {Ext.Layer} The Layer
  43338. */
  43339. setZIndex: function(zindex) {
  43340. var me = this;
  43341. me.zindex = zindex;
  43342. if (me.getShim()) {
  43343. me.shim.setStyle('z-index', zindex++);
  43344. }
  43345. if (me.shadow) {
  43346. me.shadow.setZIndex(zindex++);
  43347. }
  43348. return me.setStyle('z-index', zindex);
  43349. },
  43350. onOpacitySet: function(opacity){
  43351. var shadow = this.shadow;
  43352. if (shadow) {
  43353. shadow.setOpacity(opacity);
  43354. }
  43355. }
  43356. });
  43357. /**
  43358. * This class is used as a mixin.
  43359. *
  43360. * This class is to be used to provide basic methods for binding/unbinding stores to other
  43361. * classes. In general it will not be used directly.
  43362. */
  43363. Ext.define('Ext.util.Bindable', {
  43364. /**
  43365. * Binds a store to this instance.
  43366. * @param {Ext.data.AbstractStore/String} [store] The store to bind or ID of the store.
  43367. * When no store given (or when `null` or `undefined` passed), unbinds the existing store.
  43368. * @param {Boolean} [initial=false] True to not remove listeners from existing store.
  43369. */
  43370. bindStore: function(store, initial){
  43371. var me = this,
  43372. oldStore = me.store;
  43373. if (!initial && me.store) {
  43374. // Perform implementation-specific unbinding operations *before* possible Store destruction.
  43375. me.onUnbindStore(oldStore, initial);
  43376. if (store !== oldStore && oldStore.autoDestroy) {
  43377. oldStore.destroyStore();
  43378. } else {
  43379. me.unbindStoreListeners(oldStore);
  43380. }
  43381. }
  43382. if (store) {
  43383. store = Ext.data.StoreManager.lookup(store);
  43384. me.bindStoreListeners(store);
  43385. me.onBindStore(store, initial);
  43386. }
  43387. me.store = store || null;
  43388. return me;
  43389. },
  43390. /**
  43391. * Gets the current store instance.
  43392. * @return {Ext.data.AbstractStore} The store, null if one does not exist.
  43393. */
  43394. getStore: function(){
  43395. return this.store;
  43396. },
  43397. /**
  43398. * Unbinds listeners from this component to the store. By default it will remove
  43399. * anything bound by the bindStoreListeners method, however it can be overridden
  43400. * in a subclass to provide any more complicated handling.
  43401. * @protected
  43402. * @param {Ext.data.AbstractStore} store The store to unbind from
  43403. */
  43404. unbindStoreListeners: function(store) {
  43405. // Can be overridden in the subclass for more complex removal
  43406. var listeners = this.storeListeners;
  43407. if (listeners) {
  43408. store.un(listeners);
  43409. }
  43410. },
  43411. /**
  43412. * Binds listeners for this component to the store. By default it will add
  43413. * anything bound by the getStoreListeners method, however it can be overridden
  43414. * in a subclass to provide any more complicated handling.
  43415. * @protected
  43416. * @param {Ext.data.AbstractStore} store The store to bind to
  43417. */
  43418. bindStoreListeners: function(store) {
  43419. // Can be overridden in the subclass for more complex binding
  43420. var me = this,
  43421. listeners = Ext.apply({}, me.getStoreListeners());
  43422. if (!listeners.scope) {
  43423. listeners.scope = me;
  43424. }
  43425. me.storeListeners = listeners;
  43426. store.on(listeners);
  43427. },
  43428. /**
  43429. * Gets the listeners to bind to a new store.
  43430. * @protected
  43431. * @return {Object} The listeners to be bound to the store in object literal form. The scope
  43432. * may be omitted, it is assumed to be the current instance.
  43433. */
  43434. getStoreListeners: Ext.emptyFn,
  43435. /**
  43436. * Template method, it is called when an existing store is unbound
  43437. * from the current instance.
  43438. * @protected
  43439. * @param {Ext.data.AbstractStore} store The store being unbound
  43440. * @param {Boolean} initial True if this store is being bound as initialization of the instance.
  43441. */
  43442. onUnbindStore: Ext.emptyFn,
  43443. /**
  43444. * Template method, it is called when a new store is bound
  43445. * to the current instance.
  43446. * @protected
  43447. * @param {Ext.data.AbstractStore} store The store being bound
  43448. * @param {Boolean} initial True if this store is being bound as initialization of the instance.
  43449. */
  43450. onBindStore: Ext.emptyFn
  43451. });
  43452. /**
  43453. * A modal, floating Component which may be shown above a specified {@link Ext.Component Component} while loading data.
  43454. * When shown, the configured owning Component will be covered with a modality mask, and the LoadMask's {@link #msg} will be
  43455. * displayed centered, accompanied by a spinner image.
  43456. *
  43457. * If the {@link #store} config option is specified, the masking will be automatically shown and then hidden synchronized with
  43458. * the Store's loading process.
  43459. *
  43460. * Because this is a floating Component, its z-index will be managed by the global {@link Ext.WindowManager ZIndexManager}
  43461. * object, and upon show, it will place itsef at the top of the hierarchy.
  43462. *
  43463. * Example usage:
  43464. *
  43465. * // Basic mask:
  43466. * var myMask = new Ext.LoadMask(myPanel, {msg:"Please wait..."});
  43467. * myMask.show();
  43468. */
  43469. Ext.define('Ext.LoadMask', {
  43470. extend: 'Ext.Component',
  43471. alias: 'widget.loadmask',
  43472. /* Begin Definitions */
  43473. mixins: {
  43474. floating: 'Ext.util.Floating',
  43475. bindable: 'Ext.util.Bindable'
  43476. },
  43477. uses: ['Ext.data.StoreManager'],
  43478. /* End Definitions */
  43479. /**
  43480. * @cfg {Ext.data.Store} store
  43481. * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
  43482. * hidden on either load success, or load fail.
  43483. */
  43484. //<locale>
  43485. /**
  43486. * @cfg {String} [msg="Loading..."]
  43487. * The text to display in a centered loading message box.
  43488. */
  43489. msg : 'Loading...',
  43490. //</locale>
  43491. /**
  43492. * @cfg {String} [msgCls="x-mask-loading"]
  43493. * The CSS class to apply to the loading message element.
  43494. */
  43495. msgCls : Ext.baseCSSPrefix + 'mask-loading',
  43496. /**
  43497. * @cfg {String} [maskCls="x-mask"]
  43498. * The CSS class to apply to the mask element
  43499. */
  43500. maskCls: Ext.baseCSSPrefix + 'mask',
  43501. /**
  43502. * @cfg {Boolean} [useMsg=true]
  43503. * Whether or not to use a loading message class or simply mask the bound element.
  43504. */
  43505. useMsg: true,
  43506. /**
  43507. * @cfg {Boolean} [useTargetEl=false]
  43508. * True to mask the {@link Ext.Component#getTargetEl targetEl} of the bound Component. By default,
  43509. * the {@link Ext.Component#getEl el} will be masked.
  43510. */
  43511. useTargetEl: false,
  43512. baseCls: Ext.baseCSSPrefix + 'mask-msg',
  43513. childEls: [
  43514. 'msgEl'
  43515. ],
  43516. renderTpl: '<div id="{id}-msgEl" style="position:relative" class="{[values.$comp.msgCls]}"></div>',
  43517. // Private. Obviously, it's floating.
  43518. floating: {
  43519. shadow: 'frame'
  43520. },
  43521. // Private. Masks are not focusable
  43522. focusOnToFront: false,
  43523. // When we put the load mask to the front of it's owner, we generally don't want to also bring the owning
  43524. // component to the front.
  43525. bringParentToFront: false,
  43526. /**
  43527. * Creates new LoadMask.
  43528. * @param {Ext.Component} comp The Component you wish to mask. The the mask will be automatically sized
  43529. * upon Component resize, and the message box will be kept centered.</p>
  43530. * @param {Object} [config] The config object
  43531. */
  43532. constructor : function(comp, config) {
  43533. var me = this;
  43534. // Element support to be deprecated
  43535. if (!comp.isComponent) {
  43536. if (Ext.isDefined(Ext.global.console)) {
  43537. Ext.global.console.warn('Ext.LoadMask: LoadMask for elements has been deprecated, use Ext.dom.Element.mask & Ext.dom.Element.unmask');
  43538. }
  43539. comp = Ext.get(comp);
  43540. this.isElement = true;
  43541. }
  43542. me.ownerCt = comp;
  43543. if (!this.isElement) {
  43544. me.bindComponent(comp);
  43545. }
  43546. me.callParent([config]);
  43547. if (me.store) {
  43548. me.bindStore(me.store, true);
  43549. }
  43550. },
  43551. bindComponent: function(comp){
  43552. var me = this,
  43553. listeners = {
  43554. scope: this,
  43555. resize: me.sizeMask,
  43556. added: me.onComponentAdded,
  43557. removed: me.onComponentRemoved
  43558. },
  43559. hierarchyEventSource = Ext.container.Container.hierarchyEventSource;
  43560. if (comp.floating) {
  43561. listeners.move = me.sizeMask;
  43562. me.activeOwner = comp;
  43563. } else if (comp.ownerCt) {
  43564. me.onComponentAdded(comp.ownerCt);
  43565. } else {
  43566. // if the target comp is non-floating and under a floating comp don't bring the load mask to the front of the stack
  43567. me.preventBringToFront = true;
  43568. }
  43569. me.mon(comp, listeners);
  43570. // subscribe to the observer that manages the hierarchy
  43571. me.mon(hierarchyEventSource, {
  43572. show: me.onContainerShow,
  43573. hide: me.onContainerHide,
  43574. expand: me.onContainerExpand,
  43575. collapse: me.onContainerCollapse,
  43576. scope: me
  43577. });
  43578. },
  43579. onComponentAdded: function(owner){
  43580. var me = this;
  43581. delete me.activeOwner;
  43582. me.floatParent = owner;
  43583. if (!owner.floating) {
  43584. owner = owner.up('[floating]');
  43585. }
  43586. if (owner) {
  43587. me.activeOwner = owner;
  43588. me.mon(owner, 'move', me.sizeMask, me);
  43589. }
  43590. owner = me.floatParent.ownerCt;
  43591. if (me.rendered && me.isVisible() && owner) {
  43592. me.floatOwner = owner;
  43593. me.mon(owner, 'afterlayout', me.sizeMask, me, {single: true});
  43594. }
  43595. },
  43596. onComponentRemoved: function(owner){
  43597. var me = this,
  43598. activeOwner = me.activeOwner,
  43599. floatOwner = me.floatOwner;
  43600. if (activeOwner) {
  43601. me.mun(activeOwner, 'move', me.sizeMask, me);
  43602. }
  43603. if (floatOwner) {
  43604. me.mun(floatOwner, 'afterlayout', me.sizeMask, me);
  43605. }
  43606. delete me.activeOwner;
  43607. delete me.floatOwner;
  43608. },
  43609. afterRender: function() {
  43610. this.callParent(arguments);
  43611. this.container = this.floatParent.getContentTarget();
  43612. },
  43613. onContainerShow: function(container){
  43614. if (this.isActiveContainer(container)) {
  43615. this.onComponentShow();
  43616. }
  43617. },
  43618. onContainerHide: function(container){
  43619. if (this.isActiveContainer(container)) {
  43620. this.onComponentHide();
  43621. }
  43622. },
  43623. onContainerExpand: function(container){
  43624. if (this.isActiveContainer(container)) {
  43625. this.onComponentShow();
  43626. }
  43627. },
  43628. onContainerCollapse: function(container){
  43629. if (this.isActiveContainer(container)) {
  43630. this.onComponentHide();
  43631. }
  43632. },
  43633. isActiveContainer: function(container){
  43634. return this.isDescendantOf(container);
  43635. },
  43636. onComponentHide: function(){
  43637. var me = this;
  43638. if (me.rendered && me.isVisible()) {
  43639. me.hide();
  43640. me.showNext = true;
  43641. }
  43642. },
  43643. onComponentShow: function(){
  43644. if (this.showNext) {
  43645. this.show();
  43646. }
  43647. delete this.showNext;
  43648. },
  43649. /**
  43650. * @private
  43651. * Called when this LoadMask's Component is resized. The toFront method rebases and resizes the modal mask.
  43652. */
  43653. sizeMask: function() {
  43654. var me = this,
  43655. target;
  43656. if (me.rendered && me.isVisible()) {
  43657. me.center();
  43658. target = me.getMaskTarget();
  43659. me.getMaskEl().show().setSize(target.getSize()).alignTo(target, 'tl-tl');
  43660. }
  43661. },
  43662. /**
  43663. * Changes the data store bound to this LoadMask.
  43664. * @param {Ext.data.Store} store The store to bind to this LoadMask
  43665. */
  43666. bindStore : function(store, initial) {
  43667. var me = this;
  43668. me.mixins.bindable.bindStore.apply(me, arguments);
  43669. store = me.store;
  43670. if (store && store.isLoading()) {
  43671. me.onBeforeLoad();
  43672. }
  43673. },
  43674. getStoreListeners: function(){
  43675. return {
  43676. beforeload: this.onBeforeLoad,
  43677. load: this.onLoad,
  43678. exception: this.onLoad,
  43679. // Fired when a range is requested for rendering that is not in the cache
  43680. cachemiss: this.onBeforeLoad,
  43681. // Fired when a range for rendering which was previously missing from the cache is loaded
  43682. cachefilled: this.onLoad
  43683. };
  43684. },
  43685. onDisable : function() {
  43686. this.callParent(arguments);
  43687. if (this.loading) {
  43688. this.onLoad();
  43689. }
  43690. },
  43691. getOwner: function(){
  43692. return this.ownerCt || this.floatParent;
  43693. },
  43694. getMaskTarget: function(){
  43695. var owner = this.getOwner();
  43696. return this.useTargetEl ? owner.getTargetEl() : owner.getEl();
  43697. },
  43698. // private
  43699. onBeforeLoad : function() {
  43700. var me = this,
  43701. owner = me.getOwner(),
  43702. origin;
  43703. if (!me.disabled) {
  43704. me.loading = true;
  43705. // If the owning Component has not been layed out, defer so that the ZIndexManager
  43706. // gets to read its layed out size when sizing the modal mask
  43707. if (owner.componentLayoutCounter) {
  43708. me.maybeShow();
  43709. } else {
  43710. // The code below is a 'run-once' interceptor.
  43711. origin = owner.afterComponentLayout;
  43712. owner.afterComponentLayout = function() {
  43713. owner.afterComponentLayout = origin;
  43714. origin.apply(owner, arguments);
  43715. me.maybeShow();
  43716. };
  43717. }
  43718. }
  43719. },
  43720. maybeShow: function(){
  43721. var me = this,
  43722. owner = me.getOwner();
  43723. if (!owner.isVisible(true)) {
  43724. me.showNext = true;
  43725. }
  43726. else if (me.loading && owner.rendered) {
  43727. me.show();
  43728. }
  43729. },
  43730. getMaskEl: function(){
  43731. var me = this;
  43732. return me.maskEl || (me.maskEl = me.el.insertSibling({
  43733. cls: me.maskCls,
  43734. style: {
  43735. zIndex: me.el.getStyle('zIndex') - 2
  43736. }
  43737. }, 'before'));
  43738. },
  43739. onShow: function() {
  43740. var me = this,
  43741. msgEl = me.msgEl;
  43742. me.callParent(arguments);
  43743. me.loading = true;
  43744. if (me.useMsg) {
  43745. msgEl.show().update(me.msg);
  43746. } else {
  43747. msgEl.parent().hide();
  43748. }
  43749. },
  43750. hide: function(){
  43751. // Element support to be deprecated
  43752. if (this.isElement) {
  43753. this.ownerCt.unmask();
  43754. this.fireEvent('hide', this);
  43755. return;
  43756. }
  43757. delete this.showNext;
  43758. return this.callParent(arguments);
  43759. },
  43760. onHide: function(){
  43761. this.callParent();
  43762. this.getMaskEl().hide();
  43763. },
  43764. show: function(){
  43765. // Element support to be deprecated
  43766. if (this.isElement) {
  43767. this.ownerCt.mask(this.useMsg ? this.msg : '', this.msgCls);
  43768. this.fireEvent('show', this);
  43769. return;
  43770. }
  43771. return this.callParent(arguments);
  43772. },
  43773. afterShow: function() {
  43774. this.callParent(arguments);
  43775. this.sizeMask();
  43776. },
  43777. setZIndex: function(index) {
  43778. var me = this,
  43779. owner = me.activeOwner;
  43780. if (owner) {
  43781. // it seems silly to add 1 to have it subtracted in the call below,
  43782. // but this allows the x-mask el to have the correct z-index (same as the component)
  43783. // so instead of directly changing the zIndexStack just get the z-index of the owner comp
  43784. index = parseInt(owner.el.getStyle('zIndex'), 10) + 1;
  43785. }
  43786. me.getMaskEl().setStyle('zIndex', index - 1);
  43787. return me.mixins.floating.setZIndex.apply(me, arguments);
  43788. },
  43789. // private
  43790. onLoad : function() {
  43791. this.loading = false;
  43792. this.hide();
  43793. },
  43794. onDestroy: function(){
  43795. var me = this;
  43796. if (me.isElement) {
  43797. me.ownerCt.unmask();
  43798. }
  43799. Ext.destroy(me.maskEl);
  43800. me.callParent();
  43801. }
  43802. });
  43803. /**
  43804. * @author Ed Spencer
  43805. *
  43806. * Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
  43807. * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
  43808. * express like this:
  43809. *
  43810. * Ext.define('User', {
  43811. * extend: 'Ext.data.Model',
  43812. * fields: ['id', 'name', 'email'],
  43813. *
  43814. * hasMany: {model: 'Order', name: 'orders'}
  43815. * });
  43816. *
  43817. * Ext.define('Order', {
  43818. * extend: 'Ext.data.Model',
  43819. * fields: ['id', 'user_id', 'status', 'price'],
  43820. *
  43821. * belongsTo: 'User'
  43822. * });
  43823. *
  43824. * We've set up two models - User and Order - and told them about each other. You can set up as many associations on
  43825. * each Model as you need using the two default types - {@link Ext.data.HasManyAssociation hasMany} and {@link
  43826. * Ext.data.BelongsToAssociation belongsTo}. There's much more detail on the usage of each of those inside their
  43827. * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.
  43828. *
  43829. * **Further Reading**
  43830. *
  43831. * - {@link Ext.data.association.HasMany hasMany associations}
  43832. * - {@link Ext.data.association.BelongsTo belongsTo associations}
  43833. * - {@link Ext.data.association.HasOne hasOne associations}
  43834. * - {@link Ext.data.Model using Models}
  43835. *
  43836. * # Self association models
  43837. *
  43838. * We can also have models that create parent/child associations between the same type. Below is an example, where
  43839. * groups can be nested inside other groups:
  43840. *
  43841. * // Server Data
  43842. * {
  43843. * "groups": {
  43844. * "id": 10,
  43845. * "parent_id": 100,
  43846. * "name": "Main Group",
  43847. * "parent_group": {
  43848. * "id": 100,
  43849. * "parent_id": null,
  43850. * "name": "Parent Group"
  43851. * },
  43852. * "child_groups": [{
  43853. * "id": 2,
  43854. * "parent_id": 10,
  43855. * "name": "Child Group 1"
  43856. * },{
  43857. * "id": 3,
  43858. * "parent_id": 10,
  43859. * "name": "Child Group 2"
  43860. * },{
  43861. * "id": 4,
  43862. * "parent_id": 10,
  43863. * "name": "Child Group 3"
  43864. * }]
  43865. * }
  43866. * }
  43867. *
  43868. * // Client code
  43869. * Ext.define('Group', {
  43870. * extend: 'Ext.data.Model',
  43871. * fields: ['id', 'parent_id', 'name'],
  43872. * proxy: {
  43873. * type: 'ajax',
  43874. * url: 'data.json',
  43875. * reader: {
  43876. * type: 'json',
  43877. * root: 'groups'
  43878. * }
  43879. * },
  43880. * associations: [{
  43881. * type: 'hasMany',
  43882. * model: 'Group',
  43883. * primaryKey: 'id',
  43884. * foreignKey: 'parent_id',
  43885. * autoLoad: true,
  43886. * associationKey: 'child_groups' // read child data from child_groups
  43887. * }, {
  43888. * type: 'belongsTo',
  43889. * model: 'Group',
  43890. * primaryKey: 'id',
  43891. * foreignKey: 'parent_id',
  43892. * associationKey: 'parent_group' // read parent data from parent_group
  43893. * }]
  43894. * });
  43895. *
  43896. * Ext.onReady(function(){
  43897. *
  43898. * Group.load(10, {
  43899. * success: function(group){
  43900. * console.log(group.getGroup().get('name'));
  43901. *
  43902. * group.groups().each(function(rec){
  43903. * console.log(rec.get('name'));
  43904. * });
  43905. * }
  43906. * });
  43907. *
  43908. * });
  43909. *
  43910. */
  43911. Ext.define('Ext.data.association.Association', {
  43912. alternateClassName: 'Ext.data.Association',
  43913. /**
  43914. * @cfg {String} ownerModel
  43915. * The string name of the model that owns the association.
  43916. *
  43917. * **NB!** This config is required when instantiating the Association directly.
  43918. * However, it cannot be used at all when defining the association as a config
  43919. * object inside Model, because the name of the model itself will be supplied
  43920. * automatically as the value of this config.
  43921. */
  43922. /**
  43923. * @cfg {String} associatedModel
  43924. * The string name of the model that is being associated with.
  43925. *
  43926. * **NB!** This config is required when instantiating the Association directly.
  43927. * When defining the association as a config object inside Model, the #model
  43928. * configuration will shadow this config.
  43929. */
  43930. /**
  43931. * @cfg {String} model
  43932. * The string name of the model that is being associated with.
  43933. *
  43934. * This config option is to be used when defining the association as a config
  43935. * object within Model. The value is then mapped to #associatedModel when
  43936. * Association is instantiated inside Model.
  43937. */
  43938. /**
  43939. * @cfg {String} primaryKey
  43940. * The name of the primary key on the associated model. In general this will be the
  43941. * {@link Ext.data.Model#idProperty} of the Model.
  43942. */
  43943. primaryKey: 'id',
  43944. /**
  43945. * @cfg {Ext.data.reader.Reader} reader
  43946. * A special reader to read associated data
  43947. */
  43948. /**
  43949. * @cfg {String} associationKey
  43950. * The name of the property in the data to read the association from. Defaults to the name of the associated model.
  43951. */
  43952. defaultReaderType: 'json',
  43953. isAssociation: true,
  43954. initialConfig: null,
  43955. statics: {
  43956. AUTO_ID: 1000,
  43957. create: function(association){
  43958. if (Ext.isString(association)) {
  43959. association = {
  43960. type: association
  43961. };
  43962. }
  43963. switch (association.type) {
  43964. case 'belongsTo':
  43965. return new Ext.data.association.BelongsTo(association);
  43966. case 'hasMany':
  43967. return new Ext.data.association.HasMany(association);
  43968. case 'hasOne':
  43969. return new Ext.data.association.HasOne(association);
  43970. //TODO Add this back when it's fixed
  43971. // case 'polymorphic':
  43972. // return Ext.create('Ext.data.PolymorphicAssociation', association);
  43973. default:
  43974. Ext.Error.raise('Unknown Association type: "' + association.type + '"');
  43975. }
  43976. return association;
  43977. }
  43978. },
  43979. /**
  43980. * Creates the Association object.
  43981. * @param {Object} [config] Config object.
  43982. */
  43983. constructor: function(config) {
  43984. Ext.apply(this, config);
  43985. var me = this,
  43986. types = Ext.ModelManager.types,
  43987. ownerName = config.ownerModel,
  43988. associatedName = config.associatedModel,
  43989. ownerModel = types[ownerName],
  43990. associatedModel = types[associatedName];
  43991. me.initialConfig = config;
  43992. if (ownerModel === undefined) {
  43993. Ext.Error.raise("The configured ownerModel was not valid (you tried " + ownerName + ")");
  43994. }
  43995. if (associatedModel === undefined) {
  43996. Ext.Error.raise("The configured associatedModel was not valid (you tried " + associatedName + ")");
  43997. }
  43998. me.ownerModel = ownerModel;
  43999. me.associatedModel = associatedModel;
  44000. /**
  44001. * @property {String} ownerName
  44002. * The name of the model that 'owns' the association
  44003. */
  44004. /**
  44005. * @property {String} associatedName
  44006. * The name of the model is on the other end of the association (e.g. if a User model hasMany Orders, this is
  44007. * 'Order')
  44008. */
  44009. Ext.applyIf(me, {
  44010. ownerName : ownerName,
  44011. associatedName: associatedName
  44012. });
  44013. me.associationId = 'association' + (++me.statics().AUTO_ID);
  44014. },
  44015. /**
  44016. * Get a specialized reader for reading associated data
  44017. * @return {Ext.data.reader.Reader} The reader, null if not supplied
  44018. */
  44019. getReader: function(){
  44020. var me = this,
  44021. reader = me.reader,
  44022. model = me.associatedModel;
  44023. if (reader) {
  44024. if (Ext.isString(reader)) {
  44025. reader = {
  44026. type: reader
  44027. };
  44028. }
  44029. if (reader.isReader) {
  44030. reader.setModel(model);
  44031. } else {
  44032. Ext.applyIf(reader, {
  44033. model: model,
  44034. type : me.defaultReaderType
  44035. });
  44036. }
  44037. me.reader = Ext.createByAlias('reader.' + reader.type, reader);
  44038. }
  44039. return me.reader || null;
  44040. }
  44041. });
  44042. /**
  44043. * @author Ed Spencer
  44044. * @class Ext.ModelManager
  44045. The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
  44046. __Creating Model Instances__
  44047. Model instances can be created by using the {@link Ext#create Ext.create} method. Ext.create replaces
  44048. the deprecated {@link #create Ext.ModelManager.create} method. It is also possible to create a model instance
  44049. this by using the Model type directly. The following 3 snippets are equivalent:
  44050. Ext.define('User', {
  44051. extend: 'Ext.data.Model',
  44052. fields: ['first', 'last']
  44053. });
  44054. // method 1, create using Ext.create (recommended)
  44055. Ext.create('User', {
  44056. first: 'Ed',
  44057. last: 'Spencer'
  44058. });
  44059. // method 2, create through the manager (deprecated)
  44060. Ext.ModelManager.create({
  44061. first: 'Ed',
  44062. last: 'Spencer'
  44063. }, 'User');
  44064. // method 3, create on the type directly
  44065. new User({
  44066. first: 'Ed',
  44067. last: 'Spencer'
  44068. });
  44069. __Accessing Model Types__
  44070. A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
  44071. are normal classes, you can access the type directly. The following snippets are equivalent:
  44072. Ext.define('User', {
  44073. extend: 'Ext.data.Model',
  44074. fields: ['first', 'last']
  44075. });
  44076. // method 1, access model type through the manager
  44077. var UserType = Ext.ModelManager.getModel('User');
  44078. // method 2, reference the type directly
  44079. var UserType = User;
  44080. * @markdown
  44081. * @singleton
  44082. */
  44083. Ext.define('Ext.ModelManager', {
  44084. extend: 'Ext.AbstractManager',
  44085. alternateClassName: 'Ext.ModelMgr',
  44086. requires: ['Ext.data.association.Association'],
  44087. singleton: true,
  44088. typeName: 'mtype',
  44089. /**
  44090. * Private stack of associations that must be created once their associated model has been defined
  44091. * @property {Ext.data.association.Association[]} associationStack
  44092. */
  44093. associationStack: [],
  44094. /**
  44095. * Registers a model definition. All model plugins marked with isDefault: true are bootstrapped
  44096. * immediately, as are any addition plugins defined in the model config.
  44097. * @private
  44098. */
  44099. registerType: function(name, config) {
  44100. var proto = config.prototype,
  44101. model;
  44102. if (proto && proto.isModel) {
  44103. // registering an already defined model
  44104. model = config;
  44105. } else {
  44106. // passing in a configuration
  44107. if (!config.extend) {
  44108. config.extend = 'Ext.data.Model';
  44109. }
  44110. model = Ext.define(name, config);
  44111. }
  44112. this.types[name] = model;
  44113. return model;
  44114. },
  44115. /**
  44116. * @private
  44117. * Private callback called whenever a model has just been defined. This sets up any associations
  44118. * that were waiting for the given model to be defined
  44119. * @param {Function} model The model that was just created
  44120. */
  44121. onModelDefined: function(model) {
  44122. var stack = this.associationStack,
  44123. length = stack.length,
  44124. create = [],
  44125. association, i, created;
  44126. for (i = 0; i < length; i++) {
  44127. association = stack[i];
  44128. if (association.associatedModel == model.modelName) {
  44129. create.push(association);
  44130. }
  44131. }
  44132. for (i = 0, length = create.length; i < length; i++) {
  44133. created = create[i];
  44134. this.types[created.ownerModel].prototype.associations.add(Ext.data.association.Association.create(created));
  44135. Ext.Array.remove(stack, created);
  44136. }
  44137. },
  44138. /**
  44139. * Registers an association where one of the models defined doesn't exist yet.
  44140. * The ModelManager will check when new models are registered if it can link them
  44141. * together
  44142. * @private
  44143. * @param {Ext.data.association.Association} association The association
  44144. */
  44145. registerDeferredAssociation: function(association){
  44146. this.associationStack.push(association);
  44147. },
  44148. /**
  44149. * Returns the {@link Ext.data.Model} for a given model name
  44150. * @param {String/Object} id The id of the model or the model instance.
  44151. * @return {Ext.data.Model} a model class.
  44152. */
  44153. getModel: function(id) {
  44154. var model = id;
  44155. if (typeof model == 'string') {
  44156. model = this.types[model];
  44157. }
  44158. return model;
  44159. },
  44160. /**
  44161. * Creates a new instance of a Model using the given data. Deprecated, instead use Ext.create:
  44162. *
  44163. * Ext.create('User', {
  44164. * first: 'Ed',
  44165. * last: 'Spencer'
  44166. * });
  44167. *
  44168. * @deprecated 4.1 Use {@link Ext#create Ext.create} instead.
  44169. *
  44170. * @param {Object} data Data to initialize the Model's fields with
  44171. * @param {String} name The name of the model to create
  44172. * @param {Number} id (Optional) unique id of the Model instance (see {@link Ext.data.Model})
  44173. */
  44174. create: function(config, name, id) {
  44175. var Con = typeof name == 'function' ? name : this.types[name || config.name];
  44176. return new Con(config, id);
  44177. }
  44178. }, function() {
  44179. /**
  44180. * Old way for creating Model classes. Instead use:
  44181. *
  44182. * Ext.define("MyModel", {
  44183. * extend: "Ext.data.Model",
  44184. * fields: []
  44185. * });
  44186. *
  44187. * @param {String} name Name of the Model class.
  44188. * @param {Object} config A configuration object for the Model you wish to create.
  44189. * @return {Ext.data.Model} The newly registered Model
  44190. * @member Ext
  44191. * @deprecated 4.0.0 Use {@link Ext#define} instead.
  44192. */
  44193. Ext.regModel = function() {
  44194. if (Ext.isDefined(Ext.global.console)) {
  44195. Ext.global.console.warn('Ext.regModel has been deprecated. Models can now be created by extending Ext.data.Model: Ext.define("MyModel", {extend: "Ext.data.Model", fields: []});.');
  44196. }
  44197. return this.ModelManager.registerType.apply(this.ModelManager, arguments);
  44198. };
  44199. });
  44200. /**
  44201. * Provides a registry of available Plugin classes indexed by a mnemonic code known as the Plugin's ptype.
  44202. *
  44203. * A plugin may be specified simply as a *config object* as long as the correct `ptype` is specified:
  44204. *
  44205. * {
  44206. * ptype: 'gridviewdragdrop',
  44207. * dragText: 'Drag and drop to reorganize'
  44208. * }
  44209. *
  44210. * Or just use the ptype on its own:
  44211. *
  44212. * 'gridviewdragdrop'
  44213. *
  44214. * Alternatively you can instantiate the plugin with Ext.create:
  44215. *
  44216. * Ext.create('Ext.grid.plugin.DragDrop', {
  44217. * dragText: 'Drag and drop to reorganize'
  44218. * })
  44219. */
  44220. Ext.define('Ext.PluginManager', {
  44221. extend: 'Ext.AbstractManager',
  44222. alternateClassName: 'Ext.PluginMgr',
  44223. singleton: true,
  44224. typeName: 'ptype',
  44225. /**
  44226. * Creates a new Plugin from the specified config object using the config object's ptype to determine the class to
  44227. * instantiate.
  44228. * @param {Object} config A configuration object for the Plugin you wish to create.
  44229. * @param {Function} defaultType (optional) The constructor to provide the default Plugin type if the config object does not
  44230. * contain a `ptype`. (Optional if the config contains a `ptype`).
  44231. * @return {Ext.Component} The newly instantiated Plugin.
  44232. */
  44233. create : function(config, defaultType){
  44234. if (config.init) {
  44235. return config;
  44236. } else {
  44237. return Ext.createByAlias('plugin.' + (config.ptype || defaultType), config);
  44238. }
  44239. // Prior system supported Singleton plugins.
  44240. //var PluginCls = this.types[config.ptype || defaultType];
  44241. //if (PluginCls.init) {
  44242. // return PluginCls;
  44243. //} else {
  44244. // return new PluginCls(config);
  44245. //}
  44246. },
  44247. //create: function(plugin, defaultType) {
  44248. // if (plugin instanceof this) {
  44249. // return plugin;
  44250. // } else {
  44251. // var type, config = {};
  44252. //
  44253. // if (Ext.isString(plugin)) {
  44254. // type = plugin;
  44255. // }
  44256. // else {
  44257. // type = plugin[this.typeName] || defaultType;
  44258. // config = plugin;
  44259. // }
  44260. //
  44261. // return Ext.createByAlias('plugin.' + type, config);
  44262. // }
  44263. //},
  44264. /**
  44265. * Returns all plugins registered with the given type. Here, 'type' refers to the type of plugin, not its ptype.
  44266. * @param {String} type The type to search for
  44267. * @param {Boolean} defaultsOnly True to only return plugins of this type where the plugin's isDefault property is
  44268. * truthy
  44269. * @return {Ext.AbstractPlugin[]} All matching plugins
  44270. */
  44271. findByType: function(type, defaultsOnly) {
  44272. var matches = [],
  44273. types = this.types,
  44274. name,
  44275. item;
  44276. for (name in types) {
  44277. if (!types.hasOwnProperty(name)) {
  44278. continue;
  44279. }
  44280. item = types[name];
  44281. if (item.type == type && (!defaultsOnly || (defaultsOnly === true && item.isDefault))) {
  44282. matches.push(item);
  44283. }
  44284. }
  44285. return matches;
  44286. }
  44287. }, function() {
  44288. /**
  44289. * Shorthand for {@link Ext.PluginManager#registerType}
  44290. * @param {String} ptype The ptype mnemonic string by which the Plugin class
  44291. * may be looked up.
  44292. * @param {Function} cls The new Plugin class.
  44293. * @member Ext
  44294. * @method preg
  44295. */
  44296. Ext.preg = function() {
  44297. return Ext.PluginManager.registerType.apply(Ext.PluginManager, arguments);
  44298. };
  44299. });
  44300. /**
  44301. * @private
  44302. */
  44303. Ext.define('Ext.layout.component.ProgressBar', {
  44304. /* Begin Definitions */
  44305. alias: ['layout.progressbar'],
  44306. extend: 'Ext.layout.component.Auto',
  44307. /* End Definitions */
  44308. type: 'progressbar',
  44309. beginLayout: function (ownerContext) {
  44310. var me = this,
  44311. i, textEls;
  44312. me.callParent(arguments);
  44313. if (!ownerContext.textEls) {
  44314. textEls = me.owner.textEl; // an Ext.Element or CompositeList (raw DOM el's)
  44315. if (textEls.isComposite) {
  44316. ownerContext.textEls = [];
  44317. textEls = textEls.elements;
  44318. for (i = textEls.length; i--; ) {
  44319. ownerContext.textEls[i] = ownerContext.getEl(Ext.get(textEls[i]));
  44320. }
  44321. } else {
  44322. ownerContext.textEls = [ ownerContext.getEl('textEl') ];
  44323. }
  44324. }
  44325. },
  44326. calculate: function(ownerContext) {
  44327. var me = this,
  44328. i, textEls, width;
  44329. me.callParent(arguments);
  44330. if (Ext.isNumber(width = ownerContext.getProp('width'))) {
  44331. width -= ownerContext.getBorderInfo().width;
  44332. textEls = ownerContext.textEls;
  44333. for (i = textEls.length; i--; ) {
  44334. textEls[i].setWidth(width);
  44335. }
  44336. } else {
  44337. me.done = false;
  44338. }
  44339. }
  44340. });
  44341. //@tag dom,core
  44342. /**
  44343. * @class Ext.dom.CompositeElement
  44344. * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
  44345. * members, or to perform collective actions upon the whole set.</p>
  44346. * <p>Although they are not listed, this class supports all of the methods of {@link Ext.dom.Element} and
  44347. * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
  44348. * <p>All methods return <i>this</i> and can be chained.</p>
  44349. * Usage:
  44350. <pre><code>
  44351. var els = Ext.select("#some-el div.some-class", true);
  44352. // or select directly from an existing element
  44353. var el = Ext.get('some-el');
  44354. el.select('div.some-class', true);
  44355. els.setWidth(100); // all elements become 100 width
  44356. els.hide(true); // all elements fade out and hide
  44357. // or
  44358. els.setWidth(100).hide(true);
  44359. </code></pre>
  44360. */
  44361. Ext.define('Ext.dom.CompositeElement', {
  44362. alternateClassName: 'Ext.CompositeElement',
  44363. extend: 'Ext.dom.CompositeElementLite',
  44364. // private
  44365. getElement: function(el) {
  44366. // In this case just return it, since we already have a reference to it
  44367. return el;
  44368. },
  44369. // private
  44370. transformElement: function(el) {
  44371. return Ext.get(el);
  44372. }
  44373. }, function() {
  44374. /**
  44375. * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
  44376. * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
  44377. * {@link Ext.CompositeElementLite CompositeElementLite} object.
  44378. * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
  44379. * @param {Boolean} [unique] true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
  44380. * @param {HTMLElement/String} [root] The root element of the query or id of the root
  44381. * @return {Ext.CompositeElementLite/Ext.CompositeElement}
  44382. * @member Ext.dom.Element
  44383. * @method select
  44384. * @static
  44385. */
  44386. Ext.dom.Element.select = function(selector, unique, root) {
  44387. var elements;
  44388. if (typeof selector == "string") {
  44389. elements = Ext.dom.Element.selectorFunction(selector, root);
  44390. }
  44391. else if (selector.length !== undefined) {
  44392. elements = selector;
  44393. }
  44394. else {
  44395. throw new Error("[Ext.select] Invalid selector specified: " + selector);
  44396. }
  44397. return (unique === true) ? new Ext.CompositeElement(elements) : new Ext.CompositeElementLite(elements);
  44398. };
  44399. });
  44400. /**
  44401. * Shorthand of {@link Ext.Element#method-select}.
  44402. * @member Ext
  44403. * @method select
  44404. * @inheritdoc Ext.Element#select
  44405. */
  44406. Ext.select = Ext.Element.select;
  44407. /**
  44408. * An updateable progress bar component. The progress bar supports two different modes: manual and automatic.
  44409. *
  44410. * In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the progress bar
  44411. * as needed from your own code. This method is most appropriate when you want to show progress throughout an operation
  44412. * that has predictable points of interest at which you can update the control.
  44413. *
  44414. * In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it once the
  44415. * operation is complete. You can optionally have the progress bar wait for a specific amount of time and then clear
  44416. * itself. Automatic mode is most appropriate for timed operations or asynchronous operations in which you have no need
  44417. * for indicating intermediate progress.
  44418. *
  44419. * @example
  44420. * var p = Ext.create('Ext.ProgressBar', {
  44421. * renderTo: Ext.getBody(),
  44422. * width: 300
  44423. * });
  44424. *
  44425. * // Wait for 5 seconds, then update the status el (progress bar will auto-reset)
  44426. * p.wait({
  44427. * interval: 500, //bar will move fast!
  44428. * duration: 50000,
  44429. * increment: 15,
  44430. * text: 'Updating...',
  44431. * scope: this,
  44432. * fn: function(){
  44433. * p.updateText('Done!');
  44434. * }
  44435. * });
  44436. */
  44437. Ext.define('Ext.ProgressBar', {
  44438. extend: 'Ext.Component',
  44439. alias: 'widget.progressbar',
  44440. requires: [
  44441. 'Ext.Template',
  44442. 'Ext.CompositeElement',
  44443. 'Ext.TaskManager',
  44444. 'Ext.layout.component.ProgressBar'
  44445. ],
  44446. uses: ['Ext.fx.Anim'],
  44447. /**
  44448. * @cfg {Number} [value=0]
  44449. * A floating point value between 0 and 1 (e.g., .5)
  44450. */
  44451. /**
  44452. * @cfg {String/HTMLElement/Ext.Element} textEl
  44453. * The element to render the progress text to (defaults to the progress bar's internal text element)
  44454. */
  44455. /**
  44456. * @cfg {String} id
  44457. * The progress bar element's id (defaults to an auto-generated id)
  44458. */
  44459. /**
  44460. * @cfg {String} [baseCls='x-progress']
  44461. * The base CSS class to apply to the progress bar's wrapper element.
  44462. */
  44463. baseCls: Ext.baseCSSPrefix + 'progress',
  44464. /**
  44465. * @cfg {Boolean} animate
  44466. * True to animate the progress bar during transitions.
  44467. */
  44468. animate: false,
  44469. /**
  44470. * @cfg {String} text
  44471. * The text shown in the progress bar.
  44472. */
  44473. text: '',
  44474. // private
  44475. waitTimer: null,
  44476. childEls: [
  44477. 'bar'
  44478. ],
  44479. renderTpl: [
  44480. '<tpl if="internalText">',
  44481. '<div class="{baseCls}-text {baseCls}-text-back">{text}</div>',
  44482. '</tpl>',
  44483. '<div id="{id}-bar" class="{baseCls}-bar" style="width:{percentage}%">',
  44484. '<tpl if="internalText">',
  44485. '<div class="{baseCls}-text">',
  44486. '<div>{text}</div>',
  44487. '</div>',
  44488. '</tpl>',
  44489. '</div>'
  44490. ],
  44491. componentLayout: 'progressbar',
  44492. // private
  44493. initComponent: function() {
  44494. this.callParent();
  44495. this.addEvents(
  44496. /**
  44497. * @event update
  44498. * Fires after each update interval
  44499. * @param {Ext.ProgressBar} this
  44500. * @param {Number} value The current progress value
  44501. * @param {String} text The current progress text
  44502. */
  44503. "update"
  44504. );
  44505. },
  44506. initRenderData: function() {
  44507. var me = this;
  44508. return Ext.apply(me.callParent(), {
  44509. internalText : !me.hasOwnProperty('textEl'),
  44510. text : me.text || '&#160;',
  44511. percentage : me.value ? me.value * 100 : 0
  44512. });
  44513. },
  44514. onRender : function() {
  44515. var me = this;
  44516. me.callParent(arguments);
  44517. // External text display
  44518. if (me.textEl) {
  44519. me.textEl = Ext.get(me.textEl);
  44520. me.updateText(me.text);
  44521. }
  44522. // Inline text display
  44523. else {
  44524. // This produces a composite w/2 el's (which is why we cannot use childEls or
  44525. // renderSelectors):
  44526. me.textEl = me.el.select('.' + me.baseCls + '-text');
  44527. }
  44528. },
  44529. /**
  44530. * Updates the progress bar value, and optionally its text. If the text argument is not specified, any existing text
  44531. * value will be unchanged. To blank out existing text, pass ''. Note that even if the progress bar value exceeds 1,
  44532. * it will never automatically reset -- you are responsible for determining when the progress is complete and
  44533. * calling {@link #reset} to clear and/or hide the control.
  44534. * @param {Number} [value=0] A floating point value between 0 and 1 (e.g., .5)
  44535. * @param {String} [text=''] The string to display in the progress text element
  44536. * @param {Boolean} [animate=false] Whether to animate the transition of the progress bar. If this value is not
  44537. * specified, the default for the class is used
  44538. * @return {Ext.ProgressBar} this
  44539. */
  44540. updateProgress: function(value, text, animate) {
  44541. var me = this,
  44542. oldValue = me.value;
  44543. me.value = value || 0;
  44544. if (text) {
  44545. me.updateText(text);
  44546. }
  44547. if (me.rendered && !me.isDestroyed) {
  44548. if (animate === true || (animate !== false && me.animate)) {
  44549. me.bar.stopAnimation();
  44550. me.bar.animate(Ext.apply({
  44551. from: {
  44552. width: (oldValue * 100) + '%'
  44553. },
  44554. to: {
  44555. width: (me.value * 100) + '%'
  44556. }
  44557. }, me.animate));
  44558. } else {
  44559. me.bar.setStyle('width', (me.value * 100) + '%');
  44560. }
  44561. }
  44562. me.fireEvent('update', me, me.value, text);
  44563. return me;
  44564. },
  44565. /**
  44566. * Updates the progress bar text. If specified, textEl will be updated, otherwise the progress bar itself will
  44567. * display the updated text.
  44568. * @param {String} [text=''] The string to display in the progress text element
  44569. * @return {Ext.ProgressBar} this
  44570. */
  44571. updateText: function(text) {
  44572. var me = this;
  44573. me.text = text;
  44574. if (me.rendered) {
  44575. me.textEl.update(me.text);
  44576. }
  44577. return me;
  44578. },
  44579. applyText : function(text) {
  44580. this.updateText(text);
  44581. },
  44582. getText: function(){
  44583. return this.text;
  44584. },
  44585. /**
  44586. * Initiates an auto-updating progress bar. A duration can be specified, in which case the progress bar will
  44587. * automatically reset after a fixed amount of time and optionally call a callback function if specified. If no
  44588. * duration is passed in, then the progress bar will run indefinitely and must be manually cleared by calling
  44589. * {@link #reset}.
  44590. *
  44591. * Example usage:
  44592. *
  44593. * var p = new Ext.ProgressBar({
  44594. * renderTo: 'my-el'
  44595. * });
  44596. *
  44597. * //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
  44598. * var p = Ext.create('Ext.ProgressBar', {
  44599. * renderTo: Ext.getBody(),
  44600. * width: 300
  44601. * });
  44602. *
  44603. * //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
  44604. * p.wait({
  44605. * interval: 500, //bar will move fast!
  44606. * duration: 50000,
  44607. * increment: 15,
  44608. * text: 'Updating...',
  44609. * scope: this,
  44610. * fn: function(){
  44611. * p.updateText('Done!');
  44612. * }
  44613. * });
  44614. *
  44615. * //Or update indefinitely until some async action completes, then reset manually
  44616. * p.wait();
  44617. * myAction.on('complete', function(){
  44618. * p.reset();
  44619. * p.updateText('Done!');
  44620. * });
  44621. *
  44622. * @param {Object} config (optional) Configuration options
  44623. * @param {Number} config.duration The length of time in milliseconds that the progress bar should
  44624. * run before resetting itself (defaults to undefined, in which case it will run indefinitely
  44625. * until reset is called)
  44626. * @param {Number} config.interval The length of time in milliseconds between each progress update
  44627. * (defaults to 1000 ms)
  44628. * @param {Boolean} config.animate Whether to animate the transition of the progress bar. If this
  44629. * value is not specified, the default for the class is used.
  44630. * @param {Number} config.increment The number of progress update segments to display within the
  44631. * progress bar (defaults to 10). If the bar reaches the end and is still updating, it will
  44632. * automatically wrap back to the beginning.
  44633. * @param {String} config.text Optional text to display in the progress bar element (defaults to '').
  44634. * @param {Function} config.fn A callback function to execute after the progress bar finishes auto-
  44635. * updating. The function will be called with no arguments. This function will be ignored if
  44636. * duration is not specified since in that case the progress bar can only be stopped programmatically,
  44637. * so any required function should be called by the same code after it resets the progress bar.
  44638. * @param {Object} config.scope The scope that is passed to the callback function (only applies when
  44639. * duration and fn are both passed).
  44640. * @return {Ext.ProgressBar} this
  44641. */
  44642. wait: function(o) {
  44643. var me = this, scope;
  44644. if (!me.waitTimer) {
  44645. scope = me;
  44646. o = o || {};
  44647. me.updateText(o.text);
  44648. me.waitTimer = Ext.TaskManager.start({
  44649. run: function(i){
  44650. var inc = o.increment || 10;
  44651. i -= 1;
  44652. me.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
  44653. },
  44654. interval: o.interval || 1000,
  44655. duration: o.duration,
  44656. onStop: function(){
  44657. if (o.fn) {
  44658. o.fn.apply(o.scope || me);
  44659. }
  44660. me.reset();
  44661. },
  44662. scope: scope
  44663. });
  44664. }
  44665. return me;
  44666. },
  44667. /**
  44668. * Returns true if the progress bar is currently in a {@link #wait} operation
  44669. * @return {Boolean} True if waiting, else false
  44670. */
  44671. isWaiting: function(){
  44672. return this.waitTimer !== null;
  44673. },
  44674. /**
  44675. * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress bar will also be hidden
  44676. * (using the {@link #hideMode} property internally).
  44677. * @param {Boolean} [hide=false] True to hide the progress bar.
  44678. * @return {Ext.ProgressBar} this
  44679. */
  44680. reset: function(hide){
  44681. var me = this;
  44682. me.updateProgress(0);
  44683. me.clearTimer();
  44684. if (hide === true) {
  44685. me.hide();
  44686. }
  44687. return me;
  44688. },
  44689. // private
  44690. clearTimer: function(){
  44691. var me = this;
  44692. if (me.waitTimer) {
  44693. me.waitTimer.onStop = null; //prevent recursion
  44694. Ext.TaskManager.stop(me.waitTimer);
  44695. me.waitTimer = null;
  44696. }
  44697. },
  44698. onDestroy: function(){
  44699. var me = this;
  44700. me.clearTimer();
  44701. if (me.rendered) {
  44702. if (me.textEl.isComposite) {
  44703. me.textEl.clear();
  44704. }
  44705. Ext.destroyMembers(me, 'textEl', 'progressBar');
  44706. }
  44707. me.callParent();
  44708. }
  44709. });
  44710. /**
  44711. * Private utility class that manages the internal Shadow cache.
  44712. * @private
  44713. */
  44714. Ext.define('Ext.ShadowPool', {
  44715. singleton: true,
  44716. requires: ['Ext.DomHelper'],
  44717. markup: (function() {
  44718. return Ext.String.format(
  44719. '<div class="{0}{1}-shadow" role="presentation"></div>',
  44720. Ext.baseCSSPrefix,
  44721. Ext.isIE && !Ext.supports.CSS3BoxShadow ? 'ie' : 'css'
  44722. );
  44723. }()),
  44724. shadows: [],
  44725. pull: function() {
  44726. var sh = this.shadows.shift();
  44727. if (!sh) {
  44728. sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, this.markup));
  44729. sh.autoBoxAdjust = false;
  44730. }
  44731. return sh;
  44732. },
  44733. push: function(sh) {
  44734. this.shadows.push(sh);
  44735. },
  44736. reset: function() {
  44737. var shadows = [].concat(this.shadows),
  44738. s,
  44739. sLen = shadows.length;
  44740. for (s = 0; s < sLen; s++) {
  44741. shadows[s].remove();
  44742. }
  44743. this.shadows = [];
  44744. }
  44745. });
  44746. /**
  44747. * Simple class that can provide a shadow effect for any element. Note that the element
  44748. * MUST be absolutely positioned, and the shadow does not provide any shimming. This
  44749. * should be used only in simple cases - for more advanced functionality that can also
  44750. * provide the same shadow effect, see the {@link Ext.Layer} class.
  44751. */
  44752. Ext.define('Ext.Shadow', {
  44753. requires: ['Ext.ShadowPool'],
  44754. /**
  44755. * Creates new Shadow.
  44756. * @param {Object} config (optional) Config object.
  44757. */
  44758. constructor: function(config) {
  44759. var me = this,
  44760. adjusts,
  44761. offset,
  44762. rad;
  44763. Ext.apply(me, config);
  44764. if (!Ext.isString(me.mode)) {
  44765. me.mode = me.defaultMode;
  44766. }
  44767. offset = me.offset;
  44768. rad = Math.floor(offset / 2);
  44769. me.opacity = 50;
  44770. switch (me.mode.toLowerCase()) {
  44771. // all this hideous nonsense calculates the various offsets for shadows
  44772. case "drop":
  44773. if (Ext.supports.CSS3BoxShadow) {
  44774. adjusts = {
  44775. t: offset,
  44776. l: offset,
  44777. h: -offset,
  44778. w: -offset
  44779. };
  44780. }
  44781. else {
  44782. adjusts = {
  44783. t: -rad,
  44784. l: -rad,
  44785. h: -rad,
  44786. w: -rad
  44787. };
  44788. }
  44789. break;
  44790. case "sides":
  44791. if (Ext.supports.CSS3BoxShadow) {
  44792. adjusts = {
  44793. t: offset,
  44794. l: 0,
  44795. h: -offset,
  44796. w: 0
  44797. };
  44798. }
  44799. else {
  44800. adjusts = {
  44801. t: - (1 + rad),
  44802. l: 1 + rad - 2 * offset,
  44803. h: -1,
  44804. w: rad - 1
  44805. };
  44806. }
  44807. break;
  44808. case "frame":
  44809. if (Ext.supports.CSS3BoxShadow) {
  44810. adjusts = {
  44811. t: 0,
  44812. l: 0,
  44813. h: 0,
  44814. w: 0
  44815. };
  44816. }
  44817. else {
  44818. adjusts = {
  44819. t: 1 + rad - 2 * offset,
  44820. l: 1 + rad - 2 * offset,
  44821. h: offset - rad - 1,
  44822. w: offset - rad - 1
  44823. };
  44824. }
  44825. break;
  44826. }
  44827. me.adjusts = adjusts;
  44828. },
  44829. /**
  44830. * @private
  44831. * Returns the shadow size on each side of the element in standard CSS order: top, right, bottom, left;
  44832. * @return {Number[]} Top, right, bottom and left shadow size.
  44833. */
  44834. getShadowSize: function() {
  44835. var me = this,
  44836. offset = me.el ? me.offset : 0,
  44837. result = [offset, offset, offset, offset],
  44838. mode = me.mode.toLowerCase();
  44839. // There are only offsets if the shadow element is present.
  44840. if (me.el && mode !== 'frame') {
  44841. result[0] = 0;
  44842. if (mode == 'drop') {
  44843. result[3] = 0;
  44844. }
  44845. }
  44846. return result;
  44847. },
  44848. /**
  44849. * @cfg {String} mode
  44850. * The shadow display mode. Supports the following options:
  44851. *
  44852. * - sides : Shadow displays on both sides and bottom only</li>
  44853. * - frame : Shadow displays equally on all four sides</li>
  44854. * - drop : Traditional bottom-right drop shadow</li>
  44855. */
  44856. /**
  44857. * @cfg {Number} offset
  44858. * The number of pixels to offset the shadow from the element
  44859. */
  44860. offset: 4,
  44861. // private
  44862. defaultMode: "drop",
  44863. // private - CSS property to use to set the box shadow
  44864. boxShadowProperty: (function() {
  44865. var property = 'boxShadow',
  44866. style = document.documentElement.style;
  44867. if (!('boxShadow' in style)) {
  44868. if ('WebkitBoxShadow' in style) {
  44869. // Safari prior to version 5.1 and Chrome prior to version 10
  44870. property = 'WebkitBoxShadow';
  44871. }
  44872. else if ('MozBoxShadow' in style) {
  44873. // FF 3.5 & 3.6
  44874. property = 'MozBoxShadow';
  44875. }
  44876. }
  44877. return property;
  44878. }()),
  44879. /**
  44880. * Displays the shadow under the target element
  44881. * @param {String/HTMLElement/Ext.Element} targetEl The id or element under which the shadow should display
  44882. */
  44883. show: function(target) {
  44884. var me = this,
  44885. index;
  44886. target = Ext.get(target);
  44887. if (!me.el) {
  44888. me.el = Ext.ShadowPool.pull();
  44889. if (me.el.dom.nextSibling != target.dom) {
  44890. me.el.insertBefore(target);
  44891. }
  44892. }
  44893. index = (parseInt(target.getStyle("z-index"), 10) - 1) || 0;
  44894. me.el.setStyle("z-index", me.zIndex || index);
  44895. if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
  44896. me.el.dom.style.filter = "progid:DXImageTransform.Microsoft.alpha(opacity=" + me.opacity + ") progid:DXImageTransform.Microsoft.Blur(pixelradius=" + (me.offset) + ")";
  44897. }
  44898. me.realign(
  44899. target.getLocalX(),
  44900. target.getLocalY(),
  44901. target.dom.offsetWidth,
  44902. target.dom.offsetHeight
  44903. );
  44904. me.el.dom.style.display = "block";
  44905. },
  44906. /**
  44907. * Returns true if the shadow is visible, else false
  44908. */
  44909. isVisible: function() {
  44910. return this.el ? true: false;
  44911. },
  44912. /**
  44913. * Direct alignment when values are already available. Show must be called at least once before
  44914. * calling this method to ensure it is initialized.
  44915. * @param {Number} left The target element left position
  44916. * @param {Number} top The target element top position
  44917. * @param {Number} width The target element width
  44918. * @param {Number} height The target element height
  44919. */
  44920. realign: function(l, t, targetWidth, targetHeight) {
  44921. if (!this.el) {
  44922. return;
  44923. }
  44924. var adjusts = this.adjusts,
  44925. d = this.el.dom,
  44926. targetStyle = d.style,
  44927. shadowWidth,
  44928. shadowHeight,
  44929. sws,
  44930. shs;
  44931. targetStyle.left = (l + adjusts.l) + "px";
  44932. targetStyle.top = (t + adjusts.t) + "px";
  44933. shadowWidth = Math.max(targetWidth + adjusts.w, 0);
  44934. shadowHeight = Math.max(targetHeight + adjusts.h, 0);
  44935. sws = shadowWidth + "px";
  44936. shs = shadowHeight + "px";
  44937. if (targetStyle.width != sws || targetStyle.height != shs) {
  44938. targetStyle.width = sws;
  44939. targetStyle.height = shs;
  44940. if (Ext.supports.CSS3BoxShadow) {
  44941. targetStyle[this.boxShadowProperty] = '0 0 ' + this.offset + 'px #888';
  44942. }
  44943. }
  44944. },
  44945. /**
  44946. * Hides this shadow
  44947. */
  44948. hide: function() {
  44949. var me = this;
  44950. if (me.el) {
  44951. me.el.dom.style.display = "none";
  44952. Ext.ShadowPool.push(me.el);
  44953. delete me.el;
  44954. }
  44955. },
  44956. /**
  44957. * Adjust the z-index of this shadow
  44958. * @param {Number} zindex The new z-index
  44959. */
  44960. setZIndex: function(z) {
  44961. this.zIndex = z;
  44962. if (this.el) {
  44963. this.el.setStyle("z-index", z);
  44964. }
  44965. },
  44966. /**
  44967. * Sets the opacity of the shadow
  44968. * @param {Number} opacity The opacity
  44969. */
  44970. setOpacity: function(opacity){
  44971. if (this.el) {
  44972. if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
  44973. opacity = Math.floor(opacity * 100 / 2) / 100;
  44974. }
  44975. this.opacity = opacity;
  44976. this.el.setOpacity(opacity);
  44977. }
  44978. }
  44979. });
  44980. /**
  44981. * Controllers are the glue that binds an application together. All they really do is listen for events (usually from
  44982. * views) and take some action. Here's how we might create a Controller to manage Users:
  44983. *
  44984. * Ext.define('MyApp.controller.Users', {
  44985. * extend: 'Ext.app.Controller',
  44986. *
  44987. * init: function() {
  44988. * console.log('Initialized Users! This happens before the Application launch function is called');
  44989. * }
  44990. * });
  44991. *
  44992. * The init function is a special method that is called when your application boots. It is called before the
  44993. * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
  44994. * your Viewport is created.
  44995. *
  44996. * The init function is a great place to set up how your controller interacts with the view, and is usually used in
  44997. * conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function
  44998. * makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
  44999. * our Users controller to tell us when the panel is rendered:
  45000. *
  45001. * Ext.define('MyApp.controller.Users', {
  45002. * extend: 'Ext.app.Controller',
  45003. *
  45004. * init: function() {
  45005. * this.control({
  45006. * 'viewport > panel': {
  45007. * render: this.onPanelRendered
  45008. * }
  45009. * });
  45010. * },
  45011. *
  45012. * onPanelRendered: function() {
  45013. * console.log('The panel was rendered');
  45014. * }
  45015. * });
  45016. *
  45017. * We've updated the init function to use this.control to set up listeners on views in our application. The control
  45018. * function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
  45019. * are not familiar with ComponentQuery yet, be sure to check out the {@link Ext.ComponentQuery documentation}. In brief though,
  45020. * it allows us to pass a CSS-like selector that will find every matching component on the page.
  45021. *
  45022. * In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
  45023. * child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler
  45024. * functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our
  45025. * onPanelRendered function is called.
  45026. *
  45027. * ## Using refs
  45028. *
  45029. * One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
  45030. * make it really easy to get references to Views on your page. Let's look at an example of this now:
  45031. *
  45032. * Ext.define('MyApp.controller.Users', {
  45033. * extend: 'Ext.app.Controller',
  45034. *
  45035. * refs: [
  45036. * {
  45037. * ref: 'list',
  45038. * selector: 'grid'
  45039. * }
  45040. * ],
  45041. *
  45042. * init: function() {
  45043. * this.control({
  45044. * 'button': {
  45045. * click: this.refreshGrid
  45046. * }
  45047. * });
  45048. * },
  45049. *
  45050. * refreshGrid: function() {
  45051. * this.getList().store.load();
  45052. * }
  45053. * });
  45054. *
  45055. * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to
  45056. * refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this -
  45057. * the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
  45058. * assigns it to the reference 'list'.
  45059. *
  45060. * By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
  45061. * the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which
  45062. * was capitalized and prepended with get to go from 'list' to 'getList'.
  45063. *
  45064. * The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
  45065. * first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will
  45066. * use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
  45067. * match a single View in your application (in the case above our selector will match any grid on the page).
  45068. *
  45069. * Bringing it all together, our init function is called when the application boots, at which time we call this.control
  45070. * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will
  45071. * match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
  45072. * simplicity). When the button is clicked we use out getList function to refresh the grid.
  45073. *
  45074. * You can create any number of refs and control any number of components this way, simply adding more functions to
  45075. * your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the
  45076. * examples/app/feed-viewer folder in the SDK download.
  45077. *
  45078. * ## Generated getter methods
  45079. *
  45080. * Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and
  45081. * Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
  45082. *
  45083. * Ext.define('MyApp.controller.Users', {
  45084. * extend: 'Ext.app.Controller',
  45085. *
  45086. * models: ['User'],
  45087. * stores: ['AllUsers', 'AdminUsers'],
  45088. *
  45089. * init: function() {
  45090. * var User = this.getUserModel(),
  45091. * allUsers = this.getAllUsersStore();
  45092. *
  45093. * var ed = new User({name: 'Ed'});
  45094. * allUsers.add(ed);
  45095. * }
  45096. * });
  45097. *
  45098. * By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
  45099. * locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter
  45100. * functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
  45101. * Of course, you could do anything in this function but in this case we just did something simple to demonstrate the
  45102. * functionality.
  45103. *
  45104. * ## Further Reading
  45105. *
  45106. * For more information about writing Ext JS 4 applications, please see the
  45107. * [application architecture guide](#/guide/application_architecture). Also see the {@link Ext.app.Application} documentation.
  45108. *
  45109. * @docauthor Ed Spencer
  45110. */
  45111. Ext.define('Ext.app.Controller', {
  45112. mixins: {
  45113. observable: 'Ext.util.Observable'
  45114. },
  45115. /**
  45116. * @cfg {String} id The id of this controller. You can use this id when dispatching.
  45117. */
  45118. /**
  45119. * @cfg {String[]} models
  45120. * Array of models to require from AppName.model namespace. For example:
  45121. *
  45122. * Ext.define("MyApp.controller.Foo", {
  45123. * extend: "Ext.app.Controller",
  45124. * models: ['User', 'Vehicle']
  45125. * });
  45126. *
  45127. * This is equivalent of:
  45128. *
  45129. * Ext.define("MyApp.controller.Foo", {
  45130. * extend: "Ext.app.Controller",
  45131. * requires: ['MyApp.model.User', 'MyApp.model.Vehicle'],
  45132. * getUserModel: function() {
  45133. * return this.getModel("User");
  45134. * },
  45135. * getVehicleModel: function() {
  45136. * return this.getModel("Vehicle");
  45137. * }
  45138. * });
  45139. *
  45140. */
  45141. /**
  45142. * @cfg {String[]} views
  45143. * Array of views to require from AppName.view namespace and to generate getter methods for.
  45144. * For example:
  45145. *
  45146. * Ext.define("MyApp.controller.Foo", {
  45147. * extend: "Ext.app.Controller",
  45148. * views: ['List', 'Detail']
  45149. * });
  45150. *
  45151. * This is equivalent of:
  45152. *
  45153. * Ext.define("MyApp.controller.Foo", {
  45154. * extend: "Ext.app.Controller",
  45155. * requires: ['MyApp.view.List', 'MyApp.view.Detail'],
  45156. * getListView: function() {
  45157. * return this.getView("List");
  45158. * },
  45159. * getDetailView: function() {
  45160. * return this.getView("Detail");
  45161. * }
  45162. * });
  45163. *
  45164. */
  45165. /**
  45166. * @cfg {String[]} stores
  45167. * Array of stores to require from AppName.store namespace and to generate getter methods for.
  45168. * For example:
  45169. *
  45170. * Ext.define("MyApp.controller.Foo", {
  45171. * extend: "Ext.app.Controller",
  45172. * stores: ['Users', 'Vehicles']
  45173. * });
  45174. *
  45175. * This is equivalent of:
  45176. *
  45177. * Ext.define("MyApp.controller.Foo", {
  45178. * extend: "Ext.app.Controller",
  45179. * requires: ['MyApp.store.Users', 'MyApp.store.Vehicles']
  45180. * getUsersStore: function() {
  45181. * return this.getView("Users");
  45182. * },
  45183. * getVehiclesStore: function() {
  45184. * return this.getView("Vehicles");
  45185. * }
  45186. * });
  45187. *
  45188. */
  45189. /**
  45190. * @cfg {Object[]} refs
  45191. * Array of configs to build up references to views on page. For example:
  45192. *
  45193. * Ext.define("MyApp.controller.Foo", {
  45194. * extend: "Ext.app.Controller",
  45195. * refs: [
  45196. * {
  45197. * ref: 'list',
  45198. * selector: 'grid'
  45199. * }
  45200. * ],
  45201. * });
  45202. *
  45203. * This will add method `getList` to the controller which will internally use
  45204. * Ext.ComponentQuery to reference the grid component on page.
  45205. *
  45206. * The following fields can be used in ref definition:
  45207. *
  45208. * - `ref` - name of the reference.
  45209. * - `selector` - Ext.ComponentQuery selector to access the component.
  45210. * - `autoCreate` - True to create the component automatically if not found on page.
  45211. * - `forceCreate` - Forces the creation of the component every time reference is accessed
  45212. * (when `get<REFNAME>` is called).
  45213. */
  45214. onClassExtended: function(cls, data, hooks) {
  45215. var className = Ext.getClassName(cls),
  45216. match = className.match(/^(.*)\.controller\./),
  45217. namespace,
  45218. onBeforeClassCreated,
  45219. requires,
  45220. modules,
  45221. namespaceAndModule;
  45222. if (match !== null) {
  45223. namespace = Ext.Loader.getPrefix(className) || match[1];
  45224. onBeforeClassCreated = hooks.onBeforeCreated;
  45225. requires = [];
  45226. modules = ['model', 'view', 'store'];
  45227. hooks.onBeforeCreated = function(cls, data) {
  45228. var i, ln, module,
  45229. items, j, subLn, item;
  45230. for (i = 0,ln = modules.length; i < ln; i++) {
  45231. module = modules[i];
  45232. namespaceAndModule = namespace + '.' + module + '.';
  45233. items = Ext.Array.from(data[module + 's']);
  45234. for (j = 0,subLn = items.length; j < subLn; j++) {
  45235. item = items[j];
  45236. // Deciding if a class name must be qualified:
  45237. // 1 - if the name doesn't contains at least one dot, we must definitely qualify it
  45238. // 2 - the name may be a qualified name of a known class, but:
  45239. // 2.1 - in runtime, the loader may not know the class - specially in production - so we must check the class manager
  45240. // 2.2 - in build time, the class manager may not know the class, but the loader does, so we check the second one
  45241. // (the loader check assures it's really a class, and not a namespace, so we can have 'Books.controller.Books',
  45242. // and requesting a controller called Books will not be underqualified)
  45243. if (item.indexOf('.') !== -1 && (Ext.ClassManager.isCreated(item) || Ext.Loader.isAClassNameWithAKnownPrefix(item))) {
  45244. requires.push(item);
  45245. } else {
  45246. requires.push(namespaceAndModule + item);
  45247. }
  45248. }
  45249. }
  45250. Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
  45251. };
  45252. }
  45253. },
  45254. /**
  45255. * Creates new Controller.
  45256. * @param {Object} config (optional) Config object.
  45257. */
  45258. constructor: function(config) {
  45259. this.mixins.observable.constructor.call(this, config);
  45260. Ext.apply(this, config || {});
  45261. this.createGetters('model', this.models);
  45262. this.createGetters('store', this.stores);
  45263. this.createGetters('view', this.views);
  45264. if (this.refs) {
  45265. this.ref(this.refs);
  45266. }
  45267. },
  45268. /**
  45269. * A template method that is called when your application boots. It is called before the
  45270. * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
  45271. * your Viewport is created.
  45272. *
  45273. * @param {Ext.app.Application} application
  45274. * @template
  45275. */
  45276. init: Ext.emptyFn,
  45277. /**
  45278. * A template method like {@link #init}, but called after the viewport is created.
  45279. * This is called after the {@link Ext.app.Application#launch launch} method of Application is executed.
  45280. *
  45281. * @param {Ext.app.Application} application
  45282. * @template
  45283. */
  45284. onLaunch: Ext.emptyFn,
  45285. createGetters: function(type, refs) {
  45286. type = Ext.String.capitalize(type);
  45287. var i = 0,
  45288. length = (refs) ? refs.length : 0,
  45289. fn, ref, parts, x, numParts;
  45290. for (; i < length; i++) {
  45291. fn = 'get';
  45292. ref = refs[i];
  45293. parts = ref.split('.');
  45294. numParts = parts.length;
  45295. // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
  45296. for (x = 0 ; x < numParts; x++) {
  45297. fn += Ext.String.capitalize(parts[x]);
  45298. }
  45299. fn += type;
  45300. if (!this[fn]) {
  45301. this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
  45302. }
  45303. // Execute it right away
  45304. this[fn](ref);
  45305. }
  45306. },
  45307. ref: function(refs) {
  45308. refs = Ext.Array.from(refs);
  45309. var me = this,
  45310. i = 0,
  45311. length = refs.length,
  45312. info, ref, fn;
  45313. me.references = me.references || [];
  45314. for (; i < length; i++) {
  45315. info = refs[i];
  45316. ref = info.ref;
  45317. fn = 'get' + Ext.String.capitalize(ref);
  45318. if (!me[fn]) {
  45319. me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
  45320. }
  45321. me.references.push(ref.toLowerCase());
  45322. }
  45323. },
  45324. /**
  45325. * Registers a {@link #refs reference}.
  45326. * @param {Object} ref
  45327. */
  45328. addRef: function(ref) {
  45329. return this.ref([ref]);
  45330. },
  45331. getRef: function(ref, info, config) {
  45332. this.refCache = this.refCache || {};
  45333. info = info || {};
  45334. config = config || {};
  45335. Ext.apply(info, config);
  45336. if (info.forceCreate) {
  45337. return Ext.ComponentManager.create(info, 'component');
  45338. }
  45339. var me = this,
  45340. cached = me.refCache[ref];
  45341. if (!cached) {
  45342. me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
  45343. if (!cached && info.autoCreate) {
  45344. me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
  45345. }
  45346. if (cached) {
  45347. cached.on('beforedestroy', function() {
  45348. me.refCache[ref] = null;
  45349. });
  45350. }
  45351. }
  45352. return cached;
  45353. },
  45354. /**
  45355. * Returns true if a {@link #refs reference} is registered.
  45356. * @return {Boolean}
  45357. */
  45358. hasRef: function(ref) {
  45359. return this.references && this.references.indexOf(ref.toLowerCase()) !== -1;
  45360. },
  45361. /**
  45362. * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an
  45363. * object containing component paths mapped to a hash of listener functions.
  45364. *
  45365. * In the following example the `updateUser` function is mapped to to the `click`
  45366. * event on a button component, which is a child of the `useredit` component.
  45367. *
  45368. * Ext.define('AM.controller.Users', {
  45369. * init: function() {
  45370. * this.control({
  45371. * 'useredit button[action=save]': {
  45372. * click: this.updateUser
  45373. * }
  45374. * });
  45375. * },
  45376. *
  45377. * updateUser: function(button) {
  45378. * console.log('clicked the Save button');
  45379. * }
  45380. * });
  45381. *
  45382. * See {@link Ext.ComponentQuery} for more information on component selectors.
  45383. *
  45384. * @param {String/Object} selectors If a String, the second argument is used as the
  45385. * listeners, otherwise an object of selectors -> listeners is assumed
  45386. * @param {Object} listeners
  45387. */
  45388. control: function(selectors, listeners) {
  45389. this.application.control(selectors, listeners, this);
  45390. },
  45391. /**
  45392. * Returns instance of a {@link Ext.app.Controller controller} with the given name.
  45393. * When controller doesn't exist yet, it's created.
  45394. * @param {String} name
  45395. * @return {Ext.app.Controller} a controller instance.
  45396. */
  45397. getController: function(name) {
  45398. return this.application.getController(name);
  45399. },
  45400. /**
  45401. * Returns instance of a {@link Ext.data.Store Store} with the given name.
  45402. * When store doesn't exist yet, it's created.
  45403. * @param {String} name
  45404. * @return {Ext.data.Store} a store instance.
  45405. */
  45406. getStore: function(name) {
  45407. return this.application.getStore(name);
  45408. },
  45409. /**
  45410. * Returns a {@link Ext.data.Model Model} class with the given name.
  45411. * A shorthand for using {@link Ext.ModelManager#getModel}.
  45412. * @param {String} name
  45413. * @return {Ext.data.Model} a model class.
  45414. */
  45415. getModel: function(model) {
  45416. return this.application.getModel(model);
  45417. },
  45418. /**
  45419. * Returns a View class with the given name. To create an instance of the view,
  45420. * you can use it like it's used by Application to create the Viewport:
  45421. *
  45422. * this.getView('Viewport').create();
  45423. *
  45424. * @param {String} name
  45425. * @return {Ext.Base} a view class.
  45426. */
  45427. getView: function(view) {
  45428. return this.application.getView(view);
  45429. }
  45430. });
  45431. /**
  45432. * @author Don Griffin
  45433. *
  45434. * This class is a base for all id generators. It also provides lookup of id generators by
  45435. * their id.
  45436. *
  45437. * Generally, id generators are used to generate a primary key for new model instances. There
  45438. * are different approaches to solving this problem, so this mechanism has both simple use
  45439. * cases and is open to custom implementations. A {@link Ext.data.Model} requests id generation
  45440. * using the {@link Ext.data.Model#idgen} property.
  45441. *
  45442. * # Identity, Type and Shared IdGenerators
  45443. *
  45444. * It is often desirable to share IdGenerators to ensure uniqueness or common configuration.
  45445. * This is done by giving IdGenerator instances an id property by which they can be looked
  45446. * up using the {@link #get} method. To configure two {@link Ext.data.Model Model} classes
  45447. * to share one {@link Ext.data.SequentialIdGenerator sequential} id generator, you simply
  45448. * assign them the same id:
  45449. *
  45450. * Ext.define('MyApp.data.MyModelA', {
  45451. * extend: 'Ext.data.Model',
  45452. * idgen: {
  45453. * type: 'sequential',
  45454. * id: 'foo'
  45455. * }
  45456. * });
  45457. *
  45458. * Ext.define('MyApp.data.MyModelB', {
  45459. * extend: 'Ext.data.Model',
  45460. * idgen: {
  45461. * type: 'sequential',
  45462. * id: 'foo'
  45463. * }
  45464. * });
  45465. *
  45466. * To make this as simple as possible for generator types that are shared by many (or all)
  45467. * Models, the IdGenerator types (such as 'sequential' or 'uuid') are also reserved as
  45468. * generator id's. This is used by the {@link Ext.data.UuidGenerator} which has an id equal
  45469. * to its type ('uuid'). In other words, the following Models share the same generator:
  45470. *
  45471. * Ext.define('MyApp.data.MyModelX', {
  45472. * extend: 'Ext.data.Model',
  45473. * idgen: 'uuid'
  45474. * });
  45475. *
  45476. * Ext.define('MyApp.data.MyModelY', {
  45477. * extend: 'Ext.data.Model',
  45478. * idgen: 'uuid'
  45479. * });
  45480. *
  45481. * This can be overridden (by specifying the id explicitly), but there is no particularly
  45482. * good reason to do so for this generator type.
  45483. *
  45484. * # Creating Custom Generators
  45485. *
  45486. * An id generator should derive from this class and implement the {@link #generate} method.
  45487. * The constructor will apply config properties on new instances, so a constructor is often
  45488. * not necessary.
  45489. *
  45490. * To register an id generator type, a derived class should provide an `alias` like so:
  45491. *
  45492. * Ext.define('MyApp.data.CustomIdGenerator', {
  45493. * extend: 'Ext.data.IdGenerator',
  45494. * alias: 'idgen.custom',
  45495. *
  45496. * configProp: 42, // some config property w/default value
  45497. *
  45498. * generate: function () {
  45499. * return ... // a new id
  45500. * }
  45501. * });
  45502. *
  45503. * Using the custom id generator is then straightforward:
  45504. *
  45505. * Ext.define('MyApp.data.MyModel', {
  45506. * extend: 'Ext.data.Model',
  45507. * idgen: 'custom'
  45508. * });
  45509. * // or...
  45510. *
  45511. * Ext.define('MyApp.data.MyModel', {
  45512. * extend: 'Ext.data.Model',
  45513. * idgen: {
  45514. * type: 'custom',
  45515. * configProp: value
  45516. * }
  45517. * });
  45518. *
  45519. * It is not recommended to mix shared generators with generator configuration. This leads
  45520. * to unpredictable results unless all configurations match (which is also redundant). In
  45521. * such cases, a custom generator with a default id is the best approach.
  45522. *
  45523. * Ext.define('MyApp.data.CustomIdGenerator', {
  45524. * extend: 'Ext.data.SequentialIdGenerator',
  45525. * alias: 'idgen.custom',
  45526. *
  45527. * id: 'custom', // shared by default
  45528. *
  45529. * prefix: 'ID_',
  45530. * seed: 1000
  45531. * });
  45532. *
  45533. * Ext.define('MyApp.data.MyModelX', {
  45534. * extend: 'Ext.data.Model',
  45535. * idgen: 'custom'
  45536. * });
  45537. *
  45538. * Ext.define('MyApp.data.MyModelY', {
  45539. * extend: 'Ext.data.Model',
  45540. * idgen: 'custom'
  45541. * });
  45542. *
  45543. * // the above models share a generator that produces ID_1000, ID_1001, etc..
  45544. *
  45545. */
  45546. Ext.define('Ext.data.IdGenerator', {
  45547. /**
  45548. * @property {Boolean} isGenerator
  45549. * `true` in this class to identify an object as an instantiated IdGenerator, or subclass thereof.
  45550. */
  45551. isGenerator: true,
  45552. /**
  45553. * Initializes a new instance.
  45554. * @param {Object} config (optional) Configuration object to be applied to the new instance.
  45555. */
  45556. constructor: function(config) {
  45557. var me = this;
  45558. Ext.apply(me, config);
  45559. if (me.id) {
  45560. Ext.data.IdGenerator.all[me.id] = me;
  45561. }
  45562. },
  45563. /**
  45564. * @cfg {String} id
  45565. * The id by which to register a new instance. This instance can be found using the
  45566. * {@link Ext.data.IdGenerator#get} static method.
  45567. */
  45568. getRecId: function (rec) {
  45569. return rec.modelName + '-' + rec.internalId;
  45570. },
  45571. /**
  45572. * Generates and returns the next id. This method must be implemented by the derived
  45573. * class.
  45574. *
  45575. * @return {String} The next id.
  45576. * @method generate
  45577. * @abstract
  45578. */
  45579. statics: {
  45580. /**
  45581. * @property {Object} all
  45582. * This object is keyed by id to lookup instances.
  45583. * @private
  45584. * @static
  45585. */
  45586. all: {},
  45587. /**
  45588. * Returns the IdGenerator given its config description.
  45589. * @param {String/Object} config If this parameter is an IdGenerator instance, it is
  45590. * simply returned. If this is a string, it is first used as an id for lookup and
  45591. * then, if there is no match, as a type to create a new instance. This parameter
  45592. * can also be a config object that contains a `type` property (among others) that
  45593. * are used to create and configure the instance.
  45594. * @static
  45595. */
  45596. get: function (config) {
  45597. var generator,
  45598. id,
  45599. type;
  45600. if (typeof config == 'string') {
  45601. id = type = config;
  45602. config = null;
  45603. } else if (config.isGenerator) {
  45604. return config;
  45605. } else {
  45606. id = config.id || config.type;
  45607. type = config.type;
  45608. }
  45609. generator = this.all[id];
  45610. if (!generator) {
  45611. generator = Ext.create('idgen.' + type, config);
  45612. }
  45613. return generator;
  45614. }
  45615. }
  45616. });
  45617. /**
  45618. * @class Ext.data.SortTypes
  45619. * This class defines a series of static methods that are used on a
  45620. * {@link Ext.data.Field} for performing sorting. The methods cast the
  45621. * underlying values into a data type that is appropriate for sorting on
  45622. * that particular field. If a {@link Ext.data.Field#type} is specified,
  45623. * the sortType will be set to a sane default if the sortType is not
  45624. * explicitly defined on the field. The sortType will make any necessary
  45625. * modifications to the value and return it.
  45626. * <ul>
  45627. * <li><b>asText</b> - Removes any tags and converts the value to a string</li>
  45628. * <li><b>asUCText</b> - Removes any tags and converts the value to an uppercase string</li>
  45629. * <li><b>asUCText</b> - Converts the value to an uppercase string</li>
  45630. * <li><b>asDate</b> - Converts the value into Unix epoch time</li>
  45631. * <li><b>asFloat</b> - Converts the value to a floating point number</li>
  45632. * <li><b>asInt</b> - Converts the value to an integer number</li>
  45633. * </ul>
  45634. * <p>
  45635. * It is also possible to create a custom sortType that can be used throughout
  45636. * an application.
  45637. * <pre><code>
  45638. Ext.apply(Ext.data.SortTypes, {
  45639. asPerson: function(person){
  45640. // expects an object with a first and last name property
  45641. return person.lastName.toUpperCase() + person.firstName.toLowerCase();
  45642. }
  45643. });
  45644. Ext.define('Employee', {
  45645. extend: 'Ext.data.Model',
  45646. fields: [{
  45647. name: 'person',
  45648. sortType: 'asPerson'
  45649. }, {
  45650. name: 'salary',
  45651. type: 'float' // sortType set to asFloat
  45652. }]
  45653. });
  45654. * </code></pre>
  45655. * </p>
  45656. * @singleton
  45657. * @docauthor Evan Trimboli <evan@sencha.com>
  45658. */
  45659. Ext.define('Ext.data.SortTypes', {
  45660. singleton: true,
  45661. /**
  45662. * Default sort that does nothing
  45663. * @param {Object} s The value being converted
  45664. * @return {Object} The comparison value
  45665. */
  45666. none : function(s) {
  45667. return s;
  45668. },
  45669. /**
  45670. * The regular expression used to strip tags
  45671. * @type {RegExp}
  45672. * @property
  45673. */
  45674. stripTagsRE : /<\/?[^>]+>/gi,
  45675. /**
  45676. * Strips all HTML tags to sort on text only
  45677. * @param {Object} s The value being converted
  45678. * @return {String} The comparison value
  45679. */
  45680. asText : function(s) {
  45681. return String(s).replace(this.stripTagsRE, "");
  45682. },
  45683. /**
  45684. * Strips all HTML tags to sort on text only - Case insensitive
  45685. * @param {Object} s The value being converted
  45686. * @return {String} The comparison value
  45687. */
  45688. asUCText : function(s) {
  45689. return String(s).toUpperCase().replace(this.stripTagsRE, "");
  45690. },
  45691. /**
  45692. * Case insensitive string
  45693. * @param {Object} s The value being converted
  45694. * @return {String} The comparison value
  45695. */
  45696. asUCString : function(s) {
  45697. return String(s).toUpperCase();
  45698. },
  45699. /**
  45700. * Date sorting
  45701. * @param {Object} s The value being converted
  45702. * @return {Number} The comparison value
  45703. */
  45704. asDate : function(s) {
  45705. if(!s){
  45706. return 0;
  45707. }
  45708. if(Ext.isDate(s)){
  45709. return s.getTime();
  45710. }
  45711. return Date.parse(String(s));
  45712. },
  45713. /**
  45714. * Float sorting
  45715. * @param {Object} s The value being converted
  45716. * @return {Number} The comparison value
  45717. */
  45718. asFloat : function(s) {
  45719. var val = parseFloat(String(s).replace(/,/g, ""));
  45720. return isNaN(val) ? 0 : val;
  45721. },
  45722. /**
  45723. * Integer sorting
  45724. * @param {Object} s The value being converted
  45725. * @return {Number} The comparison value
  45726. */
  45727. asInt : function(s) {
  45728. var val = parseInt(String(s).replace(/,/g, ""), 10);
  45729. return isNaN(val) ? 0 : val;
  45730. }
  45731. });
  45732. /**
  45733. * @class Ext.data.Types
  45734. * <p>This is a static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
  45735. * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
  45736. * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
  45737. * of this class.</p>
  45738. * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
  45739. * each type definition must contain three properties:</p>
  45740. * <div class="mdetail-params"><ul>
  45741. * <li><code>convert</code> : <i>Function</i><div class="sub-desc">A function to convert raw data values from a data block into the data
  45742. * to be stored in the Field. The function is passed the collowing parameters:
  45743. * <div class="mdetail-params"><ul>
  45744. * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
  45745. * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
  45746. * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
  45747. * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
  45748. * ({@link Ext.data.reader.Json JsonReader}), or an XML element.</div></li>
  45749. * </ul></div></div></li>
  45750. * <li><code>sortType</code> : <i>Function</i> <div class="sub-desc">A function to convert the stored data into comparable form, as defined by {@link Ext.data.SortTypes}.</div></li>
  45751. * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
  45752. * </ul></div>
  45753. * <p>For example, to create a VELatLong field (See the Microsoft Bing Mapping API) containing the latitude/longitude value of a datapoint on a map from a JsonReader data block
  45754. * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
  45755. *<pre><code>
  45756. // Add a new Field data type which stores a VELatLong object in the Record.
  45757. Ext.data.Types.VELATLONG = {
  45758. convert: function(v, data) {
  45759. return new VELatLong(data.lat, data.long);
  45760. },
  45761. sortType: function(v) {
  45762. return v.Latitude; // When sorting, order by latitude
  45763. },
  45764. type: 'VELatLong'
  45765. };
  45766. </code></pre>
  45767. * <p>Then, when declaring a Model, use: <pre><code>
  45768. var types = Ext.data.Types; // allow shorthand type access
  45769. Ext.define('Unit',
  45770. extend: 'Ext.data.Model',
  45771. fields: [
  45772. { name: 'unitName', mapping: 'UnitName' },
  45773. { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
  45774. { name: 'latitude', mapping: 'lat', type: types.FLOAT },
  45775. { name: 'longitude', mapping: 'long', type: types.FLOAT },
  45776. { name: 'position', type: types.VELATLONG }
  45777. ]
  45778. });
  45779. </code></pre>
  45780. * @singleton
  45781. */
  45782. Ext.define('Ext.data.Types', {
  45783. singleton: true,
  45784. requires: ['Ext.data.SortTypes']
  45785. }, function() {
  45786. var st = Ext.data.SortTypes;
  45787. Ext.apply(Ext.data.Types, {
  45788. /**
  45789. * @property {RegExp} stripRe
  45790. * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
  45791. * This should be overridden for localization.
  45792. */
  45793. stripRe: /[\$,%]/g,
  45794. /**
  45795. * @property {Object} AUTO
  45796. * This data type means that no conversion is applied to the raw data before it is placed into a Record.
  45797. */
  45798. AUTO: {
  45799. sortType: st.none,
  45800. type: 'auto'
  45801. },
  45802. /**
  45803. * @property {Object} STRING
  45804. * This data type means that the raw data is converted into a String before it is placed into a Record.
  45805. */
  45806. STRING: {
  45807. convert: function(v) {
  45808. var defaultValue = this.useNull ? null : '';
  45809. return (v === undefined || v === null) ? defaultValue : String(v);
  45810. },
  45811. sortType: st.asUCString,
  45812. type: 'string'
  45813. },
  45814. /**
  45815. * @property {Object} INT
  45816. * This data type means that the raw data is converted into an integer before it is placed into a Record.
  45817. * <p>The synonym <code>INTEGER</code> is equivalent.</p>
  45818. */
  45819. INT: {
  45820. convert: function(v) {
  45821. return v !== undefined && v !== null && v !== '' ?
  45822. parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
  45823. },
  45824. sortType: st.none,
  45825. type: 'int'
  45826. },
  45827. /**
  45828. * @property {Object} FLOAT
  45829. * This data type means that the raw data is converted into a number before it is placed into a Record.
  45830. * <p>The synonym <code>NUMBER</code> is equivalent.</p>
  45831. */
  45832. FLOAT: {
  45833. convert: function(v) {
  45834. return v !== undefined && v !== null && v !== '' ?
  45835. parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
  45836. },
  45837. sortType: st.none,
  45838. type: 'float'
  45839. },
  45840. /**
  45841. * @property {Object} BOOL
  45842. * <p>This data type means that the raw data is converted into a boolean before it is placed into
  45843. * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
  45844. * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
  45845. */
  45846. BOOL: {
  45847. convert: function(v) {
  45848. if (this.useNull && (v === undefined || v === null || v === '')) {
  45849. return null;
  45850. }
  45851. return v === true || v === 'true' || v == 1;
  45852. },
  45853. sortType: st.none,
  45854. type: 'bool'
  45855. },
  45856. /**
  45857. * @property {Object} DATE
  45858. * This data type means that the raw data is converted into a Date before it is placed into a Record.
  45859. * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
  45860. * being applied.
  45861. */
  45862. DATE: {
  45863. convert: function(v) {
  45864. var df = this.dateFormat,
  45865. parsed;
  45866. if (!v) {
  45867. return null;
  45868. }
  45869. if (Ext.isDate(v)) {
  45870. return v;
  45871. }
  45872. if (df) {
  45873. if (df == 'timestamp') {
  45874. return new Date(v*1000);
  45875. }
  45876. if (df == 'time') {
  45877. return new Date(parseInt(v, 10));
  45878. }
  45879. return Ext.Date.parse(v, df);
  45880. }
  45881. parsed = Date.parse(v);
  45882. return parsed ? new Date(parsed) : null;
  45883. },
  45884. sortType: st.asDate,
  45885. type: 'date'
  45886. }
  45887. });
  45888. Ext.apply(Ext.data.Types, {
  45889. /**
  45890. * @property {Object} BOOLEAN
  45891. * <p>This data type means that the raw data is converted into a boolean before it is placed into
  45892. * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
  45893. * <p>The synonym <code>BOOL</code> is equivalent.</p>
  45894. */
  45895. BOOLEAN: this.BOOL,
  45896. /**
  45897. * @property {Object} INTEGER
  45898. * This data type means that the raw data is converted into an integer before it is placed into a Record.
  45899. * <p>The synonym <code>INT</code> is equivalent.</p>
  45900. */
  45901. INTEGER: this.INT,
  45902. /**
  45903. * @property {Object} NUMBER
  45904. * This data type means that the raw data is converted into a number before it is placed into a Record.
  45905. * <p>The synonym <code>FLOAT</code> is equivalent.</p>
  45906. */
  45907. NUMBER: this.FLOAT
  45908. });
  45909. });
  45910. /**
  45911. * @author Ed Spencer
  45912. *
  45913. * Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class that
  45914. * extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a {@link
  45915. * Ext.data.Model Model}. For example, we might set up a model like this:
  45916. *
  45917. * Ext.define('User', {
  45918. * extend: 'Ext.data.Model',
  45919. * fields: [
  45920. * 'name', 'email',
  45921. * {name: 'age', type: 'int'},
  45922. * {name: 'gender', type: 'string', defaultValue: 'Unknown'}
  45923. * ]
  45924. * });
  45925. *
  45926. * Four fields will have been created for the User Model - name, email, age and gender. Note that we specified a couple
  45927. * of different formats here; if we only pass in the string name of the field (as with name and email), the field is set
  45928. * up with the 'auto' type. It's as if we'd done this instead:
  45929. *
  45930. * Ext.define('User', {
  45931. * extend: 'Ext.data.Model',
  45932. * fields: [
  45933. * {name: 'name', type: 'auto'},
  45934. * {name: 'email', type: 'auto'},
  45935. * {name: 'age', type: 'int'},
  45936. * {name: 'gender', type: 'string', defaultValue: 'Unknown'}
  45937. * ]
  45938. * });
  45939. *
  45940. * # Types and conversion
  45941. *
  45942. * The {@link #type} is important - it's used to automatically convert data passed to the field into the correct format.
  45943. * In our example above, the name and email fields used the 'auto' type and will just accept anything that is passed
  45944. * into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.
  45945. *
  45946. * Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can do
  45947. * this using a {@link #convert} function. Here, we're going to create a new field based on another:
  45948. *
  45949. * Ext.define('User', {
  45950. * extend: 'Ext.data.Model',
  45951. * fields: [
  45952. * {
  45953. * name: 'firstName',
  45954. * convert: function(value, record) {
  45955. * var fullName = record.get('name'),
  45956. * splits = fullName.split(" "),
  45957. * firstName = splits[0];
  45958. *
  45959. * return firstName;
  45960. * }
  45961. * },
  45962. * 'name', 'email',
  45963. * {name: 'age', type: 'int'},
  45964. * {name: 'gender', type: 'string', defaultValue: 'Unknown'}
  45965. * ]
  45966. * });
  45967. *
  45968. * Now when we create a new User, the firstName is populated automatically based on the name:
  45969. *
  45970. * var ed = Ext.create('User', {name: 'Ed Spencer'});
  45971. *
  45972. * console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
  45973. *
  45974. * Fields which are configured with a custom ```convert``` function are read *after* all other fields
  45975. * when constructing and reading records, so that if convert functions rely on other, non-converted fields
  45976. * (as in this example), they can be sure of those fields being present.
  45977. *
  45978. * In fact, if we log out all of the data inside ed, we'll see this:
  45979. *
  45980. * console.log(ed.data);
  45981. *
  45982. * //outputs this:
  45983. * {
  45984. * age: 0,
  45985. * email: "",
  45986. * firstName: "Ed",
  45987. * gender: "Unknown",
  45988. * name: "Ed Spencer"
  45989. * }
  45990. *
  45991. * The age field has been given a default of zero because we made it an int type. As an auto field, email has defaulted
  45992. * to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown' so we see
  45993. * that now. Let's correct that and satisfy ourselves that the types work as we expect:
  45994. *
  45995. * ed.set('gender', 'Male');
  45996. * ed.get('gender'); //returns 'Male'
  45997. *
  45998. * ed.set('age', 25.4);
  45999. * ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
  46000. */
  46001. Ext.define('Ext.data.Field', {
  46002. requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
  46003. alias: 'data.field',
  46004. isField: true,
  46005. constructor : function(config) {
  46006. var me = this,
  46007. types = Ext.data.Types,
  46008. st;
  46009. if (Ext.isString(config)) {
  46010. config = {name: config};
  46011. }
  46012. Ext.apply(me, config);
  46013. st = me.sortType;
  46014. if (me.type) {
  46015. if (Ext.isString(me.type)) {
  46016. me.type = types[me.type.toUpperCase()] || types.AUTO;
  46017. }
  46018. } else {
  46019. me.type = types.AUTO;
  46020. }
  46021. // named sortTypes are supported, here we look them up
  46022. if (Ext.isString(st)) {
  46023. me.sortType = Ext.data.SortTypes[st];
  46024. } else if(Ext.isEmpty(st)) {
  46025. me.sortType = me.type.sortType;
  46026. }
  46027. // Reference this type's default converter if we did not recieve one in configuration.
  46028. if (!config.hasOwnProperty('convert')) {
  46029. me.convert = me.type.convert; // this may be undefined (e.g., AUTO)
  46030. } else if (!me.convert && me.type.convert && !config.hasOwnProperty('defaultValue')) {
  46031. // If the converter has been nulled out, and we have not been configured
  46032. // with a field-specific defaultValue, then coerce the inherited defaultValue into our data type.
  46033. me.defaultValue = me.type.convert(me.defaultValue);
  46034. }
  46035. if (config.convert) {
  46036. me.hasCustomConvert = true;
  46037. }
  46038. },
  46039. /**
  46040. * @cfg {String} name
  46041. *
  46042. * The name by which the field is referenced within the Model. This is referenced by, for example, the `dataIndex`
  46043. * property in column definition objects passed to {@link Ext.grid.property.HeaderContainer}.
  46044. *
  46045. * Note: In the simplest case, if no properties other than `name` are required, a field definition may consist of
  46046. * just a String for the field name.
  46047. */
  46048. /**
  46049. * @cfg {String/Object} type
  46050. *
  46051. * The data type for automatic conversion from received data to the *stored* value if
  46052. * `{@link Ext.data.Field#convert convert}` has not been specified. This may be specified as a string value.
  46053. * Possible values are
  46054. *
  46055. * - auto (Default, implies no conversion)
  46056. * - string
  46057. * - int
  46058. * - float
  46059. * - boolean
  46060. * - date
  46061. *
  46062. * This may also be specified by referencing a member of the {@link Ext.data.Types} class.
  46063. *
  46064. * Developers may create their own application-specific data types by defining new members of the {@link
  46065. * Ext.data.Types} class.
  46066. */
  46067. /**
  46068. * @cfg {Function} [convert]
  46069. *
  46070. * A function which converts the value provided by the Reader into an object that will be stored in the Model.
  46071. *
  46072. * If configured as `null`, then no conversion will be applied to the raw data property when this Field
  46073. * is read. This will increase performance. but you must ensure that the data is of the correct type and does
  46074. * not *need* converting.
  46075. *
  46076. * It is passed the following parameters:
  46077. *
  46078. * - **v** : Mixed
  46079. *
  46080. * The data value as read by the Reader, if undefined will use the configured `{@link Ext.data.Field#defaultValue
  46081. * defaultValue}`.
  46082. *
  46083. * - **rec** : Ext.data.Model
  46084. *
  46085. * The data object containing the Model as read so far by the Reader. Note that the Model may not be fully populated
  46086. * at this point as the fields are read in the order that they are defined in your
  46087. * {@link Ext.data.Model#cfg-fields fields} array.
  46088. *
  46089. * Example of convert functions:
  46090. *
  46091. * function fullName(v, record){
  46092. * return record.data.last + ', ' + record.data.first;
  46093. * }
  46094. *
  46095. * function location(v, record){
  46096. * return !record.data.city ? '' : (record.data.city + ', ' + record.data.state);
  46097. * }
  46098. *
  46099. * Ext.define('Dude', {
  46100. * extend: 'Ext.data.Model',
  46101. * fields: [
  46102. * {name: 'fullname', convert: fullName},
  46103. * {name: 'firstname', mapping: 'name.first'},
  46104. * {name: 'lastname', mapping: 'name.last'},
  46105. * {name: 'city', defaultValue: 'homeless'},
  46106. * 'state',
  46107. * {name: 'location', convert: location}
  46108. * ]
  46109. * });
  46110. *
  46111. * // create the data store
  46112. * var store = Ext.create('Ext.data.Store', {
  46113. * reader: {
  46114. * type: 'json',
  46115. * model: 'Dude',
  46116. * idProperty: 'key',
  46117. * root: 'daRoot',
  46118. * totalProperty: 'total'
  46119. * }
  46120. * });
  46121. *
  46122. * var myData = [
  46123. * { key: 1,
  46124. * name: { first: 'Fat', last: 'Albert' }
  46125. * // notice no city, state provided in data object
  46126. * },
  46127. * { key: 2,
  46128. * name: { first: 'Barney', last: 'Rubble' },
  46129. * city: 'Bedrock', state: 'Stoneridge'
  46130. * },
  46131. * { key: 3,
  46132. * name: { first: 'Cliff', last: 'Claven' },
  46133. * city: 'Boston', state: 'MA'
  46134. * }
  46135. * ];
  46136. */
  46137. /**
  46138. * @cfg {Function} [serialize]
  46139. * A function which converts the Model's value for this Field into a form which can be used by whatever {@link Ext.data.writer.Writer Writer}
  46140. * is being used to sync data with the server.
  46141. *
  46142. * The function should return a string which represents the Field's value.
  46143. *
  46144. * It is passed the following parameters:
  46145. *
  46146. * - **v** : Mixed
  46147. *
  46148. * The Field's value - the value to be serialized.
  46149. *
  46150. * - **rec** : Ext.data.Model
  46151. *
  46152. * The record being serialized.
  46153. *
  46154. */
  46155. /**
  46156. * @cfg {String} dateFormat
  46157. *
  46158. * Used when converting received data into a Date when the {@link #type} is specified as `"date"`.
  46159. *
  46160. * The format dtring is also used when serializing Date fields for use by {@link Ext.data.writer.Writer Writers}.
  46161. *
  46162. * A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the value provided by
  46163. * the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a javascript millisecond
  46164. * timestamp. See {@link Ext.Date}.
  46165. */
  46166. dateFormat: null,
  46167. /**
  46168. * @cfg {Boolean} useNull
  46169. *
  46170. * Use when converting received data into a INT, FLOAT, BOOL or STRING type. If the value cannot be
  46171. * parsed, `null` will be used if useNull is true, otherwise a default value for that type will be used:
  46172. *
  46173. * - for INT and FLOAT - `0`.
  46174. * - for STRING - `""`.
  46175. * - for BOOL - `false`.
  46176. *
  46177. * Note that when parsing of DATE type fails, the value will be `null` regardless of this setting.
  46178. */
  46179. useNull: false,
  46180. /**
  46181. * @cfg {Object} [defaultValue=""]
  46182. *
  46183. * The default value used when the creating an instance from a raw data object, and the property referenced by the
  46184. * `{@link Ext.data.Field#mapping mapping}` does not exist in that data object.
  46185. *
  46186. * May be specified as `undefined` to prevent defaulting in a value.
  46187. */
  46188. defaultValue: "",
  46189. /**
  46190. * @cfg {String/Number} mapping
  46191. *
  46192. * (Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation that is creating the
  46193. * {@link Ext.data.Model Model} to extract the Field value from the data object. If the path expression is the same
  46194. * as the field name, the mapping may be omitted.
  46195. *
  46196. * The form of the mapping expression depends on the Reader being used.
  46197. *
  46198. * - {@link Ext.data.reader.Json}
  46199. *
  46200. * The mapping is a string containing the javascript expression to reference the data from an element of the data
  46201. * item's {@link Ext.data.reader.Json#cfg-root root} Array. Defaults to the field name.
  46202. *
  46203. * - {@link Ext.data.reader.Xml}
  46204. *
  46205. * The mapping is an {@link Ext.DomQuery} path to the data item relative to the DOM element that represents the
  46206. * {@link Ext.data.reader.Xml#record record}. Defaults to the field name.
  46207. *
  46208. * - {@link Ext.data.reader.Array}
  46209. *
  46210. * The mapping is a number indicating the Array index of the field's value. Defaults to the field specification's
  46211. * Array position.
  46212. *
  46213. * If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
  46214. * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
  46215. * return the desired data.
  46216. */
  46217. mapping: null,
  46218. /**
  46219. * @cfg {Function} sortType
  46220. *
  46221. * A function which converts a Field's value to a comparable value in order to ensure correct sort ordering.
  46222. * Predefined functions are provided in {@link Ext.data.SortTypes}. A custom sort example:
  46223. *
  46224. * // current sort after sort we want
  46225. * // +-+------+ +-+------+
  46226. * // |1|First | |1|First |
  46227. * // |2|Last | |3|Second|
  46228. * // |3|Second| |2|Last |
  46229. * // +-+------+ +-+------+
  46230. *
  46231. * sortType: function(value) {
  46232. * switch (value.toLowerCase()) // native toLowerCase():
  46233. * {
  46234. * case 'first': return 1;
  46235. * case 'second': return 2;
  46236. * default: return 3;
  46237. * }
  46238. * }
  46239. */
  46240. sortType : null,
  46241. /**
  46242. * @cfg {String} sortDir
  46243. *
  46244. * Initial direction to sort (`"ASC"` or `"DESC"`). Defaults to `"ASC"`.
  46245. */
  46246. sortDir : "ASC",
  46247. /**
  46248. * @cfg {Boolean} allowBlank
  46249. * @private
  46250. *
  46251. * Used for validating a {@link Ext.data.Model model}. Defaults to true. An empty value here will cause
  46252. * {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid} to evaluate to false.
  46253. */
  46254. allowBlank : true,
  46255. /**
  46256. * @cfg {Boolean} persist
  46257. *
  46258. * False to exclude this field from the {@link Ext.data.Model#modified} fields in a model. This will also exclude
  46259. * the field from being written using a {@link Ext.data.writer.Writer}. This option is useful when model fields are
  46260. * used to keep state on the client but do not need to be persisted to the server. Defaults to true.
  46261. */
  46262. persist: true
  46263. });
  46264. /**
  46265. * @author Ed Spencer
  46266. * @class Ext.data.Errors
  46267. *
  46268. * <p>Wraps a collection of validation error responses and provides convenient functions for
  46269. * accessing and errors for specific fields.</p>
  46270. *
  46271. * <p>Usually this class does not need to be instantiated directly - instances are instead created
  46272. * automatically when {@link Ext.data.Model#validate validate} on a model instance:</p>
  46273. *
  46274. <pre><code>
  46275. //validate some existing model instance - in this case it returned 2 failures messages
  46276. var errors = myModel.validate();
  46277. errors.isValid(); //false
  46278. errors.length; //2
  46279. errors.getByField('name'); // [{field: 'name', message: 'must be present'}]
  46280. errors.getByField('title'); // [{field: 'title', message: 'is too short'}]
  46281. </code></pre>
  46282. */
  46283. Ext.define('Ext.data.Errors', {
  46284. extend: 'Ext.util.MixedCollection',
  46285. /**
  46286. * Returns true if there are no errors in the collection
  46287. * @return {Boolean}
  46288. */
  46289. isValid: function() {
  46290. return this.length === 0;
  46291. },
  46292. /**
  46293. * Returns all of the errors for the given field
  46294. * @param {String} fieldName The field to get errors for
  46295. * @return {Object[]} All errors for the given field
  46296. */
  46297. getByField: function(fieldName) {
  46298. var errors = [],
  46299. error, field, i;
  46300. for (i = 0; i < this.length; i++) {
  46301. error = this.items[i];
  46302. if (error.field == fieldName) {
  46303. errors.push(error);
  46304. }
  46305. }
  46306. return errors;
  46307. }
  46308. });
  46309. /**
  46310. * @author Ed Spencer
  46311. *
  46312. * Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
  46313. * used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
  46314. * Operation objects directly.
  46315. *
  46316. * Several Operations can be batched together in a {@link Ext.data.Batch batch}.
  46317. */
  46318. Ext.define('Ext.data.Operation', {
  46319. /**
  46320. * @cfg {Boolean} synchronous
  46321. * True if this Operation is to be executed synchronously. This property is inspected by a
  46322. * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
  46323. */
  46324. synchronous: true,
  46325. /**
  46326. * @cfg {String} action
  46327. * The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
  46328. */
  46329. action: undefined,
  46330. /**
  46331. * @cfg {Ext.util.Filter[]} filters
  46332. * Optional array of filter objects. Only applies to 'read' actions.
  46333. */
  46334. filters: undefined,
  46335. /**
  46336. * @cfg {Ext.util.Sorter[]} sorters
  46337. * Optional array of sorter objects. Only applies to 'read' actions.
  46338. */
  46339. sorters: undefined,
  46340. /**
  46341. * @cfg {Ext.util.Grouper[]} groupers
  46342. * Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
  46343. */
  46344. groupers: undefined,
  46345. /**
  46346. * @cfg {Number} start
  46347. * The start index (offset), used in paging when running a 'read' action.
  46348. */
  46349. start: undefined,
  46350. /**
  46351. * @cfg {Number} limit
  46352. * The number of records to load. Used on 'read' actions when paging is being used.
  46353. */
  46354. limit: undefined,
  46355. /**
  46356. * @cfg {Ext.data.Batch} batch
  46357. * The batch that this Operation is a part of.
  46358. */
  46359. batch: undefined,
  46360. /**
  46361. * @cfg {Object} params
  46362. * Parameters to pass along with the request when performing the operation.
  46363. */
  46364. /**
  46365. * @cfg {Function} callback
  46366. * Function to execute when operation completed.
  46367. * @cfg {Ext.data.Model[]} callback.records Array of records.
  46368. * @cfg {Ext.data.Operation} callback.operation The Operation itself.
  46369. * @cfg {Boolean} callback.success True when operation completed successfully.
  46370. */
  46371. callback: undefined,
  46372. /**
  46373. * @cfg {Object} scope
  46374. * Scope for the {@link #callback} function.
  46375. */
  46376. scope: undefined,
  46377. /**
  46378. * @property {Boolean} started
  46379. * The start status of this Operation. Use {@link #isStarted}.
  46380. * @readonly
  46381. * @private
  46382. */
  46383. started: false,
  46384. /**
  46385. * @property {Boolean} running
  46386. * The run status of this Operation. Use {@link #isRunning}.
  46387. * @readonly
  46388. * @private
  46389. */
  46390. running: false,
  46391. /**
  46392. * @property {Boolean} complete
  46393. * The completion status of this Operation. Use {@link #isComplete}.
  46394. * @readonly
  46395. * @private
  46396. */
  46397. complete: false,
  46398. /**
  46399. * @property {Boolean} success
  46400. * Whether the Operation was successful or not. This starts as undefined and is set to true
  46401. * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
  46402. * {@link #wasSuccessful} to query success status.
  46403. * @readonly
  46404. * @private
  46405. */
  46406. success: undefined,
  46407. /**
  46408. * @property {Boolean} exception
  46409. * The exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
  46410. * @readonly
  46411. * @private
  46412. */
  46413. exception: false,
  46414. /**
  46415. * @property {String/Object} error
  46416. * The error object passed when {@link #setException} was called. This could be any object or primitive.
  46417. * @private
  46418. */
  46419. error: undefined,
  46420. /**
  46421. * @property {RegExp} actionCommitRecordsRe
  46422. * The RegExp used to categorize actions that require record commits.
  46423. */
  46424. actionCommitRecordsRe: /^(?:create|update)$/i,
  46425. /**
  46426. * @property {RegExp} actionSkipSyncRe
  46427. * The RegExp used to categorize actions that skip local record synchronization. This defaults
  46428. * to match 'destroy'.
  46429. */
  46430. actionSkipSyncRe: /^destroy$/i,
  46431. /**
  46432. * Creates new Operation object.
  46433. * @param {Object} config (optional) Config object.
  46434. */
  46435. constructor: function(config) {
  46436. Ext.apply(this, config || {});
  46437. },
  46438. /**
  46439. * This method is called to commit data to this instance's records given the records in
  46440. * the server response. This is followed by calling {@link Ext.data.Model#commit} on all
  46441. * those records (for 'create' and 'update' actions).
  46442. *
  46443. * If this {@link #action} is 'destroy', any server records are ignored and the
  46444. * {@link Ext.data.Model#commit} method is not called.
  46445. *
  46446. * @param {Ext.data.Model[]} serverRecords An array of {@link Ext.data.Model} objects returned by
  46447. * the server.
  46448. * @markdown
  46449. */
  46450. commitRecords: function (serverRecords) {
  46451. var me = this,
  46452. mc, index, clientRecords, serverRec, clientRec, i, len;
  46453. if (!me.actionSkipSyncRe.test(me.action)) {
  46454. clientRecords = me.records;
  46455. if (clientRecords && clientRecords.length) {
  46456. if (clientRecords.length > 1) {
  46457. // If this operation has multiple records, client records need to be matched up with server records
  46458. // so that any data returned from the server can be updated in the client records. If we don't have
  46459. // a clientIdProperty specified on the model and we've done a create, just assume the data is returned in order.
  46460. // If it's an update, the records should already have an id which should match what the server returns.
  46461. if (me.action == 'update' || clientRecords[0].clientIdProperty) {
  46462. mc = new Ext.util.MixedCollection();
  46463. mc.addAll(serverRecords);
  46464. for (index = clientRecords.length; index--; ) {
  46465. clientRec = clientRecords[index];
  46466. serverRec = mc.findBy(me.matchClientRec, clientRec);
  46467. // Replace client record data with server record data
  46468. clientRec.copyFrom(serverRec);
  46469. }
  46470. } else {
  46471. for (i = 0, len = clientRecords.length; i < len; ++i) {
  46472. clientRec = clientRecords[i];
  46473. serverRec = serverRecords[i];
  46474. if (clientRec && serverRec) {
  46475. me.updateRecord(clientRec, serverRec);
  46476. }
  46477. }
  46478. }
  46479. } else {
  46480. // operation only has one record, so just match the first client record up with the first server record
  46481. this.updateRecord(clientRecords[0], serverRecords[0]);
  46482. }
  46483. if (me.actionCommitRecordsRe.test(me.action)) {
  46484. for (index = clientRecords.length; index--; ) {
  46485. clientRecords[index].commit();
  46486. }
  46487. }
  46488. }
  46489. }
  46490. },
  46491. updateRecord: function(clientRec, serverRec) {
  46492. // if the client record is not a phantom, make sure the ids match before replacing the client data with server data.
  46493. if(serverRec && (clientRec.phantom || clientRec.getId() === serverRec.getId())) {
  46494. clientRec.copyFrom(serverRec);
  46495. }
  46496. },
  46497. // Private.
  46498. // Record matching function used by commitRecords
  46499. // IMPORTANT: This is called in the scope of the clientRec being matched
  46500. matchClientRec: function(record) {
  46501. var clientRec = this,
  46502. clientRecordId = clientRec.getId();
  46503. if(clientRecordId && record.getId() === clientRecordId) {
  46504. return true;
  46505. }
  46506. // if the server record cannot be found by id, find by internalId.
  46507. // this allows client records that did not previously exist on the server
  46508. // to be updated with the correct server id and data.
  46509. return record.internalId === clientRec.internalId;
  46510. },
  46511. /**
  46512. * Marks the Operation as started.
  46513. */
  46514. setStarted: function() {
  46515. this.started = true;
  46516. this.running = true;
  46517. },
  46518. /**
  46519. * Marks the Operation as completed.
  46520. */
  46521. setCompleted: function() {
  46522. this.complete = true;
  46523. this.running = false;
  46524. },
  46525. /**
  46526. * Marks the Operation as successful.
  46527. */
  46528. setSuccessful: function() {
  46529. this.success = true;
  46530. },
  46531. /**
  46532. * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
  46533. * @param {String/Object} error (optional) error string/object
  46534. */
  46535. setException: function(error) {
  46536. this.exception = true;
  46537. this.success = false;
  46538. this.running = false;
  46539. this.error = error;
  46540. },
  46541. /**
  46542. * Returns true if this Operation encountered an exception (see also {@link #getError})
  46543. * @return {Boolean} True if there was an exception
  46544. */
  46545. hasException: function() {
  46546. return this.exception === true;
  46547. },
  46548. /**
  46549. * Returns the error string or object that was set using {@link #setException}
  46550. * @return {String/Object} The error object
  46551. */
  46552. getError: function() {
  46553. return this.error;
  46554. },
  46555. /**
  46556. * Returns the {@link Ext.data.Model record}s associated with this operation. For read operations the records as set by the {@link Ext.data.proxy.Proxy Proxy} will be returned (returns `null` if the proxy has not yet set the records).
  46557. * For create, update, and destroy operations the operation's initially configured records will be returned, although the proxy may modify these records' data at some point after the operation is initialized.
  46558. * @return {Ext.data.Model[]}
  46559. */
  46560. getRecords: function() {
  46561. var resultSet = this.getResultSet();
  46562. return this.records || (resultSet ? resultSet.records : null);
  46563. },
  46564. /**
  46565. * Returns the ResultSet object (if set by the Proxy). This object will contain the {@link Ext.data.Model model}
  46566. * instances as well as meta data such as number of instances fetched, number available etc
  46567. * @return {Ext.data.ResultSet} The ResultSet object
  46568. */
  46569. getResultSet: function() {
  46570. return this.resultSet;
  46571. },
  46572. /**
  46573. * Returns true if the Operation has been started. Note that the Operation may have started AND completed, see
  46574. * {@link #isRunning} to test if the Operation is currently running.
  46575. * @return {Boolean} True if the Operation has started
  46576. */
  46577. isStarted: function() {
  46578. return this.started === true;
  46579. },
  46580. /**
  46581. * Returns true if the Operation has been started but has not yet completed.
  46582. * @return {Boolean} True if the Operation is currently running
  46583. */
  46584. isRunning: function() {
  46585. return this.running === true;
  46586. },
  46587. /**
  46588. * Returns true if the Operation has been completed
  46589. * @return {Boolean} True if the Operation is complete
  46590. */
  46591. isComplete: function() {
  46592. return this.complete === true;
  46593. },
  46594. /**
  46595. * Returns true if the Operation has completed and was successful
  46596. * @return {Boolean} True if successful
  46597. */
  46598. wasSuccessful: function() {
  46599. return this.isComplete() && this.success === true;
  46600. },
  46601. /**
  46602. * @private
  46603. * Associates this Operation with a Batch
  46604. * @param {Ext.data.Batch} batch The batch
  46605. */
  46606. setBatch: function(batch) {
  46607. this.batch = batch;
  46608. },
  46609. /**
  46610. * Checks whether this operation should cause writing to occur.
  46611. * @return {Boolean} Whether the operation should cause a write to occur.
  46612. */
  46613. allowWrite: function() {
  46614. return this.action != 'read';
  46615. }
  46616. });
  46617. /**
  46618. * @author Ed Spencer
  46619. *
  46620. * This singleton contains a set of validation functions that can be used to validate any type of data. They are most
  46621. * often used in {@link Ext.data.Model Models}, where they are automatically set up and executed.
  46622. */
  46623. Ext.define('Ext.data.validations', {
  46624. singleton: true,
  46625. /**
  46626. * @property {String} presenceMessage
  46627. * The default error message used when a presence validation fails.
  46628. */
  46629. presenceMessage: 'must be present',
  46630. /**
  46631. * @property {String} lengthMessage
  46632. * The default error message used when a length validation fails.
  46633. */
  46634. lengthMessage: 'is the wrong length',
  46635. /**
  46636. * @property {String} formatMessage
  46637. * The default error message used when a format validation fails.
  46638. */
  46639. formatMessage: 'is the wrong format',
  46640. /**
  46641. * @property {String} inclusionMessage
  46642. * The default error message used when an inclusion validation fails.
  46643. */
  46644. inclusionMessage: 'is not included in the list of acceptable values',
  46645. /**
  46646. * @property {String} exclusionMessage
  46647. * The default error message used when an exclusion validation fails.
  46648. */
  46649. exclusionMessage: 'is not an acceptable value',
  46650. /**
  46651. * @property {String} emailMessage
  46652. * The default error message used when an email validation fails
  46653. */
  46654. emailMessage: 'is not a valid email address',
  46655. /**
  46656. * @property {RegExp} emailRe
  46657. * The regular expression used to validate email addresses
  46658. */
  46659. emailRe: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
  46660. /**
  46661. * Validates that the given value is present.
  46662. * For example:
  46663. *
  46664. * validations: [{type: 'presence', field: 'age'}]
  46665. *
  46666. * @param {Object} config Config object
  46667. * @param {Object} value The value to validate
  46668. * @return {Boolean} True if validation passed
  46669. */
  46670. presence: function(config, value) {
  46671. // No configs read, so allow just value to be passed
  46672. if (arguments.length === 1) {
  46673. value = config;
  46674. }
  46675. //we need an additional check for zero here because zero is an acceptable form of present data
  46676. return !!value || value === 0;
  46677. },
  46678. /**
  46679. * Returns true if the given value is between the configured min and max values.
  46680. * For example:
  46681. *
  46682. * validations: [{type: 'length', field: 'name', min: 2}]
  46683. *
  46684. * @param {Object} config Config object
  46685. * @param {String} value The value to validate
  46686. * @return {Boolean} True if the value passes validation
  46687. */
  46688. length: function(config, value) {
  46689. if (value === undefined || value === null) {
  46690. return false;
  46691. }
  46692. var length = value.length,
  46693. min = config.min,
  46694. max = config.max;
  46695. if ((min && length < min) || (max && length > max)) {
  46696. return false;
  46697. } else {
  46698. return true;
  46699. }
  46700. },
  46701. /**
  46702. * Validates that an email string is in the correct format
  46703. * @param {Object} config Config object
  46704. * @param {String} email The email address
  46705. * @return {Boolean} True if the value passes validation
  46706. */
  46707. email: function(config, email) {
  46708. return Ext.data.validations.emailRe.test(email);
  46709. },
  46710. /**
  46711. * Returns true if the given value passes validation against the configured `matcher` regex.
  46712. * For example:
  46713. *
  46714. * validations: [{type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}]
  46715. *
  46716. * @param {Object} config Config object
  46717. * @param {String} value The value to validate
  46718. * @return {Boolean} True if the value passes the format validation
  46719. */
  46720. format: function(config, value) {
  46721. return !!(config.matcher && config.matcher.test(value));
  46722. },
  46723. /**
  46724. * Validates that the given value is present in the configured `list`.
  46725. * For example:
  46726. *
  46727. * validations: [{type: 'inclusion', field: 'gender', list: ['Male', 'Female']}]
  46728. *
  46729. * @param {Object} config Config object
  46730. * @param {String} value The value to validate
  46731. * @return {Boolean} True if the value is present in the list
  46732. */
  46733. inclusion: function(config, value) {
  46734. return config.list && Ext.Array.indexOf(config.list,value) != -1;
  46735. },
  46736. /**
  46737. * Validates that the given value is not present in the configured `list`.
  46738. * For example:
  46739. *
  46740. * validations: [{type: 'exclusion', field: 'username', list: ['Admin', 'Operator']}]
  46741. *
  46742. * @param {Object} config Config object
  46743. * @param {String} value The value to validate
  46744. * @return {Boolean} True if the value is not present in the list
  46745. */
  46746. exclusion: function(config, value) {
  46747. return config.list && Ext.Array.indexOf(config.list,value) == -1;
  46748. }
  46749. });
  46750. /**
  46751. * @author Ed Spencer
  46752. *
  46753. * A Model represents some object that your application manages. For example, one might define a Model for Users,
  46754. * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
  46755. * {@link Ext.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
  46756. * of the data-bound components in Ext.
  46757. *
  46758. * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
  46759. *
  46760. * Ext.define('User', {
  46761. * extend: 'Ext.data.Model',
  46762. * fields: [
  46763. * {name: 'name', type: 'string'},
  46764. * {name: 'age', type: 'int', convert: null},
  46765. * {name: 'phone', type: 'string'},
  46766. * {name: 'alive', type: 'boolean', defaultValue: true, convert: null}
  46767. * ],
  46768. *
  46769. * changeName: function() {
  46770. * var oldName = this.get('name'),
  46771. * newName = oldName + " The Barbarian";
  46772. *
  46773. * this.set('name', newName);
  46774. * }
  46775. * });
  46776. *
  46777. * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
  46778. * Ext.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
  46779. *
  46780. * By default, the built in numeric and boolean field types have a (@link Ext.data.Field#convert} function which coerces string
  46781. * values in raw data into the field's type. For better performance with {@link Ext.data.reader.Json Json} or {@link Ext.data.reader.Array Array}
  46782. * readers *if you are in control of the data fed into this Model*, you can null out the default convert function which will cause
  46783. * the raw property to be copied directly into the Field's value.
  46784. *
  46785. * Now we can create instances of our User model and call any model logic we defined:
  46786. *
  46787. * var user = Ext.create('User', {
  46788. * name : 'Conan',
  46789. * age : 24,
  46790. * phone: '555-555-5555'
  46791. * });
  46792. *
  46793. * user.changeName();
  46794. * user.get('name'); //returns "Conan The Barbarian"
  46795. *
  46796. * # Validations
  46797. *
  46798. * Models have built-in support for validations, which are executed against the validator functions in {@link
  46799. * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
  46800. * models:
  46801. *
  46802. * Ext.define('User', {
  46803. * extend: 'Ext.data.Model',
  46804. * fields: [
  46805. * {name: 'name', type: 'string'},
  46806. * {name: 'age', type: 'int'},
  46807. * {name: 'phone', type: 'string'},
  46808. * {name: 'gender', type: 'string'},
  46809. * {name: 'username', type: 'string'},
  46810. * {name: 'alive', type: 'boolean', defaultValue: true}
  46811. * ],
  46812. *
  46813. * validations: [
  46814. * {type: 'presence', field: 'age'},
  46815. * {type: 'length', field: 'name', min: 2},
  46816. * {type: 'inclusion', field: 'gender', list: ['Male', 'Female']},
  46817. * {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
  46818. * {type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
  46819. * ]
  46820. * });
  46821. *
  46822. * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
  46823. * object:
  46824. *
  46825. * var instance = Ext.create('User', {
  46826. * name: 'Ed',
  46827. * gender: 'Male',
  46828. * username: 'edspencer'
  46829. * });
  46830. *
  46831. * var errors = instance.validate();
  46832. *
  46833. * # Associations
  46834. *
  46835. * Models can have associations with other Models via {@link Ext.data.association.HasOne},
  46836. * {@link Ext.data.association.BelongsTo belongsTo} and {@link Ext.data.association.HasMany hasMany} associations.
  46837. * For example, let's say we're writing a blog administration application which deals with Users, Posts and Comments.
  46838. * We can express the relationships between these models like this:
  46839. *
  46840. * Ext.define('Post', {
  46841. * extend: 'Ext.data.Model',
  46842. * fields: ['id', 'user_id'],
  46843. *
  46844. * belongsTo: 'User',
  46845. * hasMany : {model: 'Comment', name: 'comments'}
  46846. * });
  46847. *
  46848. * Ext.define('Comment', {
  46849. * extend: 'Ext.data.Model',
  46850. * fields: ['id', 'user_id', 'post_id'],
  46851. *
  46852. * belongsTo: 'Post'
  46853. * });
  46854. *
  46855. * Ext.define('User', {
  46856. * extend: 'Ext.data.Model',
  46857. * fields: ['id'],
  46858. *
  46859. * hasMany: [
  46860. * 'Post',
  46861. * {model: 'Comment', name: 'comments'}
  46862. * ]
  46863. * });
  46864. *
  46865. * See the docs for {@link Ext.data.association.HasOne}, {@link Ext.data.association.BelongsTo} and
  46866. * {@link Ext.data.association.HasMany} for details on the usage and configuration of associations.
  46867. * Note that associations can also be specified like this:
  46868. *
  46869. * Ext.define('User', {
  46870. * extend: 'Ext.data.Model',
  46871. * fields: ['id'],
  46872. *
  46873. * associations: [
  46874. * {type: 'hasMany', model: 'Post', name: 'posts'},
  46875. * {type: 'hasMany', model: 'Comment', name: 'comments'}
  46876. * ]
  46877. * });
  46878. *
  46879. * # Using a Proxy
  46880. *
  46881. * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
  46882. * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
  46883. * can be set directly on the Model:
  46884. *
  46885. * Ext.define('User', {
  46886. * extend: 'Ext.data.Model',
  46887. * fields: ['id', 'name', 'email'],
  46888. *
  46889. * proxy: {
  46890. * type: 'rest',
  46891. * url : '/users'
  46892. * }
  46893. * });
  46894. *
  46895. * Here we've set up a {@link Ext.data.proxy.Rest Rest Proxy}, which knows how to load and save data to and from a
  46896. * RESTful backend. Let's see how this works:
  46897. *
  46898. * var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
  46899. *
  46900. * user.save(); //POST /users
  46901. *
  46902. * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
  46903. * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
  46904. * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
  46905. * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
  46906. *
  46907. * Loading data via the Proxy is equally easy:
  46908. *
  46909. * //get a reference to the User model class
  46910. * var User = Ext.ModelManager.getModel('User');
  46911. *
  46912. * //Uses the configured RestProxy to make a GET request to /users/123
  46913. * User.load(123, {
  46914. * success: function(user) {
  46915. * console.log(user.getId()); //logs 123
  46916. * }
  46917. * });
  46918. *
  46919. * Models can also be updated and destroyed easily:
  46920. *
  46921. * //the user Model we loaded in the last snippet:
  46922. * user.set('name', 'Edward Spencer');
  46923. *
  46924. * //tells the Proxy to save the Model. In this case it will perform a PUT request to /users/123 as this Model already has an id
  46925. * user.save({
  46926. * success: function() {
  46927. * console.log('The User was updated');
  46928. * }
  46929. * });
  46930. *
  46931. * //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
  46932. * user.destroy({
  46933. * success: function() {
  46934. * console.log('The User was destroyed!');
  46935. * }
  46936. * });
  46937. *
  46938. * # Usage in Stores
  46939. *
  46940. * It is very common to want to load a set of Model instances to be displayed and manipulated in the UI. We do this by
  46941. * creating a {@link Ext.data.Store Store}:
  46942. *
  46943. * var store = Ext.create('Ext.data.Store', {
  46944. * model: 'User'
  46945. * });
  46946. *
  46947. * //uses the Proxy we set up on Model to load the Store data
  46948. * store.load();
  46949. *
  46950. * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
  46951. * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
  46952. * Ext.data.Store Store docs} for more information on Stores.
  46953. */
  46954. Ext.define('Ext.data.Model', {
  46955. alternateClassName: 'Ext.data.Record',
  46956. mixins: {
  46957. observable: 'Ext.util.Observable'
  46958. },
  46959. requires: [
  46960. 'Ext.ModelManager',
  46961. 'Ext.data.IdGenerator',
  46962. 'Ext.data.Field',
  46963. 'Ext.data.Errors',
  46964. 'Ext.data.Operation',
  46965. 'Ext.data.validations',
  46966. 'Ext.util.MixedCollection'
  46967. ],
  46968. compareConvertFields: function(f1, f2) {
  46969. var f1SpecialConvert = f1.convert && f1.type && f1.convert !== f1.type.convert,
  46970. f2SpecialConvert = f2.convert && f2.type && f2.convert !== f2.type.convert;
  46971. if (f1SpecialConvert && !f2SpecialConvert) {
  46972. return 1;
  46973. }
  46974. if (!f1SpecialConvert && f2SpecialConvert) {
  46975. return -1;
  46976. }
  46977. return 0;
  46978. },
  46979. itemNameFn: function(item) {
  46980. return item.name;
  46981. },
  46982. onClassExtended: function(cls, data, hooks) {
  46983. var onBeforeClassCreated = hooks.onBeforeCreated;
  46984. hooks.onBeforeCreated = function(cls, data) {
  46985. var me = this,
  46986. name = Ext.getClassName(cls),
  46987. prototype = cls.prototype,
  46988. superCls = cls.prototype.superclass,
  46989. validations = data.validations || [],
  46990. fields = data.fields || [],
  46991. field,
  46992. associationsConfigs = data.associations || [],
  46993. addAssociations = function(items, type) {
  46994. var i = 0,
  46995. len,
  46996. item;
  46997. if (items) {
  46998. items = Ext.Array.from(items);
  46999. for (len = items.length; i < len; ++i) {
  47000. item = items[i];
  47001. if (!Ext.isObject(item)) {
  47002. item = {model: item};
  47003. }
  47004. item.type = type;
  47005. associationsConfigs.push(item);
  47006. }
  47007. }
  47008. },
  47009. idgen = data.idgen,
  47010. fieldsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
  47011. associationsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
  47012. superValidations = superCls.validations,
  47013. superFields = superCls.fields,
  47014. superAssociations = superCls.associations,
  47015. associationConfig, i, ln,
  47016. dependencies = [],
  47017. idProperty = data.idProperty || cls.prototype.idProperty,
  47018. // Process each Field upon add into the collection
  47019. onFieldAddReplace = function(arg0, arg1, arg2) {
  47020. var newField,
  47021. pos;
  47022. if (fieldsMixedCollection.events.add.firing) {
  47023. // Add event signature is (position, value, key);
  47024. pos = arg0;
  47025. newField = arg1;
  47026. } else {
  47027. // Replace event signature is (key, oldValue, newValue);
  47028. newField = arg2;
  47029. pos = arg1.originalIndex;
  47030. }
  47031. // Set the originalIndex for ArrayReader to get the default mapping from in case
  47032. // compareConvertFields changes the order due to some fields having custom convert functions.
  47033. newField.originalIndex = pos;
  47034. // The field(s) which encapsulates the idProperty must never have a default value set
  47035. // if no value arrives from the server side. So override any possible prototype-provided
  47036. // defaultValue with undefined which will inhibit generation of defaulting code in Reader.buildRecordDataExtractor
  47037. if (newField.mapping === idProperty || (newField.mapping == null && newField.name === idProperty)) {
  47038. newField.defaultValue = undefined;
  47039. }
  47040. },
  47041. // Use the proxy from the class definition object if present, otherwise fall back to the inherited one, or the default
  47042. clsProxy = data.proxy || cls.prototype.proxy || cls.prototype.defaultProxyType,
  47043. // Sort upon add function to be used in case of dynamically added Fields
  47044. fieldConvertSortFn = function() {
  47045. fieldsMixedCollection.sortBy(prototype.compareConvertFields);
  47046. };
  47047. // Save modelName on class and its prototype
  47048. cls.modelName = name;
  47049. prototype.modelName = name;
  47050. // Merge the validations of the superclass and the new subclass
  47051. if (superValidations) {
  47052. validations = superValidations.concat(validations);
  47053. }
  47054. data.validations = validations;
  47055. // Merge the fields of the superclass and the new subclass
  47056. if (superFields) {
  47057. fields = superFields.items.concat(fields);
  47058. }
  47059. fieldsMixedCollection.on({
  47060. add: onFieldAddReplace,
  47061. replace: onFieldAddReplace
  47062. });
  47063. for (i = 0, ln = fields.length; i < ln; ++i) {
  47064. field = fields[i];
  47065. fieldsMixedCollection.add(field.isField ? field : new Ext.data.Field(field));
  47066. }
  47067. if (!fieldsMixedCollection.get(idProperty)) {
  47068. fieldsMixedCollection.add(new Ext.data.Field(idProperty));
  47069. }
  47070. // Ensure the Fields are on correct order: Fields with custom convert function last
  47071. fieldConvertSortFn();
  47072. fieldsMixedCollection.on({
  47073. add: fieldConvertSortFn,
  47074. replace: fieldConvertSortFn
  47075. });
  47076. data.fields = fieldsMixedCollection;
  47077. if (idgen) {
  47078. data.idgen = Ext.data.IdGenerator.get(idgen);
  47079. }
  47080. //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
  47081. //we support that here
  47082. addAssociations(data.belongsTo, 'belongsTo');
  47083. delete data.belongsTo;
  47084. addAssociations(data.hasMany, 'hasMany');
  47085. delete data.hasMany;
  47086. addAssociations(data.hasOne, 'hasOne');
  47087. delete data.hasOne;
  47088. if (superAssociations) {
  47089. associationsConfigs = superAssociations.items.concat(associationsConfigs);
  47090. }
  47091. for (i = 0, ln = associationsConfigs.length; i < ln; ++i) {
  47092. dependencies.push('association.' + associationsConfigs[i].type.toLowerCase());
  47093. }
  47094. // If we have not been supplied with a Proxy *instance*, then add the proxy type to our dependency list
  47095. if (clsProxy && !clsProxy.isProxy) {
  47096. if (typeof clsProxy !== 'string' && !clsProxy.type) {
  47097. Ext.log.warn(name + ': proxy type is ' + clsProxy.type);
  47098. }
  47099. dependencies.push('proxy.' + (typeof clsProxy === 'string' ? clsProxy : clsProxy.type));
  47100. }
  47101. Ext.require(dependencies, function() {
  47102. Ext.ModelManager.registerType(name, cls);
  47103. for (i = 0, ln = associationsConfigs.length; i < ln; ++i) {
  47104. associationConfig = associationsConfigs[i];
  47105. if (associationConfig.isAssociation) {
  47106. associationConfig = Ext.applyIf({
  47107. ownerModel: name,
  47108. associatedModel: associationConfig.model
  47109. }, associationConfig.initialConfig);
  47110. } else {
  47111. Ext.apply(associationConfig, {
  47112. ownerModel: name,
  47113. associatedModel: associationConfig.model
  47114. });
  47115. }
  47116. if (Ext.ModelManager.getModel(associationConfig.model) === undefined) {
  47117. Ext.ModelManager.registerDeferredAssociation(associationConfig);
  47118. } else {
  47119. associationsMixedCollection.add(Ext.data.association.Association.create(associationConfig));
  47120. }
  47121. }
  47122. data.associations = associationsMixedCollection;
  47123. // onBeforeCreated may get called *asynchronously* if any of those required classes caused
  47124. // an asynchronous script load. This would mean that the class definition object
  47125. // has not been applied to the prototype when the Model definition has returned.
  47126. // The Reader constructor does not attempt to buildExtractors if the fields MixedCollection
  47127. // has not yet been set. The cls.setProxy call triggers a build of extractor methods.
  47128. onBeforeClassCreated.call(me, cls, data, hooks);
  47129. cls.setProxy(clsProxy);
  47130. // Fire the onModelDefined template method on ModelManager
  47131. Ext.ModelManager.onModelDefined(cls);
  47132. });
  47133. };
  47134. },
  47135. inheritableStatics: {
  47136. /**
  47137. * Sets the Proxy to use for this model. Accepts any options that can be accepted by
  47138. * {@link Ext#createByAlias Ext.createByAlias}.
  47139. * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
  47140. * @return {Ext.data.proxy.Proxy}
  47141. * @static
  47142. * @inheritable
  47143. */
  47144. setProxy: function(proxy) {
  47145. //make sure we have an Ext.data.proxy.Proxy object
  47146. if (!proxy.isProxy) {
  47147. if (typeof proxy == "string") {
  47148. proxy = {
  47149. type: proxy
  47150. };
  47151. }
  47152. proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
  47153. }
  47154. proxy.setModel(this);
  47155. this.proxy = this.prototype.proxy = proxy;
  47156. return proxy;
  47157. },
  47158. /**
  47159. * Returns the configured Proxy for this Model
  47160. * @return {Ext.data.proxy.Proxy} The proxy
  47161. * @static
  47162. * @inheritable
  47163. */
  47164. getProxy: function() {
  47165. return this.proxy;
  47166. },
  47167. /**
  47168. * Apply a new set of field and/or property definitions to the existing model. This will replace any existing
  47169. * fields, including fields inherited from superclasses. Mainly for reconfiguring the
  47170. * model based on changes in meta data (called from Reader's onMetaChange method).
  47171. * @static
  47172. * @inheritable
  47173. */
  47174. setFields: function(fields, idProperty, clientIdProperty) {
  47175. var me = this,
  47176. proto = me.prototype,
  47177. prototypeFields = proto.fields,
  47178. len = fields ? fields.length : 0,
  47179. i = 0;
  47180. if (idProperty) {
  47181. proto.idProperty = idProperty;
  47182. }
  47183. if (clientIdProperty) {
  47184. proto.clientIdProperty = clientIdProperty;
  47185. }
  47186. if (prototypeFields) {
  47187. prototypeFields.clear();
  47188. }
  47189. else {
  47190. prototypeFields = me.prototype.fields = new Ext.util.MixedCollection(false, function(field) {
  47191. return field.name;
  47192. });
  47193. }
  47194. for (; i < len; i++) {
  47195. prototypeFields.add(new Ext.data.Field(fields[i]));
  47196. }
  47197. if (!prototypeFields.get(proto.idProperty)) {
  47198. prototypeFields.add(new Ext.data.Field(proto.idProperty));
  47199. }
  47200. me.fields = prototypeFields;
  47201. return prototypeFields;
  47202. },
  47203. /**
  47204. * Returns an Array of {@link Ext.data.Field Field} definitions which define this Model's structure
  47205. *
  47206. * Fields are sorted upon Model class definition. Fields with custom {@link Ext.data.Field#convert convert} functions
  47207. * are moved to *after* fields with no convert functions. This is so that convert functions which rely on existing
  47208. * field values will be able to read those field values.
  47209. *
  47210. * @return {Ext.data.Field[]} The defined Fields for this Model.
  47211. *
  47212. */
  47213. getFields: function() {
  47214. return this.prototype.fields.items;
  47215. },
  47216. /**
  47217. * Asynchronously loads a model instance by id. Sample usage:
  47218. *
  47219. * Ext.define('MyApp.User', {
  47220. * extend: 'Ext.data.Model',
  47221. * fields: [
  47222. * {name: 'id', type: 'int'},
  47223. * {name: 'name', type: 'string'}
  47224. * ]
  47225. * });
  47226. *
  47227. * MyApp.User.load(10, {
  47228. * scope: this,
  47229. * failure: function(record, operation) {
  47230. * //do something if the load failed
  47231. * },
  47232. * success: function(record, operation) {
  47233. * //do something if the load succeeded
  47234. * },
  47235. * callback: function(record, operation) {
  47236. * //do something whether the load succeeded or failed
  47237. * }
  47238. * });
  47239. *
  47240. * @param {Number/String} id The id of the model to load
  47241. * @param {Object} config (optional) config object containing success, failure and callback functions, plus
  47242. * optional scope
  47243. * @static
  47244. * @inheritable
  47245. */
  47246. load: function(id, config) {
  47247. config = Ext.apply({}, config);
  47248. config = Ext.applyIf(config, {
  47249. action: 'read',
  47250. id : id
  47251. });
  47252. var operation = new Ext.data.Operation(config),
  47253. scope = config.scope || this,
  47254. record = null,
  47255. callback;
  47256. callback = function(operation) {
  47257. if (operation.wasSuccessful()) {
  47258. record = operation.getRecords()[0];
  47259. Ext.callback(config.success, scope, [record, operation]);
  47260. } else {
  47261. Ext.callback(config.failure, scope, [record, operation]);
  47262. }
  47263. Ext.callback(config.callback, scope, [record, operation]);
  47264. };
  47265. this.proxy.read(operation, callback, this);
  47266. }
  47267. },
  47268. statics: {
  47269. /**
  47270. * @property
  47271. * @static
  47272. * @private
  47273. */
  47274. PREFIX : 'ext-record',
  47275. /**
  47276. * @property
  47277. * @static
  47278. * @private
  47279. */
  47280. AUTO_ID: 1,
  47281. /**
  47282. * @property
  47283. * @static
  47284. * The update operation of type 'edit'. Used by {@link Ext.data.Store#update Store.update} event.
  47285. */
  47286. EDIT : 'edit',
  47287. /**
  47288. * @property
  47289. * @static
  47290. * The update operation of type 'reject'. Used by {@link Ext.data.Store#update Store.update} event.
  47291. */
  47292. REJECT : 'reject',
  47293. /**
  47294. * @property
  47295. * @static
  47296. * The update operation of type 'commit'. Used by {@link Ext.data.Store#update Store.update} event.
  47297. */
  47298. COMMIT : 'commit',
  47299. /**
  47300. * Generates a sequential id. This method is typically called when a record is {@link Ext#create
  47301. * create}d and {@link #constructor no id has been specified}. The id will automatically be assigned to the
  47302. * record. The returned id takes the form: {PREFIX}-{AUTO_ID}.
  47303. *
  47304. * - **PREFIX** : String - Ext.data.Model.PREFIX (defaults to 'ext-record')
  47305. * - **AUTO_ID** : String - Ext.data.Model.AUTO_ID (defaults to 1 initially)
  47306. *
  47307. * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
  47308. * @return {String} auto-generated string id, `"ext-record-i++"`;
  47309. * @static
  47310. */
  47311. id: function(rec) {
  47312. var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
  47313. rec.phantom = true;
  47314. rec.internalId = id;
  47315. return id;
  47316. }
  47317. },
  47318. /**
  47319. * @cfg {String/Object} idgen
  47320. * The id generator to use for this model. The default id generator does not generate
  47321. * values for the {@link #idProperty}.
  47322. *
  47323. * This can be overridden at the model level to provide a custom generator for a model.
  47324. * The simplest form of this would be:
  47325. *
  47326. * Ext.define('MyApp.data.MyModel', {
  47327. * extend: 'Ext.data.Model',
  47328. * requires: ['Ext.data.SequentialIdGenerator'],
  47329. * idgen: 'sequential',
  47330. * ...
  47331. * });
  47332. *
  47333. * The above would generate {@link Ext.data.SequentialIdGenerator sequential} id's such
  47334. * as 1, 2, 3 etc..
  47335. *
  47336. * Another useful id generator is {@link Ext.data.UuidGenerator}:
  47337. *
  47338. * Ext.define('MyApp.data.MyModel', {
  47339. * extend: 'Ext.data.Model',
  47340. * requires: ['Ext.data.UuidGenerator'],
  47341. * idgen: 'uuid',
  47342. * ...
  47343. * });
  47344. *
  47345. * An id generation can also be further configured:
  47346. *
  47347. * Ext.define('MyApp.data.MyModel', {
  47348. * extend: 'Ext.data.Model',
  47349. * idgen: {
  47350. * type: 'sequential',
  47351. * seed: 1000,
  47352. * prefix: 'ID_'
  47353. * }
  47354. * });
  47355. *
  47356. * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..
  47357. *
  47358. * If multiple models share an id space, a single generator can be shared:
  47359. *
  47360. * Ext.define('MyApp.data.MyModelX', {
  47361. * extend: 'Ext.data.Model',
  47362. * idgen: {
  47363. * type: 'sequential',
  47364. * id: 'xy'
  47365. * }
  47366. * });
  47367. *
  47368. * Ext.define('MyApp.data.MyModelY', {
  47369. * extend: 'Ext.data.Model',
  47370. * idgen: {
  47371. * type: 'sequential',
  47372. * id: 'xy'
  47373. * }
  47374. * });
  47375. *
  47376. * For more complex, shared id generators, a custom generator is the best approach.
  47377. * See {@link Ext.data.IdGenerator} for details on creating custom id generators.
  47378. *
  47379. * @markdown
  47380. */
  47381. idgen: {
  47382. isGenerator: true,
  47383. type: 'default',
  47384. generate: function () {
  47385. return null;
  47386. },
  47387. getRecId: function (rec) {
  47388. return rec.modelName + '-' + rec.internalId;
  47389. }
  47390. },
  47391. /**
  47392. * @property {Boolean} editing
  47393. * Internal flag used to track whether or not the model instance is currently being edited.
  47394. * @readonly
  47395. */
  47396. editing : false,
  47397. /**
  47398. * @property {Boolean} dirty
  47399. * True if this Record has been modified.
  47400. * @readonly
  47401. */
  47402. dirty : false,
  47403. /**
  47404. * @cfg {String} persistenceProperty
  47405. * The name of the property on this Persistable object that its data is saved to. Defaults to 'data'
  47406. * (i.e: all persistable data resides in `this.data`.)
  47407. */
  47408. persistenceProperty: 'data',
  47409. evented: false,
  47410. /**
  47411. * @property {Boolean} isModel
  47412. * `true` in this class to identify an object as an instantiated Model, or subclass thereof.
  47413. */
  47414. isModel: true,
  47415. /**
  47416. * @property {Boolean} phantom
  47417. * True when the record does not yet exist in a server-side database (see {@link #setDirty}).
  47418. * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
  47419. */
  47420. phantom : false,
  47421. /**
  47422. * @cfg {String} idProperty
  47423. * The name of the field treated as this Model's unique id. Defaults to 'id'.
  47424. */
  47425. idProperty: 'id',
  47426. /**
  47427. * @cfg {String} [clientIdProperty]
  47428. * The name of a property that is used for submitting this Model's unique client-side identifier
  47429. * to the server when multiple phantom records are saved as part of the same {@link Ext.data.Operation Operation}.
  47430. * In such a case, the server response should include the client id for each record
  47431. * so that the server response data can be used to update the client-side records if necessary.
  47432. * This property cannot have the same name as any of this Model's fields.
  47433. */
  47434. clientIdProperty: null,
  47435. /**
  47436. * @cfg {String} defaultProxyType
  47437. * The string type of the default Model Proxy. Defaults to 'ajax'.
  47438. */
  47439. defaultProxyType: 'ajax',
  47440. // Fields config and property
  47441. /**
  47442. * @cfg {Object[]/String[]} fields
  47443. * The fields for this model. This is an Array of **{@link Ext.data.Field Field}** definition objects. A Field
  47444. * definition may simply be the *name* of the Field, but a Field encapsulates {@link Ext.data.Field#type data type},
  47445. * {@link Ext.data.Field#convert custom conversion} of raw data, and a {@link Ext.data.Field#mapping mapping}
  47446. * property to specify by name of index, how to extract a field's value from a raw data object, so it is best practice
  47447. * to specify a full set of {@link Ext.data.Field Field} config objects.
  47448. */
  47449. /**
  47450. * @property {Ext.util.MixedCollection} fields
  47451. * A {@link Ext.util.MixedCollection Collection} of the fields defined for this Model (including fields defined in superclasses)
  47452. *
  47453. * This is a collection of {@link Ext.data.Field} instances, each of which encapsulates information that the field was configured with.
  47454. * By default, you can specify a field as simply a String, representing the *name* of the field, but a Field encapsulates
  47455. * {@link Ext.data.Field#type data type}, {@link Ext.data.Field#convert custom conversion} of raw data, and a {@link Ext.data.Field#mapping mapping}
  47456. * property to specify by name of index, how to extract a field's value from a raw data object.
  47457. */
  47458. /**
  47459. * @cfg {Object[]} validations
  47460. * An array of {@link Ext.data.validations validations} for this model.
  47461. */
  47462. // Associations configs and properties
  47463. /**
  47464. * @cfg {Object[]} associations
  47465. * An array of {@link Ext.data.Association associations} for this model.
  47466. */
  47467. /**
  47468. * @cfg {String/Object/String[]/Object[]} hasMany
  47469. * One or more {@link Ext.data.HasManyAssociation HasMany associations} for this model.
  47470. */
  47471. /**
  47472. * @cfg {String/Object/String[]/Object[]} belongsTo
  47473. * One or more {@link Ext.data.BelongsToAssociation BelongsTo associations} for this model.
  47474. */
  47475. /**
  47476. * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
  47477. * The {@link Ext.data.proxy.Proxy proxy} to use for this model.
  47478. */
  47479. /**
  47480. * @event idchanged
  47481. * Fired when this model's id changes
  47482. * @param {Ext.data.Model} this
  47483. * @param {Number/String} oldId The old id
  47484. * @param {Number/String} newId The new id
  47485. */
  47486. /**
  47487. * Creates new Model instance.
  47488. * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
  47489. */
  47490. constructor: function(data, id, raw, convertedData) {
  47491. // id, raw and convertedData not documented intentionally, meant to be used internally.
  47492. // TODO: find where "raw" is used and remove it. The first parameter, "data" is raw, unconverted data. "raw" is redundant.
  47493. // The "convertedData" parameter is a converted object hash with all properties corresponding to defined Fields
  47494. // and all values of the defined type. It is used directly as this record's data property.
  47495. data = data || {};
  47496. var me = this,
  47497. fields,
  47498. length,
  47499. field,
  47500. name,
  47501. value,
  47502. newId,
  47503. persistenceProperty,
  47504. i;
  47505. /**
  47506. * @property {Number/String} internalId
  47507. * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
  47508. * @private
  47509. */
  47510. me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
  47511. /**
  47512. * @property {Object} raw The raw data used to create this model if created via a reader.
  47513. */
  47514. me.raw = raw || data; // If created using data in constructor, use data
  47515. if (!me.data) {
  47516. me.data = {};
  47517. }
  47518. /**
  47519. * @property {Object} modified Key: value pairs of all fields whose values have changed
  47520. */
  47521. me.modified = {};
  47522. // Deal with spelling error in previous releases
  47523. if (me.persistanceProperty) {
  47524. Ext.log.warn('Ext.data.Model: persistanceProperty has been deprecated. Use persistenceProperty instead.');
  47525. me.persistenceProperty = me.persistanceProperty;
  47526. }
  47527. me[me.persistenceProperty] = convertedData || {};
  47528. me.mixins.observable.constructor.call(me);
  47529. if (!convertedData) {
  47530. //add default field values if present
  47531. fields = me.fields.items;
  47532. length = fields.length;
  47533. i = 0;
  47534. persistenceProperty = me[me.persistenceProperty];
  47535. if (Ext.isArray(data)) {
  47536. for (; i < length; i++) {
  47537. field = fields[i];
  47538. name = field.name;
  47539. // Use the original ordinal position at which the Model inserted the field into its collection.
  47540. // Fields are sorted to place fields with a *convert* function last.
  47541. value = data[field.originalIndex];
  47542. if (value === undefined) {
  47543. value = field.defaultValue;
  47544. }
  47545. // Have to map array data so the values get assigned to the named fields
  47546. // rather than getting set as the field names with undefined values.
  47547. if (field.convert) {
  47548. value = field.convert(value, me);
  47549. }
  47550. // On instance construction, do not create data properties based on undefined input properties
  47551. if (value !== undefined) {
  47552. persistenceProperty[name] = value;
  47553. }
  47554. }
  47555. } else {
  47556. for (; i < length; i++) {
  47557. field = fields[i];
  47558. name = field.name;
  47559. value = data[name];
  47560. if (value === undefined) {
  47561. value = field.defaultValue;
  47562. }
  47563. if (field.convert) {
  47564. value = field.convert(value, me);
  47565. }
  47566. // On instance construction, do not create data properties based on undefined input properties
  47567. if (value !== undefined) {
  47568. persistenceProperty[name] = value;
  47569. }
  47570. }
  47571. }
  47572. }
  47573. /**
  47574. * @property {Ext.data.Store[]} stores
  47575. * The {@link Ext.data.Store Stores} to which this instance is bound.
  47576. */
  47577. me.stores = [];
  47578. if (me.getId()) {
  47579. me.phantom = false;
  47580. } else if (me.phantom) {
  47581. newId = me.idgen.generate();
  47582. if (newId !== null) {
  47583. me.setId(newId);
  47584. }
  47585. }
  47586. // clear any dirty/modified since we're initializing
  47587. me.dirty = false;
  47588. me.modified = {};
  47589. if (typeof me.init == 'function') {
  47590. me.init();
  47591. }
  47592. me.id = me.idgen.getRecId(me);
  47593. },
  47594. /**
  47595. * Returns the value of the given field
  47596. * @param {String} fieldName The field to fetch the value for
  47597. * @return {Object} The value
  47598. */
  47599. get: function(field) {
  47600. return this[this.persistenceProperty][field];
  47601. },
  47602. // This object is used whenever the set() method is called and given a string as the
  47603. // first argument. This approach saves memory (and GC costs) since we could be called
  47604. // a lot.
  47605. _singleProp: {},
  47606. /**
  47607. * Sets the given field to the given value, marks the instance as dirty
  47608. * @param {String/Object} fieldName The field to set, or an object containing key/value pairs
  47609. * @param {Object} newValue The value to set
  47610. * @return {String[]} The array of modified field names or null if nothing was modified.
  47611. */
  47612. set: function (fieldName, newValue) {
  47613. var me = this,
  47614. data = me[me.persistenceProperty],
  47615. fields = me.fields,
  47616. modified = me.modified,
  47617. single = (typeof fieldName == 'string'),
  47618. currentValue, field, idChanged, key, modifiedFieldNames, name, oldId,
  47619. newId, value, values;
  47620. if (single) {
  47621. values = me._singleProp;
  47622. values[fieldName] = newValue;
  47623. } else {
  47624. values = fieldName;
  47625. }
  47626. for (name in values) {
  47627. if (values.hasOwnProperty(name)) {
  47628. value = values[name];
  47629. if (fields && (field = fields.get(name)) && field.convert) {
  47630. value = field.convert(value, me);
  47631. }
  47632. currentValue = data[name];
  47633. if (me.isEqual(currentValue, value)) {
  47634. continue; // new value is the same, so no change...
  47635. }
  47636. data[name] = value;
  47637. (modifiedFieldNames || (modifiedFieldNames = [])).push(name);
  47638. if (field && field.persist) {
  47639. if (modified.hasOwnProperty(name)) {
  47640. if (me.isEqual(modified[name], value)) {
  47641. // The original value in me.modified equals the new value, so
  47642. // the field is no longer modified:
  47643. delete modified[name];
  47644. // We might have removed the last modified field, so check to
  47645. // see if there are any modified fields remaining and correct
  47646. // me.dirty:
  47647. me.dirty = false;
  47648. for (key in modified) {
  47649. if (modified.hasOwnProperty(key)){
  47650. me.dirty = true;
  47651. break;
  47652. }
  47653. }
  47654. }
  47655. } else {
  47656. me.dirty = true;
  47657. modified[name] = currentValue;
  47658. }
  47659. }
  47660. if (name == me.idProperty) {
  47661. idChanged = true;
  47662. oldId = currentValue;
  47663. newId = value;
  47664. }
  47665. }
  47666. }
  47667. if (single) {
  47668. // cleanup our reused object for next time... important to do this before
  47669. // we fire any events or call anyone else (like afterEdit)!
  47670. delete values[fieldName];
  47671. }
  47672. if (idChanged) {
  47673. me.fireEvent('idchanged', me, oldId, newId);
  47674. }
  47675. if (!me.editing && modifiedFieldNames) {
  47676. me.afterEdit(modifiedFieldNames);
  47677. }
  47678. return modifiedFieldNames || null;
  47679. },
  47680. /**
  47681. * @private
  47682. * Copies data from the passed record into this record. If the passed record is undefined, does nothing.
  47683. *
  47684. * If this is a phantom record (represented only in the client, with no corresponding database entry), and
  47685. * the source record is not a phantom, then this record acquires the id of the source record.
  47686. *
  47687. * @param {Ext.data.Model} sourceRecord The record to copy data from.
  47688. */
  47689. copyFrom: function(sourceRecord) {
  47690. if (sourceRecord) {
  47691. var me = this,
  47692. fields = me.fields.items,
  47693. fieldCount = fields.length,
  47694. field, i = 0,
  47695. myData = me[me.persistenceProperty],
  47696. sourceData = sourceRecord[sourceRecord.persistenceProperty],
  47697. value;
  47698. for (; i < fieldCount; i++) {
  47699. field = fields[i];
  47700. // Do not use setters.
  47701. // Copy returned values in directly from the data object.
  47702. // Converters have already been called because new Records
  47703. // have been created to copy from.
  47704. // This is a direct record-to-record value copy operation.
  47705. value = sourceData[field.name];
  47706. if (value !== undefined) {
  47707. myData[field.name] = value;
  47708. }
  47709. }
  47710. // If this is a phantom record being updated from a concrete record, copy the ID in.
  47711. if (me.phantom && !sourceRecord.phantom) {
  47712. me.setId(sourceRecord.getId());
  47713. }
  47714. }
  47715. },
  47716. /**
  47717. * Checks if two values are equal, taking into account certain
  47718. * special factors, for example dates.
  47719. * @private
  47720. * @param {Object} a The first value
  47721. * @param {Object} b The second value
  47722. * @return {Boolean} True if the values are equal
  47723. */
  47724. isEqual: function(a, b){
  47725. if (Ext.isDate(a) && Ext.isDate(b)) {
  47726. return Ext.Date.isEqual(a, b);
  47727. }
  47728. return a === b;
  47729. },
  47730. /**
  47731. * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are relayed to the containing store.
  47732. * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
  47733. */
  47734. beginEdit : function(){
  47735. var me = this;
  47736. if (!me.editing) {
  47737. me.editing = true;
  47738. me.dirtySave = me.dirty;
  47739. me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
  47740. me.modifiedSave = Ext.apply({}, me.modified);
  47741. }
  47742. },
  47743. /**
  47744. * Cancels all changes made in the current edit operation.
  47745. */
  47746. cancelEdit : function(){
  47747. var me = this;
  47748. if (me.editing) {
  47749. me.editing = false;
  47750. // reset the modified state, nothing changed since the edit began
  47751. me.modified = me.modifiedSave;
  47752. me[me.persistenceProperty] = me.dataSave;
  47753. me.dirty = me.dirtySave;
  47754. delete me.modifiedSave;
  47755. delete me.dataSave;
  47756. delete me.dirtySave;
  47757. }
  47758. },
  47759. /**
  47760. * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
  47761. * fire).
  47762. * @param {Boolean} silent True to not notify the store of the change
  47763. * @param {String[]} modifiedFieldNames Array of field names changed during edit.
  47764. */
  47765. endEdit : function(silent, modifiedFieldNames){
  47766. var me = this,
  47767. changed;
  47768. if (me.editing) {
  47769. me.editing = false;
  47770. if(!modifiedFieldNames) {
  47771. modifiedFieldNames = me.getModifiedFieldNames();
  47772. }
  47773. changed = me.dirty || modifiedFieldNames.length > 0;
  47774. delete me.modifiedSave;
  47775. delete me.dataSave;
  47776. delete me.dirtySave;
  47777. if (changed && silent !== true) {
  47778. me.afterEdit(modifiedFieldNames);
  47779. }
  47780. }
  47781. },
  47782. /**
  47783. * Gets the names of all the fields that were modified during an edit
  47784. * @private
  47785. * @return {String[]} An array of modified field names
  47786. */
  47787. getModifiedFieldNames: function(){
  47788. var me = this,
  47789. saved = me.dataSave,
  47790. data = me[me.persistenceProperty],
  47791. modified = [],
  47792. key;
  47793. for (key in data) {
  47794. if (data.hasOwnProperty(key)) {
  47795. if (!me.isEqual(data[key], saved[key])) {
  47796. modified.push(key);
  47797. }
  47798. }
  47799. }
  47800. return modified;
  47801. },
  47802. /**
  47803. * Gets a hash of only the fields that have been modified since this Model was created or commited.
  47804. * @return {Object}
  47805. */
  47806. getChanges : function(){
  47807. var modified = this.modified,
  47808. changes = {},
  47809. field;
  47810. for (field in modified) {
  47811. if (modified.hasOwnProperty(field)){
  47812. changes[field] = this.get(field);
  47813. }
  47814. }
  47815. return changes;
  47816. },
  47817. /**
  47818. * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.
  47819. * @param {String} fieldName {@link Ext.data.Field#name}
  47820. * @return {Boolean}
  47821. */
  47822. isModified : function(fieldName) {
  47823. return this.modified.hasOwnProperty(fieldName);
  47824. },
  47825. /**
  47826. * Marks this **Record** as `{@link #dirty}`. This method is used interally when adding `{@link #phantom}` records
  47827. * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
  47828. *
  47829. * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
  47830. * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
  47831. */
  47832. setDirty : function() {
  47833. var me = this,
  47834. fields = me.fields.items,
  47835. fLen = fields.length,
  47836. field, name, f;
  47837. me.dirty = true;
  47838. for (f = 0; f < fLen; f++) {
  47839. field = fields[f];
  47840. if (field.persist) {
  47841. name = field.name;
  47842. me.modified[name] = me.get(name);
  47843. }
  47844. }
  47845. },
  47846. markDirty : function() {
  47847. Ext.log.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
  47848. return this.setDirty.apply(this, arguments);
  47849. },
  47850. /**
  47851. * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
  47852. * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
  47853. * reverted to their original values.
  47854. *
  47855. * Developers should subscribe to the {@link Ext.data.Store#event-update} event to have their code notified of reject
  47856. * operations.
  47857. *
  47858. * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
  47859. * Defaults to false.
  47860. */
  47861. reject : function(silent) {
  47862. var me = this,
  47863. modified = me.modified,
  47864. field;
  47865. for (field in modified) {
  47866. if (modified.hasOwnProperty(field)) {
  47867. if (typeof modified[field] != "function") {
  47868. me[me.persistenceProperty][field] = modified[field];
  47869. }
  47870. }
  47871. }
  47872. me.dirty = false;
  47873. me.editing = false;
  47874. me.modified = {};
  47875. if (silent !== true) {
  47876. me.afterReject();
  47877. }
  47878. },
  47879. /**
  47880. * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
  47881. * instance since either creation or the last commit operation.
  47882. *
  47883. * Developers should subscribe to the {@link Ext.data.Store#event-update} event to have their code notified of commit
  47884. * operations.
  47885. *
  47886. * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
  47887. * Defaults to false.
  47888. */
  47889. commit : function(silent) {
  47890. var me = this;
  47891. me.phantom = me.dirty = me.editing = false;
  47892. me.modified = {};
  47893. if (silent !== true) {
  47894. me.afterCommit();
  47895. }
  47896. },
  47897. /**
  47898. * Creates a copy (clone) of this Model instance.
  47899. *
  47900. * @param {String} [id] A new id, defaults to the id of the instance being copied.
  47901. * See `{@link Ext.data.Model#id id}`. To generate a phantom instance with a new id use:
  47902. *
  47903. * var rec = record.copy(); // clone the record
  47904. * Ext.data.Model.id(rec); // automatically generate a unique sequential id
  47905. *
  47906. * @return {Ext.data.Model}
  47907. */
  47908. copy : function(newId) {
  47909. var me = this;
  47910. // Use raw data as the data param.
  47911. // Pass a copy iof our converted data in to be used as the new record's convertedData
  47912. return new me.self(me.raw, newId, null, Ext.apply({}, me[me.persistenceProperty]));
  47913. },
  47914. /**
  47915. * Sets the Proxy to use for this model. Accepts any options that can be accepted by
  47916. * {@link Ext#createByAlias Ext.createByAlias}.
  47917. *
  47918. * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
  47919. * @return {Ext.data.proxy.Proxy}
  47920. */
  47921. setProxy: function(proxy) {
  47922. //make sure we have an Ext.data.proxy.Proxy object
  47923. if (!proxy.isProxy) {
  47924. if (typeof proxy === "string") {
  47925. proxy = {
  47926. type: proxy
  47927. };
  47928. }
  47929. proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
  47930. }
  47931. proxy.setModel(this.self);
  47932. this.proxy = proxy;
  47933. return proxy;
  47934. },
  47935. /**
  47936. * Returns the configured Proxy for this Model.
  47937. * @return {Ext.data.proxy.Proxy} The proxy
  47938. */
  47939. getProxy: function() {
  47940. return this.proxy;
  47941. },
  47942. /**
  47943. * Validates the current data against all of its configured {@link #validations}.
  47944. * @return {Ext.data.Errors} The errors object
  47945. */
  47946. validate: function() {
  47947. var errors = new Ext.data.Errors(),
  47948. validations = this.validations,
  47949. validators = Ext.data.validations,
  47950. length, validation, field, valid, type, i;
  47951. if (validations) {
  47952. length = validations.length;
  47953. for (i = 0; i < length; i++) {
  47954. validation = validations[i];
  47955. field = validation.field || validation.name;
  47956. type = validation.type;
  47957. valid = validators[type](validation, this.get(field));
  47958. if (!valid) {
  47959. errors.add({
  47960. field : field,
  47961. message: validation.message || validators[type + 'Message']
  47962. });
  47963. }
  47964. }
  47965. }
  47966. return errors;
  47967. },
  47968. /**
  47969. * Checks if the model is valid. See {@link #validate}.
  47970. * @return {Boolean} True if the model is valid.
  47971. */
  47972. isValid: function(){
  47973. return this.validate().isValid();
  47974. },
  47975. /**
  47976. * Saves the model instance using the configured proxy.
  47977. * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
  47978. * @return {Ext.data.Model} The Model instance
  47979. */
  47980. save: function(options) {
  47981. options = Ext.apply({}, options);
  47982. var me = this,
  47983. action = me.phantom ? 'create' : 'update',
  47984. scope = options.scope || me,
  47985. stores = me.stores,
  47986. i = 0,
  47987. storeCount,
  47988. store,
  47989. args,
  47990. operation,
  47991. callback;
  47992. Ext.apply(options, {
  47993. records: [me],
  47994. action : action
  47995. });
  47996. operation = new Ext.data.Operation(options);
  47997. callback = function(operation) {
  47998. args = [me, operation];
  47999. if (operation.wasSuccessful()) {
  48000. for(storeCount = stores.length; i < storeCount; i++) {
  48001. store = stores[i];
  48002. store.fireEvent('write', store, operation);
  48003. store.fireEvent('datachanged', store);
  48004. // Not firing refresh here, since it's a single record
  48005. }
  48006. Ext.callback(options.success, scope, args);
  48007. } else {
  48008. Ext.callback(options.failure, scope, args);
  48009. }
  48010. Ext.callback(options.callback, scope, args);
  48011. };
  48012. me.getProxy()[action](operation, callback, me);
  48013. return me;
  48014. },
  48015. /**
  48016. * Destroys the model using the configured proxy.
  48017. * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
  48018. * @return {Ext.data.Model} The Model instance
  48019. */
  48020. destroy: function(options){
  48021. options = Ext.apply({}, options);
  48022. var me = this,
  48023. scope = options.scope || me,
  48024. stores = me.stores,
  48025. i = 0,
  48026. storeCount,
  48027. store,
  48028. args,
  48029. operation,
  48030. callback;
  48031. Ext.apply(options, {
  48032. records: [me],
  48033. action : 'destroy'
  48034. });
  48035. operation = new Ext.data.Operation(options);
  48036. callback = function(operation) {
  48037. args = [me, operation];
  48038. if (operation.wasSuccessful()) {
  48039. for(storeCount = stores.length; i < storeCount; i++) {
  48040. store = stores[i];
  48041. store.fireEvent('write', store, operation);
  48042. store.fireEvent('datachanged', store);
  48043. // Not firing refresh here, since it's a single record
  48044. }
  48045. me.clearListeners();
  48046. Ext.callback(options.success, scope, args);
  48047. } else {
  48048. Ext.callback(options.failure, scope, args);
  48049. }
  48050. Ext.callback(options.callback, scope, args);
  48051. };
  48052. me.getProxy().destroy(operation, callback, me);
  48053. return me;
  48054. },
  48055. /**
  48056. * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
  48057. * @return {Number/String} The id
  48058. */
  48059. getId: function() {
  48060. return this.get(this.idProperty);
  48061. },
  48062. /**
  48063. * @private
  48064. */
  48065. getObservableId: function() {
  48066. return this.id;
  48067. },
  48068. /**
  48069. * Sets the model instance's id field to the given id.
  48070. * @param {Number/String} id The new id
  48071. */
  48072. setId: function(id) {
  48073. this.set(this.idProperty, id);
  48074. this.phantom = !(id || id === 0);
  48075. },
  48076. /**
  48077. * Tells this model instance that it has been added to a store.
  48078. * @param {Ext.data.Store} store The store to which this model has been added.
  48079. */
  48080. join : function(store) {
  48081. Ext.Array.include(this.stores, store);
  48082. /**
  48083. * @property {Ext.data.Store} store
  48084. * The {@link Ext.data.Store Store} to which this instance belongs. NOTE: If this
  48085. * instance is bound to multiple stores, this property will reference only the
  48086. * first. To examine all the stores, use the {@link #stores} property instead.
  48087. */
  48088. this.store = this.stores[0]; // compat w/all releases ever
  48089. },
  48090. /**
  48091. * Tells this model instance that it has been removed from the store.
  48092. * @param {Ext.data.Store} store The store from which this model has been removed.
  48093. */
  48094. unjoin: function(store) {
  48095. Ext.Array.remove(this.stores, store);
  48096. this.store = this.stores[0] || null; // compat w/all releases ever
  48097. },
  48098. /**
  48099. * @private
  48100. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  48101. * afterEdit method is called
  48102. * @param {String[]} modifiedFieldNames Array of field names changed during edit.
  48103. */
  48104. afterEdit : function(modifiedFieldNames) {
  48105. this.callStore('afterEdit', modifiedFieldNames);
  48106. },
  48107. /**
  48108. * @private
  48109. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  48110. * afterReject method is called
  48111. */
  48112. afterReject : function() {
  48113. this.callStore("afterReject");
  48114. },
  48115. /**
  48116. * @private
  48117. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  48118. * afterCommit method is called
  48119. */
  48120. afterCommit: function() {
  48121. this.callStore('afterCommit');
  48122. },
  48123. /**
  48124. * @private
  48125. * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
  48126. * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
  48127. * will always be called with the model instance as its single argument. If this model is joined to
  48128. * a Ext.data.NodeStore, then this method calls the given method on the NodeStore and the associated Ext.data.TreeStore
  48129. * @param {String} fn The function to call on the store
  48130. */
  48131. callStore: function(fn) {
  48132. var args = Ext.Array.clone(arguments),
  48133. stores = this.stores,
  48134. i = 0,
  48135. len = stores.length,
  48136. store, treeStore;
  48137. args[0] = this;
  48138. for (; i < len; ++i) {
  48139. store = stores[i];
  48140. if (store && typeof store[fn] == "function") {
  48141. store[fn].apply(store, args);
  48142. }
  48143. // if the record is bound to a NodeStore call the TreeStore's method as well
  48144. treeStore = store.treeStore;
  48145. if (treeStore && typeof treeStore[fn] == "function") {
  48146. treeStore[fn].apply(treeStore, args);
  48147. }
  48148. }
  48149. },
  48150. /**
  48151. * Gets all values for each field in this model and returns an object
  48152. * containing the current data.
  48153. * @param {Boolean} includeAssociated True to also include associated data. Defaults to false.
  48154. * @return {Object} An object hash containing all the values in this model
  48155. */
  48156. getData: function(includeAssociated){
  48157. var me = this,
  48158. fields = me.fields.items,
  48159. fLen = fields.length,
  48160. data = {},
  48161. name, f;
  48162. for (f = 0; f < fLen; f++) {
  48163. name = fields[f].name;
  48164. data[name] = me.get(name);
  48165. }
  48166. if (includeAssociated === true) {
  48167. Ext.apply(data, me.getAssociatedData());
  48168. }
  48169. return data;
  48170. },
  48171. /**
  48172. * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
  48173. * User which hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
  48174. *
  48175. * {
  48176. * orders: [
  48177. * {
  48178. * id: 123,
  48179. * status: 'shipped',
  48180. * orderItems: [
  48181. * ...
  48182. * ]
  48183. * }
  48184. * ]
  48185. * }
  48186. *
  48187. * @return {Object} The nested data set for the Model's loaded associations
  48188. */
  48189. getAssociatedData: function(){
  48190. return this.prepareAssociatedData({}, 1);
  48191. },
  48192. /**
  48193. * @private
  48194. * This complex-looking method takes a given Model instance and returns an object containing all data from
  48195. * all of that Model's *loaded* associations. See {@link #getAssociatedData}
  48196. * @param {Object} seenKeys A hash of all the associations we've already seen
  48197. * @param {Number} depth The current depth
  48198. * @return {Object} The nested data set for the Model's loaded associations
  48199. */
  48200. prepareAssociatedData: function(seenKeys, depth) {
  48201. /*
  48202. * In this method we use a breadth first strategy instead of depth
  48203. * first. The reason for doing so is that it prevents messy & difficult
  48204. * issues when figuring out which associations we've already processed
  48205. * & at what depths.
  48206. */
  48207. var me = this,
  48208. associations = me.associations.items,
  48209. associationCount = associations.length,
  48210. associationData = {},
  48211. // We keep 3 lists at the same index instead of using an array of objects.
  48212. // The reasoning behind this is that this method gets called a lot
  48213. // So we want to minimize the amount of objects we create for GC.
  48214. toRead = [],
  48215. toReadKey = [],
  48216. toReadIndex = [],
  48217. associatedStore, associatedRecords, associatedRecord, o, index, result, seenDepth,
  48218. associationId, associatedRecordCount, association, i, j, type, name;
  48219. for (i = 0; i < associationCount; i++) {
  48220. association = associations[i];
  48221. associationId = association.associationId;
  48222. seenDepth = seenKeys[associationId];
  48223. if (seenDepth && seenDepth !== depth) {
  48224. continue;
  48225. }
  48226. seenKeys[associationId] = depth;
  48227. type = association.type;
  48228. name = association.name;
  48229. if (type == 'hasMany') {
  48230. //this is the hasMany store filled with the associated data
  48231. associatedStore = me[association.storeName];
  48232. //we will use this to contain each associated record's data
  48233. associationData[name] = [];
  48234. //if it's loaded, put it into the association data
  48235. if (associatedStore && associatedStore.getCount() > 0) {
  48236. associatedRecords = associatedStore.data.items;
  48237. associatedRecordCount = associatedRecords.length;
  48238. //now we're finally iterating over the records in the association. Get
  48239. // all the records so we can process them
  48240. for (j = 0; j < associatedRecordCount; j++) {
  48241. associatedRecord = associatedRecords[j];
  48242. associationData[name][j] = associatedRecord.getData();
  48243. toRead.push(associatedRecord);
  48244. toReadKey.push(name);
  48245. toReadIndex.push(j);
  48246. }
  48247. }
  48248. } else if (type == 'belongsTo' || type == 'hasOne') {
  48249. associatedRecord = me[association.instanceName];
  48250. // If we have a record, put it onto our list
  48251. if (associatedRecord !== undefined) {
  48252. associationData[name] = associatedRecord.getData();
  48253. toRead.push(associatedRecord);
  48254. toReadKey.push(name);
  48255. toReadIndex.push(-1);
  48256. }
  48257. }
  48258. }
  48259. for (i = 0, associatedRecordCount = toRead.length; i < associatedRecordCount; ++i) {
  48260. associatedRecord = toRead[i];
  48261. o = associationData[toReadKey[i]];
  48262. index = toReadIndex[i];
  48263. result = associatedRecord.prepareAssociatedData(seenKeys, depth + 1);
  48264. if (index === -1) {
  48265. Ext.apply(o, result);
  48266. } else {
  48267. Ext.apply(o[index], result);
  48268. }
  48269. }
  48270. return associationData;
  48271. }
  48272. });
  48273. /**
  48274. * @docauthor Evan Trimboli <evan@sencha.com>
  48275. *
  48276. * Contains a collection of all stores that are created that have an identifier. An identifier can be assigned by
  48277. * setting the {@link Ext.data.AbstractStore#storeId storeId} property. When a store is in the StoreManager, it can be
  48278. * referred to via it's identifier:
  48279. *
  48280. * Ext.create('Ext.data.Store', {
  48281. * model: 'SomeModel',
  48282. * storeId: 'myStore'
  48283. * });
  48284. *
  48285. * var store = Ext.data.StoreManager.lookup('myStore');
  48286. *
  48287. * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.
  48288. *
  48289. * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when registering
  48290. * it with any Component that consumes data from a store:
  48291. *
  48292. * Ext.create('Ext.data.Store', {
  48293. * model: 'SomeModel',
  48294. * storeId: 'myStore'
  48295. * });
  48296. *
  48297. * Ext.create('Ext.view.View', {
  48298. * store: 'myStore',
  48299. * // other configuration here
  48300. * });
  48301. *
  48302. */
  48303. Ext.define('Ext.data.StoreManager', {
  48304. extend: 'Ext.util.MixedCollection',
  48305. alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
  48306. singleton: true,
  48307. uses: ['Ext.data.ArrayStore'],
  48308. /**
  48309. * @cfg {Object} listeners
  48310. * @private
  48311. */
  48312. /**
  48313. * Registers one or more Stores with the StoreManager. You do not normally need to register stores manually. Any
  48314. * store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
  48315. * @param {Ext.data.Store...} stores Any number of Store instances
  48316. */
  48317. register : function() {
  48318. for (var i = 0, s; (s = arguments[i]); i++) {
  48319. this.add(s);
  48320. }
  48321. },
  48322. /**
  48323. * Unregisters one or more Stores with the StoreManager
  48324. * @param {String/Object...} stores Any number of Store instances or ID-s
  48325. */
  48326. unregister : function() {
  48327. for (var i = 0, s; (s = arguments[i]); i++) {
  48328. this.remove(this.lookup(s));
  48329. }
  48330. },
  48331. /**
  48332. * Gets a registered Store by id
  48333. * @param {String/Object} store The id of the Store, or a Store instance, or a store configuration
  48334. * @return {Ext.data.Store}
  48335. */
  48336. lookup : function(store) {
  48337. // handle the case when we are given an array or an array of arrays.
  48338. if (Ext.isArray(store)) {
  48339. var fields = ['field1'],
  48340. expand = !Ext.isArray(store[0]),
  48341. data = store,
  48342. i,
  48343. len;
  48344. if(expand){
  48345. data = [];
  48346. for (i = 0, len = store.length; i < len; ++i) {
  48347. data.push([store[i]]);
  48348. }
  48349. } else {
  48350. for(i = 2, len = store[0].length; i <= len; ++i){
  48351. fields.push('field' + i);
  48352. }
  48353. }
  48354. return new Ext.data.ArrayStore({
  48355. data : data,
  48356. fields: fields,
  48357. autoDestroy: true,
  48358. autoCreated: true,
  48359. expanded: expand
  48360. });
  48361. }
  48362. if (Ext.isString(store)) {
  48363. // store id
  48364. return this.get(store);
  48365. } else {
  48366. // store instance or store config
  48367. return Ext.data.AbstractStore.create(store);
  48368. }
  48369. },
  48370. // getKey implementation for MixedCollection
  48371. getKey : function(o) {
  48372. return o.storeId;
  48373. }
  48374. }, function() {
  48375. /**
  48376. * Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Manager}.
  48377. * Sample usage:
  48378. *
  48379. * Ext.regStore('AllUsers', {
  48380. * model: 'User'
  48381. * });
  48382. *
  48383. * // the store can now easily be used throughout the application
  48384. * new Ext.List({
  48385. * store: 'AllUsers',
  48386. * ... other config
  48387. * });
  48388. *
  48389. * @param {String} id The id to set on the new store
  48390. * @param {Object} config The store config
  48391. * @member Ext
  48392. * @method regStore
  48393. */
  48394. Ext.regStore = function(name, config) {
  48395. var store;
  48396. if (Ext.isObject(name)) {
  48397. config = name;
  48398. } else {
  48399. config.storeId = name;
  48400. }
  48401. if (config instanceof Ext.data.Store) {
  48402. store = config;
  48403. } else {
  48404. store = new Ext.data.Store(config);
  48405. }
  48406. return Ext.data.StoreManager.register(store);
  48407. };
  48408. /**
  48409. * Shortcut to {@link Ext.data.StoreManager#lookup}.
  48410. * @member Ext
  48411. * @method getStore
  48412. * @inheritdoc Ext.data.StoreManager#lookup
  48413. */
  48414. Ext.getStore = function(name) {
  48415. return Ext.data.StoreManager.lookup(name);
  48416. };
  48417. });
  48418. /**
  48419. * This ComponentLayout handles docking for Panels. It takes care of panels that are
  48420. * part of a ContainerLayout that sets this Panel's size and Panels that are part of
  48421. * an AutoContainerLayout in which this panel get his height based of the CSS or
  48422. * or its content.
  48423. * @private
  48424. */
  48425. Ext.define('Ext.layout.component.Dock', {
  48426. /* Begin Definitions */
  48427. extend: 'Ext.layout.component.Component',
  48428. alias: 'layout.dock',
  48429. alternateClassName: 'Ext.layout.component.AbstractDock',
  48430. /* End Definitions */
  48431. type: 'dock',
  48432. initializedBorders: -1,
  48433. horizontalCollapsePolicy: { width: true, x: true },
  48434. verticalCollapsePolicy: { height: true, y: true },
  48435. finishRender: function () {
  48436. var me = this,
  48437. target, items;
  48438. me.callParent();
  48439. target = me.getRenderTarget();
  48440. items = me.getDockedItems();
  48441. me.finishRenderItems(target, items);
  48442. },
  48443. isItemBoxParent: function (itemContext) {
  48444. return true;
  48445. },
  48446. isItemShrinkWrap: function (item) {
  48447. return true;
  48448. },
  48449. dockOpposites: {
  48450. top: 'bottom',
  48451. right: 'left',
  48452. bottom: 'top',
  48453. left: 'right'
  48454. },
  48455. handleItemBorders: function() {
  48456. var me = this,
  48457. owner = me.owner,
  48458. borders, docked,
  48459. oldBorders = me.borders,
  48460. opposites = me.dockOpposites,
  48461. currentGeneration = owner.dockedItems.generation,
  48462. i, ln, item, dock, side, borderItem,
  48463. collapsed = me.collapsed;
  48464. if (me.initializedBorders == currentGeneration || (owner.border && !owner.manageBodyBorders)) {
  48465. return;
  48466. }
  48467. me.initializedBorders = currentGeneration;
  48468. // Borders have to be calculated using expanded docked item collection.
  48469. me.collapsed = false;
  48470. docked = me.getLayoutItems();
  48471. me.collapsed = collapsed;
  48472. borders = { top: [], right: [], bottom: [], left: [] };
  48473. for (i = 0, ln = docked.length; i < ln; i++) {
  48474. item = docked[i];
  48475. dock = item.dock;
  48476. if (item.ignoreBorderManagement) {
  48477. continue;
  48478. }
  48479. if (!borders[dock].satisfied) {
  48480. borders[dock].push(item);
  48481. borders[dock].satisfied = true;
  48482. }
  48483. if (!borders.top.satisfied && opposites[dock] !== 'top') {
  48484. borders.top.push(item);
  48485. }
  48486. if (!borders.right.satisfied && opposites[dock] !== 'right') {
  48487. borders.right.push(item);
  48488. }
  48489. if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') {
  48490. borders.bottom.push(item);
  48491. }
  48492. if (!borders.left.satisfied && opposites[dock] !== 'left') {
  48493. borders.left.push(item);
  48494. }
  48495. }
  48496. if (oldBorders) {
  48497. for (side in oldBorders) {
  48498. if (oldBorders.hasOwnProperty(side)) {
  48499. ln = oldBorders[side].length;
  48500. if (!owner.manageBodyBorders) {
  48501. for (i = 0; i < ln; i++) {
  48502. borderItem = oldBorders[side][i];
  48503. if (!borderItem.isDestroyed) {
  48504. borderItem.removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
  48505. }
  48506. }
  48507. if (!oldBorders[side].satisfied && !owner.bodyBorder) {
  48508. owner.removeBodyCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
  48509. }
  48510. }
  48511. else if (oldBorders[side].satisfied) {
  48512. owner.setBodyStyle('border-' + side + '-width', '');
  48513. }
  48514. }
  48515. }
  48516. }
  48517. for (side in borders) {
  48518. if (borders.hasOwnProperty(side)) {
  48519. ln = borders[side].length;
  48520. if (!owner.manageBodyBorders) {
  48521. for (i = 0; i < ln; i++) {
  48522. borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
  48523. }
  48524. if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
  48525. owner.addBodyCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
  48526. }
  48527. }
  48528. else if (borders[side].satisfied) {
  48529. owner.setBodyStyle('border-' + side + '-width', '1px');
  48530. }
  48531. }
  48532. }
  48533. me.borders = borders;
  48534. },
  48535. beforeLayoutCycle: function (ownerContext) {
  48536. var me = this,
  48537. owner = me.owner,
  48538. shrinkWrap = me.sizeModels.shrinkWrap,
  48539. collapsedHorz, collapsedVert;
  48540. if (owner.collapsed) {
  48541. if (owner.collapsedVertical()) {
  48542. collapsedVert = true;
  48543. ownerContext.measureDimensions = 1;
  48544. } else {
  48545. collapsedHorz = true;
  48546. ownerContext.measureDimensions = 2;
  48547. }
  48548. }
  48549. ownerContext.collapsedVert = collapsedVert;
  48550. ownerContext.collapsedHorz = collapsedHorz;
  48551. // If we are collapsed, we want to auto-layout using the placeholder/expander
  48552. // instead of the normal items/dockedItems. This must be done here since we could
  48553. // be in a box layout w/stretchmax which sets the width/heightModel to allow it to
  48554. // control the size.
  48555. if (collapsedVert) {
  48556. ownerContext.heightModel = shrinkWrap;
  48557. } else if (collapsedHorz) {
  48558. ownerContext.widthModel = shrinkWrap;
  48559. }
  48560. },
  48561. beginLayout: function(ownerContext) {
  48562. var me = this,
  48563. owner = me.owner,
  48564. docked = me.getLayoutItems(),
  48565. layoutContext = ownerContext.context,
  48566. dockedItemCount = docked.length,
  48567. dockedItems, i, item, itemContext, offsets,
  48568. collapsed;
  48569. me.callParent(arguments);
  48570. me.handleItemBorders();
  48571. // Cache the children as ContextItems (like a Container). Also setup to handle
  48572. // collapsed state:
  48573. collapsed = owner.getCollapsed();
  48574. if (collapsed !== me.lastCollapsedState && Ext.isDefined(me.lastCollapsedState)) {
  48575. // If we are collapsing...
  48576. if (me.owner.collapsed) {
  48577. ownerContext.isCollapsingOrExpanding = 1;
  48578. // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken by the layout.
  48579. owner.addClsWithUI(owner.collapsedCls);
  48580. } else {
  48581. ownerContext.isCollapsingOrExpanding = 2;
  48582. // Remove the collapsed class now, before layout calculations are done.
  48583. owner.removeClsWithUI(owner.collapsedCls);
  48584. ownerContext.lastCollapsedState = me.lastCollapsedState;
  48585. }
  48586. }
  48587. me.lastCollapsedState = collapsed;
  48588. ownerContext.dockedItems = dockedItems = [];
  48589. for (i = 0; i < dockedItemCount; i++) {
  48590. item = docked[i];
  48591. if (item.rendered) {
  48592. itemContext = layoutContext.getCmp(item);
  48593. itemContext.dockedAt = { x: 0, y: 0 };
  48594. itemContext.offsets = offsets = Ext.Element.parseBox(item.offsets || {});
  48595. offsets.width = offsets.left + offsets.right;
  48596. offsets.height = offsets.top + offsets.bottom;
  48597. dockedItems.push(itemContext);
  48598. }
  48599. }
  48600. ownerContext.bodyContext = ownerContext.getEl('body');
  48601. },
  48602. beginLayoutCycle: function(ownerContext) {
  48603. var me = this,
  48604. docked = ownerContext.dockedItems,
  48605. len = docked.length,
  48606. owner = me.owner,
  48607. frameBody = owner.frameBody,
  48608. lastHeightModel = me.lastHeightModel,
  48609. i, item, dock;
  48610. me.callParent(arguments);
  48611. if (lastHeightModel && lastHeightModel.shrinkWrap &&
  48612. !ownerContext.heightModel.shrinkWrap && !me.owner.manageHeight) {
  48613. owner.body.dom.style.marginBottom = '';
  48614. }
  48615. if (ownerContext.widthModel.auto) {
  48616. if (ownerContext.widthModel.shrinkWrap) {
  48617. owner.el.setWidth(null);
  48618. }
  48619. owner.body.setWidth(null);
  48620. if (frameBody) {
  48621. frameBody.setWidth(null);
  48622. }
  48623. }
  48624. if (ownerContext.heightModel.auto) {
  48625. owner.body.setHeight(null);
  48626. //owner.el.setHeight(null); Disable this for now
  48627. if (frameBody) {
  48628. frameBody.setHeight(null);
  48629. }
  48630. }
  48631. // Each time we begin (2nd+ would be due to invalidate) we need to publish the
  48632. // known contentWidth/Height if we are collapsed:
  48633. if (ownerContext.collapsedVert) {
  48634. ownerContext.setContentHeight(0);
  48635. } else if (ownerContext.collapsedHorz) {
  48636. ownerContext.setContentWidth(0);
  48637. }
  48638. // dock: 'right' items, when a panel gets narrower get "squished". Moving them to
  48639. // left:0px avoids this!
  48640. for (i = 0; i < len; i++) {
  48641. item = docked[i].target;
  48642. dock = item.dock;
  48643. if (dock == 'right') {
  48644. item.el.setLeft(0);
  48645. } else if (dock != 'left') {
  48646. continue;
  48647. }
  48648. // TODO - clear width/height?
  48649. }
  48650. },
  48651. calculate: function (ownerContext) {
  48652. var me = this,
  48653. measure = me.measureAutoDimensions(ownerContext, ownerContext.measureDimensions),
  48654. state = ownerContext.state,
  48655. horzDone = state.horzDone,
  48656. vertDone = state.vertDone,
  48657. bodyContext = ownerContext.bodyContext,
  48658. horz, vert, forward, backward;
  48659. // make sure we can use these value w/o calling methods to get them
  48660. ownerContext.borderInfo || ownerContext.getBorderInfo();
  48661. ownerContext.paddingInfo || ownerContext.getPaddingInfo();
  48662. ownerContext.framingInfo || ownerContext.getFraming();
  48663. bodyContext.borderInfo || bodyContext.getBorderInfo();
  48664. bodyContext.paddingInfo || bodyContext.getPaddingInfo();
  48665. // Start the axes so they are ready to proceed inwards (fixed-size) or outwards
  48666. // (shrinkWrap) and stash key property names as well:
  48667. horz = !horzDone &&
  48668. me.createAxis(ownerContext, measure.contentWidth, ownerContext.widthModel,
  48669. 'left', 'right', 'x', 'width', 'Width', ownerContext.collapsedHorz);
  48670. vert = !vertDone &&
  48671. me.createAxis(ownerContext, measure.contentHeight, ownerContext.heightModel,
  48672. 'top', 'bottom', 'y', 'height', 'Height', ownerContext.collapsedVert);
  48673. // We iterate forward and backward over the dockedItems at the same time based on
  48674. // whether an axis is shrinkWrap or fixed-size. For a fixed-size axis, the outer box
  48675. // axis is allocated to docked items in forward order and is reduced accordingly.
  48676. // To handle a shrinkWrap axis, the box starts at the inner (body) size and is used to
  48677. // size docked items in backwards order. This is because the last docked item shares
  48678. // an edge with the body. The item size is used to adjust the shrinkWrap axis outwards
  48679. // until the first docked item (at the outermost edge) is processed. This backwards
  48680. // order ensures that docked items never get an incorrect size for any dimension.
  48681. for (forward = 0, backward = ownerContext.dockedItems.length; backward--; ++forward) {
  48682. if (horz) {
  48683. me.dockChild(ownerContext, horz, backward, forward);
  48684. }
  48685. if (vert) {
  48686. me.dockChild(ownerContext, vert, backward, forward);
  48687. }
  48688. }
  48689. if (horz && me.finishAxis(ownerContext, horz)) {
  48690. state.horzDone = horzDone = horz;
  48691. }
  48692. if (vert && me.finishAxis(ownerContext, vert)) {
  48693. state.vertDone = vertDone = vert;
  48694. }
  48695. // Once all items are docked, the final size of the outer panel or inner body can
  48696. // be determined. If we can determine both width and height, we are done.
  48697. if (horzDone && vertDone && me.finishConstraints(ownerContext, horzDone, vertDone)) {
  48698. // Size information is published as we dock items but position is hard to do
  48699. // that way (while avoiding published multiple times) so we publish all the
  48700. // positions at the end.
  48701. me.finishPositions(ownerContext, horzDone, vertDone);
  48702. } else {
  48703. me.done = false;
  48704. }
  48705. },
  48706. /**
  48707. * Creates an axis object given the particulars.
  48708. * @private
  48709. */
  48710. createAxis: function (ownerContext, contentSize, sizeModel, dockBegin, dockEnd, posProp,
  48711. sizeProp, sizePropCap, collapsedAxis) {
  48712. var begin = 0,
  48713. owner = this.owner,
  48714. maxSize = owner['max' + sizePropCap],
  48715. minSize = owner['min' + sizePropCap] || 0,
  48716. hasMaxSize = maxSize != null, // exactly the same as "maxSize !== null && maxSize !== undefined"
  48717. setSize = 'set' + sizePropCap,
  48718. border, bodyContext, frameSize, padding, end;
  48719. if (sizeModel.shrinkWrap) {
  48720. // End position before adding docks around the content is content size plus the body borders in this axis.
  48721. // If collapsed in this axis, the body borders will not be shown.
  48722. if (collapsedAxis) {
  48723. end = 0;
  48724. } else {
  48725. bodyContext = ownerContext.bodyContext;
  48726. end = contentSize + bodyContext.borderInfo[sizeProp];
  48727. }
  48728. } else {
  48729. border = ownerContext.borderInfo;
  48730. frameSize = ownerContext.framingInfo;
  48731. padding = ownerContext.paddingInfo;
  48732. end = ownerContext.getProp(sizeProp);
  48733. end -= border[dockEnd] + padding[dockEnd] + frameSize[dockEnd];
  48734. begin = border[dockBegin] + padding[dockBegin] + frameSize[dockBegin];
  48735. }
  48736. return {
  48737. shrinkWrap: sizeModel.shrinkWrap,
  48738. sizeModel: sizeModel,
  48739. // An axis tracks start and end+1 px positions. eg 0 to 10 for 10px high
  48740. begin: begin,
  48741. end: end,
  48742. collapsed: collapsedAxis,
  48743. horizontal: posProp == 'x',
  48744. ignoreFrameBegin: false,
  48745. ignoreFrameEnd: false,
  48746. initialSize: end - begin,
  48747. hasMinMaxConstraints: (minSize || hasMaxSize) && sizeModel.shrinkWrap,
  48748. minSize: minSize,
  48749. maxSize: hasMaxSize ? maxSize : 1e9,
  48750. bodyPosProp: this.owner.manageHeight ? posProp : ('margin-' + dockBegin), // 'margin-left' or 'margin-top'
  48751. dockBegin: dockBegin, // 'left' or 'top'
  48752. dockEnd: dockEnd, // 'right' or 'end'
  48753. posProp: posProp, // 'x' or 'y'
  48754. sizeProp: sizeProp, // 'width' or 'height'
  48755. sizePropCap: sizePropCap, // 'Width' or 'Height'
  48756. setSize: setSize,
  48757. dockedPixelsEnd: 0
  48758. };
  48759. },
  48760. /**
  48761. * Docks a child item on the specified axis. This boils down to determining if the item
  48762. * is docked at the "beginning" of the axis ("left" if horizontal, "top" if vertical),
  48763. * the "end" of the axis ("right" if horizontal, "bottom" if vertical) or stretches
  48764. * along the axis ("top" or "bottom" if horizontal, "left" or "right" if vertical). It
  48765. * also has to differentiate between fixed and shrinkWrap sized dimensions.
  48766. * @private
  48767. */
  48768. dockChild: function (ownerContext, axis, backward, forward) {
  48769. var me = this,
  48770. itemContext = ownerContext.dockedItems[axis.shrinkWrap ? backward : forward],
  48771. item = itemContext.target,
  48772. dock = item.dock, // left/top/right/bottom
  48773. pos;
  48774. if(item.ignoreParentFrame && ownerContext.isCollapsingOrExpanding) {
  48775. // collapsed window header margins may differ from expanded window header margins
  48776. // so we need to make sure the old cached values are not used in axis calculations
  48777. itemContext.clearMarginCache();
  48778. }
  48779. if (dock == axis.dockBegin) {
  48780. if (axis.shrinkWrap) {
  48781. pos = me.dockOutwardBegin(ownerContext, itemContext, item, axis);
  48782. } else {
  48783. pos = me.dockInwardBegin(ownerContext, itemContext, item, axis);
  48784. }
  48785. } else if (dock == axis.dockEnd) {
  48786. if (axis.shrinkWrap) {
  48787. pos = me.dockOutwardEnd(ownerContext, itemContext, item, axis);
  48788. } else {
  48789. pos = me.dockInwardEnd(ownerContext, itemContext, item, axis);
  48790. }
  48791. } else {
  48792. pos = me.dockStretch(ownerContext, itemContext, item, axis);
  48793. }
  48794. itemContext.dockedAt[axis.posProp] = pos;
  48795. },
  48796. /**
  48797. * Docks an item on a fixed-size axis at the "beginning". The "beginning" of the horizontal
  48798. * axis is "left" and the vertical is "top". For a fixed-size axis, the size works from
  48799. * the outer element (the panel) towards the body.
  48800. * @private
  48801. */
  48802. dockInwardBegin: function (ownerContext, itemContext, item, axis) {
  48803. var pos = axis.begin,
  48804. sizeProp = axis.sizeProp,
  48805. size,
  48806. dock;
  48807. if (item.ignoreParentFrame) {
  48808. dock = item.dock;
  48809. pos -= ownerContext.borderInfo[dock] + ownerContext.paddingInfo[dock] +
  48810. ownerContext.framingInfo[dock];
  48811. }
  48812. if (!item.overlay) {
  48813. size = itemContext.getProp(sizeProp) + itemContext.getMarginInfo()[sizeProp];
  48814. axis.begin += size;
  48815. }
  48816. return pos;
  48817. },
  48818. /**
  48819. * Docks an item on a fixed-size axis at the "end". The "end" of the horizontal axis is
  48820. * "right" and the vertical is "bottom".
  48821. * @private
  48822. */
  48823. dockInwardEnd: function (ownerContext, itemContext, item, axis) {
  48824. var sizeProp = axis.sizeProp,
  48825. size = itemContext.getProp(sizeProp) + itemContext.getMarginInfo()[sizeProp],
  48826. pos = axis.end - size;
  48827. if (!item.overlay) {
  48828. axis.end = pos;
  48829. }
  48830. if (item.ignoreParentFrame) {
  48831. pos += ownerContext.borderInfo[item.dock] + ownerContext.paddingInfo[item.dock] +
  48832. ownerContext.framingInfo[item.dock];
  48833. }
  48834. return pos;
  48835. },
  48836. /**
  48837. * Docks an item on a shrinkWrap axis at the "beginning". The "beginning" of the horizontal
  48838. * axis is "left" and the vertical is "top". For a shrinkWrap axis, the size works from
  48839. * the body outward to the outermost element (the panel).
  48840. *
  48841. * During the docking process, coordinates are allowed to be negative. We start with the
  48842. * body at (0,0) so items docked "top" or "left" will simply be assigned negative x/y. In
  48843. * the {@link #finishPositions} method these are corrected and framing is added. This way
  48844. * the correction is applied as a simple translation of delta x/y on all coordinates to
  48845. * bring the origin back to (0,0).
  48846. * @private
  48847. */
  48848. dockOutwardBegin: function (ownerContext, itemContext, item, axis) {
  48849. var pos = axis.begin,
  48850. sizeProp = axis.sizeProp,
  48851. dock, size;
  48852. if (axis.collapsed) {
  48853. axis.ignoreFrameBegin = axis.ignoreFrameEnd = true;
  48854. } else if (item.ignoreParentFrame) {
  48855. dock = item.dock;
  48856. pos -= ownerContext.borderInfo[dock] + ownerContext.paddingInfo[dock] +
  48857. ownerContext.framingInfo[dock];
  48858. axis.ignoreFrameBegin = true;
  48859. }
  48860. if (!item.overlay) {
  48861. size = itemContext.getProp(sizeProp) + itemContext.getMarginInfo()[sizeProp];
  48862. pos -= size;
  48863. axis.begin = pos;
  48864. }
  48865. return pos;
  48866. },
  48867. /**
  48868. * Docks an item on a shrinkWrap axis at the "end". The "end" of the horizontal axis is
  48869. * "right" and the vertical is "bottom".
  48870. * @private
  48871. */
  48872. dockOutwardEnd: function (ownerContext, itemContext, item, axis) {
  48873. var pos = axis.end,
  48874. sizeProp = axis.sizeProp,
  48875. dock, size;
  48876. size = itemContext.getProp(sizeProp) + itemContext.getMarginInfo()[sizeProp];
  48877. if (axis.collapsed) {
  48878. axis.ignoreFrameBegin = axis.ignoreFrameEnd = true;
  48879. } else if (item.ignoreParentFrame) {
  48880. dock = item.dock;
  48881. pos += ownerContext.borderInfo[dock] + ownerContext.paddingInfo[dock] +
  48882. ownerContext.framingInfo[dock];
  48883. axis.ignoreFrameEnd = true;
  48884. }
  48885. if (!item.overlay) {
  48886. axis.end = pos + size;
  48887. axis.dockedPixelsEnd += size;
  48888. }
  48889. return pos;
  48890. },
  48891. /**
  48892. * Docks an item that might stretch across an axis. This is done for dock "top" and
  48893. * "bottom" items on the horizontal axis and dock "left" and "right" on the vertical.
  48894. * @private
  48895. */
  48896. dockStretch: function (ownerContext, itemContext, item, axis) {
  48897. var dock = item.dock, // left/top/right/bottom (also used to index padding/border)
  48898. sizeProp = axis.sizeProp, // 'width' or 'height'
  48899. horizontal = dock == 'top' || dock == 'bottom',
  48900. offsets = itemContext.offsets,
  48901. border = ownerContext.borderInfo,
  48902. padding = ownerContext.paddingInfo,
  48903. endProp = horizontal ? 'right' : 'bottom',
  48904. startProp = horizontal ? 'left' : 'top',
  48905. pos = axis.begin + offsets[startProp],
  48906. margin, size, framing;
  48907. if (item.stretch !== false) {
  48908. size = axis.end - pos - offsets[endProp];
  48909. if (item.ignoreParentFrame) {
  48910. framing = ownerContext.framingInfo;
  48911. pos -= border[startProp] + padding[startProp] + framing[startProp];
  48912. size += border[sizeProp] + padding[sizeProp] + framing[sizeProp];
  48913. }
  48914. margin = itemContext.getMarginInfo();
  48915. size -= margin[sizeProp];
  48916. itemContext[axis.setSize](size);
  48917. }
  48918. return pos;
  48919. },
  48920. /**
  48921. * Finishes the calculation of an axis by determining its size. In non-shrink-wrap
  48922. * cases, this is also where we set the body size.
  48923. * @private
  48924. */
  48925. finishAxis: function (ownerContext, axis) {
  48926. var size = axis.end - axis.begin,
  48927. setSizeMethod = axis.setSize,
  48928. beginName = axis.dockBegin, // left or top
  48929. endName = axis.dockEnd, // right or bottom
  48930. border = ownerContext.borderInfo,
  48931. padding = ownerContext.paddingInfo,
  48932. framing = ownerContext.framingInfo,
  48933. frameSize = padding[beginName] + border[beginName] + framing[beginName],
  48934. bodyContext = ownerContext.bodyContext,
  48935. bodyPos, bodySize, dirty;
  48936. if (axis.shrinkWrap) {
  48937. // Since items docked left/top on a shrinkWrap axis go into negative coordinates,
  48938. // we apply a delta to all coordinates to adjust their relative origin back to
  48939. // (0,0).
  48940. axis.delta = -axis.begin; // either 0 or a positive number
  48941. bodySize = axis.initialSize;
  48942. if (axis.ignoreFrameBegin) {
  48943. axis.delta -= border[beginName];
  48944. bodyPos = -axis.begin - frameSize;
  48945. } else {
  48946. size += frameSize;
  48947. axis.delta += padding[beginName] + framing[beginName];
  48948. bodyPos = -axis.begin;
  48949. }
  48950. if (!axis.ignoreFrameEnd) {
  48951. size += padding[endName] + border[endName] + framing[endName];
  48952. }
  48953. axis.size = size; // we have to wait for min/maxWidth/Height processing
  48954. if (!axis.horizontal && !this.owner.manageHeight) {
  48955. // the height of the bodyEl will give the proper height to the outerEl so
  48956. // we don't need to set heights in the DOM
  48957. dirty = false;
  48958. }
  48959. } else {
  48960. // For a fixed-size axis, we started at the outer box and already have the
  48961. // proper origin... almost... except for the owner's border.
  48962. axis.delta = -border[axis.dockBegin]; // 'left' or 'top'
  48963. // Body size is remaining space between ends of Axis.
  48964. bodySize = size;
  48965. bodyPos = axis.begin - frameSize;
  48966. }
  48967. bodyContext[setSizeMethod](bodySize, dirty);
  48968. bodyContext.setProp(axis.bodyPosProp, bodyPos);
  48969. return !isNaN(size);
  48970. },
  48971. /**
  48972. * Finishes processing of each axis by applying the min/max size constraints.
  48973. * @private
  48974. */
  48975. finishConstraints: function (ownerContext, horz, vert) {
  48976. var sizeModels = this.sizeModels,
  48977. publishWidth = horz.shrinkWrap,
  48978. publishHeight = vert.shrinkWrap,
  48979. dirty, height, width, heightModel, widthModel, size;
  48980. if (publishWidth) {
  48981. size = horz.size;
  48982. if (size < horz.minSize) {
  48983. widthModel = sizeModels.constrainedMin;
  48984. width = horz.minSize;
  48985. } else if (size > horz.maxSize) {
  48986. widthModel = sizeModels.constrainedMax;
  48987. width = horz.maxSize;
  48988. } else {
  48989. width = size;
  48990. }
  48991. }
  48992. if (publishHeight) {
  48993. size = vert.size;
  48994. if (size < vert.minSize) {
  48995. heightModel = sizeModels.constrainedMin;
  48996. height = vert.minSize;
  48997. } else if (size > vert.maxSize) {
  48998. heightModel = sizeModels.constrainedMax;
  48999. height = vert.maxSize;
  49000. } else {
  49001. if (!ownerContext.collapsedVert && !this.owner.manageHeight) {
  49002. // height of the outerEl is provided by the height (including margins)
  49003. // of the bodyEl, so this value does not need to be written to the DOM
  49004. dirty = false;
  49005. // so long as we set top and bottom margins on the bodyEl!
  49006. ownerContext.bodyContext.setProp('margin-bottom', vert.dockedPixelsEnd);
  49007. }
  49008. height = size;
  49009. }
  49010. }
  49011. // Handle the constraints...
  49012. if (widthModel || heightModel) {
  49013. // See ContextItem#init for an analysis of why this case is special. Basically,
  49014. // in this case, we only know the width and the height could be anything.
  49015. if (widthModel && heightModel &&
  49016. widthModel.constrainedMax && heightModel.constrainedMin) {
  49017. ownerContext.invalidate({ widthModel: widthModel });
  49018. return false;
  49019. }
  49020. // To process a width or height other than that to which we have shrinkWrapped,
  49021. // we need to invalidate our component and carry forward w/these constrains...
  49022. // unless the ownerLayout wants these results and will invalidate us anyway.
  49023. if (!ownerContext.widthModel.calculatedFromShrinkWrap &&
  49024. !ownerContext.heightModel.calculatedFromShrinkWrap) {
  49025. // nope, just us to handle the constraint...
  49026. ownerContext.invalidate({ widthModel: widthModel, heightModel: heightModel });
  49027. return false;
  49028. }
  49029. // We have a constraint to deal with, so we just adjust the size models and
  49030. // allow the ownerLayout to invalidate us with its contribution to our final
  49031. // size...
  49032. }
  49033. // we only publish the sizes if we are not invalidating the result...
  49034. if (publishWidth) {
  49035. ownerContext.setWidth(width);
  49036. if (widthModel) {
  49037. ownerContext.widthModel = widthModel; // important to the ownerLayout
  49038. }
  49039. }
  49040. if (publishHeight) {
  49041. ownerContext.setHeight(height, dirty);
  49042. if (heightModel) {
  49043. ownerContext.heightModel = heightModel; // important to the ownerLayout
  49044. }
  49045. }
  49046. return true;
  49047. },
  49048. /**
  49049. * Finishes the calculation by setting positions on the body and all of the items.
  49050. * @private
  49051. */
  49052. finishPositions: function (ownerContext, horz, vert) {
  49053. var dockedItems = ownerContext.dockedItems,
  49054. length = dockedItems.length,
  49055. deltaX = horz.delta,
  49056. deltaY = vert.delta,
  49057. index, itemContext;
  49058. for (index = 0; index < length; ++index) {
  49059. itemContext = dockedItems[index];
  49060. itemContext.setProp('x', deltaX + itemContext.dockedAt.x);
  49061. itemContext.setProp('y', deltaY + itemContext.dockedAt.y);
  49062. }
  49063. },
  49064. finishedLayout: function(ownerContext) {
  49065. var me = this,
  49066. target = ownerContext.target;
  49067. me.callParent(arguments);
  49068. if (!ownerContext.animatePolicy) {
  49069. if (ownerContext.isCollapsingOrExpanding === 1) {
  49070. target.afterCollapse(false);
  49071. } else if (ownerContext.isCollapsingOrExpanding === 2) {
  49072. target.afterExpand(false);
  49073. }
  49074. }
  49075. },
  49076. getAnimatePolicy: function(ownerContext) {
  49077. var me = this,
  49078. lastCollapsedState, policy;
  49079. if (ownerContext.isCollapsingOrExpanding == 1) {
  49080. lastCollapsedState = me.lastCollapsedState;
  49081. } else if (ownerContext.isCollapsingOrExpanding == 2) {
  49082. lastCollapsedState = ownerContext.lastCollapsedState;
  49083. }
  49084. if (lastCollapsedState == 'left' || lastCollapsedState == 'right') {
  49085. policy = me.horizontalCollapsePolicy;
  49086. } else if (lastCollapsedState == 'top' || lastCollapsedState == 'bottom') {
  49087. policy = me.verticalCollapsePolicy;
  49088. }
  49089. return policy;
  49090. },
  49091. /**
  49092. * Retrieve an ordered and/or filtered array of all docked Components.
  49093. * @param {String} [order='render'] The desired ordering of the items ('render' or 'visual').
  49094. * @param {Boolean} [beforeBody] An optional flag to limit the set of items to only those
  49095. * before the body (true) or after the body (false). All components are returned by
  49096. * default.
  49097. * @return {Ext.Component[]} An array of components.
  49098. * @protected
  49099. */
  49100. getDockedItems: function(order, beforeBody) {
  49101. var me = this,
  49102. renderedOnly = (order === 'visual'),
  49103. all = renderedOnly ? Ext.ComponentQuery.query('[rendered]', me.owner.dockedItems.items) : me.owner.dockedItems.items,
  49104. sort = all && all.length && order !== false,
  49105. renderOrder,
  49106. dock, dockedItems, i, isBefore, length;
  49107. if (beforeBody == null) {
  49108. dockedItems = sort && !renderedOnly ? all.slice() : all;
  49109. } else {
  49110. dockedItems = [];
  49111. for (i = 0, length = all.length; i < length; ++i) {
  49112. dock = all[i].dock;
  49113. isBefore = (dock == 'top' || dock == 'left');
  49114. if (beforeBody ? isBefore : !isBefore) {
  49115. dockedItems.push(all[i]);
  49116. }
  49117. }
  49118. sort = sort && dockedItems.length;
  49119. }
  49120. if (sort) {
  49121. renderOrder = (order = order || 'render') == 'render';
  49122. Ext.Array.sort(dockedItems, function(a, b) {
  49123. var aw,
  49124. bw;
  49125. // If the two items are on opposite sides of the body, they must not be sorted by any weight value:
  49126. // For rendering purposes, left/top *always* sorts before right/bottom
  49127. if (renderOrder && ((aw = me.owner.dockOrder[a.dock]) !== (bw = me.owner.dockOrder[b.dock]))) {
  49128. // The two dockOrder values cancel out when two items are on opposite sides.
  49129. if (!(aw + bw)) {
  49130. return aw - bw;
  49131. }
  49132. }
  49133. aw = me.getItemWeight(a, order);
  49134. bw = me.getItemWeight(b, order);
  49135. if ((aw !== undefined) && (bw !== undefined)) {
  49136. return aw - bw;
  49137. }
  49138. return 0;
  49139. });
  49140. }
  49141. return dockedItems || [];
  49142. },
  49143. getItemWeight: function (item, order) {
  49144. var weight = item.weight || this.owner.defaultDockWeights[item.dock];
  49145. return weight[order] || weight;
  49146. },
  49147. /**
  49148. * @protected
  49149. * Returns an array containing all the **visible** docked items inside this layout's owner Panel
  49150. * @return {Array} An array containing all the **visible** docked items of the Panel
  49151. */
  49152. getLayoutItems : function() {
  49153. var me = this,
  49154. items,
  49155. itemCount,
  49156. item,
  49157. i,
  49158. result;
  49159. if (me.owner.collapsed) {
  49160. result = me.owner.getCollapsedDockedItems();
  49161. } else {
  49162. items = me.getDockedItems('visual');
  49163. itemCount = items.length;
  49164. result = [];
  49165. for (i = 0; i < itemCount; i++) {
  49166. item = items[i];
  49167. if (!item.hidden) {
  49168. result.push(item);
  49169. }
  49170. }
  49171. }
  49172. return result;
  49173. },
  49174. // Content size includes padding but not borders, so subtract them off
  49175. measureContentWidth: function (ownerContext) {
  49176. var bodyContext = ownerContext.bodyContext;
  49177. return bodyContext.el.getWidth() - bodyContext.getBorderInfo().width;
  49178. },
  49179. measureContentHeight: function (ownerContext) {
  49180. var bodyContext = ownerContext.bodyContext;
  49181. return bodyContext.el.getHeight() - bodyContext.getBorderInfo().height;
  49182. },
  49183. redoLayout: function(ownerContext) {
  49184. var me = this,
  49185. owner = me.owner;
  49186. // If we are collapsing...
  49187. if (ownerContext.isCollapsingOrExpanding == 1) {
  49188. if (owner.reExpander) {
  49189. owner.reExpander.el.show();
  49190. }
  49191. // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken by the layout.
  49192. owner.addClsWithUI(owner.collapsedCls);
  49193. ownerContext.redo(true);
  49194. } else if (ownerContext.isCollapsingOrExpanding == 2) {
  49195. // Remove the collapsed class now, before layout calculations are done.
  49196. owner.removeClsWithUI(owner.collapsedCls);
  49197. ownerContext.bodyContext.redo();
  49198. }
  49199. },
  49200. // @private override inherited.
  49201. // We need to render in the correct order, top/left before bottom/right
  49202. renderChildren: function() {
  49203. var me = this,
  49204. items = me.getDockedItems(),
  49205. target = me.getRenderTarget();
  49206. me.renderItems(items, target);
  49207. },
  49208. /**
  49209. * @protected
  49210. * Render the top and left docked items before any existing DOM nodes in our render target,
  49211. * and then render the right and bottom docked items after. This is important, for such things
  49212. * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order.
  49213. * Our collection of docked items will already be ordered via Panel.getDockedItems().
  49214. */
  49215. renderItems: function(items, target) {
  49216. var me = this,
  49217. dockedItemCount = items.length,
  49218. itemIndex = 0,
  49219. correctPosition = 0,
  49220. staticNodeCount = 0,
  49221. targetNodes = me.getRenderTarget().dom.childNodes,
  49222. targetChildCount = targetNodes.length,
  49223. i, j, targetChildNode, item;
  49224. // Calculate the number of DOM nodes in our target that are not our docked items
  49225. for (i = 0, j = 0; i < targetChildCount; i++) {
  49226. targetChildNode = targetNodes[i];
  49227. if (Ext.fly(targetChildNode).hasCls('x-resizable-handle')) {
  49228. break;
  49229. }
  49230. for (j = 0; j < dockedItemCount; j++) {
  49231. item = items[j];
  49232. if (item.rendered && item.el.dom === targetChildNode) {
  49233. break;
  49234. }
  49235. }
  49236. // Walked off the end of the docked items without matching the found child node;
  49237. // Then it's a static node.
  49238. if (j === dockedItemCount) {
  49239. staticNodeCount++;
  49240. }
  49241. }
  49242. // Now we go through our docked items and render/move them
  49243. for (; itemIndex < dockedItemCount; itemIndex++, correctPosition++) {
  49244. item = items[itemIndex];
  49245. // If we're now at the first right/bottom docked item, we jump over the body element.
  49246. //
  49247. // TODO: This is affected if users provide custom weight values to their
  49248. // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
  49249. // sort operation here, for now, in the name of performance. getDockedItems()
  49250. // needs the sort operation not just for this layout-time rendering, but
  49251. // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al).
  49252. if (itemIndex === correctPosition && (item.dock === 'right' || item.dock === 'bottom')) {
  49253. correctPosition += staticNodeCount;
  49254. }
  49255. // Same logic as Layout.renderItems()
  49256. if (item && !item.rendered) {
  49257. me.renderItem(item, target, correctPosition);
  49258. }
  49259. else if (!me.isValidParent(item, target, correctPosition)) {
  49260. me.moveItem(item, target, correctPosition);
  49261. }
  49262. }
  49263. },
  49264. undoLayout: function(ownerContext) {
  49265. var me = this,
  49266. owner = me.owner;
  49267. // If we are collapsing...
  49268. if (ownerContext.isCollapsingOrExpanding == 1) {
  49269. // We do not want to see the re-expander header until the final collapse is complete
  49270. if (owner.reExpander) {
  49271. owner.reExpander.el.hide();
  49272. }
  49273. // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken by the layout.
  49274. owner.removeClsWithUI(owner.collapsedCls);
  49275. ownerContext.undo(true);
  49276. } else if (ownerContext.isCollapsingOrExpanding == 2) {
  49277. // Remove the collapsed class now, before layout calculations are done.
  49278. owner.addClsWithUI(owner.collapsedCls);
  49279. ownerContext.bodyContext.undo();
  49280. }
  49281. },
  49282. sizePolicy: {
  49283. nostretch: {
  49284. setsWidth: 0,
  49285. setsHeight: 0
  49286. },
  49287. stretchH: {
  49288. setsWidth: 1,
  49289. setsHeight: 0
  49290. },
  49291. stretchV: {
  49292. setsWidth: 0,
  49293. setsHeight: 1
  49294. },
  49295. // Circular dependency with partial auto-sized panels:
  49296. //
  49297. // If we have an autoHeight docked item being stretched horizontally (top/bottom),
  49298. // that stretching will determine its width and its width must be set before its
  49299. // autoHeight can be determined. If that item is docked in an autoWidth panel, the
  49300. // body will need its height set before it can determine its width, but the height
  49301. // of the docked item is needed to subtract from the panel height in order to set
  49302. // the body height.
  49303. //
  49304. // This same pattern occurs with autoHeight panels with autoWidth docked items on
  49305. // left or right. If the panel is fully auto or fully fixed, these problems don't
  49306. // come up because there is no dependency between the dimensions.
  49307. //
  49308. // Cutting the Gordian Knot: In these cases, we have to allow something to measure
  49309. // itself without full context. This is OK as long as the managed dimension doesn't
  49310. // effect the auto-dimension, which is often the case for things like toolbars. The
  49311. // managed dimension only effects overflow handlers and such and does not change the
  49312. // auto-dimension. To encourage the item to measure itself without waiting for the
  49313. // managed dimension, we have to tell it that the layout will also be reading that
  49314. // dimension. This is similar to how stretchmax works.
  49315. autoStretchH: {
  49316. readsWidth: 1,
  49317. setsWidth: 1,
  49318. setsHeight: 0
  49319. },
  49320. autoStretchV: {
  49321. readsHeight: 1,
  49322. setsWidth: 0,
  49323. setsHeight: 1
  49324. }
  49325. },
  49326. getItemSizePolicy: function (item) {
  49327. var policy = this.sizePolicy,
  49328. dock, vertical;
  49329. if (item.stretch === false) {
  49330. return policy.nostretch;
  49331. }
  49332. dock = item.dock;
  49333. vertical = (dock == 'left' || dock == 'right');
  49334. /*
  49335. owner = this.owner;
  49336. autoWidth = !owner.isFixedWidth();
  49337. autoHeight = !owner.isFixedHeight();
  49338. if (autoWidth !== autoHeight) { // if (partial auto)
  49339. // see above...
  49340. if (vertical) {
  49341. if (autoHeight) {
  49342. return policy.autoStretchV;
  49343. }
  49344. } else if (autoWidth) {
  49345. return policy.autoStretchH;
  49346. }
  49347. }*/
  49348. if (vertical) {
  49349. return policy.stretchV;
  49350. }
  49351. return policy.stretchH;
  49352. },
  49353. /**
  49354. * @protected
  49355. * We are overriding the Ext.layout.Layout configureItem method to also add a class that
  49356. * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
  49357. * An example of a class added to a dock: right item is x-docked-right
  49358. * @param {Ext.Component} item The item we are configuring
  49359. */
  49360. configureItem : function(item, pos) {
  49361. this.callParent(arguments);
  49362. item.addCls(Ext.baseCSSPrefix + 'docked');
  49363. item.addClsWithUI('docked-' + item.dock);
  49364. },
  49365. afterRemove : function(item) {
  49366. this.callParent(arguments);
  49367. if (this.itemCls) {
  49368. item.el.removeCls(this.itemCls + '-' + item.dock);
  49369. }
  49370. var dom = item.el.dom;
  49371. if (!item.destroying && dom) {
  49372. dom.parentNode.removeChild(dom);
  49373. }
  49374. this.childrenChanged = true;
  49375. }
  49376. });
  49377. /**
  49378. * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
  49379. * the right-justified button container.
  49380. *
  49381. * @example
  49382. * Ext.create('Ext.panel.Panel', {
  49383. * title: 'Toolbar Fill Example',
  49384. * width: 300,
  49385. * height: 200,
  49386. * tbar : [
  49387. * 'Item 1',
  49388. * { xtype: 'tbfill' },
  49389. * 'Item 2'
  49390. * ],
  49391. * renderTo: Ext.getBody()
  49392. * });
  49393. */
  49394. Ext.define('Ext.toolbar.Fill', {
  49395. extend: 'Ext.Component',
  49396. alias: 'widget.tbfill',
  49397. alternateClassName: 'Ext.Toolbar.Fill',
  49398. /**
  49399. * @property {Boolean} isFill
  49400. * `true` in this class to identify an object as an instantiated Fill, or subclass thereof.
  49401. */
  49402. isFill : true,
  49403. flex: 1
  49404. });
  49405. /**
  49406. * @private
  49407. * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
  49408. * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
  49409. * for its container.
  49410. */
  49411. Ext.define('Ext.layout.container.boxOverflow.None', {
  49412. alternateClassName: 'Ext.layout.boxOverflow.None',
  49413. constructor: function(layout, config) {
  49414. this.layout = layout;
  49415. Ext.apply(this, config);
  49416. },
  49417. handleOverflow: Ext.emptyFn,
  49418. clearOverflow: Ext.emptyFn,
  49419. beginLayout: Ext.emptyFn,
  49420. beginLayoutCycle: Ext.emptyFn,
  49421. finishedLayout: Ext.emptyFn,
  49422. completeLayout: function (ownerContext) {
  49423. var me = this,
  49424. plan = ownerContext.state.boxPlan,
  49425. overflow;
  49426. if (plan && plan.tooNarrow) {
  49427. overflow = me.handleOverflow(ownerContext);
  49428. if (overflow) {
  49429. if (overflow.reservedSpace) {
  49430. me.layout.publishInnerCtSize(ownerContext, overflow.reservedSpace);
  49431. }
  49432. // TODO: If we need to use the code below then we will need to pass along
  49433. // the new targetSize as state and use it calculate somehow...
  49434. //
  49435. //if (overflow.recalculate) {
  49436. // ownerContext.invalidate({
  49437. // state: {
  49438. // overflow: overflow
  49439. // }
  49440. // });
  49441. //}
  49442. }
  49443. } else {
  49444. me.clearOverflow();
  49445. }
  49446. },
  49447. onRemove: Ext.emptyFn,
  49448. /**
  49449. * @private
  49450. * Normalizes an item reference, string id or numerical index into a reference to the item
  49451. * @param {Ext.Component/String/Number} item The item reference, id or index
  49452. * @return {Ext.Component} The item
  49453. */
  49454. getItem: function(item) {
  49455. return this.layout.owner.getComponent(item);
  49456. },
  49457. getOwnerType: function(owner){
  49458. var type;
  49459. if (owner.isToolbar) {
  49460. type = 'toolbar';
  49461. } else if (owner.isTabBar) {
  49462. type = 'tabbar';
  49463. } else if (owner.isMenu) {
  49464. type = 'menu';
  49465. } else {
  49466. type = owner.getXType();
  49467. }
  49468. return type;
  49469. },
  49470. getPrefixConfig: Ext.emptyFn,
  49471. getSuffixConfig: Ext.emptyFn,
  49472. getOverflowCls: function() {
  49473. return '';
  49474. }
  49475. });
  49476. /**
  49477. * The base class that other non-interacting Toolbar Item classes should extend in order to
  49478. * get some basic common toolbar item functionality.
  49479. */
  49480. Ext.define('Ext.toolbar.Item', {
  49481. extend: 'Ext.Component',
  49482. alias: 'widget.tbitem',
  49483. alternateClassName: 'Ext.Toolbar.Item',
  49484. enable:Ext.emptyFn,
  49485. disable:Ext.emptyFn,
  49486. focus:Ext.emptyFn
  49487. /**
  49488. * @cfg {String} overflowText
  49489. * Text to be used for the menu if the item is overflowed.
  49490. */
  49491. });
  49492. /**
  49493. * A simple class that adds a vertical separator bar between toolbar items (css class: 'x-toolbar-separator').
  49494. *
  49495. * @example
  49496. * Ext.create('Ext.panel.Panel', {
  49497. * title: 'Toolbar Separator Example',
  49498. * width: 300,
  49499. * height: 200,
  49500. * tbar : [
  49501. * 'Item 1',
  49502. * { xtype: 'tbseparator' },
  49503. * 'Item 2'
  49504. * ],
  49505. * renderTo: Ext.getBody()
  49506. * });
  49507. */
  49508. Ext.define('Ext.toolbar.Separator', {
  49509. extend: 'Ext.toolbar.Item',
  49510. alias: 'widget.tbseparator',
  49511. alternateClassName: 'Ext.Toolbar.Separator',
  49512. baseCls: Ext.baseCSSPrefix + 'toolbar-separator',
  49513. focusable: false,
  49514. // Force border: true so container border is not set on this
  49515. border: true
  49516. });
  49517. /**
  49518. * Component layout for buttons
  49519. * @private
  49520. */
  49521. Ext.define('Ext.layout.component.Button', {
  49522. /* Begin Definitions */
  49523. alias: ['layout.button'],
  49524. extend: 'Ext.layout.component.Auto',
  49525. /* End Definitions */
  49526. type: 'button',
  49527. cellClsRE: /-btn-(tl|br)\b/,
  49528. htmlRE: /<.*>/,
  49529. constructor: function () {
  49530. this.callParent(arguments);
  49531. this.hackWidth = Ext.isIE && (!Ext.isStrict || Ext.isIE6 || Ext.isIE7 || Ext.isIE8);
  49532. this.heightIncludesPadding = Ext.isIE6 && Ext.isStrict;
  49533. },
  49534. // TODO - use last run results if text has not changed?
  49535. beginLayout: function (ownerContext) {
  49536. this.callParent(arguments);
  49537. this.cacheTargetInfo(ownerContext);
  49538. },
  49539. beginLayoutCycle: function(ownerContext) {
  49540. var me = this,
  49541. empty = '',
  49542. owner = me.owner,
  49543. btnEl = owner.btnEl,
  49544. btnInnerEl = owner.btnInnerEl,
  49545. text = owner.text,
  49546. htmlAutoHeight;
  49547. me.callParent(arguments);
  49548. btnInnerEl.setStyle('overflow', empty);
  49549. // Clear all element widths
  49550. if (!ownerContext.widthModel.natural) {
  49551. owner.el.setStyle('width', empty);
  49552. }
  49553. // If the text is HTML we need to let the browser automatically size things to cope with the case where the text
  49554. // is multi-line. This incurs a cost as we then have to measure those elements to derive other sizes
  49555. htmlAutoHeight = ownerContext.heightModel.shrinkWrap && text && me.htmlRE.test(text);
  49556. btnEl.setStyle('width', empty);
  49557. btnEl.setStyle('height', htmlAutoHeight ? 'auto' : empty);
  49558. btnInnerEl.setStyle('width', empty);
  49559. btnInnerEl.setStyle('height', htmlAutoHeight ? 'auto' : empty);
  49560. btnInnerEl.setStyle('line-height', htmlAutoHeight ? 'normal' : empty);
  49561. btnInnerEl.setStyle('padding-top', empty);
  49562. owner.btnIconEl.setStyle('width', empty);
  49563. },
  49564. calculateOwnerHeightFromContentHeight: function (ownerContext, contentHeight) {
  49565. return contentHeight;
  49566. },
  49567. calculateOwnerWidthFromContentWidth: function (ownerContext, contentWidth) {
  49568. return contentWidth;
  49569. },
  49570. measureContentWidth: function (ownerContext) {
  49571. var me = this,
  49572. owner = me.owner,
  49573. btnEl = owner.btnEl,
  49574. btnInnerEl = owner.btnInnerEl,
  49575. text = owner.text,
  49576. btnFrameWidth, metrics, sizeIconEl, width, btnElContext, btnInnerElContext;
  49577. // IE suffers from various sizing problems, usually caused by relying on it to size elements automatically. Even
  49578. // if an element is sized correctly it can prove necessary to set that size explicitly on the element to get it
  49579. // to size and position its children correctly. While the exact nature of the problems varies depending on the
  49580. // browser version, doctype and button configuration there is a common solution: set the sizes manually.
  49581. if (owner.text && me.hackWidth && btnEl) {
  49582. btnFrameWidth = me.btnFrameWidth;
  49583. // If the button text is something like '<' or '<<' then we need to escape it or it won't be measured
  49584. // correctly. The button text is supposed to be HTML and strictly speaking '<' and '<<' aren't valid HTML.
  49585. // However in practice they are commonly used and have worked 'correctly' in previous versions.
  49586. if (text.indexOf('>') === -1) {
  49587. text = text.replace(/</g, '&lt;');
  49588. }
  49589. metrics = Ext.util.TextMetrics.measure(btnInnerEl, text);
  49590. width = metrics.width + btnFrameWidth + me.adjWidth;
  49591. btnElContext = ownerContext.getEl('btnEl');
  49592. btnInnerElContext = ownerContext.getEl('btnInnerEl');
  49593. sizeIconEl = (owner.icon || owner.iconCls) &&
  49594. (owner.iconAlign == "top" || owner.iconAlign == "bottom");
  49595. // This cheat works (barely) with publishOwnerWidth which calls setProp also
  49596. // to publish the width. Since it is the same value we set here, the dirty bit
  49597. // we set true will not be cleared by publishOwnerWidth.
  49598. ownerContext.setWidth(width); // not setWidth (no framing)
  49599. btnElContext.setWidth(metrics.width + btnFrameWidth);
  49600. btnInnerElContext.setWidth(metrics.width + btnFrameWidth);
  49601. if (sizeIconEl) {
  49602. owner.btnIconEl.setWidth(metrics.width + btnFrameWidth);
  49603. }
  49604. } else {
  49605. width = ownerContext.el.getWidth();
  49606. }
  49607. return width;
  49608. },
  49609. measureContentHeight: function (ownerContext) {
  49610. var me = this,
  49611. owner = me.owner,
  49612. btnInnerEl = owner.btnInnerEl,
  49613. btnItem = ownerContext.getEl('btnEl'),
  49614. btnInnerItem = ownerContext.getEl('btnInnerEl'),
  49615. minTextHeight = me.minTextHeight,
  49616. adjHeight = me.adjHeight,
  49617. text = owner.getText(),
  49618. height,
  49619. textHeight,
  49620. topPadding;
  49621. if (owner.vertical) {
  49622. height = Ext.util.TextMetrics.measure(btnInnerEl, owner.text).width;
  49623. height += me.btnFrameHeight + adjHeight;
  49624. // Vertical buttons need height explicitly set
  49625. ownerContext.setHeight(height, /*dirty=*/true, /*force=*/true);
  49626. }
  49627. else {
  49628. // If the button text is HTML we have to handle it specially as it could contain multiple lines
  49629. if (text && me.htmlRE.test(text)) {
  49630. textHeight = btnInnerEl.getHeight();
  49631. // HTML content doesn't guarantee multiple lines: in the single line case it could now be too short for the icon
  49632. if (textHeight < minTextHeight) {
  49633. topPadding = Math.floor((minTextHeight - textHeight) / 2);
  49634. // Resize the span and use padding to center the text vertically. The hack to remove the padding
  49635. // from the height on IE6 is especially needed for link buttons
  49636. btnInnerItem.setHeight(minTextHeight - (me.heightIncludesPadding ? topPadding : 0));
  49637. btnInnerItem.setProp('padding-top', topPadding);
  49638. textHeight = minTextHeight;
  49639. }
  49640. // Calculate the height relative to the text span, auto can't be trusted in IE quirks
  49641. height = textHeight + adjHeight;
  49642. }
  49643. else {
  49644. height = ownerContext.el.getHeight();
  49645. }
  49646. }
  49647. // IE quirks needs the button height setting using style or it won't position the icon correctly (even if the height was already correct)
  49648. btnItem.setHeight(height - adjHeight);
  49649. return height;
  49650. },
  49651. publishInnerHeight: function(ownerContext, height) {
  49652. var me = this,
  49653. owner = me.owner,
  49654. isNum = Ext.isNumber,
  49655. btnItem = ownerContext.getEl('btnEl'),
  49656. btnInnerEl = owner.btnInnerEl,
  49657. btnInnerItem = ownerContext.getEl('btnInnerEl'),
  49658. btnHeight = isNum(height) ? height - me.adjHeight : height,
  49659. btnFrameHeight = me.btnFrameHeight,
  49660. text = owner.getText(),
  49661. textHeight,
  49662. paddingTop;
  49663. btnItem.setHeight(btnHeight);
  49664. btnInnerItem.setHeight(btnHeight);
  49665. // Only need the line-height setting for regular, horizontal Buttons
  49666. if (!owner.vertical && btnHeight >= 0) {
  49667. btnInnerItem.setProp('line-height', btnHeight - btnFrameHeight + 'px');
  49668. }
  49669. // Button text may contain markup that would force it to wrap to more than one line (e.g. 'Button<br>Label').
  49670. // When this happens, we cannot use the line-height set above for vertical centering; we instead reset the
  49671. // line-height to normal, measure the rendered text height, and add padding-top to center the text block
  49672. // vertically within the button's height. This is more expensive than the basic line-height approach so
  49673. // we only do it if the text contains markup.
  49674. if (text && me.htmlRE.test(text)) {
  49675. btnInnerItem.setProp('line-height', 'normal');
  49676. btnInnerEl.setStyle('line-height', 'normal');
  49677. textHeight = Ext.util.TextMetrics.measure(btnInnerEl, text).height;
  49678. paddingTop = Math.floor(Math.max(btnHeight - btnFrameHeight - textHeight, 0) / 2);
  49679. btnInnerItem.setProp('padding-top', me.btnFrameTop + paddingTop);
  49680. btnInnerItem.setHeight(btnHeight - (me.heightIncludesPadding ? paddingTop : 0));
  49681. }
  49682. },
  49683. publishInnerWidth: function(ownerContext, width) {
  49684. var me = this,
  49685. isNum = Ext.isNumber,
  49686. btnItem = ownerContext.getEl('btnEl'),
  49687. btnInnerItem = ownerContext.getEl('btnInnerEl'),
  49688. btnWidth = isNum(width) ? width - me.adjWidth : width;
  49689. btnItem.setWidth(btnWidth);
  49690. btnInnerItem.setWidth(btnWidth);
  49691. },
  49692. clearTargetCache: function(){
  49693. delete this.adjWidth;
  49694. },
  49695. cacheTargetInfo: function(ownerContext) {
  49696. var me = this,
  49697. owner = me.owner,
  49698. scale = owner.scale,
  49699. padding, frameSize, btnWrapPadding, btnInnerEl, innerFrameSize;
  49700. // The cache is only valid for a particular scale
  49701. if (!('adjWidth' in me) || me.lastScale !== scale) {
  49702. // If there has been a previous layout run it could have sullied the line-height
  49703. if (me.lastScale) {
  49704. owner.btnInnerEl.setStyle('line-height', '');
  49705. }
  49706. me.lastScale = scale;
  49707. padding = ownerContext.getPaddingInfo();
  49708. frameSize = ownerContext.getFrameInfo();
  49709. btnWrapPadding = ownerContext.getEl('btnWrap').getPaddingInfo();
  49710. btnInnerEl = ownerContext.getEl('btnInnerEl');
  49711. innerFrameSize = btnInnerEl.getPaddingInfo();
  49712. Ext.apply(me, {
  49713. // Width adjustment must take into account the arrow area. The btnWrap is the <em> which has padding to accommodate the arrow.
  49714. adjWidth : btnWrapPadding.width + frameSize.width + padding.width,
  49715. adjHeight : btnWrapPadding.height + frameSize.height + padding.height,
  49716. btnFrameWidth : innerFrameSize.width,
  49717. btnFrameHeight : innerFrameSize.height,
  49718. btnFrameTop : innerFrameSize.top,
  49719. // Use the line-height rather than height because if the text is multi-line then the height will be 'wrong'
  49720. minTextHeight : parseInt(btnInnerEl.getStyle('line-height'), 10)
  49721. });
  49722. }
  49723. me.callParent(arguments);
  49724. },
  49725. finishedLayout: function(){
  49726. var owner = this.owner;
  49727. this.callParent(arguments);
  49728. // Fixes issue EXTJSIV-5989. Looks like a browser repaint bug
  49729. // This hack can be removed once it is resolved.
  49730. if (Ext.isWebKit) {
  49731. owner.el.dom.offsetWidth;
  49732. }
  49733. }
  49734. });
  49735. /**
  49736. * Provides a common registry of all menus on a page.
  49737. * @singleton
  49738. */
  49739. Ext.define('Ext.menu.Manager', {
  49740. singleton: true,
  49741. requires: [
  49742. 'Ext.util.MixedCollection',
  49743. 'Ext.util.KeyMap'
  49744. ],
  49745. alternateClassName: 'Ext.menu.MenuMgr',
  49746. uses: ['Ext.menu.Menu'],
  49747. menus: {},
  49748. groups: {},
  49749. attached: false,
  49750. lastShow: new Date(),
  49751. init: function() {
  49752. var me = this;
  49753. me.active = new Ext.util.MixedCollection();
  49754. Ext.getDoc().addKeyListener(27, function() {
  49755. if (me.active.length > 0) {
  49756. me.hideAll();
  49757. }
  49758. }, me);
  49759. },
  49760. /**
  49761. * Hides all menus that are currently visible
  49762. * @return {Boolean} success True if any active menus were hidden.
  49763. */
  49764. hideAll: function() {
  49765. var active = this.active,
  49766. clone, menus, m, mLen;
  49767. if (active && active.length > 0) {
  49768. clone = active.clone();
  49769. menus = clone.items;
  49770. mLen = menus.length;
  49771. for (m = 0; m < mLen; m++) {
  49772. menus[m].hide();
  49773. }
  49774. return true;
  49775. }
  49776. return false;
  49777. },
  49778. onHide: function(m) {
  49779. var me = this,
  49780. active = me.active;
  49781. active.remove(m);
  49782. if (active.length < 1) {
  49783. Ext.getDoc().un('mousedown', me.onMouseDown, me);
  49784. me.attached = false;
  49785. }
  49786. },
  49787. onShow: function(m) {
  49788. var me = this,
  49789. active = me.active,
  49790. last = active.last(),
  49791. attached = me.attached,
  49792. menuEl = m.getEl(),
  49793. zIndex;
  49794. me.lastShow = new Date();
  49795. active.add(m);
  49796. if (!attached) {
  49797. Ext.getDoc().on('mousedown', me.onMouseDown, me, {
  49798. // On IE we have issues with the menu stealing focus at certain points
  49799. // during the head, so give it a short buffer
  49800. buffer: Ext.isIE ? 10 : undefined
  49801. });
  49802. me.attached = true;
  49803. }
  49804. m.toFront();
  49805. },
  49806. onBeforeHide: function(m) {
  49807. if (m.activeChild) {
  49808. m.activeChild.hide();
  49809. }
  49810. if (m.autoHideTimer) {
  49811. clearTimeout(m.autoHideTimer);
  49812. delete m.autoHideTimer;
  49813. }
  49814. },
  49815. onBeforeShow: function(m) {
  49816. var active = this.active,
  49817. parentMenu = m.parentMenu;
  49818. active.remove(m);
  49819. if (!parentMenu && !m.allowOtherMenus) {
  49820. this.hideAll();
  49821. }
  49822. else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
  49823. parentMenu.activeChild.hide();
  49824. }
  49825. },
  49826. // private
  49827. onMouseDown: function(e) {
  49828. var me = this,
  49829. active = me.active,
  49830. lastShow = me.lastShow;
  49831. if (Ext.Date.getElapsed(lastShow) > 50 && active.length > 0 && !e.getTarget('.' + Ext.baseCSSPrefix + 'menu')) {
  49832. me.hideAll();
  49833. }
  49834. },
  49835. // private
  49836. register: function(menu) {
  49837. var me = this;
  49838. if (!me.active) {
  49839. me.init();
  49840. }
  49841. if (menu.floating) {
  49842. me.menus[menu.id] = menu;
  49843. menu.on({
  49844. beforehide: me.onBeforeHide,
  49845. hide: me.onHide,
  49846. beforeshow: me.onBeforeShow,
  49847. show: me.onShow,
  49848. scope: me
  49849. });
  49850. }
  49851. },
  49852. /**
  49853. * Returns a {@link Ext.menu.Menu} object
  49854. * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
  49855. * be used to generate and return a new Menu this.
  49856. * @return {Ext.menu.Menu} The specified menu, or null if none are found
  49857. */
  49858. get: function(menu) {
  49859. var menus = this.menus;
  49860. if (typeof menu == 'string') { // menu id
  49861. if (!menus) { // not initialized, no menus to return
  49862. return null;
  49863. }
  49864. return menus[menu];
  49865. } else if (menu.isMenu) { // menu instance
  49866. return menu;
  49867. } else if (Ext.isArray(menu)) { // array of menu items
  49868. return new Ext.menu.Menu({items:menu});
  49869. } else { // otherwise, must be a config
  49870. return Ext.ComponentManager.create(menu, 'menu');
  49871. }
  49872. },
  49873. // private
  49874. unregister: function(menu) {
  49875. var me = this,
  49876. menus = me.menus,
  49877. active = me.active;
  49878. delete menus[menu.id];
  49879. active.remove(menu);
  49880. menu.un({
  49881. beforehide: me.onBeforeHide,
  49882. hide: me.onHide,
  49883. beforeshow: me.onBeforeShow,
  49884. show: me.onShow,
  49885. scope: me
  49886. });
  49887. },
  49888. // private
  49889. registerCheckable: function(menuItem) {
  49890. var groups = this.groups,
  49891. groupId = menuItem.group;
  49892. if (groupId) {
  49893. if (!groups[groupId]) {
  49894. groups[groupId] = [];
  49895. }
  49896. groups[groupId].push(menuItem);
  49897. }
  49898. },
  49899. // private
  49900. unregisterCheckable: function(menuItem) {
  49901. var groups = this.groups,
  49902. groupId = menuItem.group;
  49903. if (groupId) {
  49904. Ext.Array.remove(groups[groupId], menuItem);
  49905. }
  49906. },
  49907. onCheckChange: function(menuItem, state) {
  49908. var groups = this.groups,
  49909. groupId = menuItem.group,
  49910. i = 0,
  49911. group, ln, curr;
  49912. if (groupId && state) {
  49913. group = groups[groupId];
  49914. ln = group.length;
  49915. for (; i < ln; i++) {
  49916. curr = group[i];
  49917. if (curr != menuItem) {
  49918. curr.setChecked(false);
  49919. }
  49920. }
  49921. }
  49922. }
  49923. });
  49924. /**
  49925. * A wrapper class which can be applied to any element. Fires a "click" event while the
  49926. * mouse is pressed. The interval between firings may be specified in the config but
  49927. * defaults to 20 milliseconds.
  49928. *
  49929. * Optionally, a CSS class may be applied to the element during the time it is pressed.
  49930. */
  49931. Ext.define('Ext.util.ClickRepeater', {
  49932. extend: 'Ext.util.Observable',
  49933. /**
  49934. * Creates new ClickRepeater.
  49935. * @param {String/HTMLElement/Ext.Element} el The element or its ID to listen on
  49936. * @param {Object} [config] Config object.
  49937. */
  49938. constructor : function(el, config){
  49939. var me = this;
  49940. me.el = Ext.get(el);
  49941. me.el.unselectable();
  49942. Ext.apply(me, config);
  49943. me.callParent();
  49944. me.addEvents(
  49945. /**
  49946. * @event mousedown
  49947. * Fires when the mouse button is depressed.
  49948. * @param {Ext.util.ClickRepeater} this
  49949. * @param {Ext.EventObject} e
  49950. */
  49951. "mousedown",
  49952. /**
  49953. * @event click
  49954. * Fires on a specified interval during the time the element is pressed.
  49955. * @param {Ext.util.ClickRepeater} this
  49956. * @param {Ext.EventObject} e
  49957. */
  49958. "click",
  49959. /**
  49960. * @event mouseup
  49961. * Fires when the mouse key is released.
  49962. * @param {Ext.util.ClickRepeater} this
  49963. * @param {Ext.EventObject} e
  49964. */
  49965. "mouseup"
  49966. );
  49967. if(!me.disabled){
  49968. me.disabled = true;
  49969. me.enable();
  49970. }
  49971. // allow inline handler
  49972. if(me.handler){
  49973. me.on("click", me.handler, me.scope || me);
  49974. }
  49975. },
  49976. /**
  49977. * @cfg {String/HTMLElement/Ext.Element} el
  49978. * The element to act as a button.
  49979. */
  49980. /**
  49981. * @cfg {String} pressedCls
  49982. * A CSS class name to be applied to the element while pressed.
  49983. */
  49984. /**
  49985. * @cfg {Boolean} accelerate
  49986. * True if autorepeating should start slowly and accelerate.
  49987. * "interval" and "delay" are ignored.
  49988. */
  49989. /**
  49990. * @cfg {Number} interval
  49991. * The interval between firings of the "click" event (in milliseconds).
  49992. */
  49993. interval : 20,
  49994. /**
  49995. * @cfg {Number} delay
  49996. * The initial delay before the repeating event begins firing.
  49997. * Similar to an autorepeat key delay.
  49998. */
  49999. delay: 250,
  50000. /**
  50001. * @cfg {Boolean} preventDefault
  50002. * True to prevent the default click event
  50003. */
  50004. preventDefault : true,
  50005. /**
  50006. * @cfg {Boolean} stopDefault
  50007. * True to stop the default click event
  50008. */
  50009. stopDefault : false,
  50010. timer : 0,
  50011. /**
  50012. * Enables the repeater and allows events to fire.
  50013. */
  50014. enable: function(){
  50015. if(this.disabled){
  50016. this.el.on('mousedown', this.handleMouseDown, this);
  50017. // IE versions will detect clicks as in sequence as dblclicks
  50018. // if they happen in quick succession
  50019. if (Ext.isIE && !(Ext.isStrict && Ext.isIE9)){
  50020. this.el.on('dblclick', this.handleDblClick, this);
  50021. }
  50022. if(this.preventDefault || this.stopDefault){
  50023. this.el.on('click', this.eventOptions, this);
  50024. }
  50025. }
  50026. this.disabled = false;
  50027. },
  50028. /**
  50029. * Disables the repeater and stops events from firing.
  50030. */
  50031. disable: function(/* private */ force){
  50032. if(force || !this.disabled){
  50033. clearTimeout(this.timer);
  50034. if(this.pressedCls){
  50035. this.el.removeCls(this.pressedCls);
  50036. }
  50037. Ext.getDoc().un('mouseup', this.handleMouseUp, this);
  50038. this.el.removeAllListeners();
  50039. }
  50040. this.disabled = true;
  50041. },
  50042. /**
  50043. * Convenience function for setting disabled/enabled by boolean.
  50044. * @param {Boolean} disabled
  50045. */
  50046. setDisabled: function(disabled){
  50047. this[disabled ? 'disable' : 'enable']();
  50048. },
  50049. eventOptions: function(e){
  50050. if(this.preventDefault){
  50051. e.preventDefault();
  50052. }
  50053. if(this.stopDefault){
  50054. e.stopEvent();
  50055. }
  50056. },
  50057. // private
  50058. destroy : function() {
  50059. this.disable(true);
  50060. Ext.destroy(this.el);
  50061. this.clearListeners();
  50062. },
  50063. handleDblClick : function(e){
  50064. clearTimeout(this.timer);
  50065. this.el.blur();
  50066. this.fireEvent("mousedown", this, e);
  50067. this.fireEvent("click", this, e);
  50068. },
  50069. // private
  50070. handleMouseDown : function(e){
  50071. clearTimeout(this.timer);
  50072. this.el.blur();
  50073. if(this.pressedCls){
  50074. this.el.addCls(this.pressedCls);
  50075. }
  50076. this.mousedownTime = new Date();
  50077. Ext.getDoc().on("mouseup", this.handleMouseUp, this);
  50078. this.el.on("mouseout", this.handleMouseOut, this);
  50079. this.fireEvent("mousedown", this, e);
  50080. this.fireEvent("click", this, e);
  50081. // Do not honor delay or interval if acceleration wanted.
  50082. if (this.accelerate) {
  50083. this.delay = 400;
  50084. }
  50085. // Re-wrap the event object in a non-shared object, so it doesn't lose its context if
  50086. // the global shared EventObject gets a new Event put into it before the timer fires.
  50087. e = new Ext.EventObjectImpl(e);
  50088. this.timer = Ext.defer(this.click, this.delay || this.interval, this, [e]);
  50089. },
  50090. // private
  50091. click : function(e){
  50092. this.fireEvent("click", this, e);
  50093. this.timer = Ext.defer(this.click, this.accelerate ?
  50094. this.easeOutExpo(Ext.Date.getElapsed(this.mousedownTime),
  50095. 400,
  50096. -390,
  50097. 12000) :
  50098. this.interval, this, [e]);
  50099. },
  50100. easeOutExpo : function (t, b, c, d) {
  50101. return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
  50102. },
  50103. // private
  50104. handleMouseOut : function(){
  50105. clearTimeout(this.timer);
  50106. if(this.pressedCls){
  50107. this.el.removeCls(this.pressedCls);
  50108. }
  50109. this.el.on("mouseover", this.handleMouseReturn, this);
  50110. },
  50111. // private
  50112. handleMouseReturn : function(){
  50113. this.el.un("mouseover", this.handleMouseReturn, this);
  50114. if(this.pressedCls){
  50115. this.el.addCls(this.pressedCls);
  50116. }
  50117. this.click();
  50118. },
  50119. // private
  50120. handleMouseUp : function(e){
  50121. clearTimeout(this.timer);
  50122. this.el.un("mouseover", this.handleMouseReturn, this);
  50123. this.el.un("mouseout", this.handleMouseOut, this);
  50124. Ext.getDoc().un("mouseup", this.handleMouseUp, this);
  50125. if(this.pressedCls){
  50126. this.el.removeCls(this.pressedCls);
  50127. }
  50128. this.fireEvent("mouseup", this, e);
  50129. }
  50130. });
  50131. /**
  50132. * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
  50133. * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
  50134. * should not contain any HTML, otherwise it may not be measured correctly.
  50135. *
  50136. * The measurement works by copying the relevant CSS styles that can affect the font related display,
  50137. * then checking the size of an element that is auto-sized. Note that if the text is multi-lined, you must
  50138. * provide a **fixed width** when doing the measurement.
  50139. *
  50140. * If multiple measurements are being done on the same element, you create a new instance to initialize
  50141. * to avoid the overhead of copying the styles to the element repeatedly.
  50142. */
  50143. Ext.define('Ext.util.TextMetrics', {
  50144. statics: {
  50145. shared: null,
  50146. /**
  50147. * Measures the size of the specified text
  50148. * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
  50149. * that can affect the size of the rendered text
  50150. * @param {String} text The text to measure
  50151. * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
  50152. * in order to accurately measure the text height
  50153. * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
  50154. * @static
  50155. */
  50156. measure: function(el, text, fixedWidth){
  50157. var me = this,
  50158. shared = me.shared;
  50159. if(!shared){
  50160. shared = me.shared = new me(el, fixedWidth);
  50161. }
  50162. shared.bind(el);
  50163. shared.setFixedWidth(fixedWidth || 'auto');
  50164. return shared.getSize(text);
  50165. },
  50166. /**
  50167. * Destroy the TextMetrics instance created by {@link #measure}.
  50168. * @static
  50169. */
  50170. destroy: function(){
  50171. var me = this;
  50172. Ext.destroy(me.shared);
  50173. me.shared = null;
  50174. }
  50175. },
  50176. /**
  50177. * Creates new TextMetrics.
  50178. * @param {String/HTMLElement/Ext.Element} bindTo The element or its ID to bind to.
  50179. * @param {Number} [fixedWidth] A fixed width to apply to the measuring element.
  50180. */
  50181. constructor: function(bindTo, fixedWidth){
  50182. var measure = this.measure = Ext.getBody().createChild({
  50183. cls: Ext.baseCSSPrefix + 'textmetrics'
  50184. });
  50185. this.el = Ext.get(bindTo);
  50186. measure.position('absolute');
  50187. measure.setLeftTop(-1000, -1000);
  50188. measure.hide();
  50189. if (fixedWidth) {
  50190. measure.setWidth(fixedWidth);
  50191. }
  50192. },
  50193. /**
  50194. * Returns the size of the specified text based on the internal element's style and width properties
  50195. * @param {String} text The text to measure
  50196. * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
  50197. */
  50198. getSize: function(text){
  50199. var measure = this.measure,
  50200. size;
  50201. measure.update(text);
  50202. size = measure.getSize();
  50203. measure.update('');
  50204. return size;
  50205. },
  50206. /**
  50207. * Binds this TextMetrics instance to a new element
  50208. * @param {String/HTMLElement/Ext.Element} el The element or its ID.
  50209. */
  50210. bind: function(el){
  50211. var me = this;
  50212. me.el = Ext.get(el);
  50213. me.measure.setStyle(
  50214. me.el.getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
  50215. );
  50216. },
  50217. /**
  50218. * Sets a fixed width on the internal measurement element. If the text will be multiline, you have
  50219. * to set a fixed width in order to accurately measure the text height.
  50220. * @param {Number} width The width to set on the element
  50221. */
  50222. setFixedWidth : function(width){
  50223. this.measure.setWidth(width);
  50224. },
  50225. /**
  50226. * Returns the measured width of the specified text
  50227. * @param {String} text The text to measure
  50228. * @return {Number} width The width in pixels
  50229. */
  50230. getWidth : function(text){
  50231. this.measure.dom.style.width = 'auto';
  50232. return this.getSize(text).width;
  50233. },
  50234. /**
  50235. * Returns the measured height of the specified text
  50236. * @param {String} text The text to measure
  50237. * @return {Number} height The height in pixels
  50238. */
  50239. getHeight : function(text){
  50240. return this.getSize(text).height;
  50241. },
  50242. /**
  50243. * Destroy this instance
  50244. */
  50245. destroy: function(){
  50246. var me = this;
  50247. me.measure.remove();
  50248. delete me.el;
  50249. delete me.measure;
  50250. }
  50251. }, function(){
  50252. Ext.Element.addMethods({
  50253. /**
  50254. * Returns the width in pixels of the passed text, or the width of the text in this Element.
  50255. * @param {String} text The text to measure. Defaults to the innerHTML of the element.
  50256. * @param {Number} [min] The minumum value to return.
  50257. * @param {Number} [max] The maximum value to return.
  50258. * @return {Number} The text width in pixels.
  50259. * @member Ext.dom.Element
  50260. */
  50261. getTextWidth : function(text, min, max){
  50262. return Ext.Number.constrain(Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width, min || 0, max || 1000000);
  50263. }
  50264. });
  50265. });
  50266. /**
  50267. * @docauthor Robert Dougan <rob@sencha.com>
  50268. *
  50269. * Create simple buttons with this component. Customisations include {@link #iconAlign aligned}
  50270. * {@link #iconCls icons}, {@link #cfg-menu dropdown menus}, {@link #tooltip tooltips}
  50271. * and {@link #scale sizing options}. Specify a {@link #handler handler} to run code when
  50272. * a user clicks the button, or use {@link #listeners listeners} for other events such as
  50273. * {@link #mouseover mouseover}. Example usage:
  50274. *
  50275. * @example
  50276. * Ext.create('Ext.Button', {
  50277. * text: 'Click me',
  50278. * renderTo: Ext.getBody(),
  50279. * handler: function() {
  50280. * alert('You clicked the button!');
  50281. * }
  50282. * });
  50283. *
  50284. * The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler}
  50285. * method. Example usage:
  50286. *
  50287. * @example
  50288. * Ext.create('Ext.Button', {
  50289. * text : 'Dynamic Handler Button',
  50290. * renderTo: Ext.getBody(),
  50291. * handler : function() {
  50292. * // this button will spit out a different number every time you click it.
  50293. * // so firstly we must check if that number is already set:
  50294. * if (this.clickCount) {
  50295. * // looks like the property is already set, so lets just add 1 to that number and alert the user
  50296. * this.clickCount++;
  50297. * alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..');
  50298. * } else {
  50299. * // if the clickCount property is not set, we will set it and alert the user
  50300. * this.clickCount = 1;
  50301. * alert('You just clicked the button for the first time!\n\nTry pressing it again..');
  50302. * }
  50303. * }
  50304. * });
  50305. *
  50306. * A button within a container:
  50307. *
  50308. * @example
  50309. * Ext.create('Ext.Container', {
  50310. * renderTo: Ext.getBody(),
  50311. * items : [
  50312. * {
  50313. * xtype: 'button',
  50314. * text : 'My Button'
  50315. * }
  50316. * ]
  50317. * });
  50318. *
  50319. * A useful option of Button is the {@link #scale} configuration. This configuration has three different options:
  50320. *
  50321. * - `'small'`
  50322. * - `'medium'`
  50323. * - `'large'`
  50324. *
  50325. * Example usage:
  50326. *
  50327. * @example
  50328. * Ext.create('Ext.Button', {
  50329. * renderTo: document.body,
  50330. * text : 'Click me',
  50331. * scale : 'large'
  50332. * });
  50333. *
  50334. * Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`.
  50335. * Example usage:
  50336. *
  50337. * @example
  50338. * Ext.create('Ext.Button', {
  50339. * renderTo: Ext.getBody(),
  50340. * text: 'Click Me',
  50341. * enableToggle: true
  50342. * });
  50343. *
  50344. * You can assign a menu to a button by using the {@link #cfg-menu} configuration. This standard configuration
  50345. * can either be a reference to a {@link Ext.menu.Menu menu} object, a {@link Ext.menu.Menu menu} id or a
  50346. * {@link Ext.menu.Menu menu} config blob. When assigning a menu to a button, an arrow is automatically
  50347. * added to the button. You can change the alignment of the arrow using the {@link #arrowAlign} configuration
  50348. * on button. Example usage:
  50349. *
  50350. * @example
  50351. * Ext.create('Ext.Button', {
  50352. * text : 'Menu button',
  50353. * renderTo : Ext.getBody(),
  50354. * arrowAlign: 'bottom',
  50355. * menu : [
  50356. * {text: 'Item 1'},
  50357. * {text: 'Item 2'},
  50358. * {text: 'Item 3'},
  50359. * {text: 'Item 4'}
  50360. * ]
  50361. * });
  50362. *
  50363. * Using listeners, you can easily listen to events fired by any component, using the {@link #listeners}
  50364. * configuration or using the {@link #addListener} method. Button has a variety of different listeners:
  50365. *
  50366. * - `click`
  50367. * - `toggle`
  50368. * - `mouseover`
  50369. * - `mouseout`
  50370. * - `mouseshow`
  50371. * - `menuhide`
  50372. * - `menutriggerover`
  50373. * - `menutriggerout`
  50374. *
  50375. * Example usage:
  50376. *
  50377. * @example
  50378. * Ext.create('Ext.Button', {
  50379. * text : 'Button',
  50380. * renderTo : Ext.getBody(),
  50381. * listeners: {
  50382. * click: function() {
  50383. * // this == the button, as we are in the local scope
  50384. * this.setText('I was clicked!');
  50385. * },
  50386. * mouseover: function() {
  50387. * // set a new config which says we moused over, if not already set
  50388. * if (!this.mousedOver) {
  50389. * this.mousedOver = true;
  50390. * alert('You moused over a button!\n\nI wont do this again.');
  50391. * }
  50392. * }
  50393. * }
  50394. * });
  50395. */
  50396. Ext.define('Ext.button.Button', {
  50397. /* Begin Definitions */
  50398. alias: 'widget.button',
  50399. extend: 'Ext.Component',
  50400. requires: [
  50401. 'Ext.menu.Manager',
  50402. 'Ext.util.ClickRepeater',
  50403. 'Ext.layout.component.Button',
  50404. 'Ext.util.TextMetrics',
  50405. 'Ext.util.KeyMap'
  50406. ],
  50407. alternateClassName: 'Ext.Button',
  50408. /* End Definitions */
  50409. /*
  50410. * @property {Boolean} isAction
  50411. * `true` in this class to identify an object as an instantiated Button, or subclass thereof.
  50412. */
  50413. isButton: true,
  50414. componentLayout: 'button',
  50415. /**
  50416. * @property {Boolean} hidden
  50417. * True if this button is hidden.
  50418. * @readonly
  50419. */
  50420. hidden: false,
  50421. /**
  50422. * @property {Boolean} disabled
  50423. * True if this button is disabled.
  50424. * @readonly
  50425. */
  50426. disabled: false,
  50427. /**
  50428. * @property {Boolean} pressed
  50429. * True if this button is pressed (only if enableToggle = true).
  50430. * @readonly
  50431. */
  50432. pressed: false,
  50433. /**
  50434. * @cfg {String} text
  50435. * The button text to be used as innerHTML (html tags are accepted).
  50436. */
  50437. /**
  50438. * @cfg {String} icon
  50439. * The path to an image to display in the button.
  50440. */
  50441. /**
  50442. * @cfg {Function} handler
  50443. * A function called when the button is clicked (can be used instead of click event).
  50444. * @cfg {Ext.button.Button} handler.button This button.
  50445. * @cfg {Ext.EventObject} handler.e The click event.
  50446. */
  50447. /**
  50448. * @cfg {Number} minWidth
  50449. * The minimum width for this button (used to give a set of buttons a common width).
  50450. * See also {@link Ext.panel.Panel}.{@link Ext.panel.Panel#minButtonWidth minButtonWidth}.
  50451. */
  50452. /**
  50453. * @cfg {String/Object} tooltip
  50454. * The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or
  50455. * QuickTips config object.
  50456. */
  50457. /**
  50458. * @cfg {Boolean} [hidden=false]
  50459. * True to start hidden.
  50460. */
  50461. /**
  50462. * @cfg {Boolean} [disabled=false]
  50463. * True to start disabled.
  50464. */
  50465. /**
  50466. * @cfg {Boolean} [pressed=false]
  50467. * True to start pressed (only if enableToggle = true)
  50468. */
  50469. /**
  50470. * @cfg {String} toggleGroup
  50471. * The group this toggle button is a member of (only 1 per group can be pressed). If a toggleGroup
  50472. * is specified, the {@link #enableToggle} configuration will automatically be set to true.
  50473. */
  50474. /**
  50475. * @cfg {Boolean/Object} [repeat=false]
  50476. * True to repeat fire the click event while the mouse is down. This can also be a
  50477. * {@link Ext.util.ClickRepeater ClickRepeater} config object.
  50478. */
  50479. /**
  50480. * @cfg {Number} tabIndex
  50481. * Set a DOM tabIndex for this button.
  50482. */
  50483. /**
  50484. * @cfg {Boolean} [allowDepress=true]
  50485. * False to not allow a pressed Button to be depressed. Only valid when {@link #enableToggle} is true.
  50486. */
  50487. /**
  50488. * @cfg {Boolean} [enableToggle=false]
  50489. * True to enable pressed/not pressed toggling. If a {@link #toggleGroup} is specified, this
  50490. * option will be set to true.
  50491. */
  50492. enableToggle: false,
  50493. /**
  50494. * @cfg {Function} toggleHandler
  50495. * Function called when a Button with {@link #enableToggle} set to true is clicked.
  50496. * @cfg {Ext.button.Button} toggleHandler.button This button.
  50497. * @cfg {Boolean} toggleHandler.state The next state of the Button, true means pressed.
  50498. */
  50499. /**
  50500. * @cfg {Ext.menu.Menu/String/Object} menu
  50501. * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob.
  50502. */
  50503. /**
  50504. * @cfg {String} menuAlign
  50505. * The position to align the menu to (see {@link Ext.Element#alignTo} for more details).
  50506. */
  50507. menuAlign: 'tl-bl?',
  50508. /**
  50509. * @cfg {String} textAlign
  50510. * The text alignment for this button (center, left, right).
  50511. */
  50512. textAlign: 'center',
  50513. /**
  50514. * @cfg {String} overflowText
  50515. * If used in a {@link Ext.toolbar.Toolbar Toolbar}, the text to be used if this item is shown in the overflow menu.
  50516. * See also {@link Ext.toolbar.Item}.`{@link Ext.toolbar.Item#overflowText overflowText}`.
  50517. */
  50518. /**
  50519. * @cfg {String} iconCls
  50520. * A css class which sets a background image to be used as the icon for this button.
  50521. */
  50522. /**
  50523. * @cfg {String} type
  50524. * The type of `<input>` to create: submit, reset or button.
  50525. */
  50526. type: 'button',
  50527. /**
  50528. * @cfg {String} clickEvent
  50529. * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu).
  50530. */
  50531. clickEvent: 'click',
  50532. /**
  50533. * @cfg {Boolean} preventDefault
  50534. * True to prevent the default action when the {@link #clickEvent} is processed.
  50535. */
  50536. preventDefault: true,
  50537. /**
  50538. * @cfg {Boolean} handleMouseEvents
  50539. * False to disable visual cues on mouseover, mouseout and mousedown.
  50540. */
  50541. handleMouseEvents: true,
  50542. /**
  50543. * @cfg {String} tooltipType
  50544. * The type of tooltip to use. Either 'qtip' for QuickTips or 'title' for title attribute.
  50545. */
  50546. tooltipType: 'qtip',
  50547. /**
  50548. * @cfg {String} [baseCls='x-btn']
  50549. * The base CSS class to add to all buttons.
  50550. */
  50551. baseCls: Ext.baseCSSPrefix + 'btn',
  50552. /**
  50553. * @cfg {String} pressedCls
  50554. * The CSS class to add to a button when it is in the pressed state.
  50555. */
  50556. pressedCls: 'pressed',
  50557. /**
  50558. * @cfg {String} overCls
  50559. * The CSS class to add to a button when it is in the over (hovered) state.
  50560. */
  50561. overCls: 'over',
  50562. /**
  50563. * @cfg {String} focusCls
  50564. * The CSS class to add to a button when it is in the focussed state.
  50565. */
  50566. focusCls: 'focus',
  50567. /**
  50568. * @cfg {String} menuActiveCls
  50569. * The CSS class to add to a button when it's menu is active.
  50570. */
  50571. menuActiveCls: 'menu-active',
  50572. /**
  50573. * @cfg {String} href
  50574. * The URL to open when the button is clicked. Specifying this config causes the Button to be
  50575. * rendered with an anchor (An `<a>` element) as its active element, referencing the specified URL.
  50576. *
  50577. * This is better than specifying a click handler of
  50578. *
  50579. * function() { window.location = "http://www.sencha.com" }
  50580. *
  50581. * because the UI will provide meaningful hints to the user as to what to expect upon clicking
  50582. * the button, and will also allow the user to open in a new tab or window, bookmark or drag the URL, or directly save
  50583. * the URL stream to disk.
  50584. *
  50585. * See also the {@link #hrefTarget} config.
  50586. */
  50587. /**
  50588. * @cfg {String} [hrefTarget="_blank"]
  50589. * The target attribute to use for the underlying anchor. Only used if the {@link #href}
  50590. * property is specified.
  50591. */
  50592. hrefTarget: '_blank',
  50593. border: true,
  50594. /**
  50595. * @cfg {Object} baseParams
  50596. * An object literal of parameters to pass to the url when the {@link #href} property is specified.
  50597. */
  50598. /**
  50599. * @cfg {Object} params
  50600. * An object literal of parameters to pass to the url when the {@link #href} property is specified. Any params
  50601. * override {@link #baseParams}. New params can be set using the {@link #setParams} method.
  50602. */
  50603. childEls: [
  50604. 'btnEl', 'btnWrap', 'btnInnerEl', 'btnIconEl'
  50605. ],
  50606. renderTpl: [
  50607. '<em id="{id}-btnWrap"<tpl if="splitCls"> class="{splitCls}"</tpl>>',
  50608. '<tpl if="href">',
  50609. '<a id="{id}-btnEl" href="{href}" class="{btnCls}" target="{hrefTarget}"',
  50610. '<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl>',
  50611. '<tpl if="disabled"> disabled="disabled"</tpl>',
  50612. ' role="link">',
  50613. '<span id="{id}-btnInnerEl" class="{baseCls}-inner">',
  50614. '{text}',
  50615. '</span>',
  50616. '<span id="{id}-btnIconEl" class="{baseCls}-icon {iconCls}"<tpl if="iconUrl"> style="background-image:url({iconUrl})"</tpl>></span>',
  50617. '</a>',
  50618. '<tpl else>',
  50619. '<button id="{id}-btnEl" type="{type}" class="{btnCls}" hidefocus="true"',
  50620. // the autocomplete="off" is required to prevent Firefox from remembering
  50621. // the button's disabled state between page reloads.
  50622. '<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl>',
  50623. '<tpl if="disabled"> disabled="disabled"</tpl>',
  50624. ' role="button" autocomplete="off">',
  50625. '<span id="{id}-btnInnerEl" class="{baseCls}-inner" style="{innerSpanStyle}">',
  50626. '{text}',
  50627. '</span>',
  50628. '<span id="{id}-btnIconEl" class="{baseCls}-icon {iconCls}"<tpl if="iconUrl"> style="background-image:url({iconUrl})"</tpl>></span>',
  50629. '</button>',
  50630. '</tpl>',
  50631. '</em>',
  50632. '<tpl if="closable">',
  50633. '<a id="{id}-closeEl" href="#" class="{baseCls}-close-btn" title="{closeText}"></a>',
  50634. '</tpl>'
  50635. ],
  50636. /**
  50637. * @cfg {String} scale
  50638. * The size of the Button. Three values are allowed:
  50639. *
  50640. * - 'small' - Results in the button element being 16px high.
  50641. * - 'medium' - Results in the button element being 24px high.
  50642. * - 'large' - Results in the button element being 32px high.
  50643. */
  50644. scale: 'small',
  50645. /**
  50646. * @private
  50647. * An array of allowed scales.
  50648. */
  50649. allowedScales: ['small', 'medium', 'large'],
  50650. /**
  50651. * @cfg {Object} scope
  50652. * The scope (**this** reference) in which the `{@link #handler}` and `{@link #toggleHandler}` is executed.
  50653. * Defaults to this Button.
  50654. */
  50655. /**
  50656. * @cfg {String} iconAlign
  50657. * The side of the Button box to render the icon. Four values are allowed:
  50658. *
  50659. * - 'top'
  50660. * - 'right'
  50661. * - 'bottom'
  50662. * - 'left'
  50663. */
  50664. iconAlign: 'left',
  50665. /**
  50666. * @cfg {String} arrowAlign
  50667. * The side of the Button box to render the arrow if the button has an associated {@link #cfg-menu}. Two
  50668. * values are allowed:
  50669. *
  50670. * - 'right'
  50671. * - 'bottom'
  50672. */
  50673. arrowAlign: 'right',
  50674. /**
  50675. * @cfg {String} arrowCls
  50676. * The className used for the inner arrow element if the button has a menu.
  50677. */
  50678. arrowCls: 'arrow',
  50679. /**
  50680. * @property {Ext.Template} template
  50681. * A {@link Ext.Template Template} used to create the Button's DOM structure.
  50682. *
  50683. * Instances, or subclasses which need a different DOM structure may provide a different template layout in
  50684. * conjunction with an implementation of {@link #getTemplateArgs}.
  50685. */
  50686. /**
  50687. * @cfg {String} cls
  50688. * A CSS class string to apply to the button's main element.
  50689. */
  50690. /**
  50691. * @property {Ext.menu.Menu} menu
  50692. * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #cfg-menu} config
  50693. * option.
  50694. */
  50695. maskOnDisable: false,
  50696. /**
  50697. * @private
  50698. * @property persistentPadding
  50699. * The padding spuriously added to a &lt;button> element which must be accounted for in the margins of the innerEl.
  50700. * This is calculated at first render time by creating a hidden button and measuring its insides.
  50701. */
  50702. persistentPadding: undefined,
  50703. shrinkWrap: 3,
  50704. frame: true,
  50705. // inherit docs
  50706. initComponent: function() {
  50707. var me = this;
  50708. me.callParent(arguments);
  50709. me.addEvents(
  50710. /**
  50711. * @event click
  50712. * Fires when this button is clicked, before the configured {@link #handler} is invoked. Execution of the
  50713. * {@link #handler} may be vetoed by returning <code>false</code> to this event.
  50714. * @param {Ext.button.Button} this
  50715. * @param {Event} e The click event
  50716. */
  50717. 'click',
  50718. /**
  50719. * @event toggle
  50720. * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
  50721. * @param {Ext.button.Button} this
  50722. * @param {Boolean} pressed
  50723. */
  50724. 'toggle',
  50725. /**
  50726. * @event mouseover
  50727. * Fires when the mouse hovers over the button
  50728. * @param {Ext.button.Button} this
  50729. * @param {Event} e The event object
  50730. */
  50731. 'mouseover',
  50732. /**
  50733. * @event mouseout
  50734. * Fires when the mouse exits the button
  50735. * @param {Ext.button.Button} this
  50736. * @param {Event} e The event object
  50737. */
  50738. 'mouseout',
  50739. /**
  50740. * @event menushow
  50741. * If this button has a menu, this event fires when it is shown
  50742. * @param {Ext.button.Button} this
  50743. * @param {Ext.menu.Menu} menu
  50744. */
  50745. 'menushow',
  50746. /**
  50747. * @event menuhide
  50748. * If this button has a menu, this event fires when it is hidden
  50749. * @param {Ext.button.Button} this
  50750. * @param {Ext.menu.Menu} menu
  50751. */
  50752. 'menuhide',
  50753. /**
  50754. * @event menutriggerover
  50755. * If this button has a menu, this event fires when the mouse enters the menu triggering element
  50756. * @param {Ext.button.Button} this
  50757. * @param {Ext.menu.Menu} menu
  50758. * @param {Event} e
  50759. */
  50760. 'menutriggerover',
  50761. /**
  50762. * @event menutriggerout
  50763. * If this button has a menu, this event fires when the mouse leaves the menu triggering element
  50764. * @param {Ext.button.Button} this
  50765. * @param {Ext.menu.Menu} menu
  50766. * @param {Event} e
  50767. */
  50768. 'menutriggerout'
  50769. );
  50770. if (me.menu) {
  50771. // Flag that we'll have a splitCls
  50772. me.split = true;
  50773. // retrieve menu by id or instantiate instance if needed
  50774. me.menu = Ext.menu.Manager.get(me.menu);
  50775. me.menu.ownerButton = me;
  50776. }
  50777. // Accept url as a synonym for href
  50778. if (me.url) {
  50779. me.href = me.url;
  50780. }
  50781. // preventDefault defaults to false for links
  50782. if (me.href && !me.hasOwnProperty('preventDefault')) {
  50783. me.preventDefault = false;
  50784. }
  50785. if (Ext.isString(me.toggleGroup) && me.toggleGroup !== '') {
  50786. me.enableToggle = true;
  50787. }
  50788. if (me.html && !me.text) {
  50789. me.text = me.html;
  50790. delete me.html;
  50791. }
  50792. },
  50793. // inherit docs
  50794. getActionEl: function() {
  50795. return this.btnEl;
  50796. },
  50797. // inherit docs
  50798. getFocusEl: function() {
  50799. return this.useElForFocus ? this.el : this.btnEl;
  50800. },
  50801. // Buttons add the focus class to the *outermost element*, not the focusEl!
  50802. onFocus: function(e) {
  50803. var me = this;
  50804. // Set this flag, so that when AbstractComponent's onFocus gets the focusEl to add the focusCls
  50805. // to, it will get the encapsulating element - that's what the CSS rules for Button need right now
  50806. me.useElForFocus = true;
  50807. me.callParent(arguments);
  50808. me.useElForFocus = false;
  50809. },
  50810. // See comments in onFocus
  50811. onBlur : function(e) {
  50812. this.useElForFocus = true;
  50813. this.callParent(arguments);
  50814. this.useElForFocus = false;
  50815. },
  50816. // See comments in onFocus
  50817. onDisable: function(){
  50818. this.useElForFocus = true;
  50819. this.callParent(arguments);
  50820. this.useElForFocus = false;
  50821. },
  50822. // private
  50823. setComponentCls: function() {
  50824. var me = this,
  50825. cls = me.getComponentCls();
  50826. if (!Ext.isEmpty(me.oldCls)) {
  50827. me.removeClsWithUI(me.oldCls);
  50828. me.removeClsWithUI(me.pressedCls);
  50829. }
  50830. me.oldCls = cls;
  50831. me.addClsWithUI(cls);
  50832. },
  50833. getComponentCls: function() {
  50834. var me = this,
  50835. cls = [];
  50836. // Check whether the button has an icon or not, and if it has an icon, what is the alignment
  50837. if (me.iconCls || me.icon) {
  50838. if (me.text) {
  50839. cls.push('icon-text-' + me.iconAlign);
  50840. } else {
  50841. cls.push('icon');
  50842. }
  50843. } else if (me.text) {
  50844. cls.push('noicon');
  50845. }
  50846. if (me.pressed) {
  50847. cls.push(me.pressedCls);
  50848. }
  50849. return cls;
  50850. },
  50851. beforeRender: function () {
  50852. var me = this;
  50853. me.callParent();
  50854. // Add all needed classes to the protoElement.
  50855. me.oldCls = me.getComponentCls();
  50856. me.addClsWithUI(me.oldCls);
  50857. // Apply the renderData to the template args
  50858. Ext.applyIf(me.renderData, me.getTemplateArgs());
  50859. if (me.scale) {
  50860. me.setScale(me.scale);
  50861. }
  50862. },
  50863. // private
  50864. onRender: function() {
  50865. var me = this,
  50866. addOnclick,
  50867. btn,
  50868. btnListeners;
  50869. me.doc = Ext.getDoc();
  50870. me.callParent(arguments);
  50871. // If it is a split button + has a toolip for the arrow
  50872. if (me.split && me.arrowTooltip) {
  50873. me.arrowEl.dom.setAttribute(me.getTipAttr(), me.arrowTooltip);
  50874. }
  50875. // Set btn as a local variable for easy access
  50876. btn = me.el;
  50877. if (me.tooltip) {
  50878. me.setTooltip(me.tooltip, true);
  50879. }
  50880. // Add the mouse events to the button
  50881. if (me.handleMouseEvents) {
  50882. btnListeners = {
  50883. scope: me,
  50884. mouseover: me.onMouseOver,
  50885. mouseout: me.onMouseOut,
  50886. mousedown: me.onMouseDown
  50887. };
  50888. if (me.split) {
  50889. btnListeners.mousemove = me.onMouseMove;
  50890. }
  50891. } else {
  50892. btnListeners = {
  50893. scope: me
  50894. };
  50895. }
  50896. // Check if the button has a menu
  50897. if (me.menu) {
  50898. me.mon(me.menu, {
  50899. scope: me,
  50900. show: me.onMenuShow,
  50901. hide: me.onMenuHide
  50902. });
  50903. me.keyMap = new Ext.util.KeyMap({
  50904. target: me.el,
  50905. key: Ext.EventObject.DOWN,
  50906. handler: me.onDownKey,
  50907. scope: me
  50908. });
  50909. }
  50910. // Check if it is a repeat button
  50911. if (me.repeat) {
  50912. me.mon(new Ext.util.ClickRepeater(btn, Ext.isObject(me.repeat) ? me.repeat: {}), 'click', me.onRepeatClick, me);
  50913. } else {
  50914. // If the activation event already has a handler, make a note to add the handler later
  50915. if (btnListeners[me.clickEvent]) {
  50916. addOnclick = true;
  50917. } else {
  50918. btnListeners[me.clickEvent] = me.onClick;
  50919. }
  50920. }
  50921. // Add whatever button listeners we need
  50922. me.mon(btn, btnListeners);
  50923. // If the listeners object had an entry for our clickEvent, add a listener now
  50924. if (addOnclick) {
  50925. me.mon(btn, me.clickEvent, me.onClick, me);
  50926. }
  50927. // Register the button in the toggle manager
  50928. Ext.ButtonToggleManager.register(me);
  50929. },
  50930. /**
  50931. * This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used to
  50932. * create this Button's DOM structure.
  50933. *
  50934. * Instances or subclasses which use a different Template to create a different DOM structure may need to provide
  50935. * their own implementation of this method.
  50936. *
  50937. * @return {Object} Substitution data for a Template. The default implementation which provides data for the default
  50938. * {@link #template} returns an Object containing the following properties:
  50939. * @return {String} return.type The `<button>`'s {@link #type}
  50940. * @return {String} return.splitCls A CSS class to determine the presence and position of an arrow icon.
  50941. * (`'x-btn-arrow'` or `'x-btn-arrow-bottom'` or `''`)
  50942. * @return {String} return.cls A CSS class name applied to the Button's main `<tbody>` element which determines the
  50943. * button's scale and icon alignment.
  50944. * @return {String} return.text The {@link #text} to display ion the Button.
  50945. * @return {Number} return.tabIndex The tab index within the input flow.
  50946. */
  50947. getTemplateArgs: function() {
  50948. var me = this,
  50949. persistentPadding = me.getPersistentPadding(),
  50950. innerSpanStyle = '';
  50951. // Create negative margin offsets to counteract persistent button padding if needed
  50952. if (Math.max.apply(Math, persistentPadding) > 0) {
  50953. innerSpanStyle = 'margin:' + Ext.Array.map(persistentPadding, function(pad) {
  50954. return -pad + 'px';
  50955. }).join(' ');
  50956. }
  50957. return {
  50958. href : me.getHref(),
  50959. disabled : me.disabled,
  50960. hrefTarget: me.hrefTarget,
  50961. type : me.type,
  50962. btnCls : me.getBtnCls(),
  50963. splitCls : me.getSplitCls(),
  50964. iconUrl : me.icon,
  50965. iconCls : me.iconCls,
  50966. text : me.text || '&#160;',
  50967. tabIndex : me.tabIndex,
  50968. innerSpanStyle: innerSpanStyle
  50969. };
  50970. },
  50971. /**
  50972. * @private
  50973. * If there is a configured href for this Button, returns the href with parameters appended.
  50974. * @returns The href string with parameters appended.
  50975. */
  50976. getHref: function() {
  50977. var me = this,
  50978. params = Ext.apply({}, me.baseParams);
  50979. // write baseParams first, then write any params
  50980. params = Ext.apply(params, me.params);
  50981. return me.href ? Ext.urlAppend(me.href, Ext.Object.toQueryString(params)) : false;
  50982. },
  50983. /**
  50984. * Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured.
  50985. *
  50986. * **Only valid if the Button was originally configured with a {@link #href}**
  50987. *
  50988. * @param {Object} params Parameters to use in the href URL.
  50989. */
  50990. setParams: function(params) {
  50991. this.params = params;
  50992. this.btnEl.dom.href = this.getHref();
  50993. },
  50994. getSplitCls: function() {
  50995. var me = this;
  50996. return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : '';
  50997. },
  50998. getBtnCls: function() {
  50999. return this.textAlign ? this.baseCls + '-' + this.textAlign : '';
  51000. },
  51001. /**
  51002. * Sets the CSS class that provides a background image to use as the button's icon. This method also changes the
  51003. * value of the {@link #iconCls} config internally.
  51004. * @param {String} cls The CSS class providing the icon image
  51005. * @return {Ext.button.Button} this
  51006. */
  51007. setIconCls: function(cls) {
  51008. var me = this,
  51009. btnIconEl = me.btnIconEl,
  51010. oldCls = me.iconCls;
  51011. me.iconCls = cls;
  51012. if (btnIconEl) {
  51013. // Remove the previous iconCls from the button
  51014. btnIconEl.removeCls(oldCls);
  51015. btnIconEl.addCls(cls || '');
  51016. me.setComponentCls();
  51017. if (me.didIconStateChange(oldCls, cls)) {
  51018. me.updateLayout();
  51019. }
  51020. }
  51021. return me;
  51022. },
  51023. /**
  51024. * Sets the tooltip for this Button.
  51025. *
  51026. * @param {String/Object} tooltip This may be:
  51027. *
  51028. * - **String** : A string to be used as innerHTML (html tags are accepted) to show in a tooltip
  51029. * - **Object** : A configuration object for {@link Ext.tip.QuickTipManager#register}.
  51030. *
  51031. * @return {Ext.button.Button} this
  51032. */
  51033. setTooltip: function(tooltip, initial) {
  51034. var me = this;
  51035. if (me.rendered) {
  51036. if (!initial) {
  51037. me.clearTip();
  51038. }
  51039. if (Ext.isObject(tooltip)) {
  51040. Ext.tip.QuickTipManager.register(Ext.apply({
  51041. target: me.btnEl.id
  51042. },
  51043. tooltip));
  51044. me.tooltip = tooltip;
  51045. } else {
  51046. me.btnEl.dom.setAttribute(me.getTipAttr(), tooltip);
  51047. }
  51048. } else {
  51049. me.tooltip = tooltip;
  51050. }
  51051. return me;
  51052. },
  51053. /**
  51054. * Sets the text alignment for this button.
  51055. * @param {String} align The new alignment of the button text. See {@link #textAlign}.
  51056. */
  51057. setTextAlign: function(align) {
  51058. var me = this,
  51059. btnEl = me.btnEl;
  51060. if (btnEl) {
  51061. btnEl.removeCls(me.baseCls + '-' + me.textAlign);
  51062. btnEl.addCls(me.baseCls + '-' + align);
  51063. }
  51064. me.textAlign = align;
  51065. return me;
  51066. },
  51067. getTipAttr: function(){
  51068. return this.tooltipType == 'qtip' ? 'data-qtip' : 'title';
  51069. },
  51070. // private
  51071. getRefItems: function(deep){
  51072. var menu = this.menu,
  51073. items;
  51074. if (menu) {
  51075. items = menu.getRefItems(deep);
  51076. items.unshift(menu);
  51077. }
  51078. return items || [];
  51079. },
  51080. // private
  51081. clearTip: function() {
  51082. if (Ext.isObject(this.tooltip)) {
  51083. Ext.tip.QuickTipManager.unregister(this.btnEl);
  51084. }
  51085. },
  51086. // private
  51087. beforeDestroy: function() {
  51088. var me = this;
  51089. if (me.rendered) {
  51090. me.clearTip();
  51091. }
  51092. if (me.menu && me.destroyMenu !== false) {
  51093. Ext.destroy(me.menu);
  51094. }
  51095. Ext.destroy(me.btnInnerEl, me.repeater);
  51096. me.callParent();
  51097. },
  51098. // private
  51099. onDestroy: function() {
  51100. var me = this;
  51101. if (me.rendered) {
  51102. me.doc.un('mouseover', me.monitorMouseOver, me);
  51103. me.doc.un('mouseup', me.onMouseUp, me);
  51104. delete me.doc;
  51105. Ext.ButtonToggleManager.unregister(me);
  51106. Ext.destroy(me.keyMap);
  51107. delete me.keyMap;
  51108. }
  51109. me.callParent();
  51110. },
  51111. /**
  51112. * Assigns this Button's click handler
  51113. * @param {Function} handler The function to call when the button is clicked
  51114. * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed.
  51115. * Defaults to this Button.
  51116. * @return {Ext.button.Button} this
  51117. */
  51118. setHandler: function(handler, scope) {
  51119. this.handler = handler;
  51120. this.scope = scope;
  51121. return this;
  51122. },
  51123. /**
  51124. * Sets this Button's text
  51125. * @param {String} text The button text
  51126. * @return {Ext.button.Button} this
  51127. */
  51128. setText: function(text) {
  51129. var me = this;
  51130. me.text = text;
  51131. if (me.rendered) {
  51132. me.btnInnerEl.update(text || '&#160;');
  51133. me.setComponentCls();
  51134. if (Ext.isStrict && Ext.isIE8) {
  51135. // weird repaint issue causes it to not resize
  51136. me.el.repaint();
  51137. }
  51138. me.updateLayout();
  51139. }
  51140. return me;
  51141. },
  51142. /**
  51143. * Sets the background image (inline style) of the button. This method also changes the value of the {@link #icon}
  51144. * config internally.
  51145. * @param {String} icon The path to an image to display in the button
  51146. * @return {Ext.button.Button} this
  51147. */
  51148. setIcon: function(icon) {
  51149. var me = this,
  51150. btnIconEl = me.btnIconEl,
  51151. oldIcon = me.icon;
  51152. me.icon = icon;
  51153. if (btnIconEl) {
  51154. btnIconEl.setStyle('background-image', icon ? 'url(' + icon + ')': '');
  51155. me.setComponentCls();
  51156. if (me.didIconStateChange(oldIcon, icon)) {
  51157. me.updateLayout();
  51158. }
  51159. }
  51160. return me;
  51161. },
  51162. /**
  51163. * Checks if the icon/iconCls changed from being empty to having a value, or having a value to being empty.
  51164. * @private
  51165. * @param {String} old The old icon/iconCls
  51166. * @param {String} current The current icon/iconCls
  51167. * @return {Boolean} True if the icon state changed
  51168. */
  51169. didIconStateChange: function(old, current) {
  51170. var currentEmpty = Ext.isEmpty(current);
  51171. return Ext.isEmpty(old) ? !currentEmpty : currentEmpty;
  51172. },
  51173. /**
  51174. * Gets the text for this Button
  51175. * @return {String} The button text
  51176. */
  51177. getText: function() {
  51178. return this.text;
  51179. },
  51180. /**
  51181. * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
  51182. * @param {Boolean} [state] Force a particular state
  51183. * @param {Boolean} [suppressEvent=false] True to stop events being fired when calling this method.
  51184. * @return {Ext.button.Button} this
  51185. */
  51186. toggle: function(state, suppressEvent) {
  51187. var me = this;
  51188. state = state === undefined ? !me.pressed: !!state;
  51189. if (state !== me.pressed) {
  51190. if (me.rendered) {
  51191. me[state ? 'addClsWithUI': 'removeClsWithUI'](me.pressedCls);
  51192. }
  51193. me.pressed = state;
  51194. if (!suppressEvent) {
  51195. me.fireEvent('toggle', me, state);
  51196. Ext.callback(me.toggleHandler, me.scope || me, [me, state]);
  51197. }
  51198. }
  51199. return me;
  51200. },
  51201. maybeShowMenu: function(){
  51202. var me = this;
  51203. if (me.menu && !me.hasVisibleMenu() && !me.ignoreNextClick) {
  51204. me.showMenu();
  51205. }
  51206. },
  51207. /**
  51208. * Shows this button's menu (if it has one)
  51209. */
  51210. showMenu: function() {
  51211. var me = this;
  51212. if (me.rendered && me.menu) {
  51213. if (me.tooltip && me.getTipAttr() != 'title') {
  51214. Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.btnEl);
  51215. }
  51216. if (me.menu.isVisible()) {
  51217. me.menu.hide();
  51218. }
  51219. me.menu.showBy(me.el, me.menuAlign, ((!Ext.isStrict && Ext.isIE) || Ext.isIE6) ? [-2, -2] : undefined);
  51220. }
  51221. return me;
  51222. },
  51223. /**
  51224. * Hides this button's menu (if it has one)
  51225. */
  51226. hideMenu: function() {
  51227. if (this.hasVisibleMenu()) {
  51228. this.menu.hide();
  51229. }
  51230. return this;
  51231. },
  51232. /**
  51233. * Returns true if the button has a menu and it is visible
  51234. * @return {Boolean}
  51235. */
  51236. hasVisibleMenu: function() {
  51237. var menu = this.menu;
  51238. return menu && menu.rendered && menu.isVisible();
  51239. },
  51240. // private
  51241. onRepeatClick: function(repeat, e) {
  51242. this.onClick(e);
  51243. },
  51244. // private
  51245. onClick: function(e) {
  51246. var me = this;
  51247. if (me.preventDefault || (me.disabled && me.getHref()) && e) {
  51248. e.preventDefault();
  51249. }
  51250. if (e.button !== 0) {
  51251. return;
  51252. }
  51253. if (!me.disabled) {
  51254. me.doToggle();
  51255. me.maybeShowMenu();
  51256. me.fireHandler(e);
  51257. }
  51258. },
  51259. fireHandler: function(e){
  51260. var me = this,
  51261. handler = me.handler;
  51262. if (me.fireEvent('click', me, e) !== false) {
  51263. if (handler) {
  51264. handler.call(me.scope || me, me, e);
  51265. }
  51266. me.blur();
  51267. }
  51268. },
  51269. doToggle: function(){
  51270. var me = this;
  51271. if (me.enableToggle && (me.allowDepress !== false || !me.pressed)) {
  51272. me.toggle();
  51273. }
  51274. },
  51275. /**
  51276. * @private mouseover handler called when a mouseover event occurs anywhere within the encapsulating element.
  51277. * The targets are interrogated to see what is being entered from where.
  51278. * @param e
  51279. */
  51280. onMouseOver: function(e) {
  51281. var me = this;
  51282. if (!me.disabled && !e.within(me.el, true, true)) {
  51283. me.onMouseEnter(e);
  51284. }
  51285. },
  51286. /**
  51287. * @private
  51288. * mouseout handler called when a mouseout event occurs anywhere within the encapsulating element -
  51289. * or the mouse leaves the encapsulating element.
  51290. * The targets are interrogated to see what is being exited to where.
  51291. * @param e
  51292. */
  51293. onMouseOut: function(e) {
  51294. var me = this;
  51295. if (!e.within(me.el, true, true)) {
  51296. if (me.overMenuTrigger) {
  51297. me.onMenuTriggerOut(e);
  51298. }
  51299. me.onMouseLeave(e);
  51300. }
  51301. },
  51302. /**
  51303. * @private
  51304. * mousemove handler called when the mouse moves anywhere within the encapsulating element.
  51305. * The position is checked to determine if the mouse is entering or leaving the trigger area. Using
  51306. * mousemove to check this is more resource intensive than we'd like, but it is necessary because
  51307. * the trigger area does not line up exactly with sub-elements so we don't always get mouseover/out
  51308. * events when needed. In the future we should consider making the trigger a separate element that
  51309. * is absolutely positioned and sized over the trigger area.
  51310. */
  51311. onMouseMove: function(e) {
  51312. var me = this,
  51313. el = me.el,
  51314. over = me.overMenuTrigger,
  51315. overlap, btnSize;
  51316. if (me.split) {
  51317. if (me.arrowAlign === 'right') {
  51318. overlap = e.getX() - el.getX();
  51319. btnSize = el.getWidth();
  51320. } else {
  51321. overlap = e.getY() - el.getY();
  51322. btnSize = el.getHeight();
  51323. }
  51324. if (overlap > (btnSize - me.getTriggerSize())) {
  51325. if (!over) {
  51326. me.onMenuTriggerOver(e);
  51327. }
  51328. } else {
  51329. if (over) {
  51330. me.onMenuTriggerOut(e);
  51331. }
  51332. }
  51333. }
  51334. },
  51335. /**
  51336. * @private
  51337. * Measures the size of the trigger area for menu and split buttons. Will be a width for
  51338. * a right-aligned trigger and a height for a bottom-aligned trigger. Cached after first measurement.
  51339. */
  51340. getTriggerSize: function() {
  51341. var me = this,
  51342. size = me.triggerSize,
  51343. side, sideFirstLetter, undef;
  51344. if (size === undef) {
  51345. side = me.arrowAlign;
  51346. sideFirstLetter = side.charAt(0);
  51347. size = me.triggerSize = me.el.getFrameWidth(sideFirstLetter) + me.btnWrap.getFrameWidth(sideFirstLetter) + me.frameSize[side];
  51348. }
  51349. return size;
  51350. },
  51351. /**
  51352. * @private
  51353. * virtual mouseenter handler called when it is detected that the mouseout event
  51354. * signified the mouse entering the encapsulating element.
  51355. * @param e
  51356. */
  51357. onMouseEnter: function(e) {
  51358. var me = this;
  51359. me.addClsWithUI(me.overCls);
  51360. me.fireEvent('mouseover', me, e);
  51361. },
  51362. /**
  51363. * @private
  51364. * virtual mouseleave handler called when it is detected that the mouseover event
  51365. * signified the mouse entering the encapsulating element.
  51366. * @param e
  51367. */
  51368. onMouseLeave: function(e) {
  51369. var me = this;
  51370. me.removeClsWithUI(me.overCls);
  51371. me.fireEvent('mouseout', me, e);
  51372. },
  51373. /**
  51374. * @private
  51375. * virtual mouseenter handler called when it is detected that the mouseover event
  51376. * signified the mouse entering the arrow area of the button - the `<em>`.
  51377. * @param e
  51378. */
  51379. onMenuTriggerOver: function(e) {
  51380. var me = this;
  51381. me.overMenuTrigger = true;
  51382. me.fireEvent('menutriggerover', me, me.menu, e);
  51383. },
  51384. /**
  51385. * @private
  51386. * virtual mouseleave handler called when it is detected that the mouseout event
  51387. * signified the mouse leaving the arrow area of the button - the `<em>`.
  51388. * @param e
  51389. */
  51390. onMenuTriggerOut: function(e) {
  51391. var me = this;
  51392. delete me.overMenuTrigger;
  51393. me.fireEvent('menutriggerout', me, me.menu, e);
  51394. },
  51395. // inherit docs
  51396. enable : function(silent) {
  51397. var me = this;
  51398. me.callParent(arguments);
  51399. if (me.btnEl) {
  51400. me.btnEl.dom.disabled = false;
  51401. }
  51402. me.removeClsWithUI('disabled');
  51403. return me;
  51404. },
  51405. // inherit docs
  51406. disable : function(silent) {
  51407. var me = this;
  51408. me.callParent(arguments);
  51409. if (me.btnEl) {
  51410. me.btnEl.dom.disabled = true;
  51411. }
  51412. me.addClsWithUI('disabled');
  51413. me.removeClsWithUI(me.overCls);
  51414. // IE renders disabled text by layering gray text on top of white text, offset by 1 pixel. Normally this is fine
  51415. // but in some circumstances (such as using formBind) it gets confused and renders them side by side instead.
  51416. if (me.btnInnerEl && (Ext.isIE6 || Ext.isIE7)) {
  51417. me.btnInnerEl.repaint();
  51418. }
  51419. return me;
  51420. },
  51421. /**
  51422. * Method to change the scale of the button. See {@link #scale} for allowed configurations.
  51423. * @param {String} scale The scale to change to.
  51424. */
  51425. setScale: function(scale) {
  51426. var me = this,
  51427. ui = me.ui.replace('-' + me.scale, '');
  51428. //check if it is an allowed scale
  51429. if (!Ext.Array.contains(me.allowedScales, scale)) {
  51430. throw('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')');
  51431. }
  51432. me.scale = scale;
  51433. me.setUI(ui);
  51434. },
  51435. // inherit docs
  51436. setUI: function(ui) {
  51437. var me = this;
  51438. //we need to append the scale to the UI, if not already done
  51439. if (me.scale && !ui.match(me.scale)) {
  51440. ui = ui + '-' + me.scale;
  51441. }
  51442. me.callParent([ui]);
  51443. // Set all the state classNames, as they need to include the UI
  51444. // me.disabledCls += ' ' + me.baseCls + '-' + me.ui + '-disabled';
  51445. },
  51446. // private
  51447. onMouseDown: function(e) {
  51448. var me = this;
  51449. if (!me.disabled && e.button === 0) {
  51450. me.addClsWithUI(me.pressedCls);
  51451. me.doc.on('mouseup', me.onMouseUp, me);
  51452. }
  51453. },
  51454. // private
  51455. onMouseUp: function(e) {
  51456. var me = this;
  51457. if (e.button === 0) {
  51458. if (!me.pressed) {
  51459. me.removeClsWithUI(me.pressedCls);
  51460. }
  51461. me.doc.un('mouseup', me.onMouseUp, me);
  51462. }
  51463. },
  51464. // private
  51465. onMenuShow: function(e) {
  51466. var me = this;
  51467. me.ignoreNextClick = 0;
  51468. me.addClsWithUI(me.menuActiveCls);
  51469. me.fireEvent('menushow', me, me.menu);
  51470. },
  51471. // private
  51472. onMenuHide: function(e) {
  51473. var me = this;
  51474. me.removeClsWithUI(me.menuActiveCls);
  51475. me.ignoreNextClick = Ext.defer(me.restoreClick, 250, me);
  51476. me.fireEvent('menuhide', me, me.menu);
  51477. },
  51478. // private
  51479. restoreClick: function() {
  51480. this.ignoreNextClick = 0;
  51481. },
  51482. // private
  51483. onDownKey: function() {
  51484. var me = this;
  51485. if (!me.disabled) {
  51486. if (me.menu) {
  51487. me.showMenu();
  51488. }
  51489. }
  51490. },
  51491. /**
  51492. * @private
  51493. * Some browsers (notably Safari and older Chromes on Windows) add extra "padding" inside the button
  51494. * element that cannot be removed. This method returns the size of that padding with a one-time detection.
  51495. * @return {Number[]} [top, right, bottom, left]
  51496. */
  51497. getPersistentPadding: function() {
  51498. var me = this,
  51499. reset = Ext.scopeResetCSS,
  51500. padding = me.persistentPadding,
  51501. btn, leftTop, btnEl, btnInnerEl, wrap;
  51502. // Create auto-size button offscreen and measure its insides
  51503. // Short-circuit IE as it sometimes gives false positive for padding
  51504. if (!padding) {
  51505. padding = me.self.prototype.persistentPadding = [0, 0, 0, 0];
  51506. if (!Ext.isIE) {
  51507. btn = new Ext.button.Button({
  51508. text: 'test',
  51509. style: 'position:absolute;top:-999px;'
  51510. });
  51511. btn.el = Ext.DomHelper.append(Ext.resetElement, btn.getRenderTree(), true);
  51512. btn.applyChildEls(btn.el);
  51513. btnEl = btn.btnEl;
  51514. btnInnerEl = btn.btnInnerEl;
  51515. btnEl.setSize(null, null); //clear any hard dimensions on the button el to see what it does naturally
  51516. leftTop = btnInnerEl.getOffsetsTo(btnEl);
  51517. padding[0] = leftTop[1];
  51518. padding[1] = btnEl.getWidth() - btnInnerEl.getWidth() - leftTop[0];
  51519. padding[2] = btnEl.getHeight() - btnInnerEl.getHeight() - leftTop[1];
  51520. padding[3] = leftTop[0];
  51521. btn.destroy();
  51522. btn.el.remove();
  51523. }
  51524. }
  51525. return padding;
  51526. }
  51527. }, function() {
  51528. var groups = {},
  51529. toggleGroup = function(btn, state) {
  51530. if (state) {
  51531. var g = groups[btn.toggleGroup],
  51532. length = g.length,
  51533. i;
  51534. for (i = 0; i < length; i++) {
  51535. if (g[i] !== btn) {
  51536. g[i].toggle(false);
  51537. }
  51538. }
  51539. }
  51540. };
  51541. // Private utility class used by Button
  51542. Ext.ButtonToggleManager = {
  51543. register: function(btn) {
  51544. if (!btn.toggleGroup) {
  51545. return;
  51546. }
  51547. var group = groups[btn.toggleGroup];
  51548. if (!group) {
  51549. group = groups[btn.toggleGroup] = [];
  51550. }
  51551. group.push(btn);
  51552. btn.on('toggle', toggleGroup);
  51553. },
  51554. unregister: function(btn) {
  51555. if (!btn.toggleGroup) {
  51556. return;
  51557. }
  51558. var group = groups[btn.toggleGroup];
  51559. if (group) {
  51560. Ext.Array.remove(group, btn);
  51561. btn.un('toggle', toggleGroup);
  51562. }
  51563. },
  51564. // Gets the pressed button in the passed group or null
  51565. // @param {String} group
  51566. // @return {Ext.button.Button}
  51567. getPressed: function(group) {
  51568. var g = groups[group],
  51569. i = 0,
  51570. len;
  51571. if (g) {
  51572. for (len = g.length; i < len; i++) {
  51573. if (g[i].pressed === true) {
  51574. return g[i];
  51575. }
  51576. }
  51577. }
  51578. return null;
  51579. }
  51580. };
  51581. });
  51582. /**
  51583. * @private
  51584. */
  51585. Ext.define('Ext.layout.container.boxOverflow.Menu', {
  51586. /* Begin Definitions */
  51587. extend: 'Ext.layout.container.boxOverflow.None',
  51588. requires: ['Ext.toolbar.Separator', 'Ext.button.Button'],
  51589. alternateClassName: 'Ext.layout.boxOverflow.Menu',
  51590. /* End Definitions */
  51591. /**
  51592. * @cfg {String} triggerButtonCls
  51593. * CSS class added to the Button which shows the overflow menu.
  51594. */
  51595. /**
  51596. * @property {String} noItemsMenuText
  51597. * HTML fragment to render into the toolbar overflow menu if there are no items to display
  51598. */
  51599. noItemsMenuText : '<div class="' + Ext.baseCSSPrefix + 'toolbar-no-items">(None)</div>',
  51600. constructor: function(layout) {
  51601. var me = this;
  51602. me.callParent(arguments);
  51603. me.triggerButtonCls = me.triggerButtonCls || Ext.baseCSSPrefix + 'box-menu-' + layout.getNames().right;
  51604. /**
  51605. * @property {Array} menuItems
  51606. * Array of all items that are currently hidden and should go into the dropdown menu
  51607. */
  51608. me.menuItems = [];
  51609. },
  51610. beginLayout: function (ownerContext) {
  51611. this.callParent(arguments);
  51612. // Before layout, we need to re-show all items which we may have hidden due to a
  51613. // previous overflow...
  51614. this.clearOverflow(ownerContext);
  51615. },
  51616. beginLayoutCycle: function (ownerContext, firstCycle) {
  51617. this.callParent(arguments);
  51618. if (!firstCycle) {
  51619. // if we are being re-run, we need to clear any overflow from the last run and
  51620. // recache the childItems collection
  51621. this.clearOverflow(ownerContext);
  51622. this.layout.cacheChildItems(ownerContext);
  51623. }
  51624. },
  51625. onRemove: function(comp){
  51626. Ext.Array.remove(this.menuItems, comp);
  51627. },
  51628. // We don't define a prefix in menu overflow.
  51629. getSuffixConfig: function() {
  51630. var me = this,
  51631. layout = me.layout,
  51632. oid = layout.owner.id;
  51633. /**
  51634. * @private
  51635. * @property {Ext.menu.Menu} menu
  51636. * The expand menu - holds items for every item that cannot be shown
  51637. * because the container is currently not large enough.
  51638. */
  51639. me.menu = new Ext.menu.Menu({
  51640. listeners: {
  51641. scope: me,
  51642. beforeshow: me.beforeMenuShow
  51643. }
  51644. });
  51645. /**
  51646. * @private
  51647. * @property {Ext.button.Button} menuTrigger
  51648. * The expand button which triggers the overflow menu to be shown
  51649. */
  51650. me.menuTrigger = new Ext.button.Button({
  51651. id : oid + '-menu-trigger',
  51652. cls : Ext.layout.container.Box.prototype.innerCls + ' ' + me.triggerButtonCls,
  51653. hidden : true,
  51654. ownerCt : layout.owner, // To enable the Menu to ascertain a valid zIndexManager owner in the same tree
  51655. ownerLayout: layout,
  51656. iconCls : Ext.baseCSSPrefix + me.getOwnerType(layout.owner) + '-more-icon',
  51657. ui : layout.owner instanceof Ext.toolbar.Toolbar ? 'default-toolbar' : 'default',
  51658. menu : me.menu,
  51659. getSplitCls: function() { return '';}
  51660. });
  51661. return me.menuTrigger.getRenderTree();
  51662. },
  51663. getOverflowCls: function() {
  51664. return Ext.baseCSSPrefix + this.layout.direction + '-box-overflow-body';
  51665. },
  51666. handleOverflow: function(ownerContext) {
  51667. var me = this,
  51668. layout = me.layout,
  51669. names = layout.getNames(),
  51670. plan = ownerContext.state.boxPlan,
  51671. posArgs = [null, null];
  51672. me.showTrigger(ownerContext);
  51673. // Center the menuTrigger button.
  51674. // TODO: Should we emulate align: 'middle' like this, or should we 'stretchmax' the menuTrigger?
  51675. posArgs[names.heightIndex] = (plan.maxSize - me.menuTrigger[names.getHeight]()) / 2;
  51676. me.menuTrigger.setPosition.apply(me.menuTrigger, posArgs);
  51677. return {
  51678. reservedSpace: me.menuTrigger[names.getWidth]()
  51679. };
  51680. },
  51681. /**
  51682. * Finishes the render operation of the trigger Button.
  51683. * @private
  51684. */
  51685. captureChildElements: function() {
  51686. var menuTrigger = this.menuTrigger;
  51687. if (menuTrigger.rendering) {
  51688. menuTrigger.finishRender();
  51689. }
  51690. },
  51691. _asLayoutRoot: { isRoot: true },
  51692. /**
  51693. * @private
  51694. * Called by the layout, when it determines that there is no overflow.
  51695. * Also called as an interceptor to the layout's onLayout method to reshow
  51696. * previously hidden overflowing items.
  51697. */
  51698. clearOverflow: function(ownerContext) {
  51699. var me = this,
  51700. items = me.menuItems,
  51701. item,
  51702. i = 0,
  51703. length = items.length,
  51704. owner = me.layout.owner,
  51705. asLayoutRoot = me._asLayoutRoot;
  51706. owner.suspendLayouts();
  51707. me.captureChildElements();
  51708. me.hideTrigger();
  51709. owner.resumeLayouts();
  51710. for (; i < length; i++) {
  51711. item = items[i];
  51712. // What we are doing here is preventing the layout bubble from invalidating our
  51713. // owner component. We need just the button to be added to the layout run.
  51714. item.suspendLayouts();
  51715. item.show();
  51716. item.resumeLayouts(asLayoutRoot);
  51717. }
  51718. items.length = 0;
  51719. },
  51720. /**
  51721. * @private
  51722. * Shows the overflow trigger when enableOverflow is set to true and the items
  51723. * in the layout are too wide to fit in the space available
  51724. */
  51725. showTrigger: function(ownerContext) {
  51726. var me = this,
  51727. layout = me.layout,
  51728. owner = layout.owner,
  51729. names = layout.getNames(),
  51730. startProp = names.x,
  51731. sizeProp = names.width,
  51732. plan = ownerContext.state.boxPlan,
  51733. available = plan.targetSize[sizeProp],
  51734. childItems = ownerContext.childItems,
  51735. len = childItems.length,
  51736. menuTrigger = me.menuTrigger,
  51737. childContext,
  51738. comp, i, props;
  51739. // We don't want the menuTrigger.show to cause owner's layout to be invalidated, so
  51740. // we force just the button to be invalidated and added to the current run.
  51741. menuTrigger.suspendLayouts();
  51742. menuTrigger.show();
  51743. menuTrigger.resumeLayouts(me._asLayoutRoot);
  51744. available -= me.menuTrigger.getWidth();
  51745. owner.suspendLayouts();
  51746. // Hide all items which are off the end, and store them to allow them to be restored
  51747. // before each layout operation.
  51748. me.menuItems.length = 0;
  51749. for (i = 0; i < len; i++) {
  51750. childContext = childItems[i];
  51751. props = childContext.props;
  51752. if (props[startProp] + props[sizeProp] > available) {
  51753. comp = childContext.target;
  51754. me.menuItems.push(comp);
  51755. comp.hide();
  51756. }
  51757. }
  51758. owner.resumeLayouts();
  51759. },
  51760. /**
  51761. * @private
  51762. */
  51763. hideTrigger: function() {
  51764. var menuTrigger = this.menuTrigger;
  51765. if (menuTrigger) {
  51766. menuTrigger.hide();
  51767. }
  51768. },
  51769. /**
  51770. * @private
  51771. * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.
  51772. */
  51773. beforeMenuShow: function(menu) {
  51774. var me = this,
  51775. items = me.menuItems,
  51776. i = 0,
  51777. len = items.length,
  51778. item,
  51779. prev,
  51780. needsSep = function(group, prev){
  51781. return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);
  51782. };
  51783. menu.suspendLayouts();
  51784. me.clearMenu();
  51785. menu.removeAll();
  51786. for (; i < len; i++) {
  51787. item = items[i];
  51788. // Do not show a separator as a first item
  51789. if (!i && (item instanceof Ext.toolbar.Separator)) {
  51790. continue;
  51791. }
  51792. if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
  51793. menu.add('-');
  51794. }
  51795. me.addComponentToMenu(menu, item);
  51796. prev = item;
  51797. }
  51798. // put something so the menu isn't empty if no compatible items found
  51799. if (menu.items.length < 1) {
  51800. menu.add(me.noItemsMenuText);
  51801. }
  51802. menu.resumeLayouts();
  51803. },
  51804. /**
  51805. * @private
  51806. * Returns a menu config for a given component. This config is used to create a menu item
  51807. * to be added to the expander menu
  51808. * @param {Ext.Component} component The component to create the config for
  51809. * @param {Boolean} hideOnClick Passed through to the menu item
  51810. */
  51811. createMenuConfig : function(component, hideOnClick) {
  51812. var config = Ext.apply({}, component.initialConfig),
  51813. group = component.toggleGroup;
  51814. Ext.copyTo(config, component, [
  51815. 'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
  51816. ]);
  51817. Ext.apply(config, {
  51818. text : component.overflowText || component.text,
  51819. hideOnClick: hideOnClick,
  51820. destroyMenu: false
  51821. });
  51822. // Clone must have same value, and must sync original's value on change
  51823. if (component.isFormField) {
  51824. config.value = component.getValue();
  51825. // We're going to add a listener
  51826. if (!config.listeners) {
  51827. config.listeners = {};
  51828. }
  51829. // Sync the original component's value when the clone changes value.
  51830. // This intentionally overwrites any developer-configured change listener on the clone.
  51831. // That's because we monitor the clone's change event, and sync the
  51832. // original field by calling setValue, so the original field's change
  51833. // event will still fire.
  51834. config.listeners.change = function(c, newVal, oldVal) {
  51835. component.setValue(newVal);
  51836. }
  51837. }
  51838. // ToggleButtons become CheckItems
  51839. else if (group || component.enableToggle) {
  51840. Ext.apply(config, {
  51841. iconAlign: 'right',
  51842. hideOnClick: false,
  51843. group : group,
  51844. checked: component.pressed,
  51845. listeners: {
  51846. checkchange: function(item, checked) {
  51847. component.toggle(checked);
  51848. }
  51849. }
  51850. });
  51851. }
  51852. delete config.ownerCt;
  51853. delete config.xtype;
  51854. delete config.id;
  51855. return config;
  51856. },
  51857. /**
  51858. * @private
  51859. * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
  51860. * @param {Ext.menu.Menu} menu The menu to add to
  51861. * @param {Ext.Component} component The component to add
  51862. * TODO: Implement overrides in Ext.layout.container.boxOverflow which create overrides
  51863. * for SplitButton, Button, ButtonGroup, and TextField. And a generic one for Component
  51864. * which create clones suitable for use in an overflow menu.
  51865. */
  51866. addComponentToMenu : function(menu, component) {
  51867. var me = this,
  51868. i, items, iLen;
  51869. if (component instanceof Ext.toolbar.Separator) {
  51870. menu.add('-');
  51871. } else if (component.isComponent) {
  51872. if (component.isXType('splitbutton')) {
  51873. menu.add(me.createMenuConfig(component, true));
  51874. } else if (component.isXType('button')) {
  51875. menu.add(me.createMenuConfig(component, !component.menu));
  51876. } else if (component.isXType('buttongroup')) {
  51877. items = component.items.items;
  51878. iLen = items.length;
  51879. for (i = 0; i < iLen; i++) {
  51880. me.addComponentToMenu(menu, items[i]);
  51881. }
  51882. } else {
  51883. menu.add(Ext.create(Ext.getClassName(component), me.createMenuConfig(component)));
  51884. }
  51885. }
  51886. },
  51887. /**
  51888. * @private
  51889. * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
  51890. * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
  51891. */
  51892. clearMenu : function() {
  51893. var menu = this.menu,
  51894. items, i, iLen, item;
  51895. if (menu && menu.items) {
  51896. items = menu.items.items;
  51897. iLen = items.length;
  51898. for (i = 0; i < iLen; i++) {
  51899. item = items[i];
  51900. if (item.setMenu) {
  51901. item.setMenu(null);
  51902. }
  51903. }
  51904. }
  51905. },
  51906. /**
  51907. * @private
  51908. */
  51909. destroy: function() {
  51910. var trigger = this.menuTrigger;
  51911. if (trigger && !this.layout.owner.items.contains(trigger)) {
  51912. // Ensure we delete the ownerCt if it's not in the items
  51913. // so we don't get spurious container remove warnings.
  51914. delete trigger.ownerCt;
  51915. }
  51916. Ext.destroy(this.menu, trigger);
  51917. }
  51918. });
  51919. /**
  51920. * @private
  51921. */
  51922. Ext.define('Ext.layout.container.boxOverflow.Scroller', {
  51923. /* Begin Definitions */
  51924. extend: 'Ext.layout.container.boxOverflow.None',
  51925. requires: ['Ext.util.ClickRepeater', 'Ext.Element'],
  51926. alternateClassName: 'Ext.layout.boxOverflow.Scroller',
  51927. mixins: {
  51928. observable: 'Ext.util.Observable'
  51929. },
  51930. /* End Definitions */
  51931. /**
  51932. * @cfg {Boolean} animateScroll
  51933. * True to animate the scrolling of items within the layout (ignored if enableScroll is false)
  51934. */
  51935. animateScroll: false,
  51936. /**
  51937. * @cfg {Number} scrollIncrement
  51938. * The number of pixels to scroll by on scroller click
  51939. */
  51940. scrollIncrement: 20,
  51941. /**
  51942. * @cfg {Number} wheelIncrement
  51943. * The number of pixels to increment on mouse wheel scrolling.
  51944. */
  51945. wheelIncrement: 10,
  51946. /**
  51947. * @cfg {Number} scrollRepeatInterval
  51948. * Number of milliseconds between each scroll while a scroller button is held down
  51949. */
  51950. scrollRepeatInterval: 60,
  51951. /**
  51952. * @cfg {Number} scrollDuration
  51953. * Number of milliseconds that each scroll animation lasts
  51954. */
  51955. scrollDuration: 400,
  51956. /**
  51957. * @cfg {String} beforeCtCls
  51958. * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
  51959. * which must always be present at the leftmost edge of the Container
  51960. */
  51961. /**
  51962. * @cfg {String} afterCtCls
  51963. * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
  51964. * which must always be present at the rightmost edge of the Container
  51965. */
  51966. /**
  51967. * @cfg {String} [scrollerCls='x-box-scroller']
  51968. * CSS class added to both scroller elements if enableScroll is used
  51969. */
  51970. scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
  51971. /**
  51972. * @cfg {String} beforeScrollerCls
  51973. * CSS class added to the left scroller element if enableScroll is used
  51974. */
  51975. /**
  51976. * @cfg {String} afterScrollerCls
  51977. * CSS class added to the right scroller element if enableScroll is used
  51978. */
  51979. constructor: function(layout, config) {
  51980. var me = this;
  51981. me.layout = layout;
  51982. Ext.apply(me, config || {});
  51983. // Dont pass the config so that it is not applied to 'this' again
  51984. me.mixins.observable.constructor.call(me);
  51985. me.addEvents(
  51986. /**
  51987. * @event scroll
  51988. * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
  51989. * @param {Number} newPosition The new position of the scroller
  51990. * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
  51991. */
  51992. 'scroll'
  51993. );
  51994. me.scrollPosition = 0;
  51995. me.scrollSize = 0;
  51996. },
  51997. getPrefixConfig: function() {
  51998. var me = this;
  51999. me.initCSSClasses();
  52000. return {
  52001. cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls,
  52002. cn : {
  52003. id : me.layout.owner.id + '-before-scroller',
  52004. cls: me.scrollerCls + ' ' + me.beforeScrollerCls,
  52005. style: 'display:none'
  52006. }
  52007. };
  52008. },
  52009. getSuffixConfig: function() {
  52010. var me = this;
  52011. return {
  52012. cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls,
  52013. cn : {
  52014. id : me.layout.owner.id + '-after-scroller',
  52015. cls: me.scrollerCls + ' ' + me.afterScrollerCls,
  52016. style: 'display:none'
  52017. }
  52018. };
  52019. },
  52020. getOverflowCls: function() {
  52021. return Ext.baseCSSPrefix + this.layout.direction + '-box-overflow-body';
  52022. },
  52023. initCSSClasses: function() {
  52024. var me = this,
  52025. prefix = Ext.baseCSSPrefix,
  52026. layout = me.layout,
  52027. names = layout.getNames(),
  52028. leftName = names.left,
  52029. rightName = names.right,
  52030. type = me.getOwnerType(layout.owner);
  52031. me.beforeCtCls = me.beforeCtCls || prefix + 'box-scroller-' + leftName;
  52032. me.afterCtCls = me.afterCtCls || prefix + 'box-scroller-' + rightName;
  52033. me.beforeScrollerCls = me.beforeScrollerCls || prefix + type + '-scroll-' + leftName;
  52034. me.afterScrollerCls = me.afterScrollerCls || prefix + type + '-scroll-' + rightName;
  52035. },
  52036. beginLayout: function (ownerContext) {
  52037. var layout = this.layout,
  52038. names = layout.getNames();
  52039. ownerContext.innerCtScrollPos = layout.innerCt.dom['scroll' + names.leftCap];
  52040. this.callParent(arguments);
  52041. },
  52042. completeLayout: function (ownerContext) {
  52043. // capture this before callParent since it calls handle/clearOverflow:
  52044. this.scrollSize = ownerContext.props['content'+this.layout.getNames().widthCap];
  52045. this.callParent(arguments);
  52046. },
  52047. finishedLayout: function(ownerContext) {
  52048. var me = this,
  52049. layout = me.layout,
  52050. names = layout.getNames(),
  52051. scrollPos = Math.min(me.getMaxScrollPosition(), ownerContext.innerCtScrollPos);
  52052. layout.innerCt.dom['scroll' + names.leftCap] = scrollPos;
  52053. },
  52054. handleOverflow: function(ownerContext) {
  52055. var me = this,
  52056. layout = me.layout,
  52057. names = layout.getNames(),
  52058. methodName = 'get' + names.widthCap;
  52059. me.captureChildElements();
  52060. me.showScrollers();
  52061. return {
  52062. reservedSpace: me.beforeCt[methodName]() + me.afterCt[methodName]()
  52063. };
  52064. },
  52065. /**
  52066. * @private
  52067. * Gets references to the beforeCt and afterCt elements if they have not already been captured
  52068. * and creates click handlers for them.
  52069. */
  52070. captureChildElements: function() {
  52071. var me = this,
  52072. el = me.layout.owner.el,
  52073. before,
  52074. after;
  52075. // Grab the scroll click receiving elements
  52076. if (!me.beforeCt) {
  52077. before = me.beforeScroller = el.getById(me.layout.owner.id + '-before-scroller');
  52078. after = me.afterScroller = el.getById(me.layout.owner.id + '-after-scroller');
  52079. me.beforeCt = before.up('');
  52080. me.afterCt = after.up('');
  52081. me.createWheelListener();
  52082. before.addClsOnOver(me.beforeScrollerCls + '-hover');
  52083. after.addClsOnOver(me.afterScrollerCls + '-hover');
  52084. before.setVisibilityMode(Ext.Element.DISPLAY);
  52085. after.setVisibilityMode(Ext.Element.DISPLAY);
  52086. me.beforeRepeater = new Ext.util.ClickRepeater(before, {
  52087. interval: me.scrollRepeatInterval,
  52088. handler : me.scrollLeft,
  52089. scope : me
  52090. });
  52091. me.afterRepeater = new Ext.util.ClickRepeater(after, {
  52092. interval: me.scrollRepeatInterval,
  52093. handler : me.scrollRight,
  52094. scope : me
  52095. });
  52096. }
  52097. },
  52098. /**
  52099. * @private
  52100. * Sets up an listener to scroll on the layout's innerCt mousewheel event
  52101. */
  52102. createWheelListener: function() {
  52103. this.layout.innerCt.on({
  52104. mousewheel: function(e) {
  52105. this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
  52106. },
  52107. stopEvent: true,
  52108. scope: this
  52109. });
  52110. },
  52111. /**
  52112. * @private
  52113. */
  52114. clearOverflow: function () {
  52115. var layout = this.layout;
  52116. this.hideScrollers();
  52117. },
  52118. /**
  52119. * @private
  52120. * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
  52121. * present.
  52122. */
  52123. showScrollers: function() {
  52124. var me = this;
  52125. me.captureChildElements();
  52126. me.beforeScroller.show();
  52127. me.afterScroller.show();
  52128. me.updateScrollButtons();
  52129. me.layout.owner.addClsWithUI('scroller');
  52130. // TODO - this may invalidates data in the ContextItem's styleCache
  52131. },
  52132. /**
  52133. * @private
  52134. * Hides the scroller elements in the beforeCt and afterCt
  52135. */
  52136. hideScrollers: function() {
  52137. var me = this;
  52138. if (me.beforeScroller !== undefined) {
  52139. me.beforeScroller.hide();
  52140. me.afterScroller.hide();
  52141. me.layout.owner.removeClsWithUI('scroller');
  52142. // TODO - this may invalidates data in the ContextItem's styleCache
  52143. }
  52144. },
  52145. /**
  52146. * @private
  52147. */
  52148. destroy: function() {
  52149. var me = this;
  52150. Ext.destroy(me.beforeRepeater, me.afterRepeater, me.beforeScroller, me.afterScroller, me.beforeCt, me.afterCt);
  52151. },
  52152. /**
  52153. * @private
  52154. * Scrolls left or right by the number of pixels specified
  52155. * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
  52156. */
  52157. scrollBy: function(delta, animate) {
  52158. this.scrollTo(this.getScrollPosition() + delta, animate);
  52159. },
  52160. /**
  52161. * @private
  52162. * @return {Object} Object passed to scrollTo when scrolling
  52163. */
  52164. getScrollAnim: function() {
  52165. return {
  52166. duration: this.scrollDuration,
  52167. callback: this.updateScrollButtons,
  52168. scope : this
  52169. };
  52170. },
  52171. /**
  52172. * @private
  52173. * Enables or disables each scroller button based on the current scroll position
  52174. */
  52175. updateScrollButtons: function() {
  52176. var me = this,
  52177. beforeMeth,
  52178. afterMeth,
  52179. beforeCls,
  52180. afterCls;
  52181. if (me.beforeScroller === undefined || me.afterScroller === undefined) {
  52182. return;
  52183. }
  52184. beforeMeth = me.atExtremeBefore() ? 'addCls' : 'removeCls';
  52185. afterMeth = me.atExtremeAfter() ? 'addCls' : 'removeCls';
  52186. beforeCls = me.beforeScrollerCls + '-disabled';
  52187. afterCls = me.afterScrollerCls + '-disabled';
  52188. me.beforeScroller[beforeMeth](beforeCls);
  52189. me.afterScroller[afterMeth](afterCls);
  52190. me.scrolling = false;
  52191. },
  52192. /**
  52193. * @private
  52194. * Returns true if the innerCt scroll is already at its left-most point
  52195. * @return {Boolean} True if already at furthest left point
  52196. */
  52197. atExtremeBefore: function() {
  52198. return !this.getScrollPosition();
  52199. },
  52200. /**
  52201. * @private
  52202. * Scrolls to the left by the configured amount
  52203. */
  52204. scrollLeft: function() {
  52205. this.scrollBy(-this.scrollIncrement, false);
  52206. },
  52207. /**
  52208. * @private
  52209. * Scrolls to the right by the configured amount
  52210. */
  52211. scrollRight: function() {
  52212. this.scrollBy(this.scrollIncrement, false);
  52213. },
  52214. /**
  52215. * Returns the current scroll position of the innerCt element
  52216. * @return {Number} The current scroll position
  52217. */
  52218. getScrollPosition: function(){
  52219. var me = this,
  52220. layout = me.layout,
  52221. result;
  52222. // Until we actually scroll, the scroll[Top|Left] is stored as zero to avoid DOM hits.
  52223. if (me.hasOwnProperty('scrollPosition')) {
  52224. result = me.scrollPosition;
  52225. } else {
  52226. result = parseInt(layout.innerCt.dom['scroll' + layout.getNames().leftCap], 10) || 0;
  52227. }
  52228. return result;
  52229. },
  52230. /**
  52231. * @private
  52232. * Returns the maximum value we can scrollTo
  52233. * @return {Number} The max scroll value
  52234. */
  52235. getMaxScrollPosition: function() {
  52236. var me = this,
  52237. layout = me.layout,
  52238. names = layout.getNames(),
  52239. maxScrollPos = me.scrollSize - layout.innerCt['get'+names.widthCap]();
  52240. return (maxScrollPos < 0) ? 0 : maxScrollPos;
  52241. },
  52242. /**
  52243. * @private
  52244. * Returns true if the innerCt scroll is already at its right-most point
  52245. * @return {Boolean} True if already at furthest right point
  52246. */
  52247. atExtremeAfter: function() {
  52248. return this.getScrollPosition() >= this.getMaxScrollPosition();
  52249. },
  52250. /**
  52251. * @private
  52252. * Scrolls to the given position. Performs bounds checking.
  52253. * @param {Number} position The position to scroll to. This is constrained.
  52254. * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
  52255. */
  52256. scrollTo: function(position, animate) {
  52257. var me = this,
  52258. layout = me.layout,
  52259. names = layout.getNames(),
  52260. oldPosition = me.getScrollPosition(),
  52261. newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
  52262. if (newPosition != oldPosition && !me.scrolling) {
  52263. delete me.scrollPosition;
  52264. if (animate === undefined) {
  52265. animate = me.animateScroll;
  52266. }
  52267. layout.innerCt.scrollTo(names.left, newPosition, animate ? me.getScrollAnim() : false);
  52268. if (animate) {
  52269. me.scrolling = true;
  52270. } else {
  52271. me.updateScrollButtons();
  52272. }
  52273. me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
  52274. }
  52275. },
  52276. /**
  52277. * Scrolls to the given component.
  52278. * @param {String/Number/Ext.Component} item The item to scroll to. Can be a numerical index, component id
  52279. * or a reference to the component itself.
  52280. * @param {Boolean} animate True to animate the scrolling
  52281. */
  52282. scrollToItem: function(item, animate) {
  52283. var me = this,
  52284. layout = me.layout,
  52285. names = layout.getNames(),
  52286. visibility,
  52287. box,
  52288. newPos;
  52289. item = me.getItem(item);
  52290. if (item !== undefined) {
  52291. visibility = me.getItemVisibility(item);
  52292. if (!visibility.fullyVisible) {
  52293. box = item.getBox(true, true);
  52294. newPos = box[names.x];
  52295. if (visibility.hiddenEnd) {
  52296. newPos -= (me.layout.innerCt['get' + names.widthCap]() - box[names.width]);
  52297. }
  52298. me.scrollTo(newPos, animate);
  52299. }
  52300. }
  52301. },
  52302. /**
  52303. * @private
  52304. * For a given item in the container, return an object with information on whether the item is visible
  52305. * with the current innerCt scroll value.
  52306. * @param {Ext.Component} item The item
  52307. * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
  52308. */
  52309. getItemVisibility: function(item) {
  52310. var me = this,
  52311. box = me.getItem(item).getBox(true, true),
  52312. layout = me.layout,
  52313. names = layout.getNames(),
  52314. itemStart = box[names.x],
  52315. itemEnd = itemStart + box[names.width],
  52316. scrollStart = me.getScrollPosition(),
  52317. scrollEnd = scrollStart + layout.innerCt['get' + names.widthCap]();
  52318. return {
  52319. hiddenStart : itemStart < scrollStart,
  52320. hiddenEnd : itemEnd > scrollEnd,
  52321. fullyVisible: itemStart > scrollStart && itemEnd < scrollEnd
  52322. };
  52323. }
  52324. });
  52325. /**
  52326. * @private
  52327. */
  52328. Ext.define('Ext.util.Offset', {
  52329. /* Begin Definitions */
  52330. statics: {
  52331. fromObject: function(obj) {
  52332. return new this(obj.x, obj.y);
  52333. }
  52334. },
  52335. /* End Definitions */
  52336. constructor: function(x, y) {
  52337. this.x = (x != null && !isNaN(x)) ? x : 0;
  52338. this.y = (y != null && !isNaN(y)) ? y : 0;
  52339. return this;
  52340. },
  52341. copy: function() {
  52342. return new Ext.util.Offset(this.x, this.y);
  52343. },
  52344. copyFrom: function(p) {
  52345. this.x = p.x;
  52346. this.y = p.y;
  52347. },
  52348. toString: function() {
  52349. return "Offset[" + this.x + "," + this.y + "]";
  52350. },
  52351. equals: function(offset) {
  52352. if(!(offset instanceof this.statics())) {
  52353. Ext.Error.raise('Offset must be an instance of Ext.util.Offset');
  52354. }
  52355. return (this.x == offset.x && this.y == offset.y);
  52356. },
  52357. round: function(to) {
  52358. if (!isNaN(to)) {
  52359. var factor = Math.pow(10, to);
  52360. this.x = Math.round(this.x * factor) / factor;
  52361. this.y = Math.round(this.y * factor) / factor;
  52362. } else {
  52363. this.x = Math.round(this.x);
  52364. this.y = Math.round(this.y);
  52365. }
  52366. },
  52367. isZero: function() {
  52368. return this.x == 0 && this.y == 0;
  52369. }
  52370. });
  52371. /**
  52372. * This class represents a rectangular region in X,Y space, and performs geometric
  52373. * transformations or tests upon the region.
  52374. *
  52375. * This class may be used to compare the document regions occupied by elements.
  52376. */
  52377. Ext.define('Ext.util.Region', {
  52378. /* Begin Definitions */
  52379. requires: ['Ext.util.Offset'],
  52380. statics: {
  52381. /**
  52382. * @static
  52383. * Retrieves an Ext.util.Region for a particular element.
  52384. * @param {String/HTMLElement/Ext.Element} el An element ID, htmlElement or Ext.Element representing an element in the document.
  52385. * @returns {Ext.util.Region} region
  52386. */
  52387. getRegion: function(el) {
  52388. return Ext.fly(el).getPageBox(true);
  52389. },
  52390. /**
  52391. * @static
  52392. * Creates a Region from a "box" Object which contains four numeric properties `top`, `right`, `bottom` and `left`.
  52393. * @param {Object} o An object with `top`, `right`, `bottom` and `left` properties.
  52394. * @return {Ext.util.Region} region The Region constructed based on the passed object
  52395. */
  52396. from: function(o) {
  52397. return new this(o.top, o.right, o.bottom, o.left);
  52398. }
  52399. },
  52400. /* End Definitions */
  52401. /**
  52402. * Creates a region from the bounding sides.
  52403. * @param {Number} top Top The topmost pixel of the Region.
  52404. * @param {Number} right Right The rightmost pixel of the Region.
  52405. * @param {Number} bottom Bottom The bottom pixel of the Region.
  52406. * @param {Number} left Left The leftmost pixel of the Region.
  52407. */
  52408. constructor : function(t, r, b, l) {
  52409. var me = this;
  52410. me.y = me.top = me[1] = t;
  52411. me.right = r;
  52412. me.bottom = b;
  52413. me.x = me.left = me[0] = l;
  52414. },
  52415. /**
  52416. * Checks if this region completely contains the region that is passed in.
  52417. * @param {Ext.util.Region} region
  52418. * @return {Boolean}
  52419. */
  52420. contains : function(region) {
  52421. var me = this;
  52422. return (region.x >= me.x &&
  52423. region.right <= me.right &&
  52424. region.y >= me.y &&
  52425. region.bottom <= me.bottom);
  52426. },
  52427. /**
  52428. * Checks if this region intersects the region passed in.
  52429. * @param {Ext.util.Region} region
  52430. * @return {Ext.util.Region/Boolean} Returns the intersected region or false if there is no intersection.
  52431. */
  52432. intersect : function(region) {
  52433. var me = this,
  52434. t = Math.max(me.y, region.y),
  52435. r = Math.min(me.right, region.right),
  52436. b = Math.min(me.bottom, region.bottom),
  52437. l = Math.max(me.x, region.x);
  52438. if (b > t && r > l) {
  52439. return new this.self(t, r, b, l);
  52440. }
  52441. else {
  52442. return false;
  52443. }
  52444. },
  52445. /**
  52446. * Returns the smallest region that contains the current AND targetRegion.
  52447. * @param {Ext.util.Region} region
  52448. * @return {Ext.util.Region} a new region
  52449. */
  52450. union : function(region) {
  52451. var me = this,
  52452. t = Math.min(me.y, region.y),
  52453. r = Math.max(me.right, region.right),
  52454. b = Math.max(me.bottom, region.bottom),
  52455. l = Math.min(me.x, region.x);
  52456. return new this.self(t, r, b, l);
  52457. },
  52458. /**
  52459. * Modifies the current region to be constrained to the targetRegion.
  52460. * @param {Ext.util.Region} targetRegion
  52461. * @return {Ext.util.Region} this
  52462. */
  52463. constrainTo : function(r) {
  52464. var me = this,
  52465. constrain = Ext.Number.constrain;
  52466. me.top = me.y = constrain(me.top, r.y, r.bottom);
  52467. me.bottom = constrain(me.bottom, r.y, r.bottom);
  52468. me.left = me.x = constrain(me.left, r.x, r.right);
  52469. me.right = constrain(me.right, r.x, r.right);
  52470. return me;
  52471. },
  52472. /**
  52473. * Modifies the current region to be adjusted by offsets.
  52474. * @param {Number} top top offset
  52475. * @param {Number} right right offset
  52476. * @param {Number} bottom bottom offset
  52477. * @param {Number} left left offset
  52478. * @return {Ext.util.Region} this
  52479. */
  52480. adjust : function(t, r, b, l) {
  52481. var me = this;
  52482. me.top = me.y += t;
  52483. me.left = me.x += l;
  52484. me.right += r;
  52485. me.bottom += b;
  52486. return me;
  52487. },
  52488. /**
  52489. * Get the offset amount of a point outside the region
  52490. * @param {String} [axis]
  52491. * @param {Ext.util.Point} [p] the point
  52492. * @return {Ext.util.Offset}
  52493. */
  52494. getOutOfBoundOffset: function(axis, p) {
  52495. if (!Ext.isObject(axis)) {
  52496. if (axis == 'x') {
  52497. return this.getOutOfBoundOffsetX(p);
  52498. } else {
  52499. return this.getOutOfBoundOffsetY(p);
  52500. }
  52501. } else {
  52502. p = axis;
  52503. var d = new Ext.util.Offset();
  52504. d.x = this.getOutOfBoundOffsetX(p.x);
  52505. d.y = this.getOutOfBoundOffsetY(p.y);
  52506. return d;
  52507. }
  52508. },
  52509. /**
  52510. * Get the offset amount on the x-axis
  52511. * @param {Number} p the offset
  52512. * @return {Number}
  52513. */
  52514. getOutOfBoundOffsetX: function(p) {
  52515. if (p <= this.x) {
  52516. return this.x - p;
  52517. } else if (p >= this.right) {
  52518. return this.right - p;
  52519. }
  52520. return 0;
  52521. },
  52522. /**
  52523. * Get the offset amount on the y-axis
  52524. * @param {Number} p the offset
  52525. * @return {Number}
  52526. */
  52527. getOutOfBoundOffsetY: function(p) {
  52528. if (p <= this.y) {
  52529. return this.y - p;
  52530. } else if (p >= this.bottom) {
  52531. return this.bottom - p;
  52532. }
  52533. return 0;
  52534. },
  52535. /**
  52536. * Check whether the point / offset is out of bound
  52537. * @param {String} [axis]
  52538. * @param {Ext.util.Point/Number} [p] the point / offset
  52539. * @return {Boolean}
  52540. */
  52541. isOutOfBound: function(axis, p) {
  52542. if (!Ext.isObject(axis)) {
  52543. if (axis == 'x') {
  52544. return this.isOutOfBoundX(p);
  52545. } else {
  52546. return this.isOutOfBoundY(p);
  52547. }
  52548. } else {
  52549. p = axis;
  52550. return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
  52551. }
  52552. },
  52553. /**
  52554. * Check whether the offset is out of bound in the x-axis
  52555. * @param {Number} p the offset
  52556. * @return {Boolean}
  52557. */
  52558. isOutOfBoundX: function(p) {
  52559. return (p < this.x || p > this.right);
  52560. },
  52561. /**
  52562. * Check whether the offset is out of bound in the y-axis
  52563. * @param {Number} p the offset
  52564. * @return {Boolean}
  52565. */
  52566. isOutOfBoundY: function(p) {
  52567. return (p < this.y || p > this.bottom);
  52568. },
  52569. /**
  52570. * Restrict a point within the region by a certain factor.
  52571. * @param {String} [axis]
  52572. * @param {Ext.util.Point/Ext.util.Offset/Object} [p]
  52573. * @param {Number} [factor]
  52574. * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
  52575. * @private
  52576. */
  52577. restrict: function(axis, p, factor) {
  52578. if (Ext.isObject(axis)) {
  52579. var newP;
  52580. factor = p;
  52581. p = axis;
  52582. if (p.copy) {
  52583. newP = p.copy();
  52584. }
  52585. else {
  52586. newP = {
  52587. x: p.x,
  52588. y: p.y
  52589. };
  52590. }
  52591. newP.x = this.restrictX(p.x, factor);
  52592. newP.y = this.restrictY(p.y, factor);
  52593. return newP;
  52594. } else {
  52595. if (axis == 'x') {
  52596. return this.restrictX(p, factor);
  52597. } else {
  52598. return this.restrictY(p, factor);
  52599. }
  52600. }
  52601. },
  52602. /**
  52603. * Restrict an offset within the region by a certain factor, on the x-axis
  52604. * @param {Number} p
  52605. * @param {Number} [factor=1] The factor.
  52606. * @return {Number}
  52607. * @private
  52608. */
  52609. restrictX : function(p, factor) {
  52610. if (!factor) {
  52611. factor = 1;
  52612. }
  52613. if (p <= this.x) {
  52614. p -= (p - this.x) * factor;
  52615. }
  52616. else if (p >= this.right) {
  52617. p -= (p - this.right) * factor;
  52618. }
  52619. return p;
  52620. },
  52621. /**
  52622. * Restrict an offset within the region by a certain factor, on the y-axis
  52623. * @param {Number} p
  52624. * @param {Number} [factor] The factor, defaults to 1
  52625. * @return {Number}
  52626. * @private
  52627. */
  52628. restrictY : function(p, factor) {
  52629. if (!factor) {
  52630. factor = 1;
  52631. }
  52632. if (p <= this.y) {
  52633. p -= (p - this.y) * factor;
  52634. }
  52635. else if (p >= this.bottom) {
  52636. p -= (p - this.bottom) * factor;
  52637. }
  52638. return p;
  52639. },
  52640. /**
  52641. * Get the width / height of this region
  52642. * @return {Object} an object with width and height properties
  52643. * @private
  52644. */
  52645. getSize: function() {
  52646. return {
  52647. width: this.right - this.x,
  52648. height: this.bottom - this.y
  52649. };
  52650. },
  52651. /**
  52652. * Create a copy of this Region.
  52653. * @return {Ext.util.Region}
  52654. */
  52655. copy: function() {
  52656. return new this.self(this.y, this.right, this.bottom, this.x);
  52657. },
  52658. /**
  52659. * Copy the values of another Region to this Region
  52660. * @param {Ext.util.Region} p The region to copy from.
  52661. * @return {Ext.util.Region} This Region
  52662. */
  52663. copyFrom: function(p) {
  52664. var me = this;
  52665. me.top = me.y = me[1] = p.y;
  52666. me.right = p.right;
  52667. me.bottom = p.bottom;
  52668. me.left = me.x = me[0] = p.x;
  52669. return this;
  52670. },
  52671. /*
  52672. * Dump this to an eye-friendly string, great for debugging
  52673. * @return {String}
  52674. */
  52675. toString: function() {
  52676. return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
  52677. },
  52678. /**
  52679. * Translate this region by the given offset amount
  52680. * @param {Ext.util.Offset/Object} x Object containing the `x` and `y` properties.
  52681. * Or the x value is using the two argument form.
  52682. * @param {Number} y The y value unless using an Offset object.
  52683. * @return {Ext.util.Region} this This Region
  52684. */
  52685. translateBy: function(x, y) {
  52686. if (arguments.length == 1) {
  52687. y = x.y;
  52688. x = x.x;
  52689. }
  52690. var me = this;
  52691. me.top = me.y += y;
  52692. me.right += x;
  52693. me.bottom += y;
  52694. me.left = me.x += x;
  52695. return me;
  52696. },
  52697. /**
  52698. * Round all the properties of this region
  52699. * @return {Ext.util.Region} this This Region
  52700. */
  52701. round: function() {
  52702. var me = this;
  52703. me.top = me.y = Math.round(me.y);
  52704. me.right = Math.round(me.right);
  52705. me.bottom = Math.round(me.bottom);
  52706. me.left = me.x = Math.round(me.x);
  52707. return me;
  52708. },
  52709. /**
  52710. * Check whether this region is equivalent to the given region
  52711. * @param {Ext.util.Region} region The region to compare with
  52712. * @return {Boolean}
  52713. */
  52714. equals: function(region) {
  52715. return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left);
  52716. }
  52717. });
  52718. /*
  52719. * This is a derivative of the similarly named class in the YUI Library.
  52720. * The original license:
  52721. * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
  52722. * Code licensed under the BSD License:
  52723. * http://developer.yahoo.net/yui/license.txt
  52724. */
  52725. /**
  52726. * DragDropManager is a singleton that tracks the element interaction for
  52727. * all DragDrop items in the window. Generally, you will not call
  52728. * this class directly, but it does have helper methods that could
  52729. * be useful in your DragDrop implementations.
  52730. */
  52731. Ext.define('Ext.dd.DragDropManager', {
  52732. singleton: true,
  52733. requires: ['Ext.util.Region'],
  52734. uses: ['Ext.tip.QuickTipManager'],
  52735. // shorter ClassName, to save bytes and use internally
  52736. alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'],
  52737. /**
  52738. * @property {String[]} ids
  52739. * Two dimensional Array of registered DragDrop objects. The first
  52740. * dimension is the DragDrop item group, the second the DragDrop
  52741. * object.
  52742. * @private
  52743. */
  52744. ids: {},
  52745. /**
  52746. * @property {String[]} handleIds
  52747. * Array of element ids defined as drag handles. Used to determine
  52748. * if the element that generated the mousedown event is actually the
  52749. * handle and not the html element itself.
  52750. * @private
  52751. */
  52752. handleIds: {},
  52753. /**
  52754. * @property {Ext.dd.DragDrop} dragCurrent
  52755. * the DragDrop object that is currently being dragged
  52756. * @private
  52757. */
  52758. dragCurrent: null,
  52759. /**
  52760. * @property {Ext.dd.DragDrop[]} dragOvers
  52761. * the DragDrop object(s) that are being hovered over
  52762. * @private
  52763. */
  52764. dragOvers: {},
  52765. /**
  52766. * @property {Number} deltaX
  52767. * the X distance between the cursor and the object being dragged
  52768. * @private
  52769. */
  52770. deltaX: 0,
  52771. /**
  52772. * @property {Number} deltaY
  52773. * the Y distance between the cursor and the object being dragged
  52774. * @private
  52775. */
  52776. deltaY: 0,
  52777. /**
  52778. * @property {Boolean} preventDefault
  52779. * Flag to determine if we should prevent the default behavior of the
  52780. * events we define. By default this is true, but this can be set to
  52781. * false if you need the default behavior (not recommended)
  52782. */
  52783. preventDefault: true,
  52784. /**
  52785. * @property {Boolean} stopPropagation
  52786. * Flag to determine if we should stop the propagation of the events
  52787. * we generate. This is true by default but you may want to set it to
  52788. * false if the html element contains other features that require the
  52789. * mouse click.
  52790. */
  52791. stopPropagation: true,
  52792. /**
  52793. * Internal flag that is set to true when drag and drop has been
  52794. * intialized
  52795. * @property initialized
  52796. * @private
  52797. */
  52798. initialized: false,
  52799. /**
  52800. * All drag and drop can be disabled.
  52801. * @property locked
  52802. * @private
  52803. */
  52804. locked: false,
  52805. /**
  52806. * Called the first time an element is registered.
  52807. * @private
  52808. */
  52809. init: function() {
  52810. this.initialized = true;
  52811. },
  52812. /**
  52813. * @property {Number} POINT
  52814. * In point mode, drag and drop interaction is defined by the
  52815. * location of the cursor during the drag/drop
  52816. */
  52817. POINT: 0,
  52818. /**
  52819. * @property {Number} INTERSECT
  52820. * In intersect mode, drag and drop interaction is defined by the
  52821. * overlap of two or more drag and drop objects.
  52822. */
  52823. INTERSECT: 1,
  52824. /**
  52825. * @property {Number} mode
  52826. * The current drag and drop mode. Default: POINT
  52827. */
  52828. mode: 0,
  52829. /**
  52830. * @property {Boolean} [notifyOccluded=false]
  52831. * This config is only provided to provide old, usually unwanted drag/drop behaviour.
  52832. *
  52833. * From ExtJS 4.1.0 onwards, when drop targets are contained in floating, absolutely positioned elements
  52834. * such as in {@link Ext.window.Window Windows}, which may overlap each other, `over` and `drop` events
  52835. * are only delivered to the topmost drop target at the mouse position.
  52836. *
  52837. * If all targets below that in zIndex order should also receive notifications, set
  52838. * `notifyOccluded` to `true`.
  52839. */
  52840. notifyOccluded: false,
  52841. /**
  52842. * Runs method on all drag and drop objects
  52843. * @private
  52844. */
  52845. _execOnAll: function(sMethod, args) {
  52846. var i, j, oDD;
  52847. for (i in this.ids) {
  52848. for (j in this.ids[i]) {
  52849. oDD = this.ids[i][j];
  52850. if (! this.isTypeOfDD(oDD)) {
  52851. continue;
  52852. }
  52853. oDD[sMethod].apply(oDD, args);
  52854. }
  52855. }
  52856. },
  52857. /**
  52858. * Drag and drop initialization. Sets up the global event handlers
  52859. * @private
  52860. */
  52861. _onLoad: function() {
  52862. this.init();
  52863. var Event = Ext.EventManager;
  52864. Event.on(document, "mouseup", this.handleMouseUp, this, true);
  52865. Event.on(document, "mousemove", this.handleMouseMove, this, true);
  52866. Event.on(window, "unload", this._onUnload, this, true);
  52867. Event.on(window, "resize", this._onResize, this, true);
  52868. // Event.on(window, "mouseout", this._test);
  52869. },
  52870. /**
  52871. * Reset constraints on all drag and drop objs
  52872. * @private
  52873. */
  52874. _onResize: function(e) {
  52875. this._execOnAll("resetConstraints", []);
  52876. },
  52877. /**
  52878. * Lock all drag and drop functionality
  52879. */
  52880. lock: function() { this.locked = true; },
  52881. /**
  52882. * Unlock all drag and drop functionality
  52883. */
  52884. unlock: function() { this.locked = false; },
  52885. /**
  52886. * Is drag and drop locked?
  52887. * @return {Boolean} True if drag and drop is locked, false otherwise.
  52888. */
  52889. isLocked: function() { return this.locked; },
  52890. /**
  52891. * @property {Object} locationCache
  52892. * Location cache that is set for all drag drop objects when a drag is
  52893. * initiated, cleared when the drag is finished.
  52894. * @private
  52895. */
  52896. locationCache: {},
  52897. /**
  52898. * @property {Boolean} useCache
  52899. * Set useCache to false if you want to force object the lookup of each
  52900. * drag and drop linked element constantly during a drag.
  52901. */
  52902. useCache: true,
  52903. /**
  52904. * @property {Number} clickPixelThresh
  52905. * The number of pixels that the mouse needs to move after the
  52906. * mousedown before the drag is initiated. Default=3;
  52907. */
  52908. clickPixelThresh: 3,
  52909. /**
  52910. * @property {Number} clickTimeThresh
  52911. * The number of milliseconds after the mousedown event to initiate the
  52912. * drag if we don't get a mouseup event. Default=350
  52913. */
  52914. clickTimeThresh: 350,
  52915. /**
  52916. * @property {Boolean} dragThreshMet
  52917. * Flag that indicates that either the drag pixel threshold or the
  52918. * mousdown time threshold has been met
  52919. * @private
  52920. */
  52921. dragThreshMet: false,
  52922. /**
  52923. * @property {Object} clickTimeout
  52924. * Timeout used for the click time threshold
  52925. * @private
  52926. */
  52927. clickTimeout: null,
  52928. /**
  52929. * @property {Number} startX
  52930. * The X position of the mousedown event stored for later use when a
  52931. * drag threshold is met.
  52932. * @private
  52933. */
  52934. startX: 0,
  52935. /**
  52936. * @property {Number} startY
  52937. * The Y position of the mousedown event stored for later use when a
  52938. * drag threshold is met.
  52939. * @private
  52940. */
  52941. startY: 0,
  52942. /**
  52943. * Each DragDrop instance must be registered with the DragDropManager.
  52944. * This is executed in DragDrop.init()
  52945. * @param {Ext.dd.DragDrop} oDD the DragDrop object to register
  52946. * @param {String} sGroup the name of the group this element belongs to
  52947. */
  52948. regDragDrop: function(oDD, sGroup) {
  52949. if (!this.initialized) { this.init(); }
  52950. if (!this.ids[sGroup]) {
  52951. this.ids[sGroup] = {};
  52952. }
  52953. this.ids[sGroup][oDD.id] = oDD;
  52954. },
  52955. /**
  52956. * Removes the supplied dd instance from the supplied group. Executed
  52957. * by DragDrop.removeFromGroup, so don't call this function directly.
  52958. * @private
  52959. */
  52960. removeDDFromGroup: function(oDD, sGroup) {
  52961. if (!this.ids[sGroup]) {
  52962. this.ids[sGroup] = {};
  52963. }
  52964. var obj = this.ids[sGroup];
  52965. if (obj && obj[oDD.id]) {
  52966. delete obj[oDD.id];
  52967. }
  52968. },
  52969. /**
  52970. * Unregisters a drag and drop item. This is executed in
  52971. * DragDrop.unreg, use that method instead of calling this directly.
  52972. * @private
  52973. */
  52974. _remove: function(oDD) {
  52975. for (var g in oDD.groups) {
  52976. if (g && this.ids[g] && this.ids[g][oDD.id]) {
  52977. delete this.ids[g][oDD.id];
  52978. }
  52979. }
  52980. delete this.handleIds[oDD.id];
  52981. },
  52982. /**
  52983. * Each DragDrop handle element must be registered. This is done
  52984. * automatically when executing DragDrop.setHandleElId()
  52985. * @param {String} sDDId the DragDrop id this element is a handle for
  52986. * @param {String} sHandleId the id of the element that is the drag
  52987. * handle
  52988. */
  52989. regHandle: function(sDDId, sHandleId) {
  52990. if (!this.handleIds[sDDId]) {
  52991. this.handleIds[sDDId] = {};
  52992. }
  52993. this.handleIds[sDDId][sHandleId] = sHandleId;
  52994. },
  52995. /**
  52996. * Utility function to determine if a given element has been
  52997. * registered as a drag drop item.
  52998. * @param {String} id the element id to check
  52999. * @return {Boolean} true if this element is a DragDrop item,
  53000. * false otherwise
  53001. */
  53002. isDragDrop: function(id) {
  53003. return ( this.getDDById(id) ) ? true : false;
  53004. },
  53005. /**
  53006. * Returns the drag and drop instances that are in all groups the
  53007. * passed in instance belongs to.
  53008. * @param {Ext.dd.DragDrop} p_oDD the obj to get related data for
  53009. * @param {Boolean} bTargetsOnly if true, only return targetable objs
  53010. * @return {Ext.dd.DragDrop[]} the related instances
  53011. */
  53012. getRelated: function(p_oDD, bTargetsOnly) {
  53013. var oDDs = [],
  53014. i, j, dd;
  53015. for (i in p_oDD.groups) {
  53016. for (j in this.ids[i]) {
  53017. dd = this.ids[i][j];
  53018. if (! this.isTypeOfDD(dd)) {
  53019. continue;
  53020. }
  53021. if (!bTargetsOnly || dd.isTarget) {
  53022. oDDs[oDDs.length] = dd;
  53023. }
  53024. }
  53025. }
  53026. return oDDs;
  53027. },
  53028. /**
  53029. * Returns true if the specified dd target is a legal target for
  53030. * the specifice drag obj
  53031. * @param {Ext.dd.DragDrop} oDD the drag obj
  53032. * @param {Ext.dd.DragDrop} oTargetDD the target
  53033. * @return {Boolean} true if the target is a legal target for the
  53034. * dd obj
  53035. */
  53036. isLegalTarget: function (oDD, oTargetDD) {
  53037. var targets = this.getRelated(oDD, true),
  53038. i, len;
  53039. for (i=0, len=targets.length;i<len;++i) {
  53040. if (targets[i].id == oTargetDD.id) {
  53041. return true;
  53042. }
  53043. }
  53044. return false;
  53045. },
  53046. /**
  53047. * My goal is to be able to transparently determine if an object is
  53048. * typeof DragDrop, and the exact subclass of DragDrop. typeof
  53049. * returns "object", oDD.constructor.toString() always returns
  53050. * "DragDrop" and not the name of the subclass. So for now it just
  53051. * evaluates a well-known variable in DragDrop.
  53052. * @param {Object} the object to evaluate
  53053. * @return {Boolean} true if typeof oDD = DragDrop
  53054. */
  53055. isTypeOfDD: function (oDD) {
  53056. return (oDD && oDD.__ygDragDrop);
  53057. },
  53058. /**
  53059. * Utility function to determine if a given element has been
  53060. * registered as a drag drop handle for the given Drag Drop object.
  53061. * @param {String} id the element id to check
  53062. * @return {Boolean} true if this element is a DragDrop handle, false
  53063. * otherwise
  53064. */
  53065. isHandle: function(sDDId, sHandleId) {
  53066. return ( this.handleIds[sDDId] &&
  53067. this.handleIds[sDDId][sHandleId] );
  53068. },
  53069. /**
  53070. * Returns the DragDrop instance for a given id
  53071. * @param {String} id the id of the DragDrop object
  53072. * @return {Ext.dd.DragDrop} the drag drop object, null if it is not found
  53073. */
  53074. getDDById: function(id) {
  53075. var me = this,
  53076. i, dd;
  53077. for (i in this.ids) {
  53078. dd = this.ids[i][id];
  53079. if (dd instanceof Ext.dd.DDTarget) {
  53080. return dd;
  53081. }
  53082. }
  53083. return null;
  53084. },
  53085. /**
  53086. * Fired after a registered DragDrop object gets the mousedown event.
  53087. * Sets up the events required to track the object being dragged
  53088. * @param {Event} e the event
  53089. * @param {Ext.dd.DragDrop} oDD the DragDrop object being dragged
  53090. * @private
  53091. */
  53092. handleMouseDown: function(e, oDD) {
  53093. if(Ext.tip.QuickTipManager){
  53094. Ext.tip.QuickTipManager.ddDisable();
  53095. }
  53096. if(this.dragCurrent){
  53097. // the original browser mouseup wasn't handled (e.g. outside FF browser window)
  53098. // so clean up first to avoid breaking the next drag
  53099. this.handleMouseUp(e);
  53100. }
  53101. this.currentTarget = e.getTarget();
  53102. this.dragCurrent = oDD;
  53103. var el = oDD.getEl();
  53104. // We use this to handle an issu where a mouseup will not be detected
  53105. // if the mouseup event happens outside of the browser window. When the
  53106. // mouse comes back, any drag will still be active
  53107. // http://msdn.microsoft.com/en-us/library/ms537630(VS.85).aspx
  53108. if (Ext.isIE && el.setCapture) {
  53109. el.setCapture();
  53110. }
  53111. // track start position
  53112. this.startX = e.getPageX();
  53113. this.startY = e.getPageY();
  53114. this.deltaX = this.startX - el.offsetLeft;
  53115. this.deltaY = this.startY - el.offsetTop;
  53116. this.dragThreshMet = false;
  53117. this.clickTimeout = setTimeout(
  53118. function() {
  53119. var DDM = Ext.dd.DragDropManager;
  53120. DDM.startDrag(DDM.startX, DDM.startY);
  53121. },
  53122. this.clickTimeThresh );
  53123. },
  53124. /**
  53125. * Fired when either the drag pixel threshol or the mousedown hold
  53126. * time threshold has been met.
  53127. * @param {Number} x the X position of the original mousedown
  53128. * @param {Number} y the Y position of the original mousedown
  53129. */
  53130. startDrag: function(x, y) {
  53131. clearTimeout(this.clickTimeout);
  53132. if (this.dragCurrent) {
  53133. this.dragCurrent.b4StartDrag(x, y);
  53134. this.dragCurrent.startDrag(x, y);
  53135. }
  53136. this.dragThreshMet = true;
  53137. },
  53138. /**
  53139. * Internal function to handle the mouseup event. Will be invoked
  53140. * from the context of the document.
  53141. * @param {Event} e the event
  53142. * @private
  53143. */
  53144. handleMouseUp: function(e) {
  53145. var current = this.dragCurrent;
  53146. if(Ext.tip && Ext.tip.QuickTipManager){
  53147. Ext.tip.QuickTipManager.ddEnable();
  53148. }
  53149. if (!current) {
  53150. return;
  53151. }
  53152. // See setCapture call in handleMouseDown
  53153. if (Ext.isIE && document.releaseCapture) {
  53154. document.releaseCapture();
  53155. }
  53156. clearTimeout(this.clickTimeout);
  53157. if (this.dragThreshMet) {
  53158. this.fireEvents(e, true);
  53159. }
  53160. this.stopDrag(e);
  53161. this.stopEvent(e);
  53162. },
  53163. /**
  53164. * Utility to stop event propagation and event default, if these
  53165. * features are turned on.
  53166. * @param {Event} e the event as returned by this.getEvent()
  53167. */
  53168. stopEvent: function(e){
  53169. if(this.stopPropagation) {
  53170. e.stopPropagation();
  53171. }
  53172. if (this.preventDefault) {
  53173. e.preventDefault();
  53174. }
  53175. },
  53176. /**
  53177. * Internal function to clean up event handlers after the drag
  53178. * operation is complete
  53179. * @param {Event} e the event
  53180. * @private
  53181. */
  53182. stopDrag: function(e) {
  53183. // Fire the drag end event for the item that was dragged
  53184. if (this.dragCurrent) {
  53185. if (this.dragThreshMet) {
  53186. this.dragCurrent.b4EndDrag(e);
  53187. this.dragCurrent.endDrag(e);
  53188. }
  53189. this.dragCurrent.onMouseUp(e);
  53190. }
  53191. this.dragCurrent = null;
  53192. this.dragOvers = {};
  53193. },
  53194. /**
  53195. * Internal function to handle the mousemove event. Will be invoked
  53196. * from the context of the html element.
  53197. *
  53198. * @TODO figure out what we can do about mouse events lost when the
  53199. * user drags objects beyond the window boundary. Currently we can
  53200. * detect this in internet explorer by verifying that the mouse is
  53201. * down during the mousemove event. Firefox doesn't give us the
  53202. * button state on the mousemove event.
  53203. *
  53204. * @param {Event} e the event
  53205. * @private
  53206. */
  53207. handleMouseMove: function(e) {
  53208. var me = this,
  53209. diffX,
  53210. diffY;
  53211. if (!me.dragCurrent) {
  53212. return true;
  53213. }
  53214. if (!me.dragThreshMet) {
  53215. diffX = Math.abs(me.startX - e.getPageX());
  53216. diffY = Math.abs(me.startY - e.getPageY());
  53217. if (diffX > me.clickPixelThresh ||
  53218. diffY > me.clickPixelThresh) {
  53219. me.startDrag(me.startX, me.startY);
  53220. }
  53221. }
  53222. if (me.dragThreshMet) {
  53223. me.dragCurrent.b4Drag(e);
  53224. me.dragCurrent.onDrag(e);
  53225. if(!me.dragCurrent.moveOnly){
  53226. me.fireEvents(e, false);
  53227. }
  53228. }
  53229. me.stopEvent(e);
  53230. return true;
  53231. },
  53232. /**
  53233. * Iterates over all of the DragDrop elements to find ones we are
  53234. * hovering over or dropping on
  53235. * @param {Event} e the event
  53236. * @param {Boolean} isDrop is this a drop op or a mouseover op?
  53237. * @private
  53238. */
  53239. fireEvents: function(e, isDrop) {
  53240. var me = this,
  53241. dragCurrent = me.dragCurrent,
  53242. mousePoint = e.getPoint(),
  53243. overTarget,
  53244. overTargetEl,
  53245. allTargets = [],
  53246. oldOvers = [], // cache the previous dragOver array
  53247. outEvts = [],
  53248. overEvts = [],
  53249. dropEvts = [],
  53250. enterEvts = [],
  53251. needsSort,
  53252. i,
  53253. len,
  53254. sGroup;
  53255. // If the user did the mouse up outside of the window, we could
  53256. // get here even though we have ended the drag.
  53257. if (!dragCurrent || dragCurrent.isLocked()) {
  53258. return;
  53259. }
  53260. // Check to see if the object(s) we were hovering over is no longer
  53261. // being hovered over so we can fire the onDragOut event
  53262. for (i in me.dragOvers) {
  53263. overTarget = me.dragOvers[i];
  53264. if (! me.isTypeOfDD(overTarget)) {
  53265. continue;
  53266. }
  53267. if (! this.isOverTarget(mousePoint, overTarget, me.mode)) {
  53268. outEvts.push( overTarget );
  53269. }
  53270. oldOvers[i] = true;
  53271. delete me.dragOvers[i];
  53272. }
  53273. // Collect all targets which are members of the same ddGoups that the dragCurrent is a member of, and which may recieve mouseover and drop notifications.
  53274. // This is preparatory to seeing which one(s) we are currently over
  53275. // Begin by iterating through the ddGroups of which the dragCurrent is a member
  53276. for (sGroup in dragCurrent.groups) {
  53277. if ("string" != typeof sGroup) {
  53278. continue;
  53279. }
  53280. // Loop over the registered members of each group, testing each as a potential target
  53281. for (i in me.ids[sGroup]) {
  53282. overTarget = me.ids[sGroup][i];
  53283. // The target is valid if it is a DD type
  53284. // And it's got a DOM element
  53285. // And it's configured to be a drop target
  53286. // And it's not locked
  53287. // And the DOM element is fully visible with no hidden ancestors
  53288. // And it's either not the dragCurrent, or, if it is, tha dragCurrent is configured to not ignore itself.
  53289. if (me.isTypeOfDD(overTarget) &&
  53290. (overTargetEl = overTarget.getEl()) &&
  53291. (overTarget.isTarget) &&
  53292. (!overTarget.isLocked()) &&
  53293. (Ext.fly(overTargetEl).isVisible(true)) &&
  53294. ((overTarget != dragCurrent) || (dragCurrent.ignoreSelf === false))) {
  53295. // Only sort by zIndex if there were some which had a floating zIndex value
  53296. if ((overTarget.zIndex = me.getZIndex(overTargetEl)) !== -1) {
  53297. needsSort = true;
  53298. }
  53299. allTargets.push(overTarget);
  53300. }
  53301. }
  53302. }
  53303. // If there were floating targets, sort the highest zIndex to the top
  53304. if (needsSort) {
  53305. Ext.Array.sort(allTargets, me.byZIndex);
  53306. }
  53307. // Loop through possible targets, notifying the one(s) we are over.
  53308. // Usually we only deliver events to the topmost.
  53309. for (i = 0, len = allTargets.length; i < len; i++) {
  53310. overTarget = allTargets[i];
  53311. // If we are over the overTarget, queue it up to recieve an event of whatever type we are handling
  53312. if (me.isOverTarget(mousePoint, overTarget, me.mode)) {
  53313. // look for drop interactions
  53314. if (isDrop) {
  53315. dropEvts.push( overTarget );
  53316. // look for drag enter and drag over interactions
  53317. } else {
  53318. // initial drag over: dragEnter fires
  53319. if (!oldOvers[overTarget.id]) {
  53320. enterEvts.push( overTarget );
  53321. // subsequent drag overs: dragOver fires
  53322. } else {
  53323. overEvts.push( overTarget );
  53324. }
  53325. me.dragOvers[overTarget.id] = overTarget;
  53326. }
  53327. // Unless this DragDropManager has been explicitly configured to deliver events to multiple targets, then we are done.
  53328. if (!me.notifyOccluded) {
  53329. break;
  53330. }
  53331. }
  53332. }
  53333. if (me.mode) {
  53334. if (outEvts.length) {
  53335. dragCurrent.b4DragOut(e, outEvts);
  53336. dragCurrent.onDragOut(e, outEvts);
  53337. }
  53338. if (enterEvts.length) {
  53339. dragCurrent.onDragEnter(e, enterEvts);
  53340. }
  53341. if (overEvts.length) {
  53342. dragCurrent.b4DragOver(e, overEvts);
  53343. dragCurrent.onDragOver(e, overEvts);
  53344. }
  53345. if (dropEvts.length) {
  53346. dragCurrent.b4DragDrop(e, dropEvts);
  53347. dragCurrent.onDragDrop(e, dropEvts);
  53348. }
  53349. } else {
  53350. // fire dragout events
  53351. for (i=0, len=outEvts.length; i<len; ++i) {
  53352. dragCurrent.b4DragOut(e, outEvts[i].id);
  53353. dragCurrent.onDragOut(e, outEvts[i].id);
  53354. }
  53355. // fire enter events
  53356. for (i=0,len=enterEvts.length; i<len; ++i) {
  53357. // dc.b4DragEnter(e, oDD.id);
  53358. dragCurrent.onDragEnter(e, enterEvts[i].id);
  53359. }
  53360. // fire over events
  53361. for (i=0,len=overEvts.length; i<len; ++i) {
  53362. dragCurrent.b4DragOver(e, overEvts[i].id);
  53363. dragCurrent.onDragOver(e, overEvts[i].id);
  53364. }
  53365. // fire drop events
  53366. for (i=0, len=dropEvts.length; i<len; ++i) {
  53367. dragCurrent.b4DragDrop(e, dropEvts[i].id);
  53368. dragCurrent.onDragDrop(e, dropEvts[i].id);
  53369. }
  53370. }
  53371. // notify about a drop that did not find a target
  53372. if (isDrop && !dropEvts.length) {
  53373. dragCurrent.onInvalidDrop(e);
  53374. }
  53375. },
  53376. /**
  53377. * @private
  53378. * Collects the z-index of the passed element, looking up the parentNode axis to find an absolutely positioned ancestor
  53379. * which is able to yield a z-index. If found to be not absolutely positionedm returns -1.
  53380. *
  53381. * This is used when sorting potential drop targets into z-index order so that only the topmost receives `over` and `drop` events.
  53382. *
  53383. * @return {Number} The z-index of the element, or of its topmost absolutely positioned ancestor. Returns -1 if the element is not
  53384. * absolutely positioned.
  53385. */
  53386. getZIndex: function(element) {
  53387. var body = document.body,
  53388. z,
  53389. zIndex = -1;
  53390. element = Ext.getDom(element);
  53391. while (element !== body) {
  53392. if (!isNaN(z = Number(Ext.fly(element).getStyle('zIndex')))) {
  53393. zIndex = z;
  53394. }
  53395. element = element.parentNode;
  53396. }
  53397. return zIndex;
  53398. },
  53399. /**
  53400. * @private
  53401. * Utility method to pass to {@link Ext.Array#sort} when sorting potential drop targets by z-index.
  53402. */
  53403. byZIndex: function(d1, d2) {
  53404. return d1.zIndex < d2.zIndex;
  53405. },
  53406. /**
  53407. * Helper function for getting the best match from the list of drag
  53408. * and drop objects returned by the drag and drop events when we are
  53409. * in INTERSECT mode. It returns either the first object that the
  53410. * cursor is over, or the object that has the greatest overlap with
  53411. * the dragged element.
  53412. * @param {Ext.dd.DragDrop[]} dds The array of drag and drop objects
  53413. * targeted
  53414. * @return {Ext.dd.DragDrop} The best single match
  53415. */
  53416. getBestMatch: function(dds) {
  53417. var winner = null,
  53418. len = dds.length,
  53419. i, dd;
  53420. // Return null if the input is not what we expect
  53421. //if (!dds || !dds.length || dds.length == 0) {
  53422. // winner = null;
  53423. // If there is only one item, it wins
  53424. //} else if (dds.length == 1) {
  53425. if (len == 1) {
  53426. winner = dds[0];
  53427. } else {
  53428. // Loop through the targeted items
  53429. for (i=0; i<len; ++i) {
  53430. dd = dds[i];
  53431. // If the cursor is over the object, it wins. If the
  53432. // cursor is over multiple matches, the first one we come
  53433. // to wins.
  53434. if (dd.cursorIsOver) {
  53435. winner = dd;
  53436. break;
  53437. // Otherwise the object with the most overlap wins
  53438. } else {
  53439. if (!winner ||
  53440. winner.overlap.getArea() < dd.overlap.getArea()) {
  53441. winner = dd;
  53442. }
  53443. }
  53444. }
  53445. }
  53446. return winner;
  53447. },
  53448. /**
  53449. * Refreshes the cache of the top-left and bottom-right points of the
  53450. * drag and drop objects in the specified group(s). This is in the
  53451. * format that is stored in the drag and drop instance, so typical
  53452. * usage is:
  53453. *
  53454. * Ext.dd.DragDropManager.refreshCache(ddinstance.groups);
  53455. *
  53456. * Alternatively:
  53457. *
  53458. * Ext.dd.DragDropManager.refreshCache({group1:true, group2:true});
  53459. *
  53460. * @TODO this really should be an indexed array. Alternatively this
  53461. * method could accept both.
  53462. *
  53463. * @param {Object} groups an associative array of groups to refresh
  53464. */
  53465. refreshCache: function(groups) {
  53466. var sGroup, i, oDD, loc;
  53467. for (sGroup in groups) {
  53468. if ("string" != typeof sGroup) {
  53469. continue;
  53470. }
  53471. for (i in this.ids[sGroup]) {
  53472. oDD = this.ids[sGroup][i];
  53473. if (this.isTypeOfDD(oDD)) {
  53474. // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
  53475. loc = this.getLocation(oDD);
  53476. if (loc) {
  53477. this.locationCache[oDD.id] = loc;
  53478. } else {
  53479. delete this.locationCache[oDD.id];
  53480. // this will unregister the drag and drop object if
  53481. // the element is not in a usable state
  53482. // oDD.unreg();
  53483. }
  53484. }
  53485. }
  53486. }
  53487. },
  53488. /**
  53489. * This checks to make sure an element exists and is in the DOM. The
  53490. * main purpose is to handle cases where innerHTML is used to remove
  53491. * drag and drop objects from the DOM. IE provides an 'unspecified
  53492. * error' when trying to access the offsetParent of such an element
  53493. * @param {HTMLElement} el the element to check
  53494. * @return {Boolean} true if the element looks usable
  53495. */
  53496. verifyEl: function(el) {
  53497. if (el) {
  53498. var parent;
  53499. if(Ext.isIE){
  53500. try{
  53501. parent = el.offsetParent;
  53502. }catch(e){}
  53503. }else{
  53504. parent = el.offsetParent;
  53505. }
  53506. if (parent) {
  53507. return true;
  53508. }
  53509. }
  53510. return false;
  53511. },
  53512. /**
  53513. * Returns a Region object containing the drag and drop element's position
  53514. * and size, including the padding configured for it
  53515. * @param {Ext.dd.DragDrop} oDD the drag and drop object to get the location for.
  53516. * @return {Ext.util.Region} a Region object representing the total area
  53517. * the element occupies, including any padding
  53518. * the instance is configured for.
  53519. */
  53520. getLocation: function(oDD) {
  53521. if (! this.isTypeOfDD(oDD)) {
  53522. return null;
  53523. }
  53524. //delegate getLocation method to the
  53525. //drag and drop target.
  53526. if (oDD.getRegion) {
  53527. return oDD.getRegion();
  53528. }
  53529. var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
  53530. try {
  53531. pos= Ext.Element.getXY(el);
  53532. } catch (e) { }
  53533. if (!pos) {
  53534. return null;
  53535. }
  53536. x1 = pos[0];
  53537. x2 = x1 + el.offsetWidth;
  53538. y1 = pos[1];
  53539. y2 = y1 + el.offsetHeight;
  53540. t = y1 - oDD.padding[0];
  53541. r = x2 + oDD.padding[1];
  53542. b = y2 + oDD.padding[2];
  53543. l = x1 - oDD.padding[3];
  53544. return new Ext.util.Region(t, r, b, l);
  53545. },
  53546. /**
  53547. * Checks the cursor location to see if it over the target
  53548. * @param {Ext.util.Point} pt The point to evaluate
  53549. * @param {Ext.dd.DragDrop} oTarget the DragDrop object we are inspecting
  53550. * @return {Boolean} true if the mouse is over the target
  53551. * @private
  53552. */
  53553. isOverTarget: function(pt, oTarget, intersect) {
  53554. // use cache if available
  53555. var loc = this.locationCache[oTarget.id],
  53556. dc, pos, el, curRegion, overlap;
  53557. if (!loc || !this.useCache) {
  53558. loc = this.getLocation(oTarget);
  53559. this.locationCache[oTarget.id] = loc;
  53560. }
  53561. if (!loc) {
  53562. return false;
  53563. }
  53564. oTarget.cursorIsOver = loc.contains( pt );
  53565. // DragDrop is using this as a sanity check for the initial mousedown
  53566. // in this case we are done. In POINT mode, if the drag obj has no
  53567. // contraints, we are also done. Otherwise we need to evaluate the
  53568. // location of the target as related to the actual location of the
  53569. // dragged element.
  53570. dc = this.dragCurrent;
  53571. if (!dc || !dc.getTargetCoord ||
  53572. (!intersect && !dc.constrainX && !dc.constrainY)) {
  53573. return oTarget.cursorIsOver;
  53574. }
  53575. oTarget.overlap = null;
  53576. // Get the current location of the drag element, this is the
  53577. // location of the mouse event less the delta that represents
  53578. // where the original mousedown happened on the element. We
  53579. // need to consider constraints and ticks as well.
  53580. pos = dc.getTargetCoord(pt.x, pt.y);
  53581. el = dc.getDragEl();
  53582. curRegion = new Ext.util.Region(pos.y,
  53583. pos.x + el.offsetWidth,
  53584. pos.y + el.offsetHeight,
  53585. pos.x );
  53586. overlap = curRegion.intersect(loc);
  53587. if (overlap) {
  53588. oTarget.overlap = overlap;
  53589. return (intersect) ? true : oTarget.cursorIsOver;
  53590. } else {
  53591. return false;
  53592. }
  53593. },
  53594. /**
  53595. * unload event handler
  53596. * @private
  53597. */
  53598. _onUnload: function(e, me) {
  53599. Ext.dd.DragDropManager.unregAll();
  53600. },
  53601. /**
  53602. * Cleans up the drag and drop events and objects.
  53603. * @private
  53604. */
  53605. unregAll: function() {
  53606. if (this.dragCurrent) {
  53607. this.stopDrag();
  53608. this.dragCurrent = null;
  53609. }
  53610. this._execOnAll("unreg", []);
  53611. for (var i in this.elementCache) {
  53612. delete this.elementCache[i];
  53613. }
  53614. this.elementCache = {};
  53615. this.ids = {};
  53616. },
  53617. /**
  53618. * @property {Object} elementCache
  53619. * A cache of DOM elements
  53620. * @private
  53621. */
  53622. elementCache: {},
  53623. /**
  53624. * Get the wrapper for the DOM element specified
  53625. * @param {String} id the id of the element to get
  53626. * @return {Ext.dd.DragDropManager.ElementWrapper} the wrapped element
  53627. * @private
  53628. * @deprecated This wrapper isn't that useful
  53629. */
  53630. getElWrapper: function(id) {
  53631. var oWrapper = this.elementCache[id];
  53632. if (!oWrapper || !oWrapper.el) {
  53633. oWrapper = this.elementCache[id] =
  53634. new this.ElementWrapper(Ext.getDom(id));
  53635. }
  53636. return oWrapper;
  53637. },
  53638. /**
  53639. * Returns the actual DOM element
  53640. * @param {String} id the id of the elment to get
  53641. * @return {Object} The element
  53642. * @deprecated use Ext.lib.Ext.getDom instead
  53643. */
  53644. getElement: function(id) {
  53645. return Ext.getDom(id);
  53646. },
  53647. /**
  53648. * Returns the style property for the DOM element (i.e.,
  53649. * document.getElById(id).style)
  53650. * @param {String} id the id of the elment to get
  53651. * @return {Object} The style property of the element
  53652. */
  53653. getCss: function(id) {
  53654. var el = Ext.getDom(id);
  53655. return (el) ? el.style : null;
  53656. },
  53657. /**
  53658. * @class Ext.dd.DragDropManager.ElementWrapper
  53659. * Deprecated inner class for cached elements.
  53660. * @private
  53661. * @deprecated This wrapper isn't that useful
  53662. */
  53663. ElementWrapper: function(el) {
  53664. /** The element */
  53665. this.el = el || null;
  53666. /** The element id */
  53667. this.id = this.el && el.id;
  53668. /** A reference to the style property */
  53669. this.css = this.el && el.style;
  53670. },
  53671. // Continue class docs
  53672. /** @class Ext.dd.DragDropElement */
  53673. /**
  53674. * Returns the X position of an html element
  53675. * @param {HTMLElement} el the element for which to get the position
  53676. * @return {Number} the X coordinate
  53677. */
  53678. getPosX: function(el) {
  53679. return Ext.Element.getX(el);
  53680. },
  53681. /**
  53682. * Returns the Y position of an html element
  53683. * @param {HTMLElement} el the element for which to get the position
  53684. * @return {Number} the Y coordinate
  53685. */
  53686. getPosY: function(el) {
  53687. return Ext.Element.getY(el);
  53688. },
  53689. /**
  53690. * Swap two nodes. In IE, we use the native method, for others we
  53691. * emulate the IE behavior
  53692. * @param {HTMLElement} n1 the first node to swap
  53693. * @param {HTMLElement} n2 the other node to swap
  53694. */
  53695. swapNode: function(n1, n2) {
  53696. if (n1.swapNode) {
  53697. n1.swapNode(n2);
  53698. } else {
  53699. var p = n2.parentNode,
  53700. s = n2.nextSibling;
  53701. if (s == n1) {
  53702. p.insertBefore(n1, n2);
  53703. } else if (n2 == n1.nextSibling) {
  53704. p.insertBefore(n2, n1);
  53705. } else {
  53706. n1.parentNode.replaceChild(n2, n1);
  53707. p.insertBefore(n1, s);
  53708. }
  53709. }
  53710. },
  53711. /**
  53712. * Returns the current scroll position
  53713. * @private
  53714. */
  53715. getScroll: function () {
  53716. var doc = window.document,
  53717. docEl = doc.documentElement,
  53718. body = doc.body,
  53719. top = 0,
  53720. left = 0;
  53721. if (Ext.isGecko4) {
  53722. top = window.scrollYOffset;
  53723. left = window.scrollXOffset;
  53724. } else {
  53725. if (docEl && (docEl.scrollTop || docEl.scrollLeft)) {
  53726. top = docEl.scrollTop;
  53727. left = docEl.scrollLeft;
  53728. } else if (body) {
  53729. top = body.scrollTop;
  53730. left = body.scrollLeft;
  53731. }
  53732. }
  53733. return {
  53734. top: top,
  53735. left: left
  53736. };
  53737. },
  53738. /**
  53739. * Returns the specified element style property
  53740. * @param {HTMLElement} el the element
  53741. * @param {String} styleProp the style property
  53742. * @return {String} The value of the style property
  53743. */
  53744. getStyle: function(el, styleProp) {
  53745. return Ext.fly(el).getStyle(styleProp);
  53746. },
  53747. /**
  53748. * Gets the scrollTop
  53749. * @return {Number} the document's scrollTop
  53750. */
  53751. getScrollTop: function () {
  53752. return this.getScroll().top;
  53753. },
  53754. /**
  53755. * Gets the scrollLeft
  53756. * @return {Number} the document's scrollTop
  53757. */
  53758. getScrollLeft: function () {
  53759. return this.getScroll().left;
  53760. },
  53761. /**
  53762. * Sets the x/y position of an element to the location of the
  53763. * target element.
  53764. * @param {HTMLElement} moveEl The element to move
  53765. * @param {HTMLElement} targetEl The position reference element
  53766. */
  53767. moveToEl: function (moveEl, targetEl) {
  53768. var aCoord = Ext.Element.getXY(targetEl);
  53769. Ext.Element.setXY(moveEl, aCoord);
  53770. },
  53771. /**
  53772. * Numeric array sort function
  53773. * @param {Number} a
  53774. * @param {Number} b
  53775. * @returns {Number} positive, negative or 0
  53776. */
  53777. numericSort: function(a, b) {
  53778. return (a - b);
  53779. },
  53780. /**
  53781. * @property {Number} _timeoutCount
  53782. * Internal counter
  53783. * @private
  53784. */
  53785. _timeoutCount: 0,
  53786. /**
  53787. * Trying to make the load order less important. Without this we get
  53788. * an error if this file is loaded before the Event Utility.
  53789. * @private
  53790. */
  53791. _addListeners: function() {
  53792. if ( document ) {
  53793. this._onLoad();
  53794. } else {
  53795. if (this._timeoutCount <= 2000) {
  53796. setTimeout(this._addListeners, 10);
  53797. if (document && document.body) {
  53798. this._timeoutCount += 1;
  53799. }
  53800. }
  53801. }
  53802. },
  53803. /**
  53804. * Recursively searches the immediate parent and all child nodes for
  53805. * the handle element in order to determine wheter or not it was
  53806. * clicked.
  53807. * @param {HTMLElement} node the html element to inspect
  53808. */
  53809. handleWasClicked: function(node, id) {
  53810. if (this.isHandle(id, node.id)) {
  53811. return true;
  53812. } else {
  53813. // check to see if this is a text node child of the one we want
  53814. var p = node.parentNode;
  53815. while (p) {
  53816. if (this.isHandle(id, p.id)) {
  53817. return true;
  53818. } else {
  53819. p = p.parentNode;
  53820. }
  53821. }
  53822. }
  53823. return false;
  53824. }
  53825. }, function() {
  53826. this._addListeners();
  53827. });
  53828. /**
  53829. * Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.
  53830. */
  53831. Ext.define('Ext.layout.container.Box', {
  53832. /* Begin Definitions */
  53833. alias: ['layout.box'],
  53834. extend: 'Ext.layout.container.Container',
  53835. alternateClassName: 'Ext.layout.BoxLayout',
  53836. requires: [
  53837. 'Ext.layout.container.boxOverflow.None',
  53838. 'Ext.layout.container.boxOverflow.Menu',
  53839. 'Ext.layout.container.boxOverflow.Scroller',
  53840. 'Ext.util.Format',
  53841. 'Ext.dd.DragDropManager'
  53842. ],
  53843. /* End Definitions */
  53844. /**
  53845. * @cfg {Object} defaultMargins
  53846. * If the individual contained items do not have a margins property specified or margin specified via CSS, the
  53847. * default margins from this property will be applied to each item.
  53848. *
  53849. * This property may be specified as an object containing margins to apply in the format:
  53850. *
  53851. * {
  53852. * top: (top margin),
  53853. * right: (right margin),
  53854. * bottom: (bottom margin),
  53855. * left: (left margin)
  53856. * }
  53857. *
  53858. * This property may also be specified as a string containing space-separated, numeric margin values. The order of
  53859. * the sides associated with each value matches the way CSS processes margin values:
  53860. *
  53861. * - If there is only one value, it applies to all sides.
  53862. * - If there are two values, the top and bottom borders are set to the first value and the right and left are
  53863. * set to the second.
  53864. * - If there are three values, the top is set to the first value, the left and right are set to the second,
  53865. * and the bottom is set to the third.
  53866. * - If there are four values, they apply to the top, right, bottom, and left, respectively.
  53867. */
  53868. defaultMargins: {
  53869. top: 0,
  53870. right: 0,
  53871. bottom: 0,
  53872. left: 0
  53873. },
  53874. /**
  53875. * @cfg {String} padding
  53876. * Sets the padding to be applied to all child items managed by this layout.
  53877. *
  53878. * This property must be specified as a string containing space-separated, numeric padding values. The order of the
  53879. * sides associated with each value matches the way CSS processes padding values:
  53880. *
  53881. * - If there is only one value, it applies to all sides.
  53882. * - If there are two values, the top and bottom borders are set to the first value and the right and left are
  53883. * set to the second.
  53884. * - If there are three values, the top is set to the first value, the left and right are set to the second,
  53885. * and the bottom is set to the third.
  53886. * - If there are four values, they apply to the top, right, bottom, and left, respectively.
  53887. */
  53888. padding: 0,
  53889. /**
  53890. * @cfg {String} pack
  53891. * Controls how the child items of the container are packed together. Acceptable configuration values for this
  53892. * property are:
  53893. *
  53894. * - **start** - child items are packed together at **left** (HBox) or **top** (VBox) side of container (*default**)
  53895. * - **center** - child items are packed together at **mid-width** (HBox) or **mid-height** (VBox) of container
  53896. * - **end** - child items are packed together at **right** (HBox) or **bottom** (VBox) side of container
  53897. */
  53898. pack: 'start',
  53899. /**
  53900. * @cfg {Number} flex
  53901. * This configuration option is to be applied to **child items** of the container managed by this layout. Each child
  53902. * item with a flex property will be flexed (horizontally in `hbox`, vertically in `vbox`) according to each item's
  53903. * **relative** flex value compared to the sum of all items with a flex value specified. Any child items that have
  53904. * either a `flex = 0` or `flex = undefined` will not be 'flexed' (the initial size will not be changed).
  53905. */
  53906. flex: undefined,
  53907. /**
  53908. * @cfg {String/Ext.Component} stretchMaxPartner
  53909. * Allows stretchMax calculation to take into account the max perpendicular size (height for HBox layout and width
  53910. * for VBox layout) of another Box layout when calculating its maximum perpendicular child size.
  53911. *
  53912. * If specified as a string, this may be either a known Container ID, or a ComponentQuery selector which is rooted
  53913. * at this layout's Container (ie, to find a sibling, use `"^>#siblingItemId`).
  53914. */
  53915. stretchMaxPartner: undefined,
  53916. type: 'box',
  53917. scrollOffset: 0,
  53918. itemCls: Ext.baseCSSPrefix + 'box-item',
  53919. targetCls: Ext.baseCSSPrefix + 'box-layout-ct',
  53920. innerCls: Ext.baseCSSPrefix + 'box-inner',
  53921. // availableSpaceOffset is used to adjust the availableWidth, typically used
  53922. // to reserve space for a scrollbar
  53923. availableSpaceOffset: 0,
  53924. // whether or not to reserve the availableSpaceOffset in layout calculations
  53925. reserveOffset: true,
  53926. manageMargins: true,
  53927. childEls: [
  53928. 'innerCt',
  53929. 'targetEl'
  53930. ],
  53931. renderTpl: [
  53932. '{%var oc,l=values.$comp.layout,oh=l.overflowHandler;',
  53933. 'if (oh.getPrefixConfig!==Ext.emptyFn) {',
  53934. 'if(oc=oh.getPrefixConfig())dh.generateMarkup(oc, out)',
  53935. '}%}',
  53936. '<div id="{ownerId}-innerCt" class="{[l.innerCls]} {[oh.getOverflowCls()]}" role="presentation">',
  53937. '<div id="{ownerId}-targetEl" style="position:absolute;',
  53938. // This width for the "CSS container box" of the box child items gives
  53939. // them the room they need to avoid being "crushed" (aka, "wrapped").
  53940. // On Opera, elements cannot be wider than 32767px or else they break
  53941. // the scrollWidth (it becomes == offsetWidth) and you cannot scroll
  53942. // the content.
  53943. 'width:20000px;',
  53944. // On IE quirks and IE6/7 strict, a text-align:center style trickles
  53945. // down to this el at times and will cause it to move off the left edge.
  53946. // The easy fix is to just always set left:0px here. The top:0px part
  53947. // is just being paranoid. The requirement for targetEl is that its
  53948. // origin align with innerCt... this ensures that it does!
  53949. 'left:0px;top:0px;',
  53950. // If we don't give the element a height, it does not always participate
  53951. // in the scrollWidth.
  53952. 'height:1px">',
  53953. '{%this.renderBody(out, values)%}',
  53954. '</div>',
  53955. '</div>',
  53956. '{%if (oh.getSuffixConfig!==Ext.emptyFn) {',
  53957. 'if(oc=oh.getSuffixConfig())dh.generateMarkup(oc, out)',
  53958. '}%}',
  53959. {
  53960. disableFormats: true,
  53961. definitions: 'var dh=Ext.DomHelper;'
  53962. }
  53963. ],
  53964. constructor: function(config) {
  53965. var me = this,
  53966. type;
  53967. me.callParent(arguments);
  53968. // The sort function needs access to properties in this, so must be bound.
  53969. me.flexSortFn = Ext.Function.bind(me.flexSort, me);
  53970. me.initOverflowHandler();
  53971. type = typeof me.padding;
  53972. if (type == 'string' || type == 'number') {
  53973. me.padding = Ext.util.Format.parseBox(me.padding);
  53974. me.padding.height = me.padding.top + me.padding.bottom;
  53975. me.padding.width = me.padding.left + me.padding.right;
  53976. }
  53977. },
  53978. getNames: function () {
  53979. return this.names;
  53980. },
  53981. // Matches: <spaces>digits[.digits]<spaces>%<spaces>
  53982. // Captures: digits[.digits]
  53983. _percentageRe: /^\s*(\d+(?:\.\d*)?)\s*[%]\s*$/,
  53984. getItemSizePolicy: function (item, ownerSizeModel) {
  53985. var me = this,
  53986. policy = me.sizePolicy,
  53987. align = me.align,
  53988. flex = item.flex,
  53989. key = align,
  53990. names = me.names,
  53991. width = item[names.width],
  53992. height = item[names.height],
  53993. percentageRe = me._percentageRe,
  53994. percentageWidth = percentageRe.test(width),
  53995. isStretch = (align == 'stretch');
  53996. if ((isStretch || flex || percentageWidth) && !ownerSizeModel) {
  53997. ownerSizeModel = me.owner.getSizeModel();
  53998. }
  53999. if (isStretch) {
  54000. // If we are height.shrinkWrap, we behave as if we were stretchmax (for more
  54001. // details, see beginLayoutCycle)...
  54002. if (!percentageRe.test(height) && ownerSizeModel[names.height].shrinkWrap) {
  54003. key = 'stretchmax';
  54004. // We leave %age height as stretch since it will not participate in the
  54005. // stretchmax size calculation. This avoid running such a child in its
  54006. // shrinkWrap mode prior to supplying the calculated size.
  54007. }
  54008. } else if (align != 'stretchmax') {
  54009. if (percentageRe.test(height)) {
  54010. // Height %ages are calculated based on container size, so they are the
  54011. // same as align=stretch for this purpose...
  54012. key = 'stretch';
  54013. } else {
  54014. key = '';
  54015. }
  54016. }
  54017. if (flex || percentageWidth) {
  54018. // If we are width.shrinkWrap, we won't be flexing since that requires a
  54019. // container width...
  54020. if (!ownerSizeModel[names.width].shrinkWrap) {
  54021. policy = policy.flex; // both flex and %age width are calculated
  54022. }
  54023. }
  54024. return policy[key];
  54025. },
  54026. flexSort: function (a, b) {
  54027. var maxWidthName = this.getNames().maxWidth,
  54028. infiniteValue = Infinity;
  54029. a = a.target[maxWidthName] || infiniteValue;
  54030. b = b.target[maxWidthName] || infiniteValue;
  54031. // IE 6/7 Don't like Infinity - Infinity...
  54032. if (!isFinite(a) && !isFinite(b)) {
  54033. return 0;
  54034. }
  54035. return a - b;
  54036. },
  54037. isItemBoxParent: function (itemContext) {
  54038. return true;
  54039. },
  54040. isItemShrinkWrap: function (item) {
  54041. return true;
  54042. },
  54043. // Sort into *descending* order.
  54044. minSizeSortFn: function(a, b) {
  54045. return b.available - a.available;
  54046. },
  54047. roundFlex: function(width) {
  54048. return Math.ceil(width);
  54049. },
  54050. /**
  54051. * @private
  54052. * Called by an owning Panel before the Panel begins its collapse process.
  54053. * Most layouts will not need to override the default Ext.emptyFn implementation.
  54054. */
  54055. beginCollapse: function(child) {
  54056. var me = this;
  54057. if (me.direction === 'vertical' && child.collapsedVertical()) {
  54058. child.collapseMemento.capture(['flex']);
  54059. delete child.flex;
  54060. } else if (me.direction === 'horizontal' && child.collapsedHorizontal()) {
  54061. child.collapseMemento.capture(['flex']);
  54062. delete child.flex;
  54063. }
  54064. },
  54065. /**
  54066. * @private
  54067. * Called by an owning Panel before the Panel begins its expand process.
  54068. * Most layouts will not need to override the default Ext.emptyFn implementation.
  54069. */
  54070. beginExpand: function(child) {
  54071. // Restores the flex if we used to be flexed before
  54072. child.collapseMemento.restore(['flex']);
  54073. },
  54074. beginLayout: function (ownerContext) {
  54075. var me = this,
  54076. smp = me.owner.stretchMaxPartner,
  54077. style = me.innerCt.dom.style,
  54078. names = me.getNames();
  54079. ownerContext.boxNames = names;
  54080. // this must happen before callParent to allow the overflow handler to do its work
  54081. // that can effect the childItems collection...
  54082. me.overflowHandler.beginLayout(ownerContext);
  54083. // get the contextItem for our stretchMax buddy:
  54084. if (typeof smp === 'string') {
  54085. smp = Ext.getCmp(smp) || me.owner.query(smp)[0];
  54086. }
  54087. ownerContext.stretchMaxPartner = smp && ownerContext.context.getCmp(smp);
  54088. me.callParent(arguments);
  54089. ownerContext.innerCtContext = ownerContext.getEl('innerCt', me);
  54090. // Capture whether the owning Container is scrolling in the parallel direction
  54091. me.scrollParallel = !!(me.owner.autoScroll || me.owner[names.overflowX]);
  54092. // Capture whether the owning Container is scrolling in the perpendicular direction
  54093. me.scrollPerpendicular = !!(me.owner.autoScroll || me.owner[names.overflowY]);
  54094. // If we *are* scrolling parallel, capture the scroll position of the encapsulating element
  54095. if (me.scrollParallel) {
  54096. me.scrollPos = me.owner.getTargetEl().dom[names.scrollLeft];
  54097. }
  54098. // Don't allow sizes burned on to the innerCt to influence measurements.
  54099. style.width = '';
  54100. style.height = '';
  54101. },
  54102. beginLayoutCycle: function (ownerContext, firstCycle) {
  54103. var me = this,
  54104. align = me.align,
  54105. names = ownerContext.boxNames,
  54106. pack = me.pack,
  54107. heightModelName = names.heightModel;
  54108. // this must happen before callParent to allow the overflow handler to do its work
  54109. // that can effect the childItems collection...
  54110. me.overflowHandler.beginLayoutCycle(ownerContext, firstCycle);
  54111. me.callParent(arguments);
  54112. // Cache several of our string concat/compare results (since width/heightModel can
  54113. // change if we are invalidated, we cannot do this in beginLayout)
  54114. ownerContext.parallelSizeModel = ownerContext[names.widthModel];
  54115. ownerContext.perpendicularSizeModel = ownerContext[heightModelName];
  54116. ownerContext.boxOptions = {
  54117. align: align = {
  54118. stretch: align == 'stretch',
  54119. stretchmax: align == 'stretchmax',
  54120. center: align == names.center
  54121. },
  54122. pack: pack = {
  54123. center: pack == 'center',
  54124. end: pack == 'end'
  54125. }
  54126. };
  54127. // Consider an hbox w/stretch which means "assign all items the container's height".
  54128. // The spirit of this request is make all items the same height, but when shrinkWrap
  54129. // height is also requested, the height of the tallest item determines the height.
  54130. // This is exactly what the stretchmax option does, so we jiggle the flags here to
  54131. // act as if stretchmax were requested.
  54132. if (align.stretch && ownerContext.perpendicularSizeModel.shrinkWrap) {
  54133. align.stretchmax = true;
  54134. align.stretch = false;
  54135. }
  54136. // This is handy for knowing that we might need to apply height %ages
  54137. align.nostretch = !(align.stretch || align.stretchmax);
  54138. // In our example hbox, packing items to the right (end) or center can only work if
  54139. // there is a container width. So, if we are shrinkWrap, we just turn off the pack
  54140. // options for the run.
  54141. if (ownerContext.parallelSizeModel.shrinkWrap) {
  54142. pack.center = pack.end = false;
  54143. }
  54144. me.cacheFlexes(ownerContext);
  54145. // In webkit we set the width of the target el equal to the width of the innerCt
  54146. // when the layout cycle is finished, so we need to set it back to 20000px here
  54147. // to prevent the children from being crushed.
  54148. if (Ext.isWebKit) {
  54149. me.targetEl.setWidth(20000);
  54150. }
  54151. },
  54152. /**
  54153. * This method is called to (re)cache our understanding of flexes. This happens during beginLayout and may need to
  54154. * be called again if the flexes are changed during the layout (e.g., like ColumnLayout).
  54155. * @param {Object} ownerContext
  54156. * @protected
  54157. */
  54158. cacheFlexes: function (ownerContext) {
  54159. var me = this,
  54160. names = ownerContext.boxNames,
  54161. widthModelName = names.widthModel,
  54162. heightModelName = names.heightModel,
  54163. nostretch = ownerContext.boxOptions.align.nostretch,
  54164. totalFlex = 0,
  54165. childItems = ownerContext.childItems,
  54166. i = childItems.length,
  54167. flexedItems = [],
  54168. minWidth = 0,
  54169. minWidthName = names.minWidth,
  54170. percentageRe = me._percentageRe,
  54171. percentageWidths = 0,
  54172. percentageHeights = 0,
  54173. child, childContext, flex, match;
  54174. while (i--) {
  54175. childContext = childItems[i];
  54176. child = childContext.target;
  54177. // check widthModel to see if we are the sizing layout. If so, copy the flex
  54178. // from the item to the contextItem and add it to totalFlex
  54179. //
  54180. if (childContext[widthModelName].calculated) {
  54181. childContext.flex = flex = child.flex;
  54182. if (flex) {
  54183. totalFlex += flex;
  54184. flexedItems.push(childContext);
  54185. minWidth += child[minWidthName] || 0;
  54186. } else { // a %age width...
  54187. match = percentageRe.exec(child[names.width]);
  54188. childContext.percentageParallel = parseFloat(match[1]) / 100;
  54189. ++percentageWidths;
  54190. }
  54191. }
  54192. // the above means that "childContext.flex" is properly truthy/falsy, which is
  54193. // often times quite convenient...
  54194. if (nostretch && childContext[heightModelName].calculated) {
  54195. // the only reason we would be calculated height in this case is due to a
  54196. // height %age...
  54197. match = percentageRe.exec(child[names.height]);
  54198. childContext.percentagePerpendicular = parseFloat(match[1]) / 100;
  54199. ++percentageHeights;
  54200. }
  54201. }
  54202. ownerContext.flexedItems = flexedItems;
  54203. ownerContext.flexedMinSize = minWidth;
  54204. ownerContext.totalFlex = totalFlex;
  54205. ownerContext.percentageWidths = percentageWidths;
  54206. ownerContext.percentageHeights = percentageHeights;
  54207. // The flexed boxes need to be sorted in ascending order of maxSize to work properly
  54208. // so that unallocated space caused by maxWidth being less than flexed width can be
  54209. // reallocated to subsequent flexed boxes.
  54210. Ext.Array.sort(flexedItems, me.flexSortFn);
  54211. },
  54212. calculate: function(ownerContext) {
  54213. var me = this,
  54214. targetSize = me.getContainerSize(ownerContext),
  54215. names = ownerContext.boxNames,
  54216. state = ownerContext.state,
  54217. plan = state.boxPlan || (state.boxPlan = {});
  54218. plan.targetSize = targetSize;
  54219. // If we are not widthModel.shrinkWrap, we need the width before we can lay out boxes:
  54220. if (!ownerContext.parallelSizeModel.shrinkWrap && !targetSize[names.gotWidth]) {
  54221. me.done = false;
  54222. return;
  54223. }
  54224. if (!state.parallelDone) {
  54225. state.parallelDone = me.calculateParallel(ownerContext, names, plan);
  54226. }
  54227. if (!state.perpendicularDone) {
  54228. state.perpendicularDone = me.calculatePerpendicular(ownerContext, names, plan);
  54229. }
  54230. if (state.parallelDone && state.perpendicularDone) {
  54231. // Fix for left and right docked Components in a dock component layout. This is for docked Headers and docked Toolbars.
  54232. // Older Microsoft browsers do not size a position:absolute element's width to match its content.
  54233. // So in this case, in the publishInnerCtSize method we may need to adjust the size of the owning Container's element explicitly based upon
  54234. // the discovered max width. So here we put a calculatedWidth property in the metadata to facilitate this.
  54235. if (me.owner.dock && (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) && !me.owner.width && !me.horizontal) {
  54236. plan.isIEVerticalDock = true;
  54237. plan.calculatedWidth = plan.maxSize + ownerContext.getPaddingInfo().width + ownerContext.getFrameInfo().width;
  54238. }
  54239. me.publishInnerCtSize(ownerContext, me.reserveOffset ? me.availableSpaceOffset : 0);
  54240. // Calculate stretchmax only if there is >1 child item
  54241. if (me.done && ownerContext.childItems.length > 1 && ownerContext.boxOptions.align.stretchmax && !state.stretchMaxDone) {
  54242. me.calculateStretchMax(ownerContext, names, plan);
  54243. state.stretchMaxDone = true;
  54244. }
  54245. } else {
  54246. me.done = false;
  54247. }
  54248. },
  54249. calculateParallel: function(ownerContext, names, plan) {
  54250. var me = this,
  54251. widthName = names.width,
  54252. childItems = ownerContext.childItems,
  54253. leftName = names.left,
  54254. rightName = names.right,
  54255. setWidthName = names.setWidth,
  54256. childItemsLength = childItems.length,
  54257. flexedItems = ownerContext.flexedItems,
  54258. flexedItemsLength = flexedItems.length,
  54259. pack = ownerContext.boxOptions.pack,
  54260. padding = me.padding,
  54261. containerWidth = plan.targetSize[widthName],
  54262. totalMargin = 0,
  54263. left = padding[leftName],
  54264. nonFlexWidth = left + padding[rightName] + me.scrollOffset +
  54265. (me.reserveOffset ? me.availableSpaceOffset : 0),
  54266. scrollbarWidth = Ext.getScrollbarSize()[names.width],
  54267. i, childMargins, remainingWidth, remainingFlex, childContext, flex, flexedWidth,
  54268. contentWidth, mayNeedScrollbarAdjust, childWidth, percentageSpace;
  54269. // We may need to add scrollbar size to parallel size if
  54270. // Scrollbars take up space
  54271. // and we are scrolling in the perpendicular direction
  54272. // and shrinkWrapping in the parallel direction,
  54273. // and NOT stretching perpendicular dimensions to fit
  54274. // and NOT shrinkWrapping in the perpendicular direction
  54275. if (scrollbarWidth &&
  54276. me.scrollPerpendicular &&
  54277. ownerContext.parallelSizeModel.shrinkWrap &&
  54278. !ownerContext.boxOptions.align.stretch &&
  54279. !ownerContext.perpendicularSizeModel.shrinkWrap) {
  54280. // If its possible that we may need to add scrollbar size to the parallel size
  54281. // then we need to wait until the perpendicular size has been determined,
  54282. // so that we know if there is a scrollbar.
  54283. if (!ownerContext.state.perpendicularDone) {
  54284. return false;
  54285. }
  54286. mayNeedScrollbarAdjust = true;
  54287. }
  54288. // Gather the total size taken up by non-flexed items:
  54289. for (i = 0; i < childItemsLength; ++i) {
  54290. childContext = childItems[i];
  54291. childMargins = childContext.marginInfo || childContext.getMarginInfo();
  54292. totalMargin += childMargins[widthName];
  54293. if (!childContext[names.widthModel].calculated) {
  54294. childWidth = childContext.getProp(widthName);
  54295. nonFlexWidth += childWidth; // min/maxWidth safe
  54296. if (isNaN(nonFlexWidth)) {
  54297. return false;
  54298. }
  54299. }
  54300. }
  54301. nonFlexWidth += totalMargin;
  54302. if (ownerContext.percentageWidths) {
  54303. percentageSpace = containerWidth - totalMargin;
  54304. if (isNaN(percentageSpace)) {
  54305. return false;
  54306. }
  54307. for (i = 0; i < childItemsLength; ++i) {
  54308. childContext = childItems[i];
  54309. if (childContext.percentageParallel) {
  54310. childWidth = Math.ceil(percentageSpace * childContext.percentageParallel);
  54311. childWidth = childContext.setWidth(childWidth);
  54312. nonFlexWidth += childWidth;
  54313. }
  54314. }
  54315. }
  54316. // if we get here, we have all the childWidths for non-flexed items...
  54317. if (ownerContext.parallelSizeModel.shrinkWrap) {
  54318. plan.availableSpace = 0;
  54319. plan.tooNarrow = false;
  54320. } else {
  54321. plan.availableSpace = containerWidth - nonFlexWidth;
  54322. // If we're going to need space for a parallel scrollbar, then we need to redo the perpendicular measurements
  54323. plan.tooNarrow = plan.availableSpace < ownerContext.flexedMinSize;
  54324. if (plan.tooNarrow && Ext.getScrollbarSize()[names.height] && me.scrollParallel && ownerContext.state.perpendicularDone) {
  54325. ownerContext.state.perpendicularDone = false;
  54326. for (i = 0; i < childItemsLength; ++i) {
  54327. childItems[i].invalidate();
  54328. }
  54329. }
  54330. }
  54331. contentWidth = nonFlexWidth;
  54332. remainingWidth = plan.availableSpace;
  54333. remainingFlex = ownerContext.totalFlex;
  54334. // Calculate flexed item sizes:
  54335. for (i = 0; i < flexedItemsLength; i++) {
  54336. childContext = flexedItems[i];
  54337. flex = childContext.flex;
  54338. flexedWidth = me.roundFlex((flex / remainingFlex) * remainingWidth);
  54339. flexedWidth = childContext[setWidthName](flexedWidth); // constrained
  54340. // due to minWidth constraints, it may be that flexedWidth > remainingWidth
  54341. contentWidth += flexedWidth;
  54342. // Remaining space has already had margins subtracted, so just subtract size
  54343. remainingWidth = Math.max(0, remainingWidth - flexedWidth); // no negatives!
  54344. remainingFlex -= flex;
  54345. }
  54346. if (pack.center) {
  54347. left += remainingWidth / 2;
  54348. // If content is too wide to pack to center, do not allow the centering calculation to place it off the left edge.
  54349. if (left < 0) {
  54350. left = 0;
  54351. }
  54352. } else if (pack.end) {
  54353. left += remainingWidth;
  54354. }
  54355. // Assign parallel position for the boxes:
  54356. for (i = 0; i < childItemsLength; ++i) {
  54357. childContext = childItems[i];
  54358. childMargins = childContext.marginInfo; // already cached by first loop
  54359. left += childMargins[leftName];
  54360. childContext.setProp(names.x, left);
  54361. // We can read directly from "props.width" because we have already properly
  54362. // requested it in the calculation of nonFlexedWidths or we calculated it.
  54363. // We cannot call getProp because that would be inappropriate for flexed items
  54364. // and we don't need any extra function call overhead:
  54365. left += childMargins[rightName] + childContext.props[widthName];
  54366. }
  54367. contentWidth += ownerContext.targetContext.getPaddingInfo()[widthName];
  54368. // Stash the contentWidth on the state so that it can always be accessed later in the calculation
  54369. ownerContext.state.contentWidth = contentWidth;
  54370. // if there is perpendicular overflow, the published parallel content size includes
  54371. // the size of the perpendicular scrollbar.
  54372. if (mayNeedScrollbarAdjust &&
  54373. (ownerContext.peek(names.contentHeight) > plan.targetSize[names.height])) {
  54374. contentWidth += scrollbarWidth;
  54375. ownerContext[names.hasOverflowY] = true;
  54376. // tell the component layout to set the parallel size in the dom
  54377. ownerContext.target.componentLayout[names.setWidthInDom] = true;
  54378. // IE8 in what passes for "strict" mode will not create a scrollbar if
  54379. // there is just the *exactly correct* spare space created for it. We
  54380. // have to force that to happen once all the styles have been flushed
  54381. // to the DOM (see completeLayout):
  54382. ownerContext[names.invalidateScrollY] = (Ext.isStrict && Ext.isIE8);
  54383. }
  54384. ownerContext[names.setContentWidth](contentWidth);
  54385. return true;
  54386. },
  54387. calculatePerpendicular: function(ownerContext, names, plan) {
  54388. var me = this,
  54389. heightShrinkWrap = ownerContext.perpendicularSizeModel.shrinkWrap,
  54390. targetSize = plan.targetSize,
  54391. childItems = ownerContext.childItems,
  54392. childItemsLength = childItems.length,
  54393. mmax = Math.max,
  54394. heightName = names.height,
  54395. setHeightName = names.setHeight,
  54396. topName = names.top,
  54397. topPositionName = names.y,
  54398. padding = me.padding,
  54399. top = padding[topName],
  54400. availHeight = targetSize[heightName] - top - padding[names.bottom],
  54401. align = ownerContext.boxOptions.align,
  54402. isStretch = align.stretch, // never true if heightShrinkWrap (see beginLayoutCycle)
  54403. isStretchMax = align.stretchmax,
  54404. isCenter = align.center,
  54405. maxHeight = 0,
  54406. hasPercentageSizes = 0,
  54407. scrollbarHeight = Ext.getScrollbarSize().height,
  54408. childTop, i, childHeight, childMargins, diff, height, childContext,
  54409. stretchMaxPartner, stretchMaxChildren, shrinkWrapParallelOverflow,
  54410. percentagePerpendicular;
  54411. if (isStretch || (isCenter && !heightShrinkWrap)) {
  54412. if (isNaN(availHeight)) {
  54413. return false;
  54414. }
  54415. }
  54416. // If the intention is to horizontally scroll child components, but the container is too narrow,
  54417. // then:
  54418. // if we are shrinkwrapping height:
  54419. // Set a flag because we are going to expand the height taken by the perpendicular dimension to accommodate the scrollbar
  54420. // else
  54421. // We must allow for the parallel scrollbar to intrude into the height
  54422. if (me.scrollParallel && plan.tooNarrow) {
  54423. if (heightShrinkWrap) {
  54424. shrinkWrapParallelOverflow = true;
  54425. } else {
  54426. availHeight -= scrollbarHeight;
  54427. plan.targetSize[heightName] -= scrollbarHeight;
  54428. }
  54429. }
  54430. if (isStretch) {
  54431. height = availHeight; // never heightShrinkWrap...
  54432. } else {
  54433. for (i = 0; i < childItemsLength; i++) {
  54434. childContext = childItems[i];
  54435. childMargins = (childContext.marginInfo || childContext.getMarginInfo())[heightName];
  54436. if (!(percentagePerpendicular = childContext.percentagePerpendicular)) {
  54437. childHeight = childContext.getProp(heightName);
  54438. } else {
  54439. ++hasPercentageSizes;
  54440. if (heightShrinkWrap) {
  54441. // height %age items cannot contribute to maxHeight... they are going
  54442. // to be a %age of that maxHeight!
  54443. continue;
  54444. } else {
  54445. childHeight = percentagePerpendicular * availHeight - childMargins;
  54446. childHeight = childContext[names.setHeight](childHeight);
  54447. }
  54448. }
  54449. // Max perpendicular measurement (used for stretchmax) must take the min perpendicular size of each child into account in case any fall short.
  54450. if (isNaN(maxHeight = mmax(maxHeight, childHeight + childMargins,
  54451. childContext.target[names.minHeight] || 0))) {
  54452. return false; // heightShrinkWrap || isCenter || isStretchMax ??
  54453. }
  54454. }
  54455. // If there is going to be a parallel scrollbar maxHeight must include it to the outside world.
  54456. // ie: a stretchmaxPartner, and the setContentHeight
  54457. if (shrinkWrapParallelOverflow) {
  54458. maxHeight += scrollbarHeight;
  54459. ownerContext[names.hasOverflowX] = true;
  54460. // tell the component layout to set the perpendicular size in the dom
  54461. ownerContext.target.componentLayout[names.setHeightInDom] = true;
  54462. // IE8 in what passes for "strict" mode will not create a scrollbar if
  54463. // there is just the *exactly correct* spare space created for it. We
  54464. // have to force that to happen once all the styles have been flushed
  54465. // to the DOM (see completeLayout):
  54466. ownerContext[names.invalidateScrollX] = (Ext.isStrict && Ext.isIE8);
  54467. }
  54468. // If we are associated with another box layout, grab its maxChildHeight
  54469. // This must happen before we calculate and publish our contentHeight
  54470. stretchMaxPartner = ownerContext.stretchMaxPartner;
  54471. if (stretchMaxPartner) {
  54472. // Publish maxChildHeight as soon as it has been calculated for our partner:
  54473. ownerContext.setProp('maxChildHeight', maxHeight);
  54474. stretchMaxChildren = stretchMaxPartner.childItems;
  54475. // Only wait for maxChildHeight if our partner has visible items:
  54476. if (stretchMaxChildren && stretchMaxChildren.length) {
  54477. maxHeight = mmax(maxHeight, stretchMaxPartner.getProp('maxChildHeight'));
  54478. if (isNaN(maxHeight)) {
  54479. return false;
  54480. }
  54481. }
  54482. }
  54483. ownerContext[names.setContentHeight](maxHeight + me.padding[heightName] +
  54484. ownerContext.targetContext.getPaddingInfo()[heightName]);
  54485. // We have to publish the contentHeight with the additional scrollbarHeight
  54486. // to encourage our container to accomodate it, but we must remove the height
  54487. // of the scrollbar as we go to sizing or centering the children.
  54488. if (shrinkWrapParallelOverflow) {
  54489. maxHeight -= scrollbarHeight;
  54490. }
  54491. plan.maxSize = maxHeight;
  54492. if (isStretchMax) {
  54493. height = maxHeight;
  54494. } else if (isCenter || hasPercentageSizes) {
  54495. height = heightShrinkWrap ? maxHeight : mmax(availHeight, maxHeight);
  54496. // When calculating a centered position within the content box of the innerCt,
  54497. // the width of the borders must be subtracted from the size to yield the
  54498. // space available to center within. The publishInnerCtSize method explicitly
  54499. // adds the border widths to the set size of the innerCt.
  54500. height -= ownerContext.innerCtContext.getBorderInfo()[heightName];
  54501. }
  54502. }
  54503. for (i = 0; i < childItemsLength; i++) {
  54504. childContext = childItems[i];
  54505. childMargins = childContext.marginInfo || childContext.getMarginInfo();
  54506. childTop = top + childMargins[topName];
  54507. if (isStretch) {
  54508. childContext[setHeightName](height - childMargins[heightName]);
  54509. } else {
  54510. percentagePerpendicular = childContext.percentagePerpendicular;
  54511. if (heightShrinkWrap && percentagePerpendicular) {
  54512. childMargins = childContext.marginInfo || childContext.getMarginInfo();
  54513. childHeight = percentagePerpendicular * height - childMargins[heightName];
  54514. childHeight = childContext.setHeight(childHeight);
  54515. }
  54516. if (isCenter) {
  54517. diff = height - childContext.props[heightName];
  54518. if (diff > 0) {
  54519. childTop = top + Math.round(diff / 2);
  54520. }
  54521. }
  54522. }
  54523. childContext.setProp(topPositionName, childTop);
  54524. }
  54525. return true;
  54526. },
  54527. calculateStretchMax: function (ownerContext, names, plan) {
  54528. var me = this,
  54529. heightName = names.height,
  54530. widthName = names.width,
  54531. childItems = ownerContext.childItems,
  54532. length = childItems.length,
  54533. height = plan.maxSize,
  54534. onBeforeInvalidateChild = me.onBeforeInvalidateChild,
  54535. onAfterInvalidateChild = me.onAfterInvalidateChild,
  54536. childContext, props, i, childHeight;
  54537. for (i = 0; i < length; ++i) {
  54538. childContext = childItems[i];
  54539. props = childContext.props;
  54540. childHeight = height - childContext.getMarginInfo()[heightName];
  54541. if (childHeight != props[heightName] || // if (wrong height ...
  54542. childContext[names.heightModel].constrained) { // ...or needs invalidation)
  54543. // When we invalidate a child, since we won't be around to size or position
  54544. // it, we include an after callback that will be run after the invalidate
  54545. // that will (re)do that work. The good news here is that we can read the
  54546. // results of all that from the childContext props.
  54547. //
  54548. // We also include a before callback to change the sizeModel to calculated
  54549. // prior to the layout being invoked.
  54550. childContext.invalidate({
  54551. before: onBeforeInvalidateChild,
  54552. after: onAfterInvalidateChild,
  54553. layout: me,
  54554. // passing this data avoids a 'scope' and its Function.bind
  54555. childWidth: props[widthName],
  54556. // subtract margins from the maximum value
  54557. childHeight: childHeight,
  54558. childX: props.x,
  54559. childY: props.y,
  54560. names: names
  54561. });
  54562. }
  54563. }
  54564. },
  54565. completeLayout: function(ownerContext) {
  54566. var me = this,
  54567. names = ownerContext.boxNames,
  54568. invalidateScrollX = ownerContext.invalidateScrollX,
  54569. invalidateScrollY = ownerContext.invalidateScrollY,
  54570. dom, el, overflowX, overflowY, styles;
  54571. me.overflowHandler.completeLayout(ownerContext);
  54572. if (invalidateScrollX || invalidateScrollY) {
  54573. el = me.getTarget();
  54574. dom = el.dom;
  54575. styles = dom.style;
  54576. if (invalidateScrollX) {
  54577. // get computed style to see if we are 'auto'
  54578. overflowX = el.getStyle('overflowX');
  54579. if (overflowX == 'auto') {
  54580. // capture the inline style (if any) so we can restore it later:
  54581. overflowX = styles.overflowX;
  54582. styles.overflowX = 'scroll'; // force the scrollbar to appear
  54583. } else {
  54584. invalidateScrollX = false; // no work really since not 'auto'
  54585. }
  54586. }
  54587. if (invalidateScrollY) {
  54588. // get computed style to see if we are 'auto'
  54589. overflowY = el.getStyle('overflowY');
  54590. if (overflowY == 'auto') {
  54591. // capture the inline style (if any) so we can restore it later:
  54592. overflowY = styles.overflowY;
  54593. styles.overflowY = 'scroll'; // force the scrollbar to appear
  54594. } else {
  54595. invalidateScrollY = false; // no work really since not 'auto'
  54596. }
  54597. }
  54598. if (invalidateScrollX || invalidateScrollY) { // if (some form of 'auto' in play)
  54599. // force a reflow...
  54600. dom.scrollWidth;
  54601. if (invalidateScrollX) {
  54602. styles.overflowX = overflowX; // restore inline style
  54603. }
  54604. if (invalidateScrollY) {
  54605. styles.overflowY = overflowY; // restore inline style
  54606. }
  54607. }
  54608. }
  54609. // If we are scrolling parallel, restore the saved scroll position
  54610. if (me.scrollParallel) {
  54611. me.owner.getTargetEl().dom[names.scrollLeft] = me.scrollPos;
  54612. }
  54613. },
  54614. finishedLayout: function(ownerContext) {
  54615. this.overflowHandler.finishedLayout(ownerContext);
  54616. this.callParent(arguments);
  54617. // Fix for an obscure webkit bug (EXTJSIV-5962) caused by the targetEl's 20000px
  54618. // width. We set a very large width on the targetEl at the beginning of the
  54619. // layout cycle to prevent any "crushing" effect on the child items, however
  54620. // in some cases the very large width makes it possible to scroll the innerCt
  54621. // by dragging on certain child elements. To prevent this from happening we ensure
  54622. // that the targetEl's width is the same as the innerCt.
  54623. if (Ext.isWebKit) {
  54624. this.targetEl.setWidth(ownerContext.innerCtContext.props.width);
  54625. }
  54626. },
  54627. onBeforeInvalidateChild: function (childContext, options) {
  54628. // NOTE: No "this" pointer in here...
  54629. var heightModelName = options.names.heightModel;
  54630. // Change the childItem to calculated (i.e., "set by ownerCt"). The component layout
  54631. // of the child can course-correct (like dock layout does for a collapsed panel),
  54632. // so we must make these changes here before that layout's beginLayoutCycle is
  54633. // called.
  54634. if (!childContext[heightModelName].constrainedMax) {
  54635. // if the child hit a max constraint, it needs to be at its configured size, so
  54636. // we leave the sizeModel alone...
  54637. childContext[heightModelName] = Ext.layout.SizeModel.calculated;
  54638. }
  54639. },
  54640. onAfterInvalidateChild: function (childContext, options) {
  54641. // NOTE: No "this" pointer in here...
  54642. var names = options.names,
  54643. scrollbarSize = Ext.getScrollbarSize(),
  54644. childHeight = options.childHeight,
  54645. childWidth = options.childWidth;
  54646. childContext.setProp('x', options.childX);
  54647. childContext.setProp('y', options.childY);
  54648. if (childContext[names.heightModel].calculated) {
  54649. // We need to respect a child that is still not calculated (such as a collapsed
  54650. // panel)...
  54651. childContext[names.setHeight](childHeight);
  54652. }
  54653. if (childContext[names.widthModel].calculated) {
  54654. childContext[names.setWidth](childWidth);
  54655. }
  54656. },
  54657. publishInnerCtSize: function(ownerContext, reservedSpace) {
  54658. var me = this,
  54659. names = ownerContext.boxNames,
  54660. heightName = names.height,
  54661. widthName = names.width,
  54662. align = ownerContext.boxOptions.align,
  54663. dock = me.owner.dock,
  54664. padding = me.padding,
  54665. plan = ownerContext.state.boxPlan,
  54666. targetSize = plan.targetSize,
  54667. height = targetSize[heightName],
  54668. innerCtContext = ownerContext.innerCtContext,
  54669. innerCtWidth = (ownerContext.parallelSizeModel.shrinkWrap || (plan.tooNarrow && me.scrollParallel)
  54670. ? ownerContext.state.contentWidth
  54671. : targetSize[widthName]) - (reservedSpace || 0),
  54672. innerCtHeight;
  54673. if (align.stretch) {
  54674. innerCtHeight = height;
  54675. } else {
  54676. innerCtHeight = plan.maxSize + padding[names.top] + padding[names.bottom] + innerCtContext.getBorderInfo()[heightName];
  54677. if (!ownerContext.perpendicularSizeModel.shrinkWrap && align.center) {
  54678. innerCtHeight = Math.max(height, innerCtHeight);
  54679. }
  54680. }
  54681. innerCtContext[names.setWidth](innerCtWidth);
  54682. innerCtContext[names.setHeight](innerCtHeight);
  54683. // If unable to publish both dimensions, this layout needs to run again
  54684. if (isNaN(innerCtWidth + innerCtHeight)) {
  54685. me.done = false;
  54686. }
  54687. // If a calculated width has been found (this only happens for widthModel.shrinkWrap
  54688. // vertical docked Components in old Microsoft browsers) then, if the Component has
  54689. // not assumed the size of its content, set it to do so.
  54690. //
  54691. // We MUST pass the dirty flag to get that into the DOM, and because we are a Container
  54692. // layout, and not really supposed to perform sizing, we must also use the force flag.
  54693. if (plan.calculatedWidth && (dock == 'left' || dock == 'right')) {
  54694. // TODO: setting the owner size should be the job of the component layout.
  54695. ownerContext.setWidth(plan.calculatedWidth, true, true);
  54696. }
  54697. },
  54698. onRemove: function(comp){
  54699. var me = this;
  54700. me.callParent(arguments);
  54701. if (me.overflowHandler) {
  54702. me.overflowHandler.onRemove(comp);
  54703. }
  54704. if (comp.layoutMarginCap == me.id) {
  54705. delete comp.layoutMarginCap;
  54706. }
  54707. },
  54708. /**
  54709. * @private
  54710. */
  54711. initOverflowHandler: function() {
  54712. var me = this,
  54713. handler = me.overflowHandler,
  54714. handlerType,
  54715. constructor;
  54716. if (typeof handler == 'string') {
  54717. handler = {
  54718. type: handler
  54719. };
  54720. }
  54721. handlerType = 'None';
  54722. if (handler && handler.type !== undefined) {
  54723. handlerType = handler.type;
  54724. }
  54725. constructor = Ext.layout.container.boxOverflow[handlerType];
  54726. if (constructor[me.type]) {
  54727. constructor = constructor[me.type];
  54728. }
  54729. me.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.' + handlerType, me, handler);
  54730. },
  54731. // Overridden method from Ext.layout.container.Container.
  54732. // Used in the beforeLayout method to render all items into.
  54733. getRenderTarget: function() {
  54734. return this.targetEl;
  54735. },
  54736. // Overridden method from Ext.layout.container.Container.
  54737. // Used by Container classes to insert special DOM elements which must exist in addition to the child components
  54738. getElementTarget: function() {
  54739. return this.innerCt;
  54740. },
  54741. calculateChildBox: Ext.deprecated(),
  54742. calculateChildBoxes: Ext.deprecated(),
  54743. updateChildBoxes: Ext.deprecated(),
  54744. /**
  54745. * @private
  54746. */
  54747. destroy: function() {
  54748. Ext.destroy(this.innerCt, this.overflowHandler);
  54749. this.callParent(arguments);
  54750. }
  54751. });
  54752. /**
  54753. * A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
  54754. * space between child items containing a numeric `flex` configuration.
  54755. *
  54756. * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
  54757. *
  54758. * @example
  54759. * Ext.create('Ext.Panel', {
  54760. * width: 500,
  54761. * height: 300,
  54762. * title: "HBoxLayout Panel",
  54763. * layout: {
  54764. * type: 'hbox',
  54765. * align: 'stretch'
  54766. * },
  54767. * renderTo: document.body,
  54768. * items: [{
  54769. * xtype: 'panel',
  54770. * title: 'Inner Panel One',
  54771. * flex: 2
  54772. * },{
  54773. * xtype: 'panel',
  54774. * title: 'Inner Panel Two',
  54775. * flex: 1
  54776. * },{
  54777. * xtype: 'panel',
  54778. * title: 'Inner Panel Three',
  54779. * flex: 1
  54780. * }]
  54781. * });
  54782. */
  54783. Ext.define('Ext.layout.container.HBox', {
  54784. /* Begin Definitions */
  54785. alias: ['layout.hbox'],
  54786. extend: 'Ext.layout.container.Box',
  54787. alternateClassName: 'Ext.layout.HBoxLayout',
  54788. /* End Definitions */
  54789. /**
  54790. * @cfg {String} align
  54791. * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
  54792. *
  54793. * - **top** : **Default** child items are aligned vertically at the **top** of the container
  54794. * - **middle** : child items are aligned vertically in the **middle** of the container
  54795. * - **stretch** : child items are stretched vertically to fill the height of the container
  54796. * - **stretchmax** : child items are stretched vertically to the height of the largest item.
  54797. */
  54798. align: 'top', // top, middle, stretch, strechmax
  54799. type : 'hbox',
  54800. direction: 'horizontal',
  54801. horizontal: true,
  54802. names: {
  54803. // parallel
  54804. lr: 'lr',
  54805. left: 'left',// 'before',
  54806. leftCap: 'Left',
  54807. right: 'right',// 'after',
  54808. position: 'left',
  54809. width: 'width',
  54810. contentWidth: 'contentWidth',
  54811. minWidth: 'minWidth',
  54812. maxWidth: 'maxWidth',
  54813. widthCap: 'Width',
  54814. widthModel: 'widthModel',
  54815. widthIndex: 0,
  54816. x: 'x',
  54817. scrollLeft: 'scrollLeft',
  54818. overflowX: 'overflowX',
  54819. hasOverflowX: 'hasOverflowX',
  54820. invalidateScrollX: 'invalidateScrollX',
  54821. // perpendicular
  54822. center: 'middle',
  54823. top: 'top',
  54824. topPosition: 'top',
  54825. bottom: 'bottom',
  54826. height: 'height',
  54827. contentHeight: 'contentHeight',
  54828. minHeight: 'minHeight',
  54829. maxHeight: 'maxHeight',
  54830. heightCap: 'Height',
  54831. heightModel: 'heightModel',
  54832. heightIndex: 1,
  54833. y: 'y',
  54834. scrollTop: 'scrollTop',
  54835. overflowY: 'overflowY',
  54836. hasOverflowY: 'hasOverflowY',
  54837. invalidateScrollY: 'invalidateScrollY',
  54838. // Methods
  54839. getWidth: 'getWidth',
  54840. getHeight: 'getHeight',
  54841. setWidth: 'setWidth',
  54842. setHeight: 'setHeight',
  54843. gotWidth: 'gotWidth',
  54844. gotHeight: 'gotHeight',
  54845. setContentWidth: 'setContentWidth',
  54846. setContentHeight: 'setContentHeight',
  54847. setWidthInDom: 'setWidthInDom',
  54848. setHeightInDom: 'setHeightInDom'
  54849. },
  54850. sizePolicy: {
  54851. flex: {
  54852. '': {
  54853. setsWidth: 1,
  54854. setsHeight: 0
  54855. },
  54856. stretch: {
  54857. setsWidth: 1,
  54858. setsHeight: 1
  54859. },
  54860. stretchmax: {
  54861. readsHeight: 1,
  54862. setsWidth: 1,
  54863. setsHeight: 1
  54864. }
  54865. },
  54866. '': {
  54867. setsWidth: 0,
  54868. setsHeight: 0
  54869. },
  54870. stretch: {
  54871. setsWidth: 0,
  54872. setsHeight: 1
  54873. },
  54874. stretchmax: {
  54875. readsHeight: 1,
  54876. setsWidth: 0,
  54877. setsHeight: 1
  54878. }
  54879. }
  54880. });
  54881. /**
  54882. * A layout that arranges items vertically down a Container. This layout optionally divides available vertical space
  54883. * between child items containing a numeric `flex` configuration.
  54884. *
  54885. * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
  54886. *
  54887. * @example
  54888. * Ext.create('Ext.Panel', {
  54889. * width: 500,
  54890. * height: 400,
  54891. * title: "VBoxLayout Panel",
  54892. * layout: {
  54893. * type: 'vbox',
  54894. * align: 'center'
  54895. * },
  54896. * renderTo: document.body,
  54897. * items: [{
  54898. * xtype: 'panel',
  54899. * title: 'Inner Panel One',
  54900. * width: 250,
  54901. * flex: 2
  54902. * },
  54903. * {
  54904. * xtype: 'panel',
  54905. * title: 'Inner Panel Two',
  54906. * width: 250,
  54907. * flex: 4
  54908. * },
  54909. * {
  54910. * xtype: 'panel',
  54911. * title: 'Inner Panel Three',
  54912. * width: '50%',
  54913. * flex: 4
  54914. * }]
  54915. * });
  54916. */
  54917. Ext.define('Ext.layout.container.VBox', {
  54918. /* Begin Definitions */
  54919. alias: ['layout.vbox'],
  54920. extend: 'Ext.layout.container.Box',
  54921. alternateClassName: 'Ext.layout.VBoxLayout',
  54922. /* End Definitions */
  54923. /**
  54924. * @cfg {String} align
  54925. * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
  54926. *
  54927. * - **left** : **Default** child items are aligned horizontally at the **left** side of the container
  54928. * - **center** : child items are aligned horizontally at the **mid-width** of the container
  54929. * - **stretch** : child items are stretched horizontally to fill the width of the container
  54930. * - **stretchmax** : child items are stretched horizontally to the size of the largest item.
  54931. */
  54932. align : 'left', // left, center, stretch, strechmax
  54933. type: 'vbox',
  54934. direction: 'vertical',
  54935. horizontal: false,
  54936. names: {
  54937. // parallel
  54938. lr: 'tb',
  54939. left: 'top',
  54940. leftCap: 'Top',
  54941. right: 'bottom',
  54942. position: 'top',
  54943. width: 'height',
  54944. contentWidth: 'contentHeight',
  54945. minWidth: 'minHeight',
  54946. maxWidth: 'maxHeight',
  54947. widthCap: 'Height',
  54948. widthModel: 'heightModel',
  54949. widthIndex: 1,
  54950. x: 'y',
  54951. scrollLeft: 'scrollTop',
  54952. overflowX: 'overflowY',
  54953. hasOverflowX: 'hasOverflowY',
  54954. invalidateScrollX: 'invalidateScrollY',
  54955. // perpendicular
  54956. center: 'center',
  54957. top: 'left',// 'before',
  54958. topPosition: 'left',
  54959. bottom: 'right',// 'after',
  54960. height: 'width',
  54961. contentHeight: 'contentWidth',
  54962. minHeight: 'minWidth',
  54963. maxHeight: 'maxWidth',
  54964. heightCap: 'Width',
  54965. heightModel: 'widthModel',
  54966. heightIndex: 0,
  54967. y: 'x',
  54968. scrollTop: 'scrollLeft',
  54969. overflowY: 'overflowX',
  54970. hasOverflowY: 'hasOverflowX',
  54971. invalidateScrollY: 'invalidateScrollX',
  54972. // Methods
  54973. getWidth: 'getHeight',
  54974. getHeight: 'getWidth',
  54975. setWidth: 'setHeight',
  54976. setHeight: 'setWidth',
  54977. gotWidth: 'gotHeight',
  54978. gotHeight: 'gotWidth',
  54979. setContentWidth: 'setContentHeight',
  54980. setContentHeight: 'setContentWidth',
  54981. setWidthInDom: 'setHeightInDom',
  54982. setHeightInDom: 'setWidthInDom'
  54983. },
  54984. sizePolicy: {
  54985. flex: {
  54986. '': {
  54987. setsWidth: 0,
  54988. setsHeight: 1
  54989. },
  54990. stretch: {
  54991. setsWidth: 1,
  54992. setsHeight: 1
  54993. },
  54994. stretchmax: {
  54995. readsWidth: 1,
  54996. setsWidth: 1,
  54997. setsHeight: 1
  54998. }
  54999. },
  55000. '': {
  55001. setsWidth: 0,
  55002. setsHeight: 0
  55003. },
  55004. stretch: {
  55005. setsWidth: 1,
  55006. setsHeight: 0
  55007. },
  55008. stretchmax: {
  55009. readsWidth: 1,
  55010. setsWidth: 1,
  55011. setsHeight: 0
  55012. }
  55013. }
  55014. });
  55015. /**
  55016. * Basic Toolbar class. Although the {@link Ext.container.Container#defaultType defaultType} for
  55017. * Toolbar is {@link Ext.button.Button button}, Toolbar elements (child items for the Toolbar container)
  55018. * may be virtually any type of Component. Toolbar elements can be created explicitly via their
  55019. * constructors, or implicitly via their xtypes, and can be {@link #method-add}ed dynamically.
  55020. *
  55021. * ## Some items have shortcut strings for creation:
  55022. *
  55023. * | Shortcut | xtype | Class | Description
  55024. * |:---------|:--------------|:------------------------------|:---------------------------------------------------
  55025. * | `->` | `tbfill` | {@link Ext.toolbar.Fill} | begin using the right-justified button container
  55026. * | `-` | `tbseparator` | {@link Ext.toolbar.Separator} | add a vertical separator bar between toolbar items
  55027. * | ` ` | `tbspacer` | {@link Ext.toolbar.Spacer} | add horiztonal space between elements
  55028. *
  55029. * @example
  55030. * Ext.create('Ext.toolbar.Toolbar', {
  55031. * renderTo: document.body,
  55032. * width : 500,
  55033. * items: [
  55034. * {
  55035. * // xtype: 'button', // default for Toolbars
  55036. * text: 'Button'
  55037. * },
  55038. * {
  55039. * xtype: 'splitbutton',
  55040. * text : 'Split Button'
  55041. * },
  55042. * // begin using the right-justified button container
  55043. * '->', // same as { xtype: 'tbfill' }
  55044. * {
  55045. * xtype : 'textfield',
  55046. * name : 'field1',
  55047. * emptyText: 'enter search term'
  55048. * },
  55049. * // add a vertical separator bar between toolbar items
  55050. * '-', // same as {xtype: 'tbseparator'} to create Ext.toolbar.Separator
  55051. * 'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.toolbar.TextItem
  55052. * { xtype: 'tbspacer' },// same as ' ' to create Ext.toolbar.Spacer
  55053. * 'text 2',
  55054. * { xtype: 'tbspacer', width: 50 }, // add a 50px space
  55055. * 'text 3'
  55056. * ]
  55057. * });
  55058. *
  55059. * Toolbars have {@link #method-enable} and {@link #method-disable} methods which when called, will
  55060. * enable/disable all items within your toolbar.
  55061. *
  55062. * @example
  55063. * Ext.create('Ext.toolbar.Toolbar', {
  55064. * renderTo: document.body,
  55065. * width : 400,
  55066. * items: [
  55067. * {
  55068. * text: 'Button'
  55069. * },
  55070. * {
  55071. * xtype: 'splitbutton',
  55072. * text : 'Split Button'
  55073. * },
  55074. * '->',
  55075. * {
  55076. * xtype : 'textfield',
  55077. * name : 'field1',
  55078. * emptyText: 'enter search term'
  55079. * }
  55080. * ]
  55081. * });
  55082. *
  55083. * Example
  55084. *
  55085. * @example
  55086. * var enableBtn = Ext.create('Ext.button.Button', {
  55087. * text : 'Enable All Items',
  55088. * disabled: true,
  55089. * scope : this,
  55090. * handler : function() {
  55091. * //disable the enable button and enable the disable button
  55092. * enableBtn.disable();
  55093. * disableBtn.enable();
  55094. *
  55095. * //enable the toolbar
  55096. * toolbar.enable();
  55097. * }
  55098. * });
  55099. *
  55100. * var disableBtn = Ext.create('Ext.button.Button', {
  55101. * text : 'Disable All Items',
  55102. * scope : this,
  55103. * handler : function() {
  55104. * //enable the enable button and disable button
  55105. * disableBtn.disable();
  55106. * enableBtn.enable();
  55107. *
  55108. * //disable the toolbar
  55109. * toolbar.disable();
  55110. * }
  55111. * });
  55112. *
  55113. * var toolbar = Ext.create('Ext.toolbar.Toolbar', {
  55114. * renderTo: document.body,
  55115. * width : 400,
  55116. * margin : '5 0 0 0',
  55117. * items : [enableBtn, disableBtn]
  55118. * });
  55119. *
  55120. * Adding items to and removing items from a toolbar is as simple as calling the {@link #method-add}
  55121. * and {@link #method-remove} methods. There is also a {@link #removeAll} method
  55122. * which remove all items within the toolbar.
  55123. *
  55124. * @example
  55125. * var toolbar = Ext.create('Ext.toolbar.Toolbar', {
  55126. * renderTo: document.body,
  55127. * width : 700,
  55128. * items: [
  55129. * {
  55130. * text: 'Example Button'
  55131. * }
  55132. * ]
  55133. * });
  55134. *
  55135. * var addedItems = [];
  55136. *
  55137. * Ext.create('Ext.toolbar.Toolbar', {
  55138. * renderTo: document.body,
  55139. * width : 700,
  55140. * margin : '5 0 0 0',
  55141. * items : [
  55142. * {
  55143. * text : 'Add a button',
  55144. * scope : this,
  55145. * handler: function() {
  55146. * var text = prompt('Please enter the text for your button:');
  55147. * addedItems.push(toolbar.add({
  55148. * text: text
  55149. * }));
  55150. * }
  55151. * },
  55152. * {
  55153. * text : 'Add a text item',
  55154. * scope : this,
  55155. * handler: function() {
  55156. * var text = prompt('Please enter the text for your item:');
  55157. * addedItems.push(toolbar.add(text));
  55158. * }
  55159. * },
  55160. * {
  55161. * text : 'Add a toolbar separator',
  55162. * scope : this,
  55163. * handler: function() {
  55164. * addedItems.push(toolbar.add('-'));
  55165. * }
  55166. * },
  55167. * {
  55168. * text : 'Add a toolbar spacer',
  55169. * scope : this,
  55170. * handler: function() {
  55171. * addedItems.push(toolbar.add('->'));
  55172. * }
  55173. * },
  55174. * '->',
  55175. * {
  55176. * text : 'Remove last inserted item',
  55177. * scope : this,
  55178. * handler: function() {
  55179. * if (addedItems.length) {
  55180. * toolbar.remove(addedItems.pop());
  55181. * } else if (toolbar.items.length) {
  55182. * toolbar.remove(toolbar.items.last());
  55183. * } else {
  55184. * alert('No items in the toolbar');
  55185. * }
  55186. * }
  55187. * },
  55188. * {
  55189. * text : 'Remove all items',
  55190. * scope : this,
  55191. * handler: function() {
  55192. * toolbar.removeAll();
  55193. * }
  55194. * }
  55195. * ]
  55196. * });
  55197. *
  55198. * @constructor
  55199. * Creates a new Toolbar
  55200. * @param {Object/Object[]} config A config object or an array of buttons to {@link #method-add}
  55201. * @docauthor Robert Dougan <rob@sencha.com>
  55202. */
  55203. Ext.define('Ext.toolbar.Toolbar', {
  55204. extend: 'Ext.container.Container',
  55205. requires: [
  55206. 'Ext.toolbar.Fill',
  55207. 'Ext.layout.container.HBox',
  55208. 'Ext.layout.container.VBox'
  55209. ],
  55210. uses: [
  55211. 'Ext.toolbar.Separator'
  55212. ],
  55213. alias: 'widget.toolbar',
  55214. alternateClassName: 'Ext.Toolbar',
  55215. /**
  55216. * @property {Boolean} isToolbar
  55217. * `true` in this class to identify an object as an instantiated Toolbar, or subclass thereof.
  55218. */
  55219. isToolbar: true,
  55220. baseCls : Ext.baseCSSPrefix + 'toolbar',
  55221. ariaRole : 'toolbar',
  55222. defaultType: 'button',
  55223. /**
  55224. * @cfg {Boolean} vertical
  55225. * Set to `true` to make the toolbar vertical. The layout will become a `vbox`.
  55226. */
  55227. vertical: false,
  55228. /**
  55229. * @cfg {String/Object} layout
  55230. * This class assigns a default layout (`layout: 'hbox'`).
  55231. * Developers _may_ override this configuration option if another layout
  55232. * is required (the constructor must be passed a configuration object in this
  55233. * case instead of an array).
  55234. * See {@link Ext.container.Container#layout} for additional information.
  55235. */
  55236. /**
  55237. * @cfg {Boolean} enableOverflow
  55238. * Configure true to make the toolbar provide a button which activates a dropdown Menu to show
  55239. * items which overflow the Toolbar's width.
  55240. */
  55241. enableOverflow: false,
  55242. /**
  55243. * @cfg {String} menuTriggerCls
  55244. * Configure the icon class of the overflow button.
  55245. */
  55246. menuTriggerCls: Ext.baseCSSPrefix + 'toolbar-more-icon',
  55247. // private
  55248. trackMenus: true,
  55249. itemCls: Ext.baseCSSPrefix + 'toolbar-item',
  55250. statics: {
  55251. shortcuts: {
  55252. '-' : 'tbseparator',
  55253. ' ' : 'tbspacer'
  55254. },
  55255. shortcutsHV: {
  55256. // horizontal
  55257. 0: {
  55258. '->': { xtype: 'tbfill', height: 0 }
  55259. },
  55260. // vertical
  55261. 1: {
  55262. '->': { xtype: 'tbfill', width: 0 }
  55263. }
  55264. }
  55265. },
  55266. initComponent: function() {
  55267. var me = this,
  55268. keys;
  55269. // check for simplified (old-style) overflow config:
  55270. if (!me.layout && me.enableOverflow) {
  55271. me.layout = { overflowHandler: 'Menu' };
  55272. }
  55273. if (me.dock === 'right' || me.dock === 'left') {
  55274. me.vertical = true;
  55275. }
  55276. me.layout = Ext.applyIf(Ext.isString(me.layout) ? {
  55277. type: me.layout
  55278. } : me.layout || {}, {
  55279. type: me.vertical ? 'vbox' : 'hbox',
  55280. align: me.vertical ? 'stretchmax' : 'middle'
  55281. });
  55282. if (me.vertical) {
  55283. me.addClsWithUI('vertical');
  55284. }
  55285. // @TODO: remove this hack and implement a more general solution
  55286. if (me.ui === 'footer') {
  55287. me.ignoreBorderManagement = true;
  55288. }
  55289. me.callParent();
  55290. /**
  55291. * @event overflowchange
  55292. * Fires after the overflow state has changed.
  55293. * @param {Object} c The Container
  55294. * @param {Boolean} lastOverflow overflow state
  55295. */
  55296. me.addEvents('overflowchange');
  55297. },
  55298. getRefItems: function(deep) {
  55299. var me = this,
  55300. items = me.callParent(arguments),
  55301. layout = me.layout,
  55302. handler;
  55303. if (deep && me.enableOverflow) {
  55304. handler = layout.overflowHandler;
  55305. if (handler && handler.menu) {
  55306. items = items.concat(handler.menu.getRefItems(deep));
  55307. }
  55308. }
  55309. return items;
  55310. },
  55311. /**
  55312. * Adds element(s) to the toolbar -- this function takes a variable number of
  55313. * arguments of mixed type and adds them to the toolbar.
  55314. *
  55315. * **Note**: See the notes within {@link Ext.container.Container#method-add}.
  55316. *
  55317. * @param {Object...} args The following types of arguments are all valid:
  55318. *
  55319. * - `{@link Ext.button.Button config}`: A valid button config object
  55320. * - `HtmlElement`: Any standard HTML element
  55321. * - `Field`: Any form field
  55322. * - `Item`: Any subclass of {@link Ext.toolbar.Item}
  55323. * - `String`: Any generic string (gets wrapped in a {@link Ext.toolbar.TextItem}).
  55324. *
  55325. * Note that there are a few special strings that are treated differently as explained next:
  55326. *
  55327. * - `'-'`: Creates a separator element
  55328. * - `' '`: Creates a spacer element
  55329. * - `'->'`: Creates a fill element
  55330. *
  55331. * @method add
  55332. */
  55333. // private
  55334. lookupComponent: function(c) {
  55335. if (typeof c == 'string') {
  55336. var T = Ext.toolbar.Toolbar,
  55337. shortcut = T.shortcutsHV[this.vertical ? 1 : 0][c] || T.shortcuts[c];
  55338. if (typeof shortcut == 'string') {
  55339. c = {
  55340. xtype: shortcut
  55341. };
  55342. } else if (shortcut) {
  55343. c = Ext.apply({}, shortcut);
  55344. } else {
  55345. c = {
  55346. xtype: 'tbtext',
  55347. text: c
  55348. };
  55349. }
  55350. this.applyDefaults(c);
  55351. }
  55352. return this.callParent(arguments);
  55353. },
  55354. // private
  55355. applyDefaults: function(c) {
  55356. if (!Ext.isString(c)) {
  55357. c = this.callParent(arguments);
  55358. }
  55359. return c;
  55360. },
  55361. // private
  55362. trackMenu: function(item, remove) {
  55363. if (this.trackMenus && item.menu) {
  55364. var method = remove ? 'mun' : 'mon',
  55365. me = this;
  55366. me[method](item, 'mouseover', me.onButtonOver, me);
  55367. me[method](item, 'menushow', me.onButtonMenuShow, me);
  55368. me[method](item, 'menuhide', me.onButtonMenuHide, me);
  55369. }
  55370. },
  55371. // private
  55372. constructButton: function(item) {
  55373. return item.events ? item
  55374. : Ext.widget(item.split ? 'splitbutton' : this.defaultType, item);
  55375. },
  55376. // private
  55377. onBeforeAdd: function(component) {
  55378. if (component.is('field') || (component.is('button') && this.ui != 'footer')) {
  55379. component.ui = component.ui + '-toolbar';
  55380. }
  55381. // Any separators needs to know if is vertical or not
  55382. if (component instanceof Ext.toolbar.Separator) {
  55383. component.setUI((this.vertical) ? 'vertical' : 'horizontal');
  55384. }
  55385. this.callParent(arguments);
  55386. },
  55387. // private
  55388. onAdd: function(component) {
  55389. this.callParent(arguments);
  55390. this.trackMenu(component);
  55391. },
  55392. // private
  55393. onRemove: function(c) {
  55394. this.callParent(arguments);
  55395. this.trackMenu(c, true);
  55396. },
  55397. getChildItemsToDisable: function() {
  55398. return this.items.getRange();
  55399. },
  55400. // private
  55401. onButtonOver: function(btn){
  55402. if (this.activeMenuBtn && this.activeMenuBtn != btn) {
  55403. this.activeMenuBtn.hideMenu();
  55404. btn.showMenu();
  55405. this.activeMenuBtn = btn;
  55406. }
  55407. },
  55408. // private
  55409. onButtonMenuShow: function(btn) {
  55410. this.activeMenuBtn = btn;
  55411. },
  55412. // private
  55413. onButtonMenuHide: function(btn) {
  55414. delete this.activeMenuBtn;
  55415. }
  55416. });
  55417. /**
  55418. *
  55419. */
  55420. Ext.define('Ext.container.DockingContainer', {
  55421. /* Begin Definitions */
  55422. requires: ['Ext.util.MixedCollection', 'Ext.Element' ],
  55423. /* End Definitions */
  55424. isDockingContainer: true,
  55425. /**
  55426. * @cfg {Object} defaultDockWeights
  55427. * This object holds the default weights applied to dockedItems that have no weight. These start with a
  55428. * weight of 1, to allow negative weights to insert before top items and are odd numbers
  55429. * so that even weights can be used to get between different dock orders.
  55430. *
  55431. * To make default docking order match border layout, do this:
  55432. *
  55433. * Ext.panel.AbstractPanel.prototype.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };
  55434. *
  55435. * Changing these defaults as above or individually on this object will effect all Panels.
  55436. * To change the defaults on a single panel, you should replace the entire object:
  55437. *
  55438. * initComponent: function () {
  55439. * // NOTE: Don't change members of defaultDockWeights since the object is shared.
  55440. * this.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };
  55441. *
  55442. * this.callParent();
  55443. * }
  55444. *
  55445. * To change only one of the default values, you do this:
  55446. *
  55447. * initComponent: function () {
  55448. * // NOTE: Don't change members of defaultDockWeights since the object is shared.
  55449. * this.defaultDockWeights = Ext.applyIf({ top: 10 }, this.defaultDockWeights);
  55450. *
  55451. * this.callParent();
  55452. * }
  55453. */
  55454. defaultDockWeights: {
  55455. top: { render: 1, visual: 1 },
  55456. left: { render: 3, visual: 5 },
  55457. right: { render: 5, visual: 7 },
  55458. bottom: { render: 7, visual: 3 }
  55459. },
  55460. // @private
  55461. // Values to decide which side of the body element docked items must go
  55462. // This overides any weight. A left/top will *always* sort before a right/bottom
  55463. // regardless of any weight value. Weights sort at either side of the "body" dividing point.
  55464. dockOrder: {
  55465. top: -1,
  55466. left: -1,
  55467. right: 1,
  55468. bottom: 1
  55469. },
  55470. /**
  55471. * Adds docked item(s) to the container.
  55472. *
  55473. * @param {Object/Object[]} component The Component or array of components to add. The components
  55474. * must include a 'dock' parameter on each component to indicate where it should be docked
  55475. * ('top', 'right', 'bottom', 'left').
  55476. * @param {Number} [pos] The index at which the Component will be added
  55477. * @return {Ext.Component[]} The added components.
  55478. */
  55479. addDocked : function(items, pos) {
  55480. var me = this,
  55481. i = 0,
  55482. item, length;
  55483. items = me.prepareItems(items);
  55484. length = items.length;
  55485. for (; i < length; i++) {
  55486. item = items[i];
  55487. item.dock = item.dock || 'top';
  55488. if (pos !== undefined) {
  55489. me.dockedItems.insert(pos + i, item);
  55490. } else {
  55491. me.dockedItems.add(item);
  55492. }
  55493. if (item.onAdded !== Ext.emptyFn) {
  55494. item.onAdded(me, i);
  55495. }
  55496. if (me.onDockedAdd !== Ext.emptyFn) {
  55497. me.onDockedAdd(item);
  55498. }
  55499. }
  55500. if (me.rendered && !me.suspendLayout) {
  55501. me.updateLayout();
  55502. }
  55503. return items;
  55504. },
  55505. destroyDockedItems: function(){
  55506. var dockedItems = this.dockedItems,
  55507. c;
  55508. if (dockedItems) {
  55509. while ((c = dockedItems.first())) {
  55510. this.removeDocked(c, true);
  55511. }
  55512. }
  55513. },
  55514. doRenderDockedItems: function (out, renderData, after) {
  55515. // Careful! This method is bolted on to the frameTpl and renderTpl so all we get for
  55516. // context is the renderData! The "this" pointer is either the frameTpl or the
  55517. // renderTpl instance!
  55518. // Due to framing, we will be called in two different ways: in the frameTpl or in
  55519. // the renderTpl. The frameTpl version enters via doRenderFramingDockedItems which
  55520. // sets "$skipDockedItems" on the renderTpl's renderData.
  55521. //
  55522. var me = renderData.$comp,
  55523. layout = me.componentLayout,
  55524. items,
  55525. tree;
  55526. if (layout.getDockedItems && !renderData.$skipDockedItems) {
  55527. items = layout.getDockedItems('render', !after);
  55528. tree = items && layout.getItemsRenderTree(items);
  55529. if (tree) {
  55530. Ext.DomHelper.generateMarkup(tree, out);
  55531. }
  55532. }
  55533. },
  55534. /**
  55535. * Finds a docked component by id, itemId or position. Also see {@link #getDockedItems}
  55536. * @param {String/Number} comp The id, itemId or position of the docked component (see {@link Ext.panel.AbstractPanel#getComponent getComponent} for details)
  55537. * @return {Ext.Component} The docked component (if found)
  55538. */
  55539. getDockedComponent: function(comp) {
  55540. if (Ext.isObject(comp)) {
  55541. comp = comp.getItemId();
  55542. }
  55543. return this.dockedItems.get(comp);
  55544. },
  55545. /**
  55546. * Retrieves an array of all currently docked Components.
  55547. *
  55548. * For example to find a toolbar that has been docked at top:
  55549. *
  55550. * panel.getDockedItems('toolbar[dock="top"]');
  55551. *
  55552. * @param {String} selector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
  55553. * @param {Boolean} beforeBody An optional flag to limit the set of items to only those
  55554. * before the body (true) or after the body (false). All components are returned by
  55555. * default.
  55556. * @return {Ext.Component[]} The array of docked components meeting the specified criteria.
  55557. */
  55558. getDockedItems : function(selector, beforeBody) {
  55559. var dockedItems = this.getComponentLayout().getDockedItems('render', beforeBody);
  55560. if (selector && dockedItems.length) {
  55561. dockedItems = Ext.ComponentQuery.query(selector, dockedItems);
  55562. }
  55563. return dockedItems;
  55564. },
  55565. getDockingRefItems: function(deep, containerItems) {
  55566. // deep fetches the docked items and their descendants using '*' and then '* *'
  55567. var selector = deep && '*,* *',
  55568. // start with only the top/left docked items (and maybe their children)
  55569. dockedItems = this.getDockedItems(selector, true),
  55570. items;
  55571. // push container items (and maybe their children) after top/left docked items:
  55572. dockedItems.push.apply(dockedItems, containerItems);
  55573. // push right/bottom docked items (and maybe their children) after container items:
  55574. items = this.getDockedItems(selector, false);
  55575. dockedItems.push.apply(dockedItems, items);
  55576. return dockedItems;
  55577. },
  55578. initDockingItems: function() {
  55579. var me = this,
  55580. items = me.dockedItems;
  55581. me.dockedItems = new Ext.util.AbstractMixedCollection(false, me.getComponentId);
  55582. if (items) {
  55583. me.addDocked(items);
  55584. }
  55585. },
  55586. /**
  55587. * Inserts docked item(s) to the panel at the indicated position.
  55588. * @param {Number} pos The index at which the Component will be inserted
  55589. * @param {Object/Object[]} component. The Component or array of components to add. The components
  55590. * must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
  55591. * 'bottom', 'left').
  55592. */
  55593. insertDocked : function(pos, items) {
  55594. this.addDocked(items, pos);
  55595. },
  55596. // Placeholder empty functions
  55597. /**
  55598. * Invoked after a docked item is added to the Panel.
  55599. * @param {Ext.Component} component
  55600. * @template
  55601. * @protected
  55602. */
  55603. onDockedAdd : Ext.emptyFn,
  55604. /**
  55605. * Invoked after a docked item is removed from the Panel.
  55606. * @param {Ext.Component} component
  55607. * @template
  55608. * @protected
  55609. */
  55610. onDockedRemove : Ext.emptyFn,
  55611. /**
  55612. * Removes the docked item from the panel.
  55613. * @param {Ext.Component} item. The Component to remove.
  55614. * @param {Boolean} autoDestroy (optional) Destroy the component after removal.
  55615. */
  55616. removeDocked : function(item, autoDestroy) {
  55617. var me = this,
  55618. layout,
  55619. hasLayout;
  55620. if (!me.dockedItems.contains(item)) {
  55621. return item;
  55622. }
  55623. layout = me.componentLayout;
  55624. hasLayout = layout && me.rendered;
  55625. if (hasLayout) {
  55626. layout.onRemove(item);
  55627. }
  55628. me.dockedItems.remove(item);
  55629. item.onRemoved();
  55630. me.onDockedRemove(item);
  55631. if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
  55632. item.destroy();
  55633. } else if (hasLayout) {
  55634. // not destroying, make any layout related removals
  55635. layout.afterRemove(item);
  55636. }
  55637. if (!me.destroying && !me.suspendLayout) {
  55638. me.updateLayout();
  55639. }
  55640. return item;
  55641. },
  55642. setupDockingRenderTpl: function (renderTpl) {
  55643. renderTpl.renderDockedItems = this.doRenderDockedItems;
  55644. }
  55645. });
  55646. /**
  55647. * @class Ext.panel.AbstractPanel
  55648. * @private
  55649. *
  55650. * A base class which provides methods common to Panel classes across the Sencha product range.
  55651. *
  55652. * Please refer to sub class's documentation
  55653. */
  55654. Ext.define('Ext.panel.AbstractPanel', {
  55655. /* Begin Definitions */
  55656. extend: 'Ext.container.Container',
  55657. mixins: {
  55658. docking: 'Ext.container.DockingContainer'
  55659. },
  55660. requires: ['Ext.util.MixedCollection', 'Ext.Element', 'Ext.toolbar.Toolbar'],
  55661. /* End Definitions */
  55662. /**
  55663. * @cfg {String} [baseCls=x-panel]
  55664. * The base CSS class to apply to this panel's element.
  55665. */
  55666. baseCls : Ext.baseCSSPrefix + 'panel',
  55667. /**
  55668. * @cfg {Number/String} bodyPadding
  55669. * A shortcut for setting a padding style on the body element. The value can either be
  55670. * a number to be applied to all sides, or a normal css string describing padding.
  55671. * Defaults to <code>undefined</code>.
  55672. */
  55673. /**
  55674. * @cfg {Boolean} bodyBorder
  55675. * A shortcut to add or remove the border on the body of a panel. This only applies to a panel which has the {@link #frame} configuration set to `true`.
  55676. * Defaults to <code>undefined</code>.
  55677. */
  55678. /**
  55679. * @cfg {String/Object/Function} bodyStyle
  55680. * Custom CSS styles to be applied to the panel's body element, which can be supplied as a valid CSS style string,
  55681. * an object containing style property name/value pairs or a function that returns such a string or object.
  55682. * For example, these two formats are interpreted to be equivalent:<pre><code>
  55683. bodyStyle: 'background:#ffc; padding:10px;'
  55684. bodyStyle: {
  55685. background: '#ffc',
  55686. padding: '10px'
  55687. }
  55688. * </code></pre>
  55689. */
  55690. /**
  55691. * @cfg {String/String[]} bodyCls
  55692. * A CSS class, space-delimited string of classes, or array of classes to be applied to the panel's body element.
  55693. * The following examples are all valid:<pre><code>
  55694. bodyCls: 'foo'
  55695. bodyCls: 'foo bar'
  55696. bodyCls: ['foo', 'bar']
  55697. * </code></pre>
  55698. */
  55699. /**
  55700. * @property {Boolean} isPanel
  55701. * `true` in this class to identify an object as an instantiated Panel, or subclass thereof.
  55702. */
  55703. isPanel: true,
  55704. componentLayout: 'dock',
  55705. childEls: [
  55706. 'body'
  55707. ],
  55708. renderTpl: [
  55709. // If this Panel is framed, the framing template renders the docked items round the frame
  55710. '{% this.renderDockedItems(out,values,0); %}',
  55711. // This empty div solves an IE6/7/Quirks problem where the margin-top on the bodyEl
  55712. // is ignored. Best we can figure, this is triggered by the previousSibling being
  55713. // position absolute (a docked item). The goal is to use margins to position the
  55714. // bodyEl rather than left/top since that allows us to avoid writing a height on the
  55715. // panel and the body. This in turn allows CSS height to expand or contract the
  55716. // panel during things like portlet dragging where we want to avoid running a ton
  55717. // of layouts during the drag operation.
  55718. (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) ? '<div></div>' : '',
  55719. '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
  55720. ' {baseCls}-body-{ui}<tpl if="uiCls">',
  55721. '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
  55722. '</tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>>',
  55723. '{%this.renderContainer(out,values);%}',
  55724. '</div>',
  55725. '{% this.renderDockedItems(out,values,1); %}'
  55726. ],
  55727. bodyPosProps: {
  55728. x: 'x',
  55729. y: 'y'
  55730. },
  55731. // TODO: Move code examples into product-specific files. The code snippet below is Touch only.
  55732. /**
  55733. * @cfg {Object/Object[]} dockedItems
  55734. * A component or series of components to be added as docked items to this panel.
  55735. * The docked items can be docked to either the top, right, left or bottom of a panel.
  55736. * This is typically used for things like toolbars or tab bars:
  55737. * <pre><code>
  55738. var panel = new Ext.panel.Panel({
  55739. fullscreen: true,
  55740. dockedItems: [{
  55741. xtype: 'toolbar',
  55742. dock: 'top',
  55743. items: [{
  55744. text: 'Docked to the top'
  55745. }]
  55746. }]
  55747. });</code></pre>
  55748. */
  55749. border: true,
  55750. /**
  55751. * @private
  55752. */
  55753. emptyArray: [],
  55754. initComponent : function() {
  55755. var me = this;
  55756. //!frame
  55757. //!border
  55758. if (me.frame && me.border && me.bodyBorder === undefined) {
  55759. me.bodyBorder = false;
  55760. }
  55761. if (me.frame && me.border && (me.bodyBorder === false || me.bodyBorder === 0)) {
  55762. me.manageBodyBorders = true;
  55763. }
  55764. me.callParent();
  55765. },
  55766. beforeDestroy: function(){
  55767. this.destroyDockedItems();
  55768. this.callParent();
  55769. },
  55770. // @private
  55771. initItems : function() {
  55772. this.callParent();
  55773. this.initDockingItems();
  55774. },
  55775. /**
  55776. * Initialized the renderData to be used when rendering the renderTpl.
  55777. * @return {Object} Object with keys and values that are going to be applied to the renderTpl
  55778. * @private
  55779. */
  55780. initRenderData: function() {
  55781. var me = this,
  55782. data = me.callParent();
  55783. me.initBodyStyles();
  55784. me.protoBody.writeTo(data);
  55785. delete me.protoBody;
  55786. return data;
  55787. },
  55788. /**
  55789. * Attempts a default component lookup (see {@link Ext.container.Container#getComponent}). If the component is not found in the normal
  55790. * items, the dockedItems are searched and the matched component (if any) returned (see {@link #getDockedComponent}). Note that docked
  55791. * items will only be matched by component id or itemId -- if you pass a numeric index only non-docked child components will be searched.
  55792. * @param {String/Number} comp The component id, itemId or position to find
  55793. * @return {Ext.Component} The component (if found)
  55794. */
  55795. getComponent: function(comp) {
  55796. var component = this.callParent(arguments);
  55797. if (component === undefined && !Ext.isNumber(comp)) {
  55798. // If the arg is a numeric index skip docked items
  55799. component = this.getDockedComponent(comp);
  55800. }
  55801. return component;
  55802. },
  55803. getProtoBody: function () {
  55804. var me = this,
  55805. body = me.protoBody;
  55806. if (!body) {
  55807. me.protoBody = body = new Ext.util.ProtoElement({
  55808. cls: me.bodyCls,
  55809. style: me.bodyStyle,
  55810. clsProp: 'bodyCls',
  55811. styleProp: 'bodyStyle',
  55812. styleIsText: true
  55813. });
  55814. }
  55815. return body;
  55816. },
  55817. /**
  55818. * Parses the {@link #bodyStyle} config if available to create a style string that will be applied to the body element.
  55819. * This also includes {@link #bodyPadding} and {@link #bodyBorder} if available.
  55820. * @return {String} A CSS style string with body styles, padding and border.
  55821. * @private
  55822. */
  55823. initBodyStyles: function() {
  55824. var me = this,
  55825. body = me.getProtoBody(),
  55826. Element = Ext.Element;
  55827. if (me.bodyPadding !== undefined) {
  55828. body.setStyle('padding', Element.unitizeBox((me.bodyPadding === true) ? 5 : me.bodyPadding));
  55829. }
  55830. if (me.frame && me.bodyBorder) {
  55831. if (!Ext.isNumber(me.bodyBorder)) {
  55832. me.bodyBorder = 1;
  55833. }
  55834. body.setStyle('border-width', Element.unitizeBox(me.bodyBorder));
  55835. }
  55836. },
  55837. getCollapsedDockedItems: function () {
  55838. var me = this;
  55839. return me.collapseMode == 'placeholder' ? me.emptyArray : [ me.getReExpander() ];
  55840. },
  55841. /**
  55842. * Sets the body style according to the passed parameters.
  55843. * @param {Mixed} style A full style specification string, or object, or the name of a style property to set.
  55844. * @param {String} value If the first param was a style property name, the style property value.
  55845. * @return {Ext.panel.Panel} this
  55846. */
  55847. setBodyStyle: function(style, value) {
  55848. var me = this,
  55849. body = me.rendered ? me.body : me.getProtoBody();
  55850. if (Ext.isFunction(style)) {
  55851. style = style();
  55852. }
  55853. if (arguments.length == 1) {
  55854. if (Ext.isString(style)) {
  55855. style = Ext.Element.parseStyles(style);
  55856. }
  55857. body.setStyle(style);
  55858. } else {
  55859. body.setStyle(style, value);
  55860. }
  55861. return me;
  55862. },
  55863. /**
  55864. * Adds a CSS class to the body element. If not rendered, the class will
  55865. * be added when the panel is rendered.
  55866. * @param {String} cls The class to add
  55867. * @return {Ext.panel.Panel} this
  55868. */
  55869. addBodyCls: function(cls) {
  55870. var me = this,
  55871. body = me.rendered ? me.body : me.getProtoBody();
  55872. body.addCls(cls);
  55873. return me;
  55874. },
  55875. /**
  55876. * Removes a CSS class from the body element.
  55877. * @param {String} cls The class to remove
  55878. * @return {Ext.panel.Panel} this
  55879. */
  55880. removeBodyCls: function(cls) {
  55881. var me = this,
  55882. body = me.rendered ? me.body : me.getProtoBody();
  55883. body.removeCls(cls);
  55884. return me;
  55885. },
  55886. // inherit docs
  55887. addUIClsToElement: function(cls) {
  55888. var me = this,
  55889. result = me.callParent(arguments);
  55890. me.addBodyCls([Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls]);
  55891. return result;
  55892. },
  55893. // inherit docs
  55894. removeUIClsFromElement: function(cls) {
  55895. var me = this,
  55896. result = me.callParent(arguments);
  55897. me.removeBodyCls([Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls]);
  55898. return result;
  55899. },
  55900. // inherit docs
  55901. addUIToElement: function() {
  55902. var me = this;
  55903. me.callParent(arguments);
  55904. me.addBodyCls(me.baseCls + '-body-' + me.ui);
  55905. },
  55906. // inherit docs
  55907. removeUIFromElement: function() {
  55908. var me = this;
  55909. me.callParent(arguments);
  55910. me.removeBodyCls(me.baseCls + '-body-' + me.ui);
  55911. },
  55912. // @private
  55913. getTargetEl : function() {
  55914. return this.body;
  55915. },
  55916. getRefItems: function(deep) {
  55917. var items = this.callParent(arguments);
  55918. return this.getDockingRefItems(deep, items);
  55919. },
  55920. setupRenderTpl: function (renderTpl) {
  55921. this.callParent(arguments);
  55922. this.setupDockingRenderTpl(renderTpl);
  55923. }
  55924. });
  55925. /**
  55926. * Component layout for components which maintain an inner body element which must be resized to synchronize with the
  55927. * Component size.
  55928. * @private
  55929. */
  55930. Ext.define('Ext.layout.component.Body', {
  55931. /* Begin Definitions */
  55932. alias: ['layout.body'],
  55933. extend: 'Ext.layout.component.Auto',
  55934. /* End Definitions */
  55935. type: 'body',
  55936. beginLayout: function (ownerContext) {
  55937. this.callParent(arguments);
  55938. ownerContext.bodyContext = ownerContext.getEl('body');
  55939. },
  55940. // Padding is exciting here because we have 2 el's: owner.el and owner.body. Content
  55941. // size always includes the padding of the targetEl, which should be owner.body. But
  55942. // it is common to have padding on owner.el also (such as a panel header), so we need
  55943. // to do some more padding work if targetContext is not owner.el. The base class has
  55944. // already handled the ownerContext's frameInfo (border+framing) so all that is left
  55945. // is padding.
  55946. calculateOwnerHeightFromContentHeight: function (ownerContext, contentHeight) {
  55947. var height = this.callParent(arguments);
  55948. if (ownerContext.targetContext != ownerContext) {
  55949. height += ownerContext.getPaddingInfo().height;
  55950. }
  55951. return height;
  55952. },
  55953. calculateOwnerWidthFromContentWidth: function (ownerContext, contentWidth) {
  55954. var width = this.callParent(arguments);
  55955. if (ownerContext.targetContext != ownerContext) {
  55956. width += ownerContext.getPaddingInfo().width;
  55957. }
  55958. return width;
  55959. },
  55960. measureContentWidth: function (ownerContext) {
  55961. return ownerContext.bodyContext.setWidth(ownerContext.bodyContext.el.dom.offsetWidth, false);
  55962. },
  55963. measureContentHeight: function (ownerContext) {
  55964. return ownerContext.bodyContext.setHeight(ownerContext.bodyContext.el.dom.offsetHeight, false);
  55965. },
  55966. publishInnerHeight: function (ownerContext, height) {
  55967. var innerHeight = height - ownerContext.getFrameInfo().height,
  55968. targetContext = ownerContext.targetContext;
  55969. if (targetContext != ownerContext) {
  55970. innerHeight -= ownerContext.getPaddingInfo().height;
  55971. }
  55972. // return the value here, it may get used in a subclass
  55973. return ownerContext.bodyContext.setHeight(innerHeight, !ownerContext.heightModel.natural);
  55974. },
  55975. publishInnerWidth: function (ownerContext, width) {
  55976. var innerWidth = width - ownerContext.getFrameInfo().width,
  55977. targetContext = ownerContext.targetContext;
  55978. if (targetContext != ownerContext) {
  55979. innerWidth -= ownerContext.getPaddingInfo().width;
  55980. }
  55981. ownerContext.bodyContext.setWidth(innerWidth, !ownerContext.widthModel.natural);
  55982. }
  55983. });
  55984. /**
  55985. * Simple header class which is used for on {@link Ext.panel.Panel} and {@link Ext.window.Window}.
  55986. */
  55987. Ext.define('Ext.panel.Header', {
  55988. extend: 'Ext.container.Container',
  55989. uses: ['Ext.panel.Tool', 'Ext.draw.Component', 'Ext.util.CSS', 'Ext.layout.component.Body', 'Ext.Img'],
  55990. alias: 'widget.header',
  55991. /**
  55992. * @property {Boolean} isHeader
  55993. * `true` in this class to identify an objact as an instantiated Header, or subclass thereof.
  55994. */
  55995. isHeader : true,
  55996. defaultType : 'tool',
  55997. indicateDrag : false,
  55998. weight : -1,
  55999. componentLayout: 'body',
  56000. /**
  56001. * @cfg {String} [titleAlign='left']
  56002. * May be `"left"`, `"right"` or `"center"`.
  56003. *
  56004. * The alignment of the title text within the available space between the icon and the tools.
  56005. */
  56006. titleAlign: 'left',
  56007. childEls: [
  56008. 'body'
  56009. ],
  56010. renderTpl: [
  56011. '<div id="{id}-body" class="{baseCls}-body {bodyCls}',
  56012. '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>"',
  56013. '<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>>',
  56014. '{%this.renderContainer(out,values)%}',
  56015. '</div>'
  56016. ],
  56017. headingTpl: '<span id="{id}-textEl" class="{cls}-text {cls}-text-{ui}">{title}</span>',
  56018. shrinkWrap: 3,
  56019. /**
  56020. * @cfg {String} title
  56021. * The title text to display.
  56022. */
  56023. /**
  56024. * @cfg {String} iconCls
  56025. * CSS class for an icon in the header. Used for displaying an icon to the left of a title.
  56026. */
  56027. /**
  56028. * @cfg {String} icon
  56029. * Path to image for an icon in the header. Used for displaying an icon to the left of a title.
  56030. */
  56031. initComponent: function() {
  56032. var me = this,
  56033. ruleStyle,
  56034. rule,
  56035. style,
  56036. ui,
  56037. tempEl;
  56038. me.addEvents(
  56039. /**
  56040. * @event click
  56041. * Fires when the header is clicked. This event will not be fired
  56042. * if the click was on a {@link Ext.panel.Tool}
  56043. * @param {Ext.panel.Header} this
  56044. * @param {Ext.EventObject} e
  56045. */
  56046. 'click',
  56047. /**
  56048. * @event dblclick
  56049. * Fires when the header is double clicked. This event will not
  56050. * be fired if the click was on a {@link Ext.panel.Tool}
  56051. * @param {Ext.panel.Header} this
  56052. * @param {Ext.EventObject} e
  56053. */
  56054. 'dblclick'
  56055. );
  56056. me.indicateDragCls = me.baseCls + '-draggable';
  56057. me.title = me.title || '&#160;';
  56058. me.tools = me.tools || [];
  56059. me.items = me.items || [];
  56060. me.orientation = me.orientation || 'horizontal';
  56061. me.dock = (me.dock) ? me.dock : (me.orientation == 'horizontal') ? 'top' : 'left';
  56062. //add the dock as a ui
  56063. //this is so we support top/right/left/bottom headers
  56064. me.addClsWithUI([me.orientation, me.dock]);
  56065. if (me.indicateDrag) {
  56066. me.addCls(me.indicateDragCls);
  56067. }
  56068. // Add Icon
  56069. if (!Ext.isEmpty(me.iconCls) || !Ext.isEmpty(me.icon)) {
  56070. me.initIconCmp();
  56071. me.items.push(me.iconCmp);
  56072. }
  56073. // Add Title
  56074. if (me.orientation == 'vertical') {
  56075. me.layout = {
  56076. type : 'vbox',
  56077. align: 'center'
  56078. };
  56079. me.textConfig = {
  56080. width: 16,
  56081. cls: me.baseCls + '-text',
  56082. type: 'text',
  56083. text: me.title,
  56084. rotate: {
  56085. degrees: 90
  56086. }
  56087. };
  56088. ui = me.ui;
  56089. if (Ext.isArray(ui)) {
  56090. ui = ui[0];
  56091. }
  56092. ruleStyle = '.' + me.baseCls + '-text-' + ui;
  56093. if (Ext.scopeResetCSS) {
  56094. ruleStyle = '.' + Ext.baseCSSPrefix + 'reset ' + ruleStyle;
  56095. }
  56096. rule = Ext.util.CSS.getRule(ruleStyle);
  56097. // We might have been disallowed access to the stylesheet: https://sencha.jira.com/browse/EXTJSIV-5084
  56098. if (rule) {
  56099. style = rule.style;
  56100. } else {
  56101. style = (tempEl = Ext.resetElement.createChild({style: 'position:absolute', cls: me.baseCls + '-text-' + ui})).getStyles('fontFamily', 'fontWeight', 'fontSize', 'color');
  56102. tempEl.remove();
  56103. }
  56104. if (style) {
  56105. Ext.apply(me.textConfig, {
  56106. 'font-family': style.fontFamily,
  56107. 'font-weight': style.fontWeight,
  56108. 'font-size': style.fontSize,
  56109. fill: style.color
  56110. });
  56111. }
  56112. me.titleCmp = new Ext.draw.Component({
  56113. width : 16,
  56114. ariaRole : 'heading',
  56115. focusable : false,
  56116. viewBox : false,
  56117. flex : 1,
  56118. id : me.id + '_hd',
  56119. autoSize : true,
  56120. items : me.textConfig,
  56121. xhooks: {
  56122. setSize: function (width) {
  56123. // don't pass 2nd arg (height) on to setSize or we break 'flex:1'
  56124. this.callParent([width]);
  56125. }
  56126. },
  56127. // this is a bit of a cheat: we are not selecting an element of titleCmp
  56128. // but rather of titleCmp.items[0]
  56129. childEls : [
  56130. { name: 'textEl', select: '.' + me.baseCls + '-text' }
  56131. ]
  56132. });
  56133. } else {
  56134. me.layout = {
  56135. type : 'hbox',
  56136. align: 'middle'
  56137. };
  56138. me.titleCmp = new Ext.Component({
  56139. ariaRole : 'heading',
  56140. focusable : false,
  56141. noWrap : true,
  56142. flex : 1,
  56143. id : me.id + '_hd',
  56144. style : 'text-align:' + me.titleAlign,
  56145. cls : me.baseCls + '-text-container',
  56146. renderTpl : me.getTpl('headingTpl'),
  56147. renderData: {
  56148. title: me.title,
  56149. cls : me.baseCls,
  56150. ui : me.ui
  56151. },
  56152. childEls : ['textEl']
  56153. });
  56154. }
  56155. me.items.push(me.titleCmp);
  56156. // Add Tools
  56157. me.items = me.items.concat(me.tools);
  56158. me.callParent();
  56159. me.on({
  56160. dblclick: me.onDblClick,
  56161. click: me.onClick,
  56162. element: 'el',
  56163. scope: me
  56164. });
  56165. },
  56166. initIconCmp: function() {
  56167. var me = this,
  56168. cfg = {
  56169. focusable: false,
  56170. src: Ext.BLANK_IMAGE_URL,
  56171. cls: [me.baseCls + '-icon', me.iconCls],
  56172. id: me.id + '-iconEl',
  56173. iconCls: me.iconCls
  56174. };
  56175. if (!Ext.isEmpty(me.icon)) {
  56176. delete cfg.iconCls;
  56177. cfg.src = me.icon;
  56178. }
  56179. me.iconCmp = new Ext.Img(cfg);
  56180. },
  56181. afterRender: function() {
  56182. this.el.unselectable();
  56183. this.callParent();
  56184. },
  56185. // inherit docs
  56186. addUIClsToElement: function(cls) {
  56187. var me = this,
  56188. result = me.callParent(arguments),
  56189. classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
  56190. array, i;
  56191. if (me.bodyCls) {
  56192. array = me.bodyCls.split(' ');
  56193. for (i = 0; i < classes.length; i++) {
  56194. if (!Ext.Array.contains(array, classes[i])) {
  56195. array.push(classes[i]);
  56196. }
  56197. }
  56198. me.bodyCls = array.join(' ');
  56199. } else {
  56200. me.bodyCls = classes.join(' ');
  56201. }
  56202. return result;
  56203. },
  56204. // inherit docs
  56205. removeUIClsFromElement: function(cls) {
  56206. var me = this,
  56207. result = me.callParent(arguments),
  56208. classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
  56209. array, i;
  56210. if (me.bodyCls) {
  56211. array = me.bodyCls.split(' ');
  56212. for (i = 0; i < classes.length; i++) {
  56213. Ext.Array.remove(array, classes[i]);
  56214. }
  56215. me.bodyCls = array.join(' ');
  56216. }
  56217. return result;
  56218. },
  56219. // inherit docs
  56220. addUIToElement: function() {
  56221. var me = this,
  56222. array, cls;
  56223. me.callParent(arguments);
  56224. cls = me.baseCls + '-body-' + me.ui;
  56225. if (me.rendered) {
  56226. if (me.bodyCls) {
  56227. me.body.addCls(me.bodyCls);
  56228. } else {
  56229. me.body.addCls(cls);
  56230. }
  56231. } else {
  56232. if (me.bodyCls) {
  56233. array = me.bodyCls.split(' ');
  56234. if (!Ext.Array.contains(array, cls)) {
  56235. array.push(cls);
  56236. }
  56237. me.bodyCls = array.join(' ');
  56238. } else {
  56239. me.bodyCls = cls;
  56240. }
  56241. }
  56242. if (me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
  56243. me.titleCmp.textEl.addCls(me.baseCls + '-text-' + me.ui);
  56244. }
  56245. },
  56246. // inherit docs
  56247. removeUIFromElement: function() {
  56248. var me = this,
  56249. array, cls;
  56250. me.callParent(arguments);
  56251. cls = me.baseCls + '-body-' + me.ui;
  56252. if (me.rendered) {
  56253. if (me.bodyCls) {
  56254. me.body.removeCls(me.bodyCls);
  56255. } else {
  56256. me.body.removeCls(cls);
  56257. }
  56258. } else {
  56259. if (me.bodyCls) {
  56260. array = me.bodyCls.split(' ');
  56261. Ext.Array.remove(array, cls);
  56262. me.bodyCls = array.join(' ');
  56263. } else {
  56264. me.bodyCls = cls;
  56265. }
  56266. }
  56267. if (me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
  56268. me.titleCmp.textEl.removeCls(me.baseCls + '-text-' + me.ui);
  56269. }
  56270. },
  56271. onClick: function(e) {
  56272. this.fireClickEvent('click', e);
  56273. },
  56274. onDblClick: function(e){
  56275. this.fireClickEvent('dblclick', e);
  56276. },
  56277. fireClickEvent: function(type, e){
  56278. var toolCls = '.' + Ext.panel.Tool.prototype.baseCls;
  56279. if (!e.getTarget(toolCls)) {
  56280. this.fireEvent(type, this, e);
  56281. }
  56282. },
  56283. getFocusEl: function() {
  56284. return this.el;
  56285. },
  56286. getTargetEl: function() {
  56287. return this.body || this.frameBody || this.el;
  56288. },
  56289. /**
  56290. * Sets the title of the header.
  56291. * @param {String} title The title to be set
  56292. */
  56293. setTitle: function(title) {
  56294. var me = this,
  56295. sprite,
  56296. surface;
  56297. if (me.rendered) {
  56298. if (me.titleCmp.rendered) {
  56299. if (me.titleCmp.surface) {
  56300. me.title = title || '';
  56301. sprite = me.titleCmp.surface.items.items[0];
  56302. surface = me.titleCmp.surface;
  56303. surface.remove(sprite);
  56304. me.textConfig.type = 'text';
  56305. me.textConfig.text = title;
  56306. sprite = surface.add(me.textConfig);
  56307. sprite.setAttributes({
  56308. rotate: {
  56309. degrees: 90
  56310. }
  56311. }, true);
  56312. me.titleCmp.autoSizeSurface();
  56313. } else {
  56314. me.title = title;
  56315. me.titleCmp.textEl.update(me.title || '&#160;');
  56316. }
  56317. me.titleCmp.updateLayout();
  56318. } else {
  56319. me.titleCmp.on({
  56320. render: function() {
  56321. me.setTitle(title);
  56322. },
  56323. single: true
  56324. });
  56325. }
  56326. } else {
  56327. me.title = title;
  56328. }
  56329. },
  56330. /**
  56331. * @private
  56332. * Used when shrink wrapping a Panel to either content width or header width.
  56333. * This returns the minimum width required to display the header, icon and tools.
  56334. * **This is only intended for use with horizontal headers.**
  56335. */
  56336. getMinWidth: function() {
  56337. var me = this,
  56338. textEl = me.titleCmp.textEl.dom,
  56339. result,
  56340. tools = me.tools,
  56341. l, i;
  56342. // Measure text width as inline element so it doesn't stretch
  56343. textEl.style.display = 'inline';
  56344. result = textEl.offsetWidth;
  56345. textEl.style.display = '';
  56346. // Add tools width
  56347. if (tools && (l = tools.length)) {
  56348. for (i = 0; i < l; i++) {
  56349. if (tools[i].el) {
  56350. result += tools[i].el.dom.offsetWidth;
  56351. }
  56352. }
  56353. }
  56354. // Add iconWidth
  56355. if (me.iconCmp) {
  56356. result += me.iconCmp.el.dom.offsetWidth;
  56357. }
  56358. // Return with some space between title and tools/end of header.
  56359. return result + 10;
  56360. },
  56361. /**
  56362. * Sets the CSS class that provides the icon image for this header. This method will replace any existing
  56363. * icon class if one has already been set.
  56364. * @param {String} cls The new CSS class name
  56365. */
  56366. setIconCls: function(cls) {
  56367. var me = this,
  56368. isEmpty = !cls || !cls.length,
  56369. iconCmp = me.iconCmp;
  56370. me.iconCls = cls;
  56371. if (!me.iconCmp && !isEmpty) {
  56372. me.initIconCmp();
  56373. me.insert(0, me.iconCmp);
  56374. } else if (iconCmp) {
  56375. if (isEmpty) {
  56376. me.iconCmp.destroy();
  56377. delete me.iconCmp;
  56378. } else {
  56379. iconCmp.removeCls(iconCmp.iconCls);
  56380. iconCmp.addCls(cls);
  56381. iconCmp.iconCls = cls;
  56382. }
  56383. }
  56384. },
  56385. /**
  56386. * Sets the image path that provides the icon image for this header. This method will replace any existing
  56387. * icon if one has already been set.
  56388. * @param {String} icon The new icon path
  56389. */
  56390. setIcon: function(icon) {
  56391. var me = this,
  56392. isEmpty = !icon || !icon.length,
  56393. iconCmp = me.iconCmp;
  56394. me.icon = icon;
  56395. if (!me.iconCmp && !isEmpty) {
  56396. me.initIconCmp();
  56397. me.insert(0, me.iconCmp);
  56398. } else if (iconCmp) {
  56399. if (isEmpty) {
  56400. me.iconCmp.destroy();
  56401. delete me.iconCmp;
  56402. } else {
  56403. iconCmp.setSrc(me.icon);
  56404. }
  56405. }
  56406. },
  56407. /**
  56408. * Add a tool to the header
  56409. * @param {Object} tool
  56410. */
  56411. addTool: function(tool) {
  56412. this.tools.push(this.add(tool));
  56413. },
  56414. /**
  56415. * @protected
  56416. * Set up the `tools.<tool type>` link in the owning Panel.
  56417. * Bind the tool to its owning Panel.
  56418. * @param component
  56419. * @param index
  56420. */
  56421. onAdd: function(component, index) {
  56422. this.callParent(arguments);
  56423. if (component instanceof Ext.panel.Tool) {
  56424. component.bindTo(this.ownerCt);
  56425. this.tools[component.type] = component;
  56426. }
  56427. },
  56428. /**
  56429. * Add bodyCls to the renderData object
  56430. * @return {Object} Object with keys and values that are going to be applied to the renderTpl
  56431. * @private
  56432. */
  56433. initRenderData: function() {
  56434. return Ext.applyIf(this.callParent(), {
  56435. bodyCls: this.bodyCls
  56436. });
  56437. }
  56438. });
  56439. /**
  56440. * @class Ext.fx.target.Target
  56441. This class specifies a generic target for an animation. It provides a wrapper around a
  56442. series of different types of objects to allow for a generic animation API.
  56443. A target can be a single object or a Composite object containing other objects that are
  56444. to be animated. This class and it's subclasses are generally not created directly, the
  56445. underlying animation will create the appropriate Ext.fx.target.Target object by passing
  56446. the instance to be animated.
  56447. The following types of objects can be animated:
  56448. - {@link Ext.fx.target.Component Components}
  56449. - {@link Ext.fx.target.Element Elements}
  56450. - {@link Ext.fx.target.Sprite Sprites}
  56451. * @markdown
  56452. * @abstract
  56453. */
  56454. Ext.define('Ext.fx.target.Target', {
  56455. isAnimTarget: true,
  56456. /**
  56457. * Creates new Target.
  56458. * @param {Ext.Component/Ext.Element/Ext.draw.Sprite} target The object to be animated
  56459. */
  56460. constructor: function(target) {
  56461. this.target = target;
  56462. this.id = this.getId();
  56463. },
  56464. getId: function() {
  56465. return this.target.id;
  56466. }
  56467. });
  56468. /**
  56469. * @class Ext.fx.target.Element
  56470. *
  56471. * This class represents a animation target for an {@link Ext.Element}. In general this class will not be
  56472. * created directly, the {@link Ext.Element} will be passed to the animation and
  56473. * and the appropriate target will be created.
  56474. */
  56475. Ext.define('Ext.fx.target.Element', {
  56476. /* Begin Definitions */
  56477. extend: 'Ext.fx.target.Target',
  56478. /* End Definitions */
  56479. type: 'element',
  56480. getElVal: function(el, attr, val) {
  56481. if (val == undefined) {
  56482. if (attr === 'x') {
  56483. val = el.getX();
  56484. }
  56485. else if (attr === 'y') {
  56486. val = el.getY();
  56487. }
  56488. else if (attr === 'scrollTop') {
  56489. val = el.getScroll().top;
  56490. }
  56491. else if (attr === 'scrollLeft') {
  56492. val = el.getScroll().left;
  56493. }
  56494. else if (attr === 'height') {
  56495. val = el.getHeight();
  56496. }
  56497. else if (attr === 'width') {
  56498. val = el.getWidth();
  56499. }
  56500. else {
  56501. val = el.getStyle(attr);
  56502. }
  56503. }
  56504. return val;
  56505. },
  56506. getAttr: function(attr, val) {
  56507. var el = this.target;
  56508. return [[ el, this.getElVal(el, attr, val)]];
  56509. },
  56510. setAttr: function(targetData) {
  56511. var target = this.target,
  56512. ln = targetData.length,
  56513. attrs, attr, o, i, j, ln2, element, value;
  56514. for (i = 0; i < ln; i++) {
  56515. attrs = targetData[i].attrs;
  56516. for (attr in attrs) {
  56517. if (attrs.hasOwnProperty(attr)) {
  56518. ln2 = attrs[attr].length;
  56519. for (j = 0; j < ln2; j++) {
  56520. o = attrs[attr][j];
  56521. element = o[0];
  56522. value = o[1];
  56523. if (attr === 'x') {
  56524. element.setX(value);
  56525. } else if (attr === 'y') {
  56526. element.setY(value);
  56527. } else if (attr === 'scrollTop') {
  56528. element.scrollTo('top', value);
  56529. } else if (attr === 'scrollLeft') {
  56530. element.scrollTo('left',value);
  56531. } else if (attr === 'width') {
  56532. element.setWidth(value);
  56533. } else if (attr === 'height') {
  56534. element.setHeight(value);
  56535. } else {
  56536. element.setStyle(attr, value);
  56537. }
  56538. }
  56539. }
  56540. }
  56541. }
  56542. }
  56543. });
  56544. /**
  56545. * @class Ext.fx.target.ElementCSS
  56546. *
  56547. * This class represents a animation target for an {@link Ext.Element} that supports CSS
  56548. * based animation. In general this class will not be created directly, the {@link Ext.Element}
  56549. * will be passed to the animation and the appropriate target will be created.
  56550. */
  56551. Ext.define('Ext.fx.target.ElementCSS', {
  56552. /* Begin Definitions */
  56553. extend: 'Ext.fx.target.Element',
  56554. /* End Definitions */
  56555. setAttr: function(targetData, isFirstFrame) {
  56556. var cssArr = {
  56557. attrs: [],
  56558. duration: [],
  56559. easing: []
  56560. },
  56561. ln = targetData.length,
  56562. attributes,
  56563. attrs,
  56564. attr,
  56565. easing,
  56566. duration,
  56567. o,
  56568. i,
  56569. j,
  56570. ln2;
  56571. for (i = 0; i < ln; i++) {
  56572. attrs = targetData[i];
  56573. duration = attrs.duration;
  56574. easing = attrs.easing;
  56575. attrs = attrs.attrs;
  56576. for (attr in attrs) {
  56577. if (Ext.Array.indexOf(cssArr.attrs, attr) == -1) {
  56578. cssArr.attrs.push(attr.replace(/[A-Z]/g, function(v) {
  56579. return '-' + v.toLowerCase();
  56580. }));
  56581. cssArr.duration.push(duration + 'ms');
  56582. cssArr.easing.push(easing);
  56583. }
  56584. }
  56585. }
  56586. attributes = cssArr.attrs.join(',');
  56587. duration = cssArr.duration.join(',');
  56588. easing = cssArr.easing.join(', ');
  56589. for (i = 0; i < ln; i++) {
  56590. attrs = targetData[i].attrs;
  56591. for (attr in attrs) {
  56592. ln2 = attrs[attr].length;
  56593. for (j = 0; j < ln2; j++) {
  56594. o = attrs[attr][j];
  56595. o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', isFirstFrame ? '' : attributes);
  56596. o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', isFirstFrame ? '' : duration);
  56597. o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', isFirstFrame ? '' : easing);
  56598. o[0].setStyle(attr, o[1]);
  56599. // Must trigger reflow to make this get used as the start point for the transition that follows
  56600. if (isFirstFrame) {
  56601. o = o[0].dom.offsetWidth;
  56602. }
  56603. else {
  56604. // Remove transition properties when completed.
  56605. o[0].on(Ext.supports.CSS3TransitionEnd, function() {
  56606. this.setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', null);
  56607. this.setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', null);
  56608. this.setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', null);
  56609. }, o[0], { single: true });
  56610. }
  56611. }
  56612. }
  56613. }
  56614. }
  56615. });
  56616. /**
  56617. * @class Ext.fx.target.CompositeElement
  56618. *
  56619. * This class represents a animation target for a {@link Ext.CompositeElement}. It allows
  56620. * each {@link Ext.Element} in the group to be animated as a whole. In general this class will not be
  56621. * created directly, the {@link Ext.CompositeElement} will be passed to the animation and
  56622. * and the appropriate target will be created.
  56623. */
  56624. Ext.define('Ext.fx.target.CompositeElement', {
  56625. /* Begin Definitions */
  56626. extend: 'Ext.fx.target.Element',
  56627. /* End Definitions */
  56628. /**
  56629. * @property {Boolean} isComposite
  56630. * `true` in this class to identify an object as an instantiated CompositeElement, or subclass thereof.
  56631. */
  56632. isComposite: true,
  56633. constructor: function(target) {
  56634. target.id = target.id || Ext.id(null, 'ext-composite-');
  56635. this.callParent([target]);
  56636. },
  56637. getAttr: function(attr, val) {
  56638. var out = [],
  56639. elements = this.target.elements,
  56640. length = elements.length,
  56641. i,
  56642. el;
  56643. for (i = 0; i < length; i++) {
  56644. el = elements[i];
  56645. if (el) {
  56646. el = this.target.getElement(el);
  56647. out.push([el, this.getElVal(el, attr, val)]);
  56648. }
  56649. }
  56650. return out;
  56651. }
  56652. });
  56653. /**
  56654. * @class Ext.fx.target.CompositeElementCSS
  56655. *
  56656. * This class represents a animation target for a {@link Ext.CompositeElement}, where the
  56657. * constituent elements support CSS based animation. It allows each {@link Ext.Element} in
  56658. * the group to be animated as a whole. In general this class will not be created directly,
  56659. * the {@link Ext.CompositeElement} will be passed to the animation and the appropriate target
  56660. * will be created.
  56661. */
  56662. Ext.define('Ext.fx.target.CompositeElementCSS', {
  56663. /* Begin Definitions */
  56664. extend: 'Ext.fx.target.CompositeElement',
  56665. requires: ['Ext.fx.target.ElementCSS'],
  56666. /* End Definitions */
  56667. setAttr: function() {
  56668. return Ext.fx.target.ElementCSS.prototype.setAttr.apply(this, arguments);
  56669. }
  56670. });
  56671. /**
  56672. * @class Ext.fx.target.Sprite
  56673. This class represents an animation target for a {@link Ext.draw.Sprite}. In general this class will not be
  56674. created directly, the {@link Ext.draw.Sprite} will be passed to the animation and
  56675. and the appropriate target will be created.
  56676. * @markdown
  56677. */
  56678. Ext.define('Ext.fx.target.Sprite', {
  56679. /* Begin Definitions */
  56680. extend: 'Ext.fx.target.Target',
  56681. /* End Definitions */
  56682. type: 'draw',
  56683. getFromPrim: function (sprite, attr) {
  56684. var obj;
  56685. switch (attr) {
  56686. case 'rotate':
  56687. case 'rotation':
  56688. obj = sprite.attr.rotation;
  56689. return {
  56690. x: obj.x || 0,
  56691. y: obj.y || 0,
  56692. degrees: obj.degrees || 0
  56693. };
  56694. case 'scale':
  56695. case 'scaling':
  56696. obj = sprite.attr.scaling;
  56697. return {
  56698. x: obj.x || 1,
  56699. y: obj.y || 1,
  56700. cx: obj.cx || 0,
  56701. cy: obj.cy || 0
  56702. };
  56703. case 'translate':
  56704. case 'translation':
  56705. obj = sprite.attr.translation;
  56706. return {
  56707. x: obj.x || 0,
  56708. y: obj.y || 0
  56709. };
  56710. default:
  56711. return sprite.attr[attr];
  56712. }
  56713. },
  56714. getAttr: function (attr, val) {
  56715. return [
  56716. [this.target, val != undefined ? val : this.getFromPrim(this.target, attr)]
  56717. ];
  56718. },
  56719. setAttr: function (targetData) {
  56720. var ln = targetData.length,
  56721. spriteArr = [],
  56722. attrsConf, attr, attrArr, attrs, sprite, idx, value, i, j, x, y, ln2;
  56723. for (i = 0; i < ln; i++) {
  56724. attrsConf = targetData[i].attrs;
  56725. for (attr in attrsConf) {
  56726. attrArr = attrsConf[attr];
  56727. ln2 = attrArr.length;
  56728. for (j = 0; j < ln2; j++) {
  56729. sprite = attrArr[j][0];
  56730. attrs = attrArr[j][1];
  56731. if (attr === 'translate' || attr === 'translation') {
  56732. value = {
  56733. x: attrs.x,
  56734. y: attrs.y
  56735. };
  56736. }
  56737. else if (attr === 'rotate' || attr === 'rotation') {
  56738. x = attrs.x;
  56739. if (isNaN(x)) {
  56740. x = null;
  56741. }
  56742. y = attrs.y;
  56743. if (isNaN(y)) {
  56744. y = null;
  56745. }
  56746. value = {
  56747. degrees: attrs.degrees,
  56748. x: x,
  56749. y: y
  56750. };
  56751. } else if (attr === 'scale' || attr === 'scaling') {
  56752. x = attrs.x;
  56753. if (isNaN(x)) {
  56754. x = null;
  56755. }
  56756. y = attrs.y;
  56757. if (isNaN(y)) {
  56758. y = null;
  56759. }
  56760. value = {
  56761. x: x,
  56762. y: y,
  56763. cx: attrs.cx,
  56764. cy: attrs.cy
  56765. };
  56766. }
  56767. else if (attr === 'width' || attr === 'height' || attr === 'x' || attr === 'y') {
  56768. value = parseFloat(attrs);
  56769. }
  56770. else {
  56771. value = attrs;
  56772. }
  56773. idx = Ext.Array.indexOf(spriteArr, sprite);
  56774. if (idx == -1) {
  56775. spriteArr.push([sprite, {}]);
  56776. idx = spriteArr.length - 1;
  56777. }
  56778. spriteArr[idx][1][attr] = value;
  56779. }
  56780. }
  56781. }
  56782. ln = spriteArr.length;
  56783. for (i = 0; i < ln; i++) {
  56784. spriteArr[i][0].setAttributes(spriteArr[i][1]);
  56785. }
  56786. this.target.redraw();
  56787. }
  56788. });
  56789. /**
  56790. * @class Ext.fx.target.CompositeSprite
  56791. This class represents a animation target for a {@link Ext.draw.CompositeSprite}. It allows
  56792. each {@link Ext.draw.Sprite} in the group to be animated as a whole. In general this class will not be
  56793. created directly, the {@link Ext.draw.CompositeSprite} will be passed to the animation and
  56794. and the appropriate target will be created.
  56795. * @markdown
  56796. */
  56797. Ext.define('Ext.fx.target.CompositeSprite', {
  56798. /* Begin Definitions */
  56799. extend: 'Ext.fx.target.Sprite',
  56800. /* End Definitions */
  56801. getAttr: function(attr, val) {
  56802. var out = [],
  56803. sprites = [].concat(this.target.items),
  56804. length = sprites.length,
  56805. i,
  56806. sprite;
  56807. for (i = 0; i < length; i++) {
  56808. sprite = sprites[i];
  56809. out.push([sprite, val != undefined ? val : this.getFromPrim(sprite, attr)]);
  56810. }
  56811. return out;
  56812. }
  56813. });
  56814. /**
  56815. * @class Ext.fx.target.Component
  56816. *
  56817. * This class represents a animation target for a {@link Ext.Component}. In general this class will not be
  56818. * created directly, the {@link Ext.Component} will be passed to the animation and
  56819. * and the appropriate target will be created.
  56820. */
  56821. Ext.define('Ext.fx.target.Component', {
  56822. /* Begin Definitions */
  56823. extend: 'Ext.fx.target.Target',
  56824. /* End Definitions */
  56825. type: 'component',
  56826. // Methods to call to retrieve unspecified "from" values from a target Component
  56827. getPropMethod: {
  56828. top: function() {
  56829. return this.getPosition(true)[1];
  56830. },
  56831. left: function() {
  56832. return this.getPosition(true)[0];
  56833. },
  56834. x: function() {
  56835. return this.getPosition()[0];
  56836. },
  56837. y: function() {
  56838. return this.getPosition()[1];
  56839. },
  56840. height: function() {
  56841. return this.getHeight();
  56842. },
  56843. width: function() {
  56844. return this.getWidth();
  56845. },
  56846. opacity: function() {
  56847. return this.el.getStyle('opacity');
  56848. }
  56849. },
  56850. compMethod: {
  56851. top: 'setPosition',
  56852. left: 'setPosition',
  56853. x: 'setPagePosition',
  56854. y: 'setPagePosition',
  56855. height: 'setSize',
  56856. width: 'setSize',
  56857. opacity: 'setOpacity'
  56858. },
  56859. // Read the named attribute from the target Component. Use the defined getter for the attribute
  56860. getAttr: function(attr, val) {
  56861. return [[this.target, val !== undefined ? val : this.getPropMethod[attr].call(this.target)]];
  56862. },
  56863. setAttr: function(targetData, isFirstFrame, isLastFrame) {
  56864. var me = this,
  56865. target = me.target,
  56866. ln = targetData.length,
  56867. attrs, attr, o, i, j, meth, targets, left, top, w, h;
  56868. for (i = 0; i < ln; i++) {
  56869. attrs = targetData[i].attrs;
  56870. for (attr in attrs) {
  56871. targets = attrs[attr].length;
  56872. meth = {
  56873. setPosition: {},
  56874. setPagePosition: {},
  56875. setSize: {},
  56876. setOpacity: {}
  56877. };
  56878. for (j = 0; j < targets; j++) {
  56879. o = attrs[attr][j];
  56880. // We REALLY want a single function call, so push these down to merge them: eg
  56881. // meth.setPagePosition.target = <targetComponent>
  56882. // meth.setPagePosition['x'] = 100
  56883. // meth.setPagePosition['y'] = 100
  56884. meth[me.compMethod[attr]].target = o[0];
  56885. meth[me.compMethod[attr]][attr] = o[1];
  56886. }
  56887. if (meth.setPosition.target) {
  56888. o = meth.setPosition;
  56889. left = (o.left === undefined) ? undefined : parseFloat(o.left);
  56890. top = (o.top === undefined) ? undefined : parseFloat(o.top);
  56891. o.target.setPosition(left, top);
  56892. }
  56893. if (meth.setPagePosition.target) {
  56894. o = meth.setPagePosition;
  56895. o.target.setPagePosition(o.x, o.y);
  56896. }
  56897. if (meth.setSize.target) {
  56898. o = meth.setSize;
  56899. // Dimensions not being animated MUST NOT be autosized. They must remain at current value.
  56900. w = (o.width === undefined) ? o.target.getWidth() : parseFloat(o.width);
  56901. h = (o.height === undefined) ? o.target.getHeight() : parseFloat(o.height);
  56902. // Only set the size of the Component on the last frame, or if the animation was
  56903. // configured with dynamic: true.
  56904. // In other cases, we just set the target element size.
  56905. // This will result in either clipping if animating a reduction in size, or the revealing of
  56906. // the inner elements of the Component if animating an increase in size.
  56907. // Component's animate function initially resizes to the larger size before resizing the
  56908. // outer element to clip the contents.
  56909. if (isLastFrame || me.dynamic) {
  56910. o.target.setSize(w, h);
  56911. } else {
  56912. o.target.el.setSize(w, h);
  56913. }
  56914. }
  56915. if (meth.setOpacity.target) {
  56916. o = meth.setOpacity;
  56917. o.target.el.setStyle('opacity', o.opacity);
  56918. }
  56919. }
  56920. }
  56921. }
  56922. });
  56923. /**
  56924. * @class Ext.fx.Queue
  56925. * Animation Queue mixin to handle chaining and queueing by target.
  56926. * @private
  56927. */
  56928. Ext.define('Ext.fx.Queue', {
  56929. requires: ['Ext.util.HashMap'],
  56930. constructor: function() {
  56931. this.targets = new Ext.util.HashMap();
  56932. this.fxQueue = {};
  56933. },
  56934. // @private
  56935. getFxDefaults: function(targetId) {
  56936. var target = this.targets.get(targetId);
  56937. if (target) {
  56938. return target.fxDefaults;
  56939. }
  56940. return {};
  56941. },
  56942. // @private
  56943. setFxDefaults: function(targetId, obj) {
  56944. var target = this.targets.get(targetId);
  56945. if (target) {
  56946. target.fxDefaults = Ext.apply(target.fxDefaults || {}, obj);
  56947. }
  56948. },
  56949. // @private
  56950. stopAnimation: function(targetId) {
  56951. var me = this,
  56952. queue = me.getFxQueue(targetId),
  56953. ln = queue.length;
  56954. while (ln) {
  56955. queue[ln - 1].end();
  56956. ln--;
  56957. }
  56958. },
  56959. /**
  56960. * @private
  56961. * Returns current animation object if the element has any effects actively running or queued, else returns false.
  56962. */
  56963. getActiveAnimation: function(targetId) {
  56964. var queue = this.getFxQueue(targetId);
  56965. return (queue && !!queue.length) ? queue[0] : false;
  56966. },
  56967. // @private
  56968. hasFxBlock: function(targetId) {
  56969. var queue = this.getFxQueue(targetId);
  56970. return queue && queue[0] && queue[0].block;
  56971. },
  56972. // @private get fx queue for passed target, create if needed.
  56973. getFxQueue: function(targetId) {
  56974. if (!targetId) {
  56975. return false;
  56976. }
  56977. var me = this,
  56978. queue = me.fxQueue[targetId],
  56979. target = me.targets.get(targetId);
  56980. if (!target) {
  56981. return false;
  56982. }
  56983. if (!queue) {
  56984. me.fxQueue[targetId] = [];
  56985. // GarbageCollector will need to clean up Elements since they aren't currently observable
  56986. if (target.type != 'element') {
  56987. target.target.on('destroy', function() {
  56988. me.fxQueue[targetId] = [];
  56989. });
  56990. }
  56991. }
  56992. return me.fxQueue[targetId];
  56993. },
  56994. // @private
  56995. queueFx: function(anim) {
  56996. var me = this,
  56997. target = anim.target,
  56998. queue, ln;
  56999. if (!target) {
  57000. return;
  57001. }
  57002. queue = me.getFxQueue(target.getId());
  57003. ln = queue.length;
  57004. if (ln) {
  57005. if (anim.concurrent) {
  57006. anim.paused = false;
  57007. }
  57008. else {
  57009. queue[ln - 1].on('afteranimate', function() {
  57010. anim.paused = false;
  57011. });
  57012. }
  57013. }
  57014. else {
  57015. anim.paused = false;
  57016. }
  57017. anim.on('afteranimate', function() {
  57018. Ext.Array.remove(queue, anim);
  57019. if (anim.remove) {
  57020. if (target.type == 'element') {
  57021. var el = Ext.get(target.id);
  57022. if (el) {
  57023. el.remove();
  57024. }
  57025. }
  57026. }
  57027. }, this);
  57028. queue.push(anim);
  57029. }
  57030. });
  57031. /**
  57032. * @class Ext.fx.Manager
  57033. * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.
  57034. * @private
  57035. * @singleton
  57036. */
  57037. Ext.define('Ext.fx.Manager', {
  57038. /* Begin Definitions */
  57039. singleton: true,
  57040. requires: ['Ext.util.MixedCollection',
  57041. 'Ext.fx.target.Element',
  57042. 'Ext.fx.target.ElementCSS',
  57043. 'Ext.fx.target.CompositeElement',
  57044. 'Ext.fx.target.CompositeElementCSS',
  57045. 'Ext.fx.target.Sprite',
  57046. 'Ext.fx.target.CompositeSprite',
  57047. 'Ext.fx.target.Component'],
  57048. mixins: {
  57049. queue: 'Ext.fx.Queue'
  57050. },
  57051. /* End Definitions */
  57052. constructor: function() {
  57053. this.items = new Ext.util.MixedCollection();
  57054. this.mixins.queue.constructor.call(this);
  57055. // this.requestAnimFrame = (function() {
  57056. // var raf = window.requestAnimationFrame ||
  57057. // window.webkitRequestAnimationFrame ||
  57058. // window.mozRequestAnimationFrame ||
  57059. // window.oRequestAnimationFrame ||
  57060. // window.msRequestAnimationFrame;
  57061. // if (raf) {
  57062. // return function(callback, element) {
  57063. // raf(callback);
  57064. // };
  57065. // }
  57066. // else {
  57067. // return function(callback, element) {
  57068. // window.setTimeout(callback, Ext.fx.Manager.interval);
  57069. // };
  57070. // }
  57071. // })();
  57072. },
  57073. /**
  57074. * @cfg {Number} interval Default interval in miliseconds to calculate each frame. Defaults to 16ms (~60fps)
  57075. */
  57076. interval: 16,
  57077. /**
  57078. * @cfg {Boolean} forceJS Force the use of JavaScript-based animation instead of CSS3 animation, even when CSS3
  57079. * animation is supported by the browser. This defaults to true currently, as CSS3 animation support is still
  57080. * considered experimental at this time, and if used should be thouroughly tested across all targeted browsers.
  57081. * @protected
  57082. */
  57083. forceJS: true,
  57084. // @private Target factory
  57085. createTarget: function(target) {
  57086. var me = this,
  57087. useCSS3 = !me.forceJS && Ext.supports.Transitions,
  57088. targetObj;
  57089. me.useCSS3 = useCSS3;
  57090. if (target) {
  57091. // dom element, string or fly
  57092. if (target.tagName || Ext.isString(target) || target.isFly) {
  57093. target = Ext.get(target);
  57094. targetObj = new Ext.fx.target['Element' + (useCSS3 ? 'CSS' : '')](target);
  57095. }
  57096. // Element
  57097. else if (target.dom) {
  57098. targetObj = new Ext.fx.target['Element' + (useCSS3 ? 'CSS' : '')](target);
  57099. }
  57100. // Element Composite
  57101. else if (target.isComposite) {
  57102. targetObj = new Ext.fx.target['CompositeElement' + (useCSS3 ? 'CSS' : '')](target);
  57103. }
  57104. // Draw Sprite
  57105. else if (target.isSprite) {
  57106. targetObj = new Ext.fx.target.Sprite(target);
  57107. }
  57108. // Draw Sprite Composite
  57109. else if (target.isCompositeSprite) {
  57110. targetObj = new Ext.fx.target.CompositeSprite(target);
  57111. }
  57112. // Component
  57113. else if (target.isComponent) {
  57114. targetObj = new Ext.fx.target.Component(target);
  57115. }
  57116. else if (target.isAnimTarget) {
  57117. return target;
  57118. }
  57119. else {
  57120. return null;
  57121. }
  57122. me.targets.add(targetObj);
  57123. return targetObj;
  57124. }
  57125. else {
  57126. return null;
  57127. }
  57128. },
  57129. /**
  57130. * Add an Anim to the manager. This is done automatically when an Anim instance is created.
  57131. * @param {Ext.fx.Anim} anim
  57132. */
  57133. addAnim: function(anim) {
  57134. var items = this.items,
  57135. task = this.task;
  57136. // Make sure we use the anim's id, not the anim target's id here. The anim id will be unique on
  57137. // each call to addAnim. `anim.target` is the DOM element being targeted, and since multiple animations
  57138. // can target a single DOM node concurrently, the target id cannot be assumned to be unique.
  57139. items.add(anim.id, anim);
  57140. //Ext.log('+ added anim ', anim.id, ', target: ', anim.target.getId(), ', duration: ', anim.duration);
  57141. // Start the timer if not already running
  57142. if (!task && items.length) {
  57143. task = this.task = {
  57144. run: this.runner,
  57145. interval: this.interval,
  57146. scope: this
  57147. };
  57148. //Ext.log('--->> Starting task');
  57149. Ext.TaskManager.start(task);
  57150. }
  57151. },
  57152. /**
  57153. * Remove an Anim from the manager. This is done automatically when an Anim ends.
  57154. * @param {Ext.fx.Anim} anim
  57155. */
  57156. removeAnim: function(anim) {
  57157. var me = this,
  57158. items = me.items,
  57159. task = me.task;
  57160. items.removeAtKey(anim.id);
  57161. //Ext.log(' X removed anim ', anim.id, ', target: ', anim.target.getId(), ', frames: ', anim.frameCount, ', item count: ', items.length);
  57162. // Stop the timer if there are no more managed Anims
  57163. if (task && !items.length) {
  57164. //Ext.log('[]--- Stopping task');
  57165. Ext.TaskManager.stop(task);
  57166. delete me.task;
  57167. }
  57168. },
  57169. /**
  57170. * @private
  57171. * Runner function being called each frame
  57172. */
  57173. runner: function() {
  57174. var me = this,
  57175. items = me.items.getRange(),
  57176. i = 0,
  57177. len = items.length,
  57178. anim;
  57179. //Ext.log(' executing anim runner task with ', len, ' items');
  57180. me.targetArr = {};
  57181. // Single timestamp for all animations this interval
  57182. me.timestamp = new Date();
  57183. // Loop to start any new animations first before looping to
  57184. // execute running animations (which will also include all animations
  57185. // started in this loop). This is a subtle difference from simply
  57186. // iterating in one loop and starting then running each animation,
  57187. // but separating the loops is necessary to ensure that all new animations
  57188. // actually kick off prior to existing ones regardless of array order.
  57189. // Otherwise in edge cases when there is excess latency in overall
  57190. // performance, allowing existing animations to run before new ones can
  57191. // lead to dropped frames and subtle race conditions when they are
  57192. // interdependent, which is often the case with certain Element fx.
  57193. for (; i < len; i++) {
  57194. anim = items[i];
  57195. if (anim.isReady()) {
  57196. //Ext.log(' starting anim ', anim.id, ', target: ', anim.target.id);
  57197. me.startAnim(anim);
  57198. }
  57199. }
  57200. for (i = 0; i < len; i++) {
  57201. anim = items[i];
  57202. if (anim.isRunning()) {
  57203. //Ext.log(' running anim ', anim.target.id);
  57204. me.runAnim(anim);
  57205. } else if (!me.useCSS3) {
  57206. // When using CSS3 transitions the animations get paused since they are not
  57207. // needed once the transition is handed over to the browser, so we can
  57208. // ignore this case. However if we are doing JS animations and something is
  57209. // paused here it's possibly unintentional.
  57210. //Ext.log(' (i) anim ', anim.id, ' is active but not running...');
  57211. }
  57212. }
  57213. // Apply all the pending changes to their targets
  57214. me.applyPendingAttrs();
  57215. },
  57216. /**
  57217. * @private
  57218. * Start the individual animation (initialization)
  57219. */
  57220. startAnim: function(anim) {
  57221. anim.start(this.timestamp);
  57222. },
  57223. /**
  57224. * @private
  57225. * Run the individual animation for this frame
  57226. */
  57227. runAnim: function(anim) {
  57228. if (!anim) {
  57229. return;
  57230. }
  57231. var me = this,
  57232. targetId = anim.target.getId(),
  57233. useCSS3 = me.useCSS3 && anim.target.type == 'element',
  57234. elapsedTime = me.timestamp - anim.startTime,
  57235. lastFrame = (elapsedTime >= anim.duration),
  57236. target, o;
  57237. target = this.collectTargetData(anim, elapsedTime, useCSS3, lastFrame);
  57238. // For CSS3 animation, we need to immediately set the first frame's attributes without any transition
  57239. // to get a good initial state, then add the transition properties and set the final attributes.
  57240. if (useCSS3) {
  57241. //Ext.log(' (i) using CSS3 transitions');
  57242. // Flush the collected attributes, without transition
  57243. anim.target.setAttr(target.anims[anim.id].attributes, true);
  57244. // Add the end frame data
  57245. me.collectTargetData(anim, anim.duration, useCSS3, lastFrame);
  57246. // Pause the animation so runAnim doesn't keep getting called
  57247. anim.paused = true;
  57248. target = anim.target.target;
  57249. // We only want to attach an event on the last element in a composite
  57250. if (anim.target.isComposite) {
  57251. target = anim.target.target.last();
  57252. }
  57253. // Listen for the transitionend event
  57254. o = {};
  57255. o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame;
  57256. o.scope = anim;
  57257. o.single = true;
  57258. target.on(o);
  57259. }
  57260. },
  57261. /**
  57262. * @private
  57263. * Collect target attributes for the given Anim object at the given timestamp
  57264. * @param {Ext.fx.Anim} anim The Anim instance
  57265. * @param {Number} timestamp Time after the anim's start time
  57266. * @param {Boolean} [useCSS3=false] True if using CSS3-based animation, else false
  57267. * @param {Boolean} [isLastFrame=false] True if this is the last frame of animation to be run, else false
  57268. * @return {Object} The animation target wrapper object containing the passed animation along with the
  57269. * new attributes to set on the target's element in the next animation frame.
  57270. */
  57271. collectTargetData: function(anim, elapsedTime, useCSS3, isLastFrame) {
  57272. var targetId = anim.target.getId(),
  57273. target = this.targetArr[targetId];
  57274. if (!target) {
  57275. // Create a thin wrapper around the target so that we can create a link between the
  57276. // target element and its associated animations. This is important later when applying
  57277. // attributes to the target so that each animation can be independently run with its own
  57278. // duration and stopped at any point without affecting other animations for the same target.
  57279. target = this.targetArr[targetId] = {
  57280. id: targetId,
  57281. el: anim.target,
  57282. anims: {}
  57283. };
  57284. }
  57285. // This is a wrapper for the animation so that we can also save state along with it,
  57286. // including the current elapsed time and lastFrame status. Even though this method only
  57287. // adds a single anim object per call, each target element could have multiple animations
  57288. // associated with it, which is why the anim is added to the target's `anims` hash by id.
  57289. target.anims[anim.id] = {
  57290. id: anim.id,
  57291. anim: anim,
  57292. elapsed: elapsedTime,
  57293. isLastFrame: isLastFrame,
  57294. // This is the object that gets applied to the target element below in applyPendingAttrs():
  57295. attributes: [{
  57296. duration: anim.duration,
  57297. easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing,
  57298. // This is where the magic happens. The anim calculates what its new attributes should
  57299. // be based on the current frame and returns those as a hash of values.
  57300. attrs: anim.runAnim(elapsedTime)
  57301. }]
  57302. };
  57303. return target;
  57304. },
  57305. /**
  57306. * @private
  57307. * Apply all pending attribute changes to their targets
  57308. */
  57309. applyPendingAttrs: function() {
  57310. var targetArr = this.targetArr,
  57311. target, targetId, animWrap, anim, animId;
  57312. // Loop through each target
  57313. for (targetId in targetArr) {
  57314. if (targetArr.hasOwnProperty(targetId)) {
  57315. target = targetArr[targetId];
  57316. // Each target could have multiple associated animations, so iterate those
  57317. for (animId in target.anims) {
  57318. if (target.anims.hasOwnProperty(animId)) {
  57319. animWrap = target.anims[animId];
  57320. anim = animWrap.anim;
  57321. // If the animation has valid attributes, set them on the target
  57322. if (animWrap.attributes && anim.isRunning()) {
  57323. //Ext.log(' > applying attributes for anim ', animWrap.id, ', target: ', target.id, ', elapsed: ', animWrap.elapsed);
  57324. target.el.setAttr(animWrap.attributes, false, animWrap.isLastFrame);
  57325. // If this particular anim is at the last frame end it
  57326. if (animWrap.isLastFrame) {
  57327. //Ext.log(' running last frame for ', animWrap.id, ', target: ', targetId);
  57328. anim.lastFrame();
  57329. }
  57330. }
  57331. }
  57332. }
  57333. }
  57334. }
  57335. }
  57336. });
  57337. /**
  57338. * @class Ext.fx.Animator
  57339. *
  57340. * This class is used to run keyframe based animations, which follows the CSS3 based animation structure.
  57341. * Keyframe animations differ from typical from/to animations in that they offer the ability to specify values
  57342. * at various points throughout the animation.
  57343. *
  57344. * ## Using Keyframes
  57345. *
  57346. * The {@link #keyframes} option is the most important part of specifying an animation when using this
  57347. * class. A key frame is a point in a particular animation. We represent this as a percentage of the
  57348. * total animation duration. At each key frame, we can specify the target values at that time. Note that
  57349. * you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link #keyframe}
  57350. * event that fires after each key frame is reached.
  57351. *
  57352. * ## Example
  57353. *
  57354. * In the example below, we modify the values of the element at each fifth throughout the animation.
  57355. *
  57356. * @example
  57357. * Ext.create('Ext.fx.Animator', {
  57358. * target: Ext.getBody().createChild({
  57359. * style: {
  57360. * width: '100px',
  57361. * height: '100px',
  57362. * 'background-color': 'red'
  57363. * }
  57364. * }),
  57365. * duration: 10000, // 10 seconds
  57366. * keyframes: {
  57367. * 0: {
  57368. * opacity: 1,
  57369. * backgroundColor: 'FF0000'
  57370. * },
  57371. * 20: {
  57372. * x: 30,
  57373. * opacity: 0.5
  57374. * },
  57375. * 40: {
  57376. * x: 130,
  57377. * backgroundColor: '0000FF'
  57378. * },
  57379. * 60: {
  57380. * y: 80,
  57381. * opacity: 0.3
  57382. * },
  57383. * 80: {
  57384. * width: 200,
  57385. * y: 200
  57386. * },
  57387. * 100: {
  57388. * opacity: 1,
  57389. * backgroundColor: '00FF00'
  57390. * }
  57391. * }
  57392. * });
  57393. */
  57394. Ext.define('Ext.fx.Animator', {
  57395. /* Begin Definitions */
  57396. mixins: {
  57397. observable: 'Ext.util.Observable'
  57398. },
  57399. requires: ['Ext.fx.Manager'],
  57400. /* End Definitions */
  57401. /**
  57402. * @property {Boolean} isAnimator
  57403. * `true` in this class to identify an object as an instantiated Animator, or subclass thereof.
  57404. */
  57405. isAnimator: true,
  57406. /**
  57407. * @cfg {Number} duration
  57408. * Time in milliseconds for the animation to last. Defaults to 250.
  57409. */
  57410. duration: 250,
  57411. /**
  57412. * @cfg {Number} delay
  57413. * Time to delay before starting the animation. Defaults to 0.
  57414. */
  57415. delay: 0,
  57416. /* private used to track a delayed starting time */
  57417. delayStart: 0,
  57418. /**
  57419. * @cfg {Boolean} dynamic
  57420. * Currently only for Component Animation: Only set a component's outer element size bypassing layouts. Set to true to do full layouts for every frame of the animation. Defaults to false.
  57421. */
  57422. dynamic: false,
  57423. /**
  57424. * @cfg {String} easing
  57425. *
  57426. * This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
  57427. * speed over its duration.
  57428. *
  57429. * - backIn
  57430. * - backOut
  57431. * - bounceIn
  57432. * - bounceOut
  57433. * - ease
  57434. * - easeIn
  57435. * - easeOut
  57436. * - easeInOut
  57437. * - elasticIn
  57438. * - elasticOut
  57439. * - cubic-bezier(x1, y1, x2, y2)
  57440. *
  57441. * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
  57442. * specification. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
  57443. * be in the range [0, 1] or the definition is invalid.
  57444. *
  57445. * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
  57446. */
  57447. easing: 'ease',
  57448. /**
  57449. * Flag to determine if the animation has started
  57450. * @property running
  57451. * @type Boolean
  57452. */
  57453. running: false,
  57454. /**
  57455. * Flag to determine if the animation is paused. Only set this to true if you need to
  57456. * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
  57457. * @property paused
  57458. * @type Boolean
  57459. */
  57460. paused: false,
  57461. /**
  57462. * @private
  57463. */
  57464. damper: 1,
  57465. /**
  57466. * @cfg {Number} iterations
  57467. * Number of times to execute the animation. Defaults to 1.
  57468. */
  57469. iterations: 1,
  57470. /**
  57471. * Current iteration the animation is running.
  57472. * @property currentIteration
  57473. * @type Number
  57474. */
  57475. currentIteration: 0,
  57476. /**
  57477. * Current keyframe step of the animation.
  57478. * @property keyframeStep
  57479. * @type Number
  57480. */
  57481. keyframeStep: 0,
  57482. /**
  57483. * @private
  57484. */
  57485. animKeyFramesRE: /^(from|to|\d+%?)$/,
  57486. /**
  57487. * @cfg {Ext.fx.target.Target} target
  57488. * The Ext.fx.target to apply the animation to. If not specified during initialization, this can be passed to the applyAnimator
  57489. * method to apply the same animation to many targets.
  57490. */
  57491. /**
  57492. * @cfg {Object} keyframes
  57493. * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
  57494. * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
  57495. * "from" or "to"</b>. A keyframe declaration without these keyframe selectors is invalid and will not be available for
  57496. * animation. The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
  57497. * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
  57498. <pre><code>
  57499. keyframes : {
  57500. '0%': {
  57501. left: 100
  57502. },
  57503. '40%': {
  57504. left: 150
  57505. },
  57506. '60%': {
  57507. left: 75
  57508. },
  57509. '100%': {
  57510. left: 100
  57511. }
  57512. }
  57513. </code></pre>
  57514. */
  57515. constructor: function(config) {
  57516. var me = this;
  57517. config = Ext.apply(me, config || {});
  57518. me.config = config;
  57519. me.id = Ext.id(null, 'ext-animator-');
  57520. me.addEvents(
  57521. /**
  57522. * @event beforeanimate
  57523. * Fires before the animation starts. A handler can return false to cancel the animation.
  57524. * @param {Ext.fx.Animator} this
  57525. */
  57526. 'beforeanimate',
  57527. /**
  57528. * @event keyframe
  57529. * Fires at each keyframe.
  57530. * @param {Ext.fx.Animator} this
  57531. * @param {Number} keyframe step number
  57532. */
  57533. 'keyframe',
  57534. /**
  57535. * @event afteranimate
  57536. * Fires when the animation is complete.
  57537. * @param {Ext.fx.Animator} this
  57538. * @param {Date} startTime
  57539. */
  57540. 'afteranimate'
  57541. );
  57542. me.mixins.observable.constructor.call(me, config);
  57543. me.timeline = [];
  57544. me.createTimeline(me.keyframes);
  57545. if (me.target) {
  57546. me.applyAnimator(me.target);
  57547. Ext.fx.Manager.addAnim(me);
  57548. }
  57549. },
  57550. /**
  57551. * @private
  57552. */
  57553. sorter: function (a, b) {
  57554. return a.pct - b.pct;
  57555. },
  57556. /**
  57557. * @private
  57558. * Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe
  57559. * or applying the 'to' configuration to all keyframes. Also calculates the proper animation duration per keyframe.
  57560. */
  57561. createTimeline: function(keyframes) {
  57562. var me = this,
  57563. attrs = [],
  57564. to = me.to || {},
  57565. duration = me.duration,
  57566. prevMs, ms, i, ln, pct, anim, nextAnim, attr;
  57567. for (pct in keyframes) {
  57568. if (keyframes.hasOwnProperty(pct) && me.animKeyFramesRE.test(pct)) {
  57569. attr = {attrs: Ext.apply(keyframes[pct], to)};
  57570. // CSS3 spec allow for from/to to be specified.
  57571. if (pct == "from") {
  57572. pct = 0;
  57573. }
  57574. else if (pct == "to") {
  57575. pct = 100;
  57576. }
  57577. // convert % values into integers
  57578. attr.pct = parseInt(pct, 10);
  57579. attrs.push(attr);
  57580. }
  57581. }
  57582. // Sort by pct property
  57583. Ext.Array.sort(attrs, me.sorter);
  57584. // Only an end
  57585. //if (attrs[0].pct) {
  57586. // attrs.unshift({pct: 0, attrs: element.attrs});
  57587. //}
  57588. ln = attrs.length;
  57589. for (i = 0; i < ln; i++) {
  57590. prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;
  57591. ms = duration * (attrs[i].pct / 100);
  57592. me.timeline.push({
  57593. duration: ms - prevMs,
  57594. attrs: attrs[i].attrs
  57595. });
  57596. }
  57597. },
  57598. /**
  57599. * Applies animation to the Ext.fx.target
  57600. * @private
  57601. * @param target
  57602. * @type String/Object
  57603. */
  57604. applyAnimator: function(target) {
  57605. var me = this,
  57606. anims = [],
  57607. timeline = me.timeline,
  57608. reverse = me.reverse,
  57609. ln = timeline.length,
  57610. anim, easing, damper, initial, attrs, lastAttrs, i;
  57611. if (me.fireEvent('beforeanimate', me) !== false) {
  57612. for (i = 0; i < ln; i++) {
  57613. anim = timeline[i];
  57614. attrs = anim.attrs;
  57615. easing = attrs.easing || me.easing;
  57616. damper = attrs.damper || me.damper;
  57617. delete attrs.easing;
  57618. delete attrs.damper;
  57619. anim = new Ext.fx.Anim({
  57620. target: target,
  57621. easing: easing,
  57622. damper: damper,
  57623. duration: anim.duration,
  57624. paused: true,
  57625. to: attrs
  57626. });
  57627. anims.push(anim);
  57628. }
  57629. me.animations = anims;
  57630. me.target = anim.target;
  57631. for (i = 0; i < ln - 1; i++) {
  57632. anim = anims[i];
  57633. anim.nextAnim = anims[i + 1];
  57634. anim.on('afteranimate', function() {
  57635. this.nextAnim.paused = false;
  57636. });
  57637. anim.on('afteranimate', function() {
  57638. this.fireEvent('keyframe', this, ++this.keyframeStep);
  57639. }, me);
  57640. }
  57641. anims[ln - 1].on('afteranimate', function() {
  57642. this.lastFrame();
  57643. }, me);
  57644. }
  57645. },
  57646. /**
  57647. * @private
  57648. * Fires beforeanimate and sets the running flag.
  57649. */
  57650. start: function(startTime) {
  57651. var me = this,
  57652. delay = me.delay,
  57653. delayStart = me.delayStart,
  57654. delayDelta;
  57655. if (delay) {
  57656. if (!delayStart) {
  57657. me.delayStart = startTime;
  57658. return;
  57659. }
  57660. else {
  57661. delayDelta = startTime - delayStart;
  57662. if (delayDelta < delay) {
  57663. return;
  57664. }
  57665. else {
  57666. // Compensate for frame delay;
  57667. startTime = new Date(delayStart.getTime() + delay);
  57668. }
  57669. }
  57670. }
  57671. if (me.fireEvent('beforeanimate', me) !== false) {
  57672. me.startTime = startTime;
  57673. me.running = true;
  57674. me.animations[me.keyframeStep].paused = false;
  57675. }
  57676. },
  57677. /**
  57678. * @private
  57679. * Perform lastFrame cleanup and handle iterations
  57680. * @returns a hash of the new attributes.
  57681. */
  57682. lastFrame: function() {
  57683. var me = this,
  57684. iter = me.iterations,
  57685. iterCount = me.currentIteration;
  57686. iterCount++;
  57687. if (iterCount < iter) {
  57688. me.startTime = new Date();
  57689. me.currentIteration = iterCount;
  57690. me.keyframeStep = 0;
  57691. me.applyAnimator(me.target);
  57692. me.animations[me.keyframeStep].paused = false;
  57693. }
  57694. else {
  57695. me.currentIteration = 0;
  57696. me.end();
  57697. }
  57698. },
  57699. /**
  57700. * Fire afteranimate event and end the animation. Usually called automatically when the
  57701. * animation reaches its final frame, but can also be called manually to pre-emptively
  57702. * stop and destroy the running animation.
  57703. */
  57704. end: function() {
  57705. var me = this;
  57706. me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);
  57707. },
  57708. isReady: function() {
  57709. return this.paused === false && this.running === false && this.iterations > 0;
  57710. },
  57711. isRunning: function() {
  57712. // Explicitly return false, we don't want to be run continuously by the manager
  57713. return false;
  57714. }
  57715. });
  57716. /**
  57717. * @private
  57718. */
  57719. Ext.define('Ext.fx.CubicBezier', {
  57720. /* Begin Definitions */
  57721. singleton: true,
  57722. /* End Definitions */
  57723. cubicBezierAtTime: function(t, p1x, p1y, p2x, p2y, duration) {
  57724. var cx = 3 * p1x,
  57725. bx = 3 * (p2x - p1x) - cx,
  57726. ax = 1 - cx - bx,
  57727. cy = 3 * p1y,
  57728. by = 3 * (p2y - p1y) - cy,
  57729. ay = 1 - cy - by;
  57730. function sampleCurveX(t) {
  57731. return ((ax * t + bx) * t + cx) * t;
  57732. }
  57733. function solve(x, epsilon) {
  57734. var t = solveCurveX(x, epsilon);
  57735. return ((ay * t + by) * t + cy) * t;
  57736. }
  57737. function solveCurveX(x, epsilon) {
  57738. var t0, t1, t2, x2, d2, i;
  57739. for (t2 = x, i = 0; i < 8; i++) {
  57740. x2 = sampleCurveX(t2) - x;
  57741. if (Math.abs(x2) < epsilon) {
  57742. return t2;
  57743. }
  57744. d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
  57745. if (Math.abs(d2) < 1e-6) {
  57746. break;
  57747. }
  57748. t2 = t2 - x2 / d2;
  57749. }
  57750. t0 = 0;
  57751. t1 = 1;
  57752. t2 = x;
  57753. if (t2 < t0) {
  57754. return t0;
  57755. }
  57756. if (t2 > t1) {
  57757. return t1;
  57758. }
  57759. while (t0 < t1) {
  57760. x2 = sampleCurveX(t2);
  57761. if (Math.abs(x2 - x) < epsilon) {
  57762. return t2;
  57763. }
  57764. if (x > x2) {
  57765. t0 = t2;
  57766. } else {
  57767. t1 = t2;
  57768. }
  57769. t2 = (t1 - t0) / 2 + t0;
  57770. }
  57771. return t2;
  57772. }
  57773. return solve(t, 1 / (200 * duration));
  57774. },
  57775. cubicBezier: function(x1, y1, x2, y2) {
  57776. var fn = function(pos) {
  57777. return Ext.fx.CubicBezier.cubicBezierAtTime(pos, x1, y1, x2, y2, 1);
  57778. };
  57779. fn.toCSS3 = function() {
  57780. return 'cubic-bezier(' + [x1, y1, x2, y2].join(',') + ')';
  57781. };
  57782. fn.reverse = function() {
  57783. return Ext.fx.CubicBezier.cubicBezier(1 - x2, 1 - y2, 1 - x1, 1 - y1);
  57784. };
  57785. return fn;
  57786. }
  57787. });
  57788. //@define Ext.fx.Easing
  57789. /**
  57790. * @class Ext.fx.Easing
  57791. *
  57792. * This class contains a series of function definitions used to modify values during an animation.
  57793. * They describe how the intermediate values used during a transition will be calculated. It allows for a transition to change
  57794. * speed over its duration. The following options are available:
  57795. *
  57796. * - linear The default easing type
  57797. * - backIn
  57798. * - backOut
  57799. * - bounceIn
  57800. * - bounceOut
  57801. * - ease
  57802. * - easeIn
  57803. * - easeOut
  57804. * - easeInOut
  57805. * - elasticIn
  57806. * - elasticOut
  57807. * - cubic-bezier(x1, y1, x2, y2)
  57808. *
  57809. * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
  57810. * specification. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
  57811. * be in the range [0, 1] or the definition is invalid.
  57812. *
  57813. * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
  57814. *
  57815. * @singleton
  57816. */
  57817. Ext.ns('Ext.fx');
  57818. Ext.require('Ext.fx.CubicBezier', function() {
  57819. var math = Math,
  57820. pi = math.PI,
  57821. pow = math.pow,
  57822. sin = math.sin,
  57823. sqrt = math.sqrt,
  57824. abs = math.abs,
  57825. backInSeed = 1.70158;
  57826. Ext.fx.Easing = {
  57827. // ease: Ext.fx.CubicBezier.cubicBezier(0.25, 0.1, 0.25, 1),
  57828. // linear: Ext.fx.CubicBezier.cubicBezier(0, 0, 1, 1),
  57829. // 'ease-in': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
  57830. // 'ease-out': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
  57831. // 'ease-in-out': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1),
  57832. // 'easeIn': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
  57833. // 'easeOut': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
  57834. // 'easeInOut': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1)
  57835. };
  57836. Ext.apply(Ext.fx.Easing, {
  57837. linear: function(n) {
  57838. return n;
  57839. },
  57840. ease: function(n) {
  57841. var q = 0.07813 - n / 2,
  57842. alpha = -0.25,
  57843. Q = sqrt(0.0066 + q * q),
  57844. x = Q - q,
  57845. X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
  57846. y = -Q - q,
  57847. Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
  57848. t = X + Y + 0.25;
  57849. return pow(1 - t, 2) * 3 * t * 0.1 + (1 - t) * 3 * t * t + t * t * t;
  57850. },
  57851. easeIn: function (n) {
  57852. return pow(n, 1.7);
  57853. },
  57854. easeOut: function (n) {
  57855. return pow(n, 0.48);
  57856. },
  57857. easeInOut: function(n) {
  57858. var q = 0.48 - n / 1.04,
  57859. Q = sqrt(0.1734 + q * q),
  57860. x = Q - q,
  57861. X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
  57862. y = -Q - q,
  57863. Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
  57864. t = X + Y + 0.5;
  57865. return (1 - t) * 3 * t * t + t * t * t;
  57866. },
  57867. backIn: function (n) {
  57868. return n * n * ((backInSeed + 1) * n - backInSeed);
  57869. },
  57870. backOut: function (n) {
  57871. n = n - 1;
  57872. return n * n * ((backInSeed + 1) * n + backInSeed) + 1;
  57873. },
  57874. elasticIn: function (n) {
  57875. if (n === 0 || n === 1) {
  57876. return n;
  57877. }
  57878. var p = 0.3,
  57879. s = p / 4;
  57880. return pow(2, -10 * n) * sin((n - s) * (2 * pi) / p) + 1;
  57881. },
  57882. elasticOut: function (n) {
  57883. return 1 - Ext.fx.Easing.elasticIn(1 - n);
  57884. },
  57885. bounceIn: function (n) {
  57886. return 1 - Ext.fx.Easing.bounceOut(1 - n);
  57887. },
  57888. bounceOut: function (n) {
  57889. var s = 7.5625,
  57890. p = 2.75,
  57891. l;
  57892. if (n < (1 / p)) {
  57893. l = s * n * n;
  57894. } else {
  57895. if (n < (2 / p)) {
  57896. n -= (1.5 / p);
  57897. l = s * n * n + 0.75;
  57898. } else {
  57899. if (n < (2.5 / p)) {
  57900. n -= (2.25 / p);
  57901. l = s * n * n + 0.9375;
  57902. } else {
  57903. n -= (2.625 / p);
  57904. l = s * n * n + 0.984375;
  57905. }
  57906. }
  57907. }
  57908. return l;
  57909. }
  57910. });
  57911. Ext.apply(Ext.fx.Easing, {
  57912. 'back-in': Ext.fx.Easing.backIn,
  57913. 'back-out': Ext.fx.Easing.backOut,
  57914. 'ease-in': Ext.fx.Easing.easeIn,
  57915. 'ease-out': Ext.fx.Easing.easeOut,
  57916. 'elastic-in': Ext.fx.Easing.elasticIn,
  57917. 'elastic-out': Ext.fx.Easing.elasticIn,
  57918. 'bounce-in': Ext.fx.Easing.bounceIn,
  57919. 'bounce-out': Ext.fx.Easing.bounceOut,
  57920. 'ease-in-out': Ext.fx.Easing.easeInOut
  57921. });
  57922. });
  57923. /**
  57924. * Represents an RGB color and provides helper functions get
  57925. * color components in HSL color space.
  57926. */
  57927. Ext.define('Ext.draw.Color', {
  57928. /* Begin Definitions */
  57929. /* End Definitions */
  57930. colorToHexRe: /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/,
  57931. rgbRe: /\s*rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
  57932. hexRe: /\s*#([0-9a-fA-F][0-9a-fA-F]?)([0-9a-fA-F][0-9a-fA-F]?)([0-9a-fA-F][0-9a-fA-F]?)\s*/,
  57933. /**
  57934. * @cfg {Number} lightnessFactor
  57935. *
  57936. * The default factor to compute the lighter or darker color. Defaults to 0.2.
  57937. */
  57938. lightnessFactor: 0.2,
  57939. /**
  57940. * Creates new Color.
  57941. * @param {Number} red Red component (0..255)
  57942. * @param {Number} green Green component (0..255)
  57943. * @param {Number} blue Blue component (0..255)
  57944. */
  57945. constructor : function(red, green, blue) {
  57946. var me = this,
  57947. clamp = Ext.Number.constrain;
  57948. me.r = clamp(red, 0, 255);
  57949. me.g = clamp(green, 0, 255);
  57950. me.b = clamp(blue, 0, 255);
  57951. },
  57952. /**
  57953. * Get the red component of the color, in the range 0..255.
  57954. * @return {Number}
  57955. */
  57956. getRed: function() {
  57957. return this.r;
  57958. },
  57959. /**
  57960. * Get the green component of the color, in the range 0..255.
  57961. * @return {Number}
  57962. */
  57963. getGreen: function() {
  57964. return this.g;
  57965. },
  57966. /**
  57967. * Get the blue component of the color, in the range 0..255.
  57968. * @return {Number}
  57969. */
  57970. getBlue: function() {
  57971. return this.b;
  57972. },
  57973. /**
  57974. * Get the RGB values.
  57975. * @return {Number[]}
  57976. */
  57977. getRGB: function() {
  57978. var me = this;
  57979. return [me.r, me.g, me.b];
  57980. },
  57981. /**
  57982. * Get the equivalent HSL components of the color.
  57983. * @return {Number[]}
  57984. */
  57985. getHSL: function() {
  57986. var me = this,
  57987. r = me.r / 255,
  57988. g = me.g / 255,
  57989. b = me.b / 255,
  57990. max = Math.max(r, g, b),
  57991. min = Math.min(r, g, b),
  57992. delta = max - min,
  57993. h,
  57994. s = 0,
  57995. l = 0.5 * (max + min);
  57996. // min==max means achromatic (hue is undefined)
  57997. if (min != max) {
  57998. s = (l < 0.5) ? delta / (max + min) : delta / (2 - max - min);
  57999. if (r == max) {
  58000. h = 60 * (g - b) / delta;
  58001. } else if (g == max) {
  58002. h = 120 + 60 * (b - r) / delta;
  58003. } else {
  58004. h = 240 + 60 * (r - g) / delta;
  58005. }
  58006. if (h < 0) {
  58007. h += 360;
  58008. }
  58009. if (h >= 360) {
  58010. h -= 360;
  58011. }
  58012. }
  58013. return [h, s, l];
  58014. },
  58015. /**
  58016. * Return a new color that is lighter than this color.
  58017. * @param {Number} factor Lighter factor (0..1), default to 0.2
  58018. * @return Ext.draw.Color
  58019. */
  58020. getLighter: function(factor) {
  58021. var hsl = this.getHSL();
  58022. factor = factor || this.lightnessFactor;
  58023. hsl[2] = Ext.Number.constrain(hsl[2] + factor, 0, 1);
  58024. return this.fromHSL(hsl[0], hsl[1], hsl[2]);
  58025. },
  58026. /**
  58027. * Return a new color that is darker than this color.
  58028. * @param {Number} factor Darker factor (0..1), default to 0.2
  58029. * @return Ext.draw.Color
  58030. */
  58031. getDarker: function(factor) {
  58032. factor = factor || this.lightnessFactor;
  58033. return this.getLighter(-factor);
  58034. },
  58035. /**
  58036. * Return the color in the hex format, i.e. '#rrggbb'.
  58037. * @return {String}
  58038. */
  58039. toString: function() {
  58040. var me = this,
  58041. round = Math.round,
  58042. r = round(me.r).toString(16),
  58043. g = round(me.g).toString(16),
  58044. b = round(me.b).toString(16);
  58045. r = (r.length == 1) ? '0' + r : r;
  58046. g = (g.length == 1) ? '0' + g : g;
  58047. b = (b.length == 1) ? '0' + b : b;
  58048. return ['#', r, g, b].join('');
  58049. },
  58050. /**
  58051. * Convert a color to hexadecimal format.
  58052. *
  58053. * **Note:** This method is both static and instance.
  58054. *
  58055. * @param {String/String[]} color The color value (i.e 'rgb(255, 255, 255)', 'color: #ffffff').
  58056. * Can also be an Array, in this case the function handles the first member.
  58057. * @returns {String} The color in hexadecimal format.
  58058. * @static
  58059. */
  58060. toHex: function(color) {
  58061. if (Ext.isArray(color)) {
  58062. color = color[0];
  58063. }
  58064. if (!Ext.isString(color)) {
  58065. return '';
  58066. }
  58067. if (color.substr(0, 1) === '#') {
  58068. return color;
  58069. }
  58070. var digits = this.colorToHexRe.exec(color),
  58071. red,
  58072. green,
  58073. blue,
  58074. rgb;
  58075. if (Ext.isArray(digits)) {
  58076. red = parseInt(digits[2], 10);
  58077. green = parseInt(digits[3], 10);
  58078. blue = parseInt(digits[4], 10);
  58079. rgb = blue | (green << 8) | (red << 16);
  58080. return digits[1] + '#' + ("000000" + rgb.toString(16)).slice(-6);
  58081. }
  58082. else {
  58083. return color;
  58084. }
  58085. },
  58086. /**
  58087. * Parse the string and create a new color.
  58088. *
  58089. * Supported formats: '#rrggbb', '#rgb', and 'rgb(r,g,b)'.
  58090. *
  58091. * If the string is not recognized, an undefined will be returned instead.
  58092. *
  58093. * **Note:** This method is both static and instance.
  58094. *
  58095. * @param {String} str Color in string.
  58096. * @returns Ext.draw.Color
  58097. * @static
  58098. */
  58099. fromString: function(str) {
  58100. var values, r, g, b,
  58101. parse = parseInt;
  58102. if ((str.length == 4 || str.length == 7) && str.substr(0, 1) === '#') {
  58103. values = str.match(this.hexRe);
  58104. if (values) {
  58105. r = parse(values[1], 16) >> 0;
  58106. g = parse(values[2], 16) >> 0;
  58107. b = parse(values[3], 16) >> 0;
  58108. if (str.length == 4) {
  58109. r += (r * 16);
  58110. g += (g * 16);
  58111. b += (b * 16);
  58112. }
  58113. }
  58114. }
  58115. else {
  58116. values = str.match(this.rgbRe);
  58117. if (values) {
  58118. r = values[1];
  58119. g = values[2];
  58120. b = values[3];
  58121. }
  58122. }
  58123. return (typeof r == 'undefined') ? undefined : new Ext.draw.Color(r, g, b);
  58124. },
  58125. /**
  58126. * Returns the gray value (0 to 255) of the color.
  58127. *
  58128. * The gray value is calculated using the formula r*0.3 + g*0.59 + b*0.11.
  58129. *
  58130. * @returns {Number}
  58131. */
  58132. getGrayscale: function() {
  58133. // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
  58134. return this.r * 0.3 + this.g * 0.59 + this.b * 0.11;
  58135. },
  58136. /**
  58137. * Create a new color based on the specified HSL values.
  58138. *
  58139. * **Note:** This method is both static and instance.
  58140. *
  58141. * @param {Number} h Hue component (0..359)
  58142. * @param {Number} s Saturation component (0..1)
  58143. * @param {Number} l Lightness component (0..1)
  58144. * @returns Ext.draw.Color
  58145. * @static
  58146. */
  58147. fromHSL: function(h, s, l) {
  58148. var C, X, m, i, rgb = [],
  58149. abs = Math.abs,
  58150. floor = Math.floor;
  58151. if (s == 0 || h == null) {
  58152. // achromatic
  58153. rgb = [l, l, l];
  58154. }
  58155. else {
  58156. // http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
  58157. // C is the chroma
  58158. // X is the second largest component
  58159. // m is the lightness adjustment
  58160. h /= 60;
  58161. C = s * (1 - abs(2 * l - 1));
  58162. X = C * (1 - abs(h - 2 * floor(h / 2) - 1));
  58163. m = l - C / 2;
  58164. switch (floor(h)) {
  58165. case 0:
  58166. rgb = [C, X, 0];
  58167. break;
  58168. case 1:
  58169. rgb = [X, C, 0];
  58170. break;
  58171. case 2:
  58172. rgb = [0, C, X];
  58173. break;
  58174. case 3:
  58175. rgb = [0, X, C];
  58176. break;
  58177. case 4:
  58178. rgb = [X, 0, C];
  58179. break;
  58180. case 5:
  58181. rgb = [C, 0, X];
  58182. break;
  58183. }
  58184. rgb = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
  58185. }
  58186. return new Ext.draw.Color(rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);
  58187. }
  58188. }, function() {
  58189. var prototype = this.prototype;
  58190. //These functions are both static and instance. TODO: find a more elegant way of copying them
  58191. this.addStatics({
  58192. fromHSL: function() {
  58193. return prototype.fromHSL.apply(prototype, arguments);
  58194. },
  58195. fromString: function() {
  58196. return prototype.fromString.apply(prototype, arguments);
  58197. },
  58198. toHex: function() {
  58199. return prototype.toHex.apply(prototype, arguments);
  58200. }
  58201. });
  58202. });
  58203. /**
  58204. * @class Ext.draw.Draw
  58205. * Base Drawing class. Provides base drawing functions.
  58206. * @private
  58207. */
  58208. Ext.define('Ext.draw.Draw', {
  58209. /* Begin Definitions */
  58210. singleton: true,
  58211. requires: ['Ext.draw.Color'],
  58212. /* End Definitions */
  58213. pathToStringRE: /,?([achlmqrstvxz]),?/gi,
  58214. pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
  58215. pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
  58216. stopsRE: /^(\d+%?)$/,
  58217. radian: Math.PI / 180,
  58218. availableAnimAttrs: {
  58219. along: "along",
  58220. blur: null,
  58221. "clip-rect": "csv",
  58222. cx: null,
  58223. cy: null,
  58224. fill: "color",
  58225. "fill-opacity": null,
  58226. "font-size": null,
  58227. height: null,
  58228. opacity: null,
  58229. path: "path",
  58230. r: null,
  58231. rotation: "csv",
  58232. rx: null,
  58233. ry: null,
  58234. scale: "csv",
  58235. stroke: "color",
  58236. "stroke-opacity": null,
  58237. "stroke-width": null,
  58238. translation: "csv",
  58239. width: null,
  58240. x: null,
  58241. y: null
  58242. },
  58243. is: function(o, type) {
  58244. type = String(type).toLowerCase();
  58245. return (type == "object" && o === Object(o)) ||
  58246. (type == "undefined" && typeof o == type) ||
  58247. (type == "null" && o === null) ||
  58248. (type == "array" && Array.isArray && Array.isArray(o)) ||
  58249. (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
  58250. },
  58251. ellipsePath: function(sprite) {
  58252. var attr = sprite.attr;
  58253. return Ext.String.format("M{0},{1}A{2},{3},0,1,1,{0},{4}A{2},{3},0,1,1,{0},{1}z", attr.x, attr.y - attr.ry, attr.rx, attr.ry, attr.y + attr.ry);
  58254. },
  58255. rectPath: function(sprite) {
  58256. var attr = sprite.attr;
  58257. if (attr.radius) {
  58258. return Ext.String.format("M{0},{1}l{2},0a{3},{3},0,0,1,{3},{3}l0,{5}a{3},{3},0,0,1,{4},{3}l{6},0a{3},{3},0,0,1,{4},{4}l0,{7}a{3},{3},0,0,1,{3},{4}z", attr.x + attr.radius, attr.y, attr.width - attr.radius * 2, attr.radius, -attr.radius, attr.height - attr.radius * 2, attr.radius * 2 - attr.width, attr.radius * 2 - attr.height);
  58259. }
  58260. else {
  58261. return Ext.String.format("M{0},{1}L{2},{1},{2},{3},{0},{3}z", attr.x, attr.y, attr.width + attr.x, attr.height + attr.y);
  58262. }
  58263. },
  58264. // To be deprecated, converts itself (an arrayPath) to a proper SVG path string
  58265. path2string: function () {
  58266. return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
  58267. },
  58268. // Convert the passed arrayPath to a proper SVG path string (d attribute)
  58269. pathToString: function(arrayPath) {
  58270. return arrayPath.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
  58271. },
  58272. parsePathString: function (pathString) {
  58273. if (!pathString) {
  58274. return null;
  58275. }
  58276. var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
  58277. data = [],
  58278. me = this;
  58279. if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
  58280. data = me.pathClone(pathString);
  58281. }
  58282. if (!data.length) {
  58283. String(pathString).replace(me.pathCommandRE, function (a, b, c) {
  58284. var params = [],
  58285. name = b.toLowerCase();
  58286. c.replace(me.pathValuesRE, function (a, b) {
  58287. b && params.push(+b);
  58288. });
  58289. if (name == "m" && params.length > 2) {
  58290. data.push([b].concat(Ext.Array.splice(params, 0, 2)));
  58291. name = "l";
  58292. b = (b == "m") ? "l" : "L";
  58293. }
  58294. while (params.length >= paramCounts[name]) {
  58295. data.push([b].concat(Ext.Array.splice(params, 0, paramCounts[name])));
  58296. if (!paramCounts[name]) {
  58297. break;
  58298. }
  58299. }
  58300. });
  58301. }
  58302. data.toString = me.path2string;
  58303. return data;
  58304. },
  58305. mapPath: function (path, matrix) {
  58306. if (!matrix) {
  58307. return path;
  58308. }
  58309. var x, y, i, ii, j, jj, pathi;
  58310. path = this.path2curve(path);
  58311. for (i = 0, ii = path.length; i < ii; i++) {
  58312. pathi = path[i];
  58313. for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
  58314. x = matrix.x(pathi[j], pathi[j + 1]);
  58315. y = matrix.y(pathi[j], pathi[j + 1]);
  58316. pathi[j] = x;
  58317. pathi[j + 1] = y;
  58318. }
  58319. }
  58320. return path;
  58321. },
  58322. pathClone: function(pathArray) {
  58323. var res = [],
  58324. j, jj, i, ii;
  58325. if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
  58326. pathArray = this.parsePathString(pathArray);
  58327. }
  58328. for (i = 0, ii = pathArray.length; i < ii; i++) {
  58329. res[i] = [];
  58330. for (j = 0, jj = pathArray[i].length; j < jj; j++) {
  58331. res[i][j] = pathArray[i][j];
  58332. }
  58333. }
  58334. res.toString = this.path2string;
  58335. return res;
  58336. },
  58337. pathToAbsolute: function (pathArray) {
  58338. if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
  58339. pathArray = this.parsePathString(pathArray);
  58340. }
  58341. var res = [],
  58342. x = 0,
  58343. y = 0,
  58344. mx = 0,
  58345. my = 0,
  58346. i = 0,
  58347. ln = pathArray.length,
  58348. r, pathSegment, j, ln2;
  58349. // MoveTo initial x/y position
  58350. if (ln && pathArray[0][0] == "M") {
  58351. x = +pathArray[0][1];
  58352. y = +pathArray[0][2];
  58353. mx = x;
  58354. my = y;
  58355. i++;
  58356. res[0] = ["M", x, y];
  58357. }
  58358. for (; i < ln; i++) {
  58359. r = res[i] = [];
  58360. pathSegment = pathArray[i];
  58361. if (pathSegment[0] != pathSegment[0].toUpperCase()) {
  58362. r[0] = pathSegment[0].toUpperCase();
  58363. switch (r[0]) {
  58364. // Elliptical Arc
  58365. case "A":
  58366. r[1] = pathSegment[1];
  58367. r[2] = pathSegment[2];
  58368. r[3] = pathSegment[3];
  58369. r[4] = pathSegment[4];
  58370. r[5] = pathSegment[5];
  58371. r[6] = +(pathSegment[6] + x);
  58372. r[7] = +(pathSegment[7] + y);
  58373. break;
  58374. // Vertical LineTo
  58375. case "V":
  58376. r[1] = +pathSegment[1] + y;
  58377. break;
  58378. // Horizontal LineTo
  58379. case "H":
  58380. r[1] = +pathSegment[1] + x;
  58381. break;
  58382. case "M":
  58383. // MoveTo
  58384. mx = +pathSegment[1] + x;
  58385. my = +pathSegment[2] + y;
  58386. default:
  58387. j = 1;
  58388. ln2 = pathSegment.length;
  58389. for (; j < ln2; j++) {
  58390. r[j] = +pathSegment[j] + ((j % 2) ? x : y);
  58391. }
  58392. }
  58393. }
  58394. else {
  58395. j = 0;
  58396. ln2 = pathSegment.length;
  58397. for (; j < ln2; j++) {
  58398. res[i][j] = pathSegment[j];
  58399. }
  58400. }
  58401. switch (r[0]) {
  58402. // ClosePath
  58403. case "Z":
  58404. x = mx;
  58405. y = my;
  58406. break;
  58407. // Horizontal LineTo
  58408. case "H":
  58409. x = r[1];
  58410. break;
  58411. // Vertical LineTo
  58412. case "V":
  58413. y = r[1];
  58414. break;
  58415. // MoveTo
  58416. case "M":
  58417. pathSegment = res[i];
  58418. ln2 = pathSegment.length;
  58419. mx = pathSegment[ln2 - 2];
  58420. my = pathSegment[ln2 - 1];
  58421. default:
  58422. pathSegment = res[i];
  58423. ln2 = pathSegment.length;
  58424. x = pathSegment[ln2 - 2];
  58425. y = pathSegment[ln2 - 1];
  58426. }
  58427. }
  58428. res.toString = this.path2string;
  58429. return res;
  58430. },
  58431. // TO BE DEPRECATED
  58432. pathToRelative: function (pathArray) {
  58433. if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
  58434. pathArray = this.parsePathString(pathArray);
  58435. }
  58436. var res = [],
  58437. x = 0,
  58438. y = 0,
  58439. mx = 0,
  58440. my = 0,
  58441. start = 0,
  58442. r,
  58443. pa,
  58444. i,
  58445. j,
  58446. k,
  58447. len,
  58448. ii,
  58449. jj,
  58450. kk;
  58451. if (pathArray[0][0] == "M") {
  58452. x = pathArray[0][1];
  58453. y = pathArray[0][2];
  58454. mx = x;
  58455. my = y;
  58456. start++;
  58457. res.push(["M", x, y]);
  58458. }
  58459. for (i = start, ii = pathArray.length; i < ii; i++) {
  58460. r = res[i] = [];
  58461. pa = pathArray[i];
  58462. if (pa[0] != pa[0].toLowerCase()) {
  58463. r[0] = pa[0].toLowerCase();
  58464. switch (r[0]) {
  58465. case "a":
  58466. r[1] = pa[1];
  58467. r[2] = pa[2];
  58468. r[3] = pa[3];
  58469. r[4] = pa[4];
  58470. r[5] = pa[5];
  58471. r[6] = +(pa[6] - x).toFixed(3);
  58472. r[7] = +(pa[7] - y).toFixed(3);
  58473. break;
  58474. case "v":
  58475. r[1] = +(pa[1] - y).toFixed(3);
  58476. break;
  58477. case "m":
  58478. mx = pa[1];
  58479. my = pa[2];
  58480. default:
  58481. for (j = 1, jj = pa.length; j < jj; j++) {
  58482. r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
  58483. }
  58484. }
  58485. } else {
  58486. r = res[i] = [];
  58487. if (pa[0] == "m") {
  58488. mx = pa[1] + x;
  58489. my = pa[2] + y;
  58490. }
  58491. for (k = 0, kk = pa.length; k < kk; k++) {
  58492. res[i][k] = pa[k];
  58493. }
  58494. }
  58495. len = res[i].length;
  58496. switch (res[i][0]) {
  58497. case "z":
  58498. x = mx;
  58499. y = my;
  58500. break;
  58501. case "h":
  58502. x += +res[i][len - 1];
  58503. break;
  58504. case "v":
  58505. y += +res[i][len - 1];
  58506. break;
  58507. default:
  58508. x += +res[i][len - 2];
  58509. y += +res[i][len - 1];
  58510. }
  58511. }
  58512. res.toString = this.path2string;
  58513. return res;
  58514. },
  58515. // Returns a path converted to a set of curveto commands
  58516. path2curve: function (path) {
  58517. var me = this,
  58518. points = me.pathToAbsolute(path),
  58519. ln = points.length,
  58520. attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
  58521. i, seg, segLn, point;
  58522. for (i = 0; i < ln; i++) {
  58523. points[i] = me.command2curve(points[i], attrs);
  58524. if (points[i].length > 7) {
  58525. points[i].shift();
  58526. point = points[i];
  58527. while (point.length) {
  58528. Ext.Array.splice(points, i++, 0, ["C"].concat(Ext.Array.splice(point, 0, 6)));
  58529. }
  58530. Ext.Array.erase(points, i, 1);
  58531. ln = points.length;
  58532. i--;
  58533. }
  58534. seg = points[i];
  58535. segLn = seg.length;
  58536. attrs.x = seg[segLn - 2];
  58537. attrs.y = seg[segLn - 1];
  58538. attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
  58539. attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
  58540. }
  58541. return points;
  58542. },
  58543. interpolatePaths: function (path, path2) {
  58544. var me = this,
  58545. p = me.pathToAbsolute(path),
  58546. p2 = me.pathToAbsolute(path2),
  58547. attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
  58548. attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
  58549. fixArc = function (pp, i) {
  58550. if (pp[i].length > 7) {
  58551. pp[i].shift();
  58552. var pi = pp[i];
  58553. while (pi.length) {
  58554. Ext.Array.splice(pp, i++, 0, ["C"].concat(Ext.Array.splice(pi, 0, 6)));
  58555. }
  58556. Ext.Array.erase(pp, i, 1);
  58557. ii = Math.max(p.length, p2.length || 0);
  58558. }
  58559. },
  58560. fixM = function (path1, path2, a1, a2, i) {
  58561. if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
  58562. Ext.Array.splice(path2, i, 0, ["M", a2.x, a2.y]);
  58563. a1.bx = 0;
  58564. a1.by = 0;
  58565. a1.x = path1[i][1];
  58566. a1.y = path1[i][2];
  58567. ii = Math.max(p.length, p2.length || 0);
  58568. }
  58569. },
  58570. i, ii,
  58571. seg, seg2, seglen, seg2len;
  58572. for (i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
  58573. p[i] = me.command2curve(p[i], attrs);
  58574. fixArc(p, i);
  58575. (p2[i] = me.command2curve(p2[i], attrs2));
  58576. fixArc(p2, i);
  58577. fixM(p, p2, attrs, attrs2, i);
  58578. fixM(p2, p, attrs2, attrs, i);
  58579. seg = p[i];
  58580. seg2 = p2[i];
  58581. seglen = seg.length;
  58582. seg2len = seg2.length;
  58583. attrs.x = seg[seglen - 2];
  58584. attrs.y = seg[seglen - 1];
  58585. attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
  58586. attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
  58587. attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
  58588. attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
  58589. attrs2.x = seg2[seg2len - 2];
  58590. attrs2.y = seg2[seg2len - 1];
  58591. }
  58592. return [p, p2];
  58593. },
  58594. //Returns any path command as a curveto command based on the attrs passed
  58595. command2curve: function (pathCommand, d) {
  58596. var me = this;
  58597. if (!pathCommand) {
  58598. return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
  58599. }
  58600. if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
  58601. d.qx = d.qy = null;
  58602. }
  58603. switch (pathCommand[0]) {
  58604. case "M":
  58605. d.X = pathCommand[1];
  58606. d.Y = pathCommand[2];
  58607. break;
  58608. case "A":
  58609. pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
  58610. break;
  58611. case "S":
  58612. pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
  58613. break;
  58614. case "T":
  58615. d.qx = d.x + (d.x - (d.qx || d.x));
  58616. d.qy = d.y + (d.y - (d.qy || d.y));
  58617. pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
  58618. break;
  58619. case "Q":
  58620. d.qx = pathCommand[1];
  58621. d.qy = pathCommand[2];
  58622. pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
  58623. break;
  58624. case "L":
  58625. pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
  58626. break;
  58627. case "H":
  58628. pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
  58629. break;
  58630. case "V":
  58631. pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
  58632. break;
  58633. case "Z":
  58634. pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
  58635. break;
  58636. }
  58637. return pathCommand;
  58638. },
  58639. quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
  58640. var _13 = 1 / 3,
  58641. _23 = 2 / 3;
  58642. return [
  58643. _13 * x1 + _23 * ax,
  58644. _13 * y1 + _23 * ay,
  58645. _13 * x2 + _23 * ax,
  58646. _13 * y2 + _23 * ay,
  58647. x2,
  58648. y2
  58649. ];
  58650. },
  58651. rotate: function (x, y, rad) {
  58652. var cos = Math.cos(rad),
  58653. sin = Math.sin(rad),
  58654. X = x * cos - y * sin,
  58655. Y = x * sin + y * cos;
  58656. return {x: X, y: Y};
  58657. },
  58658. arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
  58659. // for more information of where this Math came from visit:
  58660. // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
  58661. var me = this,
  58662. PI = Math.PI,
  58663. radian = me.radian,
  58664. _120 = PI * 120 / 180,
  58665. rad = radian * (+angle || 0),
  58666. res = [],
  58667. math = Math,
  58668. mcos = math.cos,
  58669. msin = math.sin,
  58670. msqrt = math.sqrt,
  58671. mabs = math.abs,
  58672. masin = math.asin,
  58673. xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
  58674. t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
  58675. if (!recursive) {
  58676. xy = me.rotate(x1, y1, -rad);
  58677. x1 = xy.x;
  58678. y1 = xy.y;
  58679. xy = me.rotate(x2, y2, -rad);
  58680. x2 = xy.x;
  58681. y2 = xy.y;
  58682. cos = mcos(radian * angle);
  58683. sin = msin(radian * angle);
  58684. x = (x1 - x2) / 2;
  58685. y = (y1 - y2) / 2;
  58686. h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
  58687. if (h > 1) {
  58688. h = msqrt(h);
  58689. rx = h * rx;
  58690. ry = h * ry;
  58691. }
  58692. rx2 = rx * rx;
  58693. ry2 = ry * ry;
  58694. k = (large_arc_flag == sweep_flag ? -1 : 1) *
  58695. msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
  58696. cx = k * rx * y / ry + (x1 + x2) / 2;
  58697. cy = k * -ry * x / rx + (y1 + y2) / 2;
  58698. f1 = masin(((y1 - cy) / ry).toFixed(7));
  58699. f2 = masin(((y2 - cy) / ry).toFixed(7));
  58700. f1 = x1 < cx ? PI - f1 : f1;
  58701. f2 = x2 < cx ? PI - f2 : f2;
  58702. if (f1 < 0) {
  58703. f1 = PI * 2 + f1;
  58704. }
  58705. if (f2 < 0) {
  58706. f2 = PI * 2 + f2;
  58707. }
  58708. if (sweep_flag && f1 > f2) {
  58709. f1 = f1 - PI * 2;
  58710. }
  58711. if (!sweep_flag && f2 > f1) {
  58712. f2 = f2 - PI * 2;
  58713. }
  58714. }
  58715. else {
  58716. f1 = recursive[0];
  58717. f2 = recursive[1];
  58718. cx = recursive[2];
  58719. cy = recursive[3];
  58720. }
  58721. df = f2 - f1;
  58722. if (mabs(df) > _120) {
  58723. f2old = f2;
  58724. x2old = x2;
  58725. y2old = y2;
  58726. f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
  58727. x2 = cx + rx * mcos(f2);
  58728. y2 = cy + ry * msin(f2);
  58729. res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
  58730. }
  58731. df = f2 - f1;
  58732. c1 = mcos(f1);
  58733. s1 = msin(f1);
  58734. c2 = mcos(f2);
  58735. s2 = msin(f2);
  58736. t = math.tan(df / 4);
  58737. hx = 4 / 3 * rx * t;
  58738. hy = 4 / 3 * ry * t;
  58739. m1 = [x1, y1];
  58740. m2 = [x1 + hx * s1, y1 - hy * c1];
  58741. m3 = [x2 + hx * s2, y2 - hy * c2];
  58742. m4 = [x2, y2];
  58743. m2[0] = 2 * m1[0] - m2[0];
  58744. m2[1] = 2 * m1[1] - m2[1];
  58745. if (recursive) {
  58746. return [m2, m3, m4].concat(res);
  58747. }
  58748. else {
  58749. res = [m2, m3, m4].concat(res).join().split(",");
  58750. newres = [];
  58751. ln = res.length;
  58752. for (i = 0; i < ln; i++) {
  58753. newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
  58754. }
  58755. return newres;
  58756. }
  58757. },
  58758. // TO BE DEPRECATED
  58759. rotateAndTranslatePath: function (sprite) {
  58760. var alpha = sprite.rotation.degrees,
  58761. cx = sprite.rotation.x,
  58762. cy = sprite.rotation.y,
  58763. dx = sprite.translation.x,
  58764. dy = sprite.translation.y,
  58765. path,
  58766. i,
  58767. p,
  58768. xy,
  58769. j,
  58770. res = [];
  58771. if (!alpha && !dx && !dy) {
  58772. return this.pathToAbsolute(sprite.attr.path);
  58773. }
  58774. dx = dx || 0;
  58775. dy = dy || 0;
  58776. path = this.pathToAbsolute(sprite.attr.path);
  58777. for (i = path.length; i--;) {
  58778. p = res[i] = path[i].slice();
  58779. if (p[0] == "A") {
  58780. xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
  58781. p[6] = xy.x + dx;
  58782. p[7] = xy.y + dy;
  58783. } else {
  58784. j = 1;
  58785. while (p[j + 1] != null) {
  58786. xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
  58787. p[j] = xy.x + dx;
  58788. p[j + 1] = xy.y + dy;
  58789. j += 2;
  58790. }
  58791. }
  58792. }
  58793. return res;
  58794. },
  58795. // TO BE DEPRECATED
  58796. rotatePoint: function (x, y, alpha, cx, cy) {
  58797. if (!alpha) {
  58798. return {
  58799. x: x,
  58800. y: y
  58801. };
  58802. }
  58803. cx = cx || 0;
  58804. cy = cy || 0;
  58805. x = x - cx;
  58806. y = y - cy;
  58807. alpha = alpha * this.radian;
  58808. var cos = Math.cos(alpha),
  58809. sin = Math.sin(alpha);
  58810. return {
  58811. x: x * cos - y * sin + cx,
  58812. y: x * sin + y * cos + cy
  58813. };
  58814. },
  58815. pathDimensions: function (path) {
  58816. if (!path || !(path + "")) {
  58817. return {x: 0, y: 0, width: 0, height: 0};
  58818. }
  58819. path = this.path2curve(path);
  58820. var x = 0,
  58821. y = 0,
  58822. X = [],
  58823. Y = [],
  58824. i = 0,
  58825. ln = path.length,
  58826. p, xmin, ymin, dim;
  58827. for (; i < ln; i++) {
  58828. p = path[i];
  58829. if (p[0] == "M") {
  58830. x = p[1];
  58831. y = p[2];
  58832. X.push(x);
  58833. Y.push(y);
  58834. }
  58835. else {
  58836. dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
  58837. X = X.concat(dim.min.x, dim.max.x);
  58838. Y = Y.concat(dim.min.y, dim.max.y);
  58839. x = p[5];
  58840. y = p[6];
  58841. }
  58842. }
  58843. xmin = Math.min.apply(0, X);
  58844. ymin = Math.min.apply(0, Y);
  58845. return {
  58846. x: xmin,
  58847. y: ymin,
  58848. path: path,
  58849. width: Math.max.apply(0, X) - xmin,
  58850. height: Math.max.apply(0, Y) - ymin
  58851. };
  58852. },
  58853. intersectInside: function(path, cp1, cp2) {
  58854. return (cp2[0] - cp1[0]) * (path[1] - cp1[1]) > (cp2[1] - cp1[1]) * (path[0] - cp1[0]);
  58855. },
  58856. intersectIntersection: function(s, e, cp1, cp2) {
  58857. var p = [],
  58858. dcx = cp1[0] - cp2[0],
  58859. dcy = cp1[1] - cp2[1],
  58860. dpx = s[0] - e[0],
  58861. dpy = s[1] - e[1],
  58862. n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0],
  58863. n2 = s[0] * e[1] - s[1] * e[0],
  58864. n3 = 1 / (dcx * dpy - dcy * dpx);
  58865. p[0] = (n1 * dpx - n2 * dcx) * n3;
  58866. p[1] = (n1 * dpy - n2 * dcy) * n3;
  58867. return p;
  58868. },
  58869. intersect: function(subjectPolygon, clipPolygon) {
  58870. var me = this,
  58871. i = 0,
  58872. ln = clipPolygon.length,
  58873. cp1 = clipPolygon[ln - 1],
  58874. outputList = subjectPolygon,
  58875. cp2, s, e, point, ln2, inputList, j;
  58876. for (; i < ln; ++i) {
  58877. cp2 = clipPolygon[i];
  58878. inputList = outputList;
  58879. outputList = [];
  58880. s = inputList[inputList.length - 1];
  58881. j = 0;
  58882. ln2 = inputList.length;
  58883. for (; j < ln2; j++) {
  58884. e = inputList[j];
  58885. if (me.intersectInside(e, cp1, cp2)) {
  58886. if (!me.intersectInside(s, cp1, cp2)) {
  58887. outputList.push(me.intersectIntersection(s, e, cp1, cp2));
  58888. }
  58889. outputList.push(e);
  58890. }
  58891. else if (me.intersectInside(s, cp1, cp2)) {
  58892. outputList.push(me.intersectIntersection(s, e, cp1, cp2));
  58893. }
  58894. s = e;
  58895. }
  58896. cp1 = cp2;
  58897. }
  58898. return outputList;
  58899. },
  58900. bezier : function (a, b, c, d, x) {
  58901. if (x === 0) {
  58902. return a;
  58903. }
  58904. else if (x === 1) {
  58905. return d;
  58906. }
  58907. var du = 1 - x,
  58908. d3 = du * du * du,
  58909. r = x / du;
  58910. return d3 * (a + r * (3 * b + r * (3 * c + d * r)));
  58911. },
  58912. bezierDim : function (a, b, c, d) {
  58913. var points = [], r,
  58914. A, top, C, delta, bottom, s,
  58915. min, max, i;
  58916. // The min and max happens on boundary or b' == 0
  58917. if (a + 3 * c == d + 3 * b) {
  58918. r = a - b;
  58919. r /= 2 * (a - b - b + c);
  58920. if ( r < 1 && r > 0) {
  58921. points.push(r);
  58922. }
  58923. } else {
  58924. // b'(x) / -3 = (a-3b+3c-d)x^2+ (-2a+4b-2c)x + (a-b)
  58925. // delta = -4 (-b^2+a c+b c-c^2-a d+b d)
  58926. A = a - 3 * b + 3 * c - d;
  58927. top = 2 * (a - b - b + c);
  58928. C = a - b;
  58929. delta = top * top - 4 * A * C;
  58930. bottom = A + A;
  58931. if (delta === 0) {
  58932. r = top / bottom;
  58933. if (r < 1 && r > 0) {
  58934. points.push(r);
  58935. }
  58936. } else if (delta > 0) {
  58937. s = Math.sqrt(delta);
  58938. r = (s + top) / bottom;
  58939. if (r < 1 && r > 0) {
  58940. points.push(r);
  58941. }
  58942. r = (top - s) / bottom;
  58943. if (r < 1 && r > 0) {
  58944. points.push(r);
  58945. }
  58946. }
  58947. }
  58948. min = Math.min(a, d);
  58949. max = Math.max(a, d);
  58950. for (i = 0; i < points.length; i++) {
  58951. min = Math.min(min, this.bezier(a, b, c, d, points[i]));
  58952. max = Math.max(max, this.bezier(a, b, c, d, points[i]));
  58953. }
  58954. return [min, max];
  58955. },
  58956. curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
  58957. var x = this.bezierDim(p1x, c1x, c2x, p2x),
  58958. y = this.bezierDim(p1y, c1y, c2y, p2y);
  58959. return {
  58960. min: {
  58961. x: x[0],
  58962. y: y[0]
  58963. },
  58964. max: {
  58965. x: x[1],
  58966. y: y[1]
  58967. }
  58968. };
  58969. },
  58970. /**
  58971. * @private
  58972. *
  58973. * Calculates bezier curve control anchor points for a particular point in a path, with a
  58974. * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
  58975. * Note that this algorithm assumes that the line being smoothed is normalized going from left
  58976. * to right; it makes special adjustments assuming this orientation.
  58977. *
  58978. * @param {Number} prevX X coordinate of the previous point in the path
  58979. * @param {Number} prevY Y coordinate of the previous point in the path
  58980. * @param {Number} curX X coordinate of the current point in the path
  58981. * @param {Number} curY Y coordinate of the current point in the path
  58982. * @param {Number} nextX X coordinate of the next point in the path
  58983. * @param {Number} nextY Y coordinate of the next point in the path
  58984. * @param {Number} value A value to control the smoothness of the curve; this is used to
  58985. * divide the distance between points, so a value of 2 corresponds to
  58986. * half the distance between points (a very smooth line) while higher values
  58987. * result in less smooth curves. Defaults to 4.
  58988. * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
  58989. * are the control point for the curve toward the previous path point, and
  58990. * x2 and y2 are the control point for the curve toward the next path point.
  58991. */
  58992. getAnchors: function (prevX, prevY, curX, curY, nextX, nextY, value) {
  58993. value = value || 4;
  58994. var M = Math,
  58995. PI = M.PI,
  58996. halfPI = PI / 2,
  58997. abs = M.abs,
  58998. sin = M.sin,
  58999. cos = M.cos,
  59000. atan = M.atan,
  59001. control1Length, control2Length, control1Angle, control2Angle,
  59002. control1X, control1Y, control2X, control2Y, alpha;
  59003. // Find the length of each control anchor line, by dividing the horizontal distance
  59004. // between points by the value parameter.
  59005. control1Length = (curX - prevX) / value;
  59006. control2Length = (nextX - curX) / value;
  59007. // Determine the angle of each control anchor line. If the middle point is a vertical
  59008. // turnaround then we force it to a flat horizontal angle to prevent the curve from
  59009. // dipping above or below the middle point. Otherwise we use an angle that points
  59010. // toward the previous/next target point.
  59011. if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
  59012. control1Angle = control2Angle = halfPI;
  59013. } else {
  59014. control1Angle = atan((curX - prevX) / abs(curY - prevY));
  59015. if (prevY < curY) {
  59016. control1Angle = PI - control1Angle;
  59017. }
  59018. control2Angle = atan((nextX - curX) / abs(curY - nextY));
  59019. if (nextY < curY) {
  59020. control2Angle = PI - control2Angle;
  59021. }
  59022. }
  59023. // Adjust the calculated angles so they point away from each other on the same line
  59024. alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
  59025. if (alpha > halfPI) {
  59026. alpha -= PI;
  59027. }
  59028. control1Angle += alpha;
  59029. control2Angle += alpha;
  59030. // Find the control anchor points from the angles and length
  59031. control1X = curX - control1Length * sin(control1Angle);
  59032. control1Y = curY + control1Length * cos(control1Angle);
  59033. control2X = curX + control2Length * sin(control2Angle);
  59034. control2Y = curY + control2Length * cos(control2Angle);
  59035. // One last adjustment, make sure that no control anchor point extends vertically past
  59036. // its target prev/next point, as that results in curves dipping above or below and
  59037. // bending back strangely. If we find this happening we keep the control angle but
  59038. // reduce the length of the control line so it stays within bounds.
  59039. if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
  59040. control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
  59041. control1Y = prevY;
  59042. }
  59043. if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
  59044. control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
  59045. control2Y = nextY;
  59046. }
  59047. return {
  59048. x1: control1X,
  59049. y1: control1Y,
  59050. x2: control2X,
  59051. y2: control2Y
  59052. };
  59053. },
  59054. /* Smoothing function for a path. Converts a path into cubic beziers. Value defines the divider of the distance between points.
  59055. * Defaults to a value of 4.
  59056. */
  59057. smooth: function (originalPath, value) {
  59058. var path = this.path2curve(originalPath),
  59059. newp = [path[0]],
  59060. x = path[0][1],
  59061. y = path[0][2],
  59062. j,
  59063. points,
  59064. i = 1,
  59065. ii = path.length,
  59066. beg = 1,
  59067. mx = x,
  59068. my = y,
  59069. cx = 0,
  59070. cy = 0,
  59071. pathi,
  59072. pathil,
  59073. pathim,
  59074. pathiml,
  59075. pathip,
  59076. pathipl,
  59077. begl;
  59078. for (; i < ii; i++) {
  59079. pathi = path[i];
  59080. pathil = pathi.length;
  59081. pathim = path[i - 1];
  59082. pathiml = pathim.length;
  59083. pathip = path[i + 1];
  59084. pathipl = pathip && pathip.length;
  59085. if (pathi[0] == "M") {
  59086. mx = pathi[1];
  59087. my = pathi[2];
  59088. j = i + 1;
  59089. while (path[j][0] != "C") {
  59090. j++;
  59091. }
  59092. cx = path[j][5];
  59093. cy = path[j][6];
  59094. newp.push(["M", mx, my]);
  59095. beg = newp.length;
  59096. x = mx;
  59097. y = my;
  59098. continue;
  59099. }
  59100. if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
  59101. begl = newp[beg].length;
  59102. points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
  59103. newp[beg][1] = points.x2;
  59104. newp[beg][2] = points.y2;
  59105. }
  59106. else if (!pathip || pathip[0] == "M") {
  59107. points = {
  59108. x1: pathi[pathil - 2],
  59109. y1: pathi[pathil - 1]
  59110. };
  59111. } else {
  59112. points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
  59113. }
  59114. newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
  59115. x = points.x2;
  59116. y = points.y2;
  59117. }
  59118. return newp;
  59119. },
  59120. findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
  59121. var t1 = 1 - t;
  59122. return {
  59123. x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
  59124. y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
  59125. };
  59126. },
  59127. /**
  59128. * @private
  59129. */
  59130. snapEnds: function (from, to, stepsMax, prettyNumbers) {
  59131. if (Ext.isDate(from)) {
  59132. return this.snapEndsByDate(from, to, stepsMax);
  59133. }
  59134. var step = (to - from) / stepsMax,
  59135. level = Math.floor(Math.log(step) / Math.LN10) + 1,
  59136. m = Math.pow(10, level),
  59137. cur,
  59138. modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
  59139. interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
  59140. stepCount = 0,
  59141. value,
  59142. weight,
  59143. i,
  59144. topValue,
  59145. topWeight = 1e9,
  59146. ln = interval.length;
  59147. cur = from = Math.floor(from / m) * m;
  59148. if(prettyNumbers){
  59149. for (i = 0; i < ln; i++) {
  59150. value = interval[i][0];
  59151. weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
  59152. if (weight < topWeight) {
  59153. topValue = value;
  59154. topWeight = weight;
  59155. }
  59156. }
  59157. step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
  59158. while (cur < to) {
  59159. cur += step;
  59160. stepCount++;
  59161. }
  59162. to = +cur.toFixed(10);
  59163. }else{
  59164. stepCount = stepsMax;
  59165. }
  59166. return {
  59167. from: from,
  59168. to: to,
  59169. power: level,
  59170. step: step,
  59171. steps: stepCount
  59172. };
  59173. },
  59174. /**
  59175. * snapEndsByDate is a utility method to deduce an appropriate tick configuration for the data set of given
  59176. * feature. Refer to {@link #snapEnds}.
  59177. *
  59178. * @param {Date} from The minimum value in the data
  59179. * @param {Date} to The maximum value in the data
  59180. * @param {Number} stepsMax The maximum number of ticks
  59181. * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
  59182. * and will not be adjusted
  59183. * @return {Object} The calculated step and ends info; properties are:
  59184. * - from: The result start value, which may be lower than the original start value
  59185. * - to: The result end value, which may be higher than the original end value
  59186. * - step: The value size of each step
  59187. * - steps: The number of steps. NOTE: the steps may not divide the from/to range perfectly evenly;
  59188. * there may be a smaller distance between the last step and the end value than between prior
  59189. * steps, particularly when the `endsLocked` param is true. Therefore it is best to not use
  59190. * the `steps` result when finding the axis tick points, instead use the `step`, `to`, and
  59191. * `from` to find the correct point for each tick.
  59192. */
  59193. snapEndsByDate: function (from, to, stepsMax, lockEnds) {
  59194. var selectedStep = false,
  59195. scales = [
  59196. [Ext.Date.MILLI, [1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500]],
  59197. [Ext.Date.SECOND, [1, 2, 3, 5, 10, 15, 30]],
  59198. [Ext.Date.MINUTE, [1, 2, 3, 5, 10, 20, 30]],
  59199. [Ext.Date.HOUR, [1, 2, 3, 4, 6, 12]],
  59200. [Ext.Date.DAY, [1, 2, 3, 7, 14]],
  59201. [Ext.Date.MONTH, [1, 2, 3, 4, 6]]
  59202. ],
  59203. sLen = scales.length,
  59204. stop = false,
  59205. scale, j, yearDiff, s;
  59206. // Find the most desirable scale
  59207. for (s = 0; s < sLen; s++) {
  59208. scale = scales[s];
  59209. if (!stop) {
  59210. for (j = 0; j < scale[1].length; j++) {
  59211. if (to < Ext.Date.add(from, scale[0], scale[1][j] * stepsMax)) {
  59212. selectedStep = [scale[0], scale[1][j]];
  59213. stop = true;
  59214. break;
  59215. }
  59216. }
  59217. }
  59218. }
  59219. if (!selectedStep) {
  59220. yearDiff = this.snapEnds(from.getFullYear(), to.getFullYear() + 1, stepsMax, lockEnds);
  59221. selectedStep = [Date.YEAR, Math.round(yearDiff.step)];
  59222. }
  59223. return this.snapEndsByDateAndStep(from, to, selectedStep, lockEnds);
  59224. },
  59225. /**
  59226. * snapEndsByDateAndStep is a utility method to deduce an appropriate tick configuration for the data set of given
  59227. * feature and specific step size.
  59228. * @param {Date} from The minimum value in the data
  59229. * @param {Date} to The maximum value in the data
  59230. * @param {Array} step An array with two components: The first is the unit of the step (day, month, year, etc). The second one is the number of units for the step (1, 2, etc.).
  59231. * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
  59232. * and will not be adjusted
  59233. */
  59234. snapEndsByDateAndStep: function(from, to, step, lockEnds) {
  59235. var fromStat = [from.getFullYear(), from.getMonth(), from.getDate(),
  59236. from.getHours(), from.getMinutes(), from.getSeconds(), from.getMilliseconds()],
  59237. steps = 0, testFrom, testTo;
  59238. if (lockEnds) {
  59239. testFrom = from;
  59240. } else {
  59241. switch (step[0]) {
  59242. case Ext.Date.MILLI:
  59243. testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
  59244. fromStat[4], fromStat[5], Math.floor(fromStat[6] / step[1]) * step[1]);
  59245. break;
  59246. case Ext.Date.SECOND:
  59247. testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
  59248. fromStat[4], Math.floor(fromStat[5] / step[1]) * step[1], 0);
  59249. break;
  59250. case Ext.Date.MINUTE:
  59251. testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
  59252. Math.floor(fromStat[4] / step[1]) * step[1], 0, 0);
  59253. break;
  59254. case Ext.Date.HOUR:
  59255. testFrom = new Date(fromStat[0], fromStat[1], fromStat[2],
  59256. Math.floor(fromStat[3] / step[1]) * step[1], 0, 0, 0);
  59257. break;
  59258. case Ext.Date.DAY:
  59259. testFrom = new Date(fromStat[0], fromStat[1],
  59260. Math.floor(fromStat[2] - 1 / step[1]) * step[1] + 1, 0, 0, 0, 0);
  59261. break;
  59262. case Ext.Date.MONTH:
  59263. testFrom = new Date(fromStat[0], Math.floor(fromStat[1] / step[1]) * step[1], 1, 0, 0, 0, 0);
  59264. break;
  59265. default: // Ext.Date.YEAR
  59266. testFrom = new Date(Math.floor(fromStat[0] / step[1]) * step[1], 0, 1, 0, 0, 0, 0);
  59267. break;
  59268. }
  59269. }
  59270. testTo = testFrom;
  59271. // TODO(zhangbei) : We can do it better somehow...
  59272. while (testTo < to) {
  59273. testTo = Ext.Date.add(testTo, step[0], step[1]);
  59274. steps++;
  59275. }
  59276. if (lockEnds) {
  59277. testTo = to;
  59278. }
  59279. return {
  59280. from : +testFrom,
  59281. to : +testTo,
  59282. step : (testTo - testFrom) / steps,
  59283. steps : steps
  59284. };
  59285. },
  59286. sorter: function (a, b) {
  59287. return a.offset - b.offset;
  59288. },
  59289. rad: function(degrees) {
  59290. return degrees % 360 * Math.PI / 180;
  59291. },
  59292. degrees: function(radian) {
  59293. return radian * 180 / Math.PI % 360;
  59294. },
  59295. withinBox: function(x, y, bbox) {
  59296. bbox = bbox || {};
  59297. return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
  59298. },
  59299. parseGradient: function(gradient) {
  59300. var me = this,
  59301. type = gradient.type || 'linear',
  59302. angle = gradient.angle || 0,
  59303. radian = me.radian,
  59304. stops = gradient.stops,
  59305. stopsArr = [],
  59306. stop,
  59307. vector,
  59308. max,
  59309. stopObj;
  59310. if (type == 'linear') {
  59311. vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
  59312. max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
  59313. vector[2] *= max;
  59314. vector[3] *= max;
  59315. if (vector[2] < 0) {
  59316. vector[0] = -vector[2];
  59317. vector[2] = 0;
  59318. }
  59319. if (vector[3] < 0) {
  59320. vector[1] = -vector[3];
  59321. vector[3] = 0;
  59322. }
  59323. }
  59324. for (stop in stops) {
  59325. if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
  59326. stopObj = {
  59327. offset: parseInt(stop, 10),
  59328. color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
  59329. opacity: stops[stop].opacity || 1
  59330. };
  59331. stopsArr.push(stopObj);
  59332. }
  59333. }
  59334. // Sort by pct property
  59335. Ext.Array.sort(stopsArr, me.sorter);
  59336. if (type == 'linear') {
  59337. return {
  59338. id: gradient.id,
  59339. type: type,
  59340. vector: vector,
  59341. stops: stopsArr
  59342. };
  59343. }
  59344. else {
  59345. return {
  59346. id: gradient.id,
  59347. type: type,
  59348. centerX: gradient.centerX,
  59349. centerY: gradient.centerY,
  59350. focalX: gradient.focalX,
  59351. focalY: gradient.focalY,
  59352. radius: gradient.radius,
  59353. vector: vector,
  59354. stops: stopsArr
  59355. };
  59356. }
  59357. }
  59358. });
  59359. /**
  59360. * @private
  59361. */
  59362. Ext.define('Ext.fx.PropertyHandler', {
  59363. /* Begin Definitions */
  59364. requires: ['Ext.draw.Draw'],
  59365. statics: {
  59366. defaultHandler: {
  59367. pixelDefaultsRE: /width|height|top$|bottom$|left$|right$/i,
  59368. unitRE: /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/,
  59369. scrollRE: /^scroll/i,
  59370. computeDelta: function(from, end, damper, initial, attr) {
  59371. damper = (typeof damper == 'number') ? damper : 1;
  59372. var unitRE = this.unitRE,
  59373. match = unitRE.exec(from),
  59374. start, units;
  59375. if (match) {
  59376. from = match[1];
  59377. units = match[2];
  59378. if (!this.scrollRE.test(attr) && !units && this.pixelDefaultsRE.test(attr)) {
  59379. units = 'px';
  59380. }
  59381. }
  59382. from = +from || 0;
  59383. match = unitRE.exec(end);
  59384. if (match) {
  59385. end = match[1];
  59386. units = match[2] || units;
  59387. }
  59388. end = +end || 0;
  59389. start = (initial != null) ? initial : from;
  59390. return {
  59391. from: from,
  59392. delta: (end - start) * damper,
  59393. units: units
  59394. };
  59395. },
  59396. get: function(from, end, damper, initialFrom, attr) {
  59397. var ln = from.length,
  59398. out = [],
  59399. i, initial, res, j, len;
  59400. for (i = 0; i < ln; i++) {
  59401. if (initialFrom) {
  59402. initial = initialFrom[i][1].from;
  59403. }
  59404. if (Ext.isArray(from[i][1]) && Ext.isArray(end)) {
  59405. res = [];
  59406. j = 0;
  59407. len = from[i][1].length;
  59408. for (; j < len; j++) {
  59409. res.push(this.computeDelta(from[i][1][j], end[j], damper, initial, attr));
  59410. }
  59411. out.push([from[i][0], res]);
  59412. }
  59413. else {
  59414. out.push([from[i][0], this.computeDelta(from[i][1], end, damper, initial, attr)]);
  59415. }
  59416. }
  59417. return out;
  59418. },
  59419. set: function(values, easing) {
  59420. var ln = values.length,
  59421. out = [],
  59422. i, val, res, len, j;
  59423. for (i = 0; i < ln; i++) {
  59424. val = values[i][1];
  59425. if (Ext.isArray(val)) {
  59426. res = [];
  59427. j = 0;
  59428. len = val.length;
  59429. for (; j < len; j++) {
  59430. res.push(val[j].from + val[j].delta * easing + (val[j].units || 0));
  59431. }
  59432. out.push([values[i][0], res]);
  59433. } else {
  59434. out.push([values[i][0], val.from + val.delta * easing + (val.units || 0)]);
  59435. }
  59436. }
  59437. return out;
  59438. }
  59439. },
  59440. stringHandler: {
  59441. computeDelta: function(from, end, damper, initial, attr) {
  59442. return {
  59443. from: from,
  59444. delta: end
  59445. };
  59446. },
  59447. get: function(from, end, damper, initialFrom, attr) {
  59448. var ln = from.length,
  59449. out = [],
  59450. i, initial, res, j, len;
  59451. for (i = 0; i < ln; i++) {
  59452. out.push([from[i][0], this.computeDelta(from[i][1], end, damper, initial, attr)]);
  59453. }
  59454. return out;
  59455. },
  59456. set: function(values, easing) {
  59457. var ln = values.length,
  59458. out = [],
  59459. i, val, res, len, j;
  59460. for (i = 0; i < ln; i++) {
  59461. val = values[i][1];
  59462. out.push([values[i][0], val.delta]);
  59463. }
  59464. return out;
  59465. }
  59466. },
  59467. color: {
  59468. rgbRE: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
  59469. hexRE: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
  59470. hex3RE: /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,
  59471. parseColor : function(color, damper) {
  59472. damper = (typeof damper == 'number') ? damper : 1;
  59473. var out = false,
  59474. reList = [this.hexRE, this.rgbRE, this.hex3RE],
  59475. length = reList.length,
  59476. match, base, re, i;
  59477. for (i = 0; i < length; i++) {
  59478. re = reList[i];
  59479. base = (i % 2 === 0) ? 16 : 10;
  59480. match = re.exec(color);
  59481. if (match && match.length === 4) {
  59482. if (i === 2) {
  59483. match[1] += match[1];
  59484. match[2] += match[2];
  59485. match[3] += match[3];
  59486. }
  59487. out = {
  59488. red: parseInt(match[1], base),
  59489. green: parseInt(match[2], base),
  59490. blue: parseInt(match[3], base)
  59491. };
  59492. break;
  59493. }
  59494. }
  59495. return out || color;
  59496. },
  59497. computeDelta: function(from, end, damper, initial) {
  59498. from = this.parseColor(from);
  59499. end = this.parseColor(end, damper);
  59500. var start = initial ? initial : from,
  59501. tfrom = typeof start,
  59502. tend = typeof end;
  59503. //Extra check for when the color string is not recognized.
  59504. if (tfrom == 'string' || tfrom == 'undefined'
  59505. || tend == 'string' || tend == 'undefined') {
  59506. return end || start;
  59507. }
  59508. return {
  59509. from: from,
  59510. delta: {
  59511. red: Math.round((end.red - start.red) * damper),
  59512. green: Math.round((end.green - start.green) * damper),
  59513. blue: Math.round((end.blue - start.blue) * damper)
  59514. }
  59515. };
  59516. },
  59517. get: function(start, end, damper, initialFrom) {
  59518. var ln = start.length,
  59519. out = [],
  59520. i, initial;
  59521. for (i = 0; i < ln; i++) {
  59522. if (initialFrom) {
  59523. initial = initialFrom[i][1].from;
  59524. }
  59525. out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
  59526. }
  59527. return out;
  59528. },
  59529. set: function(values, easing) {
  59530. var ln = values.length,
  59531. out = [],
  59532. i, val, parsedString, from, delta;
  59533. for (i = 0; i < ln; i++) {
  59534. val = values[i][1];
  59535. if (val) {
  59536. from = val.from;
  59537. delta = val.delta;
  59538. //multiple checks to reformat the color if it can't recognized by computeDelta.
  59539. val = (typeof val == 'object' && 'red' in val)?
  59540. 'rgb(' + val.red + ', ' + val.green + ', ' + val.blue + ')' : val;
  59541. val = (typeof val == 'object' && val.length)? val[0] : val;
  59542. if (typeof val == 'undefined') {
  59543. return [];
  59544. }
  59545. parsedString = typeof val == 'string'? val :
  59546. 'rgb(' + [
  59547. (from.red + Math.round(delta.red * easing)) % 256,
  59548. (from.green + Math.round(delta.green * easing)) % 256,
  59549. (from.blue + Math.round(delta.blue * easing)) % 256
  59550. ].join(',') + ')';
  59551. out.push([
  59552. values[i][0],
  59553. parsedString
  59554. ]);
  59555. }
  59556. }
  59557. return out;
  59558. }
  59559. },
  59560. object: {
  59561. interpolate: function(prop, damper) {
  59562. damper = (typeof damper == 'number') ? damper : 1;
  59563. var out = {},
  59564. p;
  59565. for(p in prop) {
  59566. out[p] = parseFloat(prop[p]) * damper;
  59567. }
  59568. return out;
  59569. },
  59570. computeDelta: function(from, end, damper, initial) {
  59571. from = this.interpolate(from);
  59572. end = this.interpolate(end, damper);
  59573. var start = initial ? initial : from,
  59574. delta = {},
  59575. p;
  59576. for(p in end) {
  59577. delta[p] = end[p] - start[p];
  59578. }
  59579. return {
  59580. from: from,
  59581. delta: delta
  59582. };
  59583. },
  59584. get: function(start, end, damper, initialFrom) {
  59585. var ln = start.length,
  59586. out = [],
  59587. i, initial;
  59588. for (i = 0; i < ln; i++) {
  59589. if (initialFrom) {
  59590. initial = initialFrom[i][1].from;
  59591. }
  59592. out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
  59593. }
  59594. return out;
  59595. },
  59596. set: function(values, easing) {
  59597. var ln = values.length,
  59598. out = [],
  59599. outObject = {},
  59600. i, from, delta, val, p;
  59601. for (i = 0; i < ln; i++) {
  59602. val = values[i][1];
  59603. from = val.from;
  59604. delta = val.delta;
  59605. for (p in from) {
  59606. outObject[p] = from[p] + delta[p] * easing;
  59607. }
  59608. out.push([
  59609. values[i][0],
  59610. outObject
  59611. ]);
  59612. }
  59613. return out;
  59614. }
  59615. },
  59616. path: {
  59617. computeDelta: function(from, end, damper, initial) {
  59618. damper = (typeof damper == 'number') ? damper : 1;
  59619. var start;
  59620. from = +from || 0;
  59621. end = +end || 0;
  59622. start = (initial != null) ? initial : from;
  59623. return {
  59624. from: from,
  59625. delta: (end - start) * damper
  59626. };
  59627. },
  59628. forcePath: function(path) {
  59629. if (!Ext.isArray(path) && !Ext.isArray(path[0])) {
  59630. path = Ext.draw.Draw.parsePathString(path);
  59631. }
  59632. return path;
  59633. },
  59634. get: function(start, end, damper, initialFrom) {
  59635. var endPath = this.forcePath(end),
  59636. out = [],
  59637. startLn = start.length,
  59638. startPathLn, pointsLn, i, deltaPath, initial, j, k, path, startPath;
  59639. for (i = 0; i < startLn; i++) {
  59640. startPath = this.forcePath(start[i][1]);
  59641. deltaPath = Ext.draw.Draw.interpolatePaths(startPath, endPath);
  59642. startPath = deltaPath[0];
  59643. endPath = deltaPath[1];
  59644. startPathLn = startPath.length;
  59645. path = [];
  59646. for (j = 0; j < startPathLn; j++) {
  59647. deltaPath = [startPath[j][0]];
  59648. pointsLn = startPath[j].length;
  59649. for (k = 1; k < pointsLn; k++) {
  59650. initial = initialFrom && initialFrom[0][1][j][k].from;
  59651. deltaPath.push(this.computeDelta(startPath[j][k], endPath[j][k], damper, initial));
  59652. }
  59653. path.push(deltaPath);
  59654. }
  59655. out.push([start[i][0], path]);
  59656. }
  59657. return out;
  59658. },
  59659. set: function(values, easing) {
  59660. var ln = values.length,
  59661. out = [],
  59662. i, j, k, newPath, calcPath, deltaPath, deltaPathLn, pointsLn;
  59663. for (i = 0; i < ln; i++) {
  59664. deltaPath = values[i][1];
  59665. newPath = [];
  59666. deltaPathLn = deltaPath.length;
  59667. for (j = 0; j < deltaPathLn; j++) {
  59668. calcPath = [deltaPath[j][0]];
  59669. pointsLn = deltaPath[j].length;
  59670. for (k = 1; k < pointsLn; k++) {
  59671. calcPath.push(deltaPath[j][k].from + deltaPath[j][k].delta * easing);
  59672. }
  59673. newPath.push(calcPath.join(','));
  59674. }
  59675. out.push([values[i][0], newPath.join(',')]);
  59676. }
  59677. return out;
  59678. }
  59679. }
  59680. /* End Definitions */
  59681. }
  59682. }, function() {
  59683. //set color properties to color interpolator
  59684. var props = [
  59685. 'outlineColor',
  59686. 'backgroundColor',
  59687. 'borderColor',
  59688. 'borderTopColor',
  59689. 'borderRightColor',
  59690. 'borderBottomColor',
  59691. 'borderLeftColor',
  59692. 'fill',
  59693. 'stroke'
  59694. ],
  59695. length = props.length,
  59696. i = 0,
  59697. prop;
  59698. for (; i<length; i++) {
  59699. prop = props[i];
  59700. this[prop] = this.color;
  59701. }
  59702. //set string properties to string
  59703. props = ['cursor'];
  59704. length = props.length;
  59705. i = 0;
  59706. for (; i<length; i++) {
  59707. prop = props[i];
  59708. this[prop] = this.stringHandler;
  59709. }
  59710. });
  59711. /**
  59712. * This class manages animation for a specific {@link #target}. The animation allows
  59713. * animation of various properties on the target, such as size, position, color and others.
  59714. *
  59715. * ## Starting Conditions
  59716. *
  59717. * The starting conditions for the animation are provided by the {@link #from} configuration.
  59718. * Any/all of the properties in the {@link #from} configuration can be specified. If a particular
  59719. * property is not defined, the starting value for that property will be read directly from the target.
  59720. *
  59721. * ## End Conditions
  59722. *
  59723. * The ending conditions for the animation are provided by the {@link #to} configuration. These mark
  59724. * the final values once the animations has finished. The values in the {@link #from} can mirror
  59725. * those in the {@link #to} configuration to provide a starting point.
  59726. *
  59727. * ## Other Options
  59728. *
  59729. * - {@link #duration}: Specifies the time period of the animation.
  59730. * - {@link #easing}: Specifies the easing of the animation.
  59731. * - {@link #iterations}: Allows the animation to repeat a number of times.
  59732. * - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
  59733. *
  59734. * ## Example Code
  59735. *
  59736. * @example
  59737. * var myComponent = Ext.create('Ext.Component', {
  59738. * renderTo: document.body,
  59739. * width: 200,
  59740. * height: 200,
  59741. * style: 'border: 1px solid red;'
  59742. * });
  59743. *
  59744. * Ext.create('Ext.fx.Anim', {
  59745. * target: myComponent,
  59746. * duration: 1000,
  59747. * from: {
  59748. * width: 400 //starting width 400
  59749. * },
  59750. * to: {
  59751. * width: 300, //end width 300
  59752. * height: 300 // end height 300
  59753. * }
  59754. * });
  59755. */
  59756. Ext.define('Ext.fx.Anim', {
  59757. /* Begin Definitions */
  59758. mixins: {
  59759. observable: 'Ext.util.Observable'
  59760. },
  59761. requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
  59762. /* End Definitions */
  59763. /**
  59764. * @property {Boolean} isAnimation
  59765. * `true` in this class to identify an object as an instantiated Anim, or subclass thereof.
  59766. */
  59767. isAnimation: true,
  59768. /**
  59769. * @cfg {Function} callback
  59770. * A function to be run after the animation has completed.
  59771. */
  59772. /**
  59773. * @cfg {Function} scope
  59774. * The scope that the {@link #callback} function will be called with
  59775. */
  59776. /**
  59777. * @cfg {Number} duration
  59778. * Time in milliseconds for a single animation to last. If the {@link #iterations} property is
  59779. * specified, then each animate will take the same duration for each iteration.
  59780. */
  59781. duration: 250,
  59782. /**
  59783. * @cfg {Number} delay
  59784. * Time to delay before starting the animation.
  59785. */
  59786. delay: 0,
  59787. /* @private used to track a delayed starting time */
  59788. delayStart: 0,
  59789. /**
  59790. * @cfg {Boolean} dynamic
  59791. * Currently only for Component Animation: Only set a component's outer element size bypassing layouts.
  59792. * Set to true to do full layouts for every frame of the animation.
  59793. */
  59794. dynamic: false,
  59795. /**
  59796. * @cfg {String} easing
  59797. * This describes how the intermediate values used during a transition will be calculated.
  59798. * It allows for a transition to change speed over its duration.
  59799. *
  59800. * - backIn
  59801. * - backOut
  59802. * - bounceIn
  59803. * - bounceOut
  59804. * - ease
  59805. * - easeIn
  59806. * - easeOut
  59807. * - easeInOut
  59808. * - elasticIn
  59809. * - elasticOut
  59810. * - cubic-bezier(x1, y1, x2, y2)
  59811. *
  59812. * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
  59813. * specification. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
  59814. * be in the range [0, 1] or the definition is invalid.
  59815. *
  59816. * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
  59817. */
  59818. easing: 'ease',
  59819. /**
  59820. * @cfg {Object} keyframes
  59821. * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
  59822. * is considered '100%'. **Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
  59823. * "from" or "to".** A keyframe declaration without these keyframe selectors is invalid and will not be available for
  59824. * animation. The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
  59825. * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
  59826. *
  59827. * keyframes : {
  59828. * '0%': {
  59829. * left: 100
  59830. * },
  59831. * '40%': {
  59832. * left: 150
  59833. * },
  59834. * '60%': {
  59835. * left: 75
  59836. * },
  59837. * '100%': {
  59838. * left: 100
  59839. * }
  59840. * }
  59841. */
  59842. /**
  59843. * @private
  59844. */
  59845. damper: 1,
  59846. /**
  59847. * @private
  59848. */
  59849. bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
  59850. /**
  59851. * Run the animation from the end to the beginning
  59852. * Defaults to false.
  59853. * @cfg {Boolean} reverse
  59854. */
  59855. reverse: false,
  59856. /**
  59857. * Flag to determine if the animation has started
  59858. * @property running
  59859. * @type Boolean
  59860. */
  59861. running: false,
  59862. /**
  59863. * Flag to determine if the animation is paused. Only set this to true if you need to
  59864. * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
  59865. * @property paused
  59866. * @type Boolean
  59867. */
  59868. paused: false,
  59869. /**
  59870. * @cfg {Number} iterations
  59871. * Number of times to execute the animation.
  59872. */
  59873. iterations: 1,
  59874. /**
  59875. * @cfg {Boolean} alternate
  59876. * Used in conjunction with iterations to reverse the animation each time an iteration completes.
  59877. */
  59878. alternate: false,
  59879. /**
  59880. * Current iteration the animation is running.
  59881. * @property currentIteration
  59882. * @type Number
  59883. */
  59884. currentIteration: 0,
  59885. /**
  59886. * Starting time of the animation.
  59887. * @property startTime
  59888. * @type Date
  59889. */
  59890. startTime: 0,
  59891. /**
  59892. * Contains a cache of the interpolators to be used.
  59893. * @private
  59894. * @property propHandlers
  59895. * @type Object
  59896. */
  59897. /**
  59898. * @cfg {String/Object} target
  59899. * The {@link Ext.fx.target.Target} to apply the animation to. This should only be specified when creating an Ext.fx.Anim directly.
  59900. * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
  59901. * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
  59902. * automatically.
  59903. */
  59904. /**
  59905. * @cfg {Object} from
  59906. * An object containing property/value pairs for the beginning of the animation. If not specified, the current state of the
  59907. * Ext.fx.target will be used. For example:
  59908. *
  59909. * from: {
  59910. * opacity: 0, // Transparent
  59911. * color: '#ffffff', // White
  59912. * left: 0
  59913. * }
  59914. *
  59915. */
  59916. /**
  59917. * @cfg {Object} to (required)
  59918. * An object containing property/value pairs for the end of the animation. For example:
  59919. *
  59920. * to: {
  59921. * opacity: 1, // Opaque
  59922. * color: '#00ff00', // Green
  59923. * left: 500
  59924. * }
  59925. *
  59926. */
  59927. // @private
  59928. frameCount: 0,
  59929. // @private
  59930. constructor: function(config) {
  59931. var me = this,
  59932. curve;
  59933. config = config || {};
  59934. // If keyframes are passed, they really want an Animator instead.
  59935. if (config.keyframes) {
  59936. return new Ext.fx.Animator(config);
  59937. }
  59938. Ext.apply(me, config);
  59939. if (me.from === undefined) {
  59940. me.from = {};
  59941. }
  59942. me.propHandlers = {};
  59943. me.config = config;
  59944. me.target = Ext.fx.Manager.createTarget(me.target);
  59945. me.easingFn = Ext.fx.Easing[me.easing];
  59946. me.target.dynamic = me.dynamic;
  59947. // If not a pre-defined curve, try a cubic-bezier
  59948. if (!me.easingFn) {
  59949. me.easingFn = String(me.easing).match(me.bezierRE);
  59950. if (me.easingFn && me.easingFn.length == 5) {
  59951. curve = me.easingFn;
  59952. me.easingFn = Ext.fx.CubicBezier.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
  59953. }
  59954. }
  59955. me.id = Ext.id(null, 'ext-anim-');
  59956. me.addEvents(
  59957. /**
  59958. * @event beforeanimate
  59959. * Fires before the animation starts. A handler can return false to cancel the animation.
  59960. * @param {Ext.fx.Anim} this
  59961. */
  59962. 'beforeanimate',
  59963. /**
  59964. * @event afteranimate
  59965. * Fires when the animation is complete.
  59966. * @param {Ext.fx.Anim} this
  59967. * @param {Date} startTime
  59968. */
  59969. 'afteranimate',
  59970. /**
  59971. * @event lastframe
  59972. * Fires when the animation's last frame has been set.
  59973. * @param {Ext.fx.Anim} this
  59974. * @param {Date} startTime
  59975. */
  59976. 'lastframe'
  59977. );
  59978. me.mixins.observable.constructor.call(me);
  59979. Ext.fx.Manager.addAnim(me);
  59980. },
  59981. /**
  59982. * @private
  59983. * Helper to the target
  59984. */
  59985. setAttr: function(attr, value) {
  59986. return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
  59987. },
  59988. /**
  59989. * @private
  59990. * Set up the initial currentAttrs hash.
  59991. */
  59992. initAttrs: function() {
  59993. var me = this,
  59994. from = me.from,
  59995. to = me.to,
  59996. initialFrom = me.initialFrom || {},
  59997. out = {},
  59998. start, end, propHandler, attr;
  59999. for (attr in to) {
  60000. if (to.hasOwnProperty(attr)) {
  60001. start = me.target.getAttr(attr, from[attr]);
  60002. end = to[attr];
  60003. // Use default (numeric) property handler
  60004. if (!Ext.fx.PropertyHandler[attr]) {
  60005. if (Ext.isObject(end)) {
  60006. propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
  60007. } else {
  60008. propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
  60009. }
  60010. }
  60011. // Use custom handler
  60012. else {
  60013. propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
  60014. }
  60015. out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
  60016. }
  60017. }
  60018. me.currentAttrs = out;
  60019. },
  60020. /**
  60021. * @private
  60022. * Fires beforeanimate and sets the running flag.
  60023. */
  60024. start: function(startTime) {
  60025. var me = this,
  60026. delay = me.delay,
  60027. delayStart = me.delayStart,
  60028. delayDelta;
  60029. if (delay) {
  60030. if (!delayStart) {
  60031. me.delayStart = startTime;
  60032. return;
  60033. }
  60034. else {
  60035. delayDelta = startTime - delayStart;
  60036. if (delayDelta < delay) {
  60037. return;
  60038. }
  60039. else {
  60040. // Compensate for frame delay;
  60041. startTime = new Date(delayStart.getTime() + delay);
  60042. }
  60043. }
  60044. }
  60045. if (me.fireEvent('beforeanimate', me) !== false) {
  60046. me.startTime = startTime;
  60047. if (!me.paused && !me.currentAttrs) {
  60048. me.initAttrs();
  60049. }
  60050. me.running = true;
  60051. me.frameCount = 0;
  60052. }
  60053. },
  60054. /**
  60055. * @private
  60056. * Calculate attribute value at the passed timestamp.
  60057. * @returns a hash of the new attributes.
  60058. */
  60059. runAnim: function(elapsedTime) {
  60060. var me = this,
  60061. attrs = me.currentAttrs,
  60062. duration = me.duration,
  60063. easingFn = me.easingFn,
  60064. propHandlers = me.propHandlers,
  60065. ret = {},
  60066. easing, values, attr, lastFrame;
  60067. if (elapsedTime >= duration) {
  60068. elapsedTime = duration;
  60069. lastFrame = true;
  60070. }
  60071. if (me.reverse) {
  60072. elapsedTime = duration - elapsedTime;
  60073. }
  60074. for (attr in attrs) {
  60075. if (attrs.hasOwnProperty(attr)) {
  60076. values = attrs[attr];
  60077. easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
  60078. ret[attr] = propHandlers[attr].set(values, easing);
  60079. }
  60080. }
  60081. me.frameCount++;
  60082. return ret;
  60083. },
  60084. /**
  60085. * @private
  60086. * Perform lastFrame cleanup and handle iterations
  60087. * @returns a hash of the new attributes.
  60088. */
  60089. lastFrame: function() {
  60090. var me = this,
  60091. iter = me.iterations,
  60092. iterCount = me.currentIteration;
  60093. iterCount++;
  60094. if (iterCount < iter) {
  60095. if (me.alternate) {
  60096. me.reverse = !me.reverse;
  60097. }
  60098. me.startTime = new Date();
  60099. me.currentIteration = iterCount;
  60100. // Turn off paused for CSS3 Transitions
  60101. me.paused = false;
  60102. }
  60103. else {
  60104. me.currentIteration = 0;
  60105. me.end();
  60106. me.fireEvent('lastframe', me, me.startTime);
  60107. }
  60108. },
  60109. endWasCalled: 0,
  60110. /**
  60111. * Fire afteranimate event and end the animation. Usually called automatically when the
  60112. * animation reaches its final frame, but can also be called manually to pre-emptively
  60113. * stop and destroy the running animation.
  60114. */
  60115. end: function() {
  60116. if (this.endWasCalled++) {
  60117. return;
  60118. }
  60119. var me = this;
  60120. me.startTime = 0;
  60121. me.paused = false;
  60122. me.running = false;
  60123. Ext.fx.Manager.removeAnim(me);
  60124. me.fireEvent('afteranimate', me, me.startTime);
  60125. Ext.callback(me.callback, me.scope, [me, me.startTime]);
  60126. },
  60127. isReady: function() {
  60128. return this.paused === false && this.running === false && this.iterations > 0;
  60129. },
  60130. isRunning: function() {
  60131. return this.paused === false && this.running === true && this.isAnimator !== true;
  60132. }
  60133. });
  60134. // Set flag to indicate that Fx is available. Class might not be available immediately.
  60135. Ext.enableFx = true;
  60136. /*
  60137. * This is a derivative of the similarly named class in the YUI Library.
  60138. * The original license:
  60139. * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
  60140. * Code licensed under the BSD License:
  60141. * http://developer.yahoo.net/yui/license.txt
  60142. */
  60143. /**
  60144. * Defines the interface and base operation of items that that can be
  60145. * dragged or can be drop targets. It was designed to be extended, overriding
  60146. * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
  60147. * Up to three html elements can be associated with a DragDrop instance:
  60148. *
  60149. * - linked element: the element that is passed into the constructor.
  60150. * This is the element which defines the boundaries for interaction with
  60151. * other DragDrop objects.
  60152. *
  60153. * - handle element(s): The drag operation only occurs if the element that
  60154. * was clicked matches a handle element. By default this is the linked
  60155. * element, but there are times that you will want only a portion of the
  60156. * linked element to initiate the drag operation, and the setHandleElId()
  60157. * method provides a way to define this.
  60158. *
  60159. * - drag element: this represents the element that would be moved along
  60160. * with the cursor during a drag operation. By default, this is the linked
  60161. * element itself as in {@link Ext.dd.DD}. setDragElId() lets you define
  60162. * a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
  60163. *
  60164. * This class should not be instantiated until the onload event to ensure that
  60165. * the associated elements are available.
  60166. * The following would define a DragDrop obj that would interact with any
  60167. * other DragDrop obj in the "group1" group:
  60168. *
  60169. * dd = new Ext.dd.DragDrop("div1", "group1");
  60170. *
  60171. * Since none of the event handlers have been implemented, nothing would
  60172. * actually happen if you were to run the code above. Normally you would
  60173. * override this class or one of the default implementations, but you can
  60174. * also override the methods you want on an instance of the class...
  60175. *
  60176. * dd.onDragDrop = function(e, id) {
  60177. * alert("dd was dropped on " + id);
  60178. * }
  60179. *
  60180. */
  60181. Ext.define('Ext.dd.DragDrop', {
  60182. requires: ['Ext.dd.DragDropManager'],
  60183. /**
  60184. * Creates new DragDrop.
  60185. * @param {String} id of the element that is linked to this instance
  60186. * @param {String} sGroup the group of related DragDrop objects
  60187. * @param {Object} config an object containing configurable attributes.
  60188. * Valid properties for DragDrop:
  60189. *
  60190. * - padding
  60191. * - isTarget
  60192. * - maintainOffset
  60193. * - primaryButtonOnly
  60194. */
  60195. constructor: function(id, sGroup, config) {
  60196. if(id) {
  60197. this.init(id, sGroup, config);
  60198. }
  60199. },
  60200. /**
  60201. * @property {Boolean} ignoreSelf
  60202. * Set to false to enable a DragDrop object to fire drag events while dragging
  60203. * over its own Element. Defaults to true - DragDrop objects do not by default
  60204. * fire drag events to themselves.
  60205. */
  60206. /**
  60207. * @property {String} id
  60208. * The id of the element associated with this object. This is what we
  60209. * refer to as the "linked element" because the size and position of
  60210. * this element is used to determine when the drag and drop objects have
  60211. * interacted.
  60212. */
  60213. id: null,
  60214. /**
  60215. * @property {Object} config
  60216. * Configuration attributes passed into the constructor
  60217. */
  60218. config: null,
  60219. /**
  60220. * @property {String} dragElId
  60221. * The id of the element that will be dragged. By default this is same
  60222. * as the linked element, but could be changed to another element. Ex:
  60223. * Ext.dd.DDProxy
  60224. * @private
  60225. */
  60226. dragElId: null,
  60227. /**
  60228. * @property {String} handleElId
  60229. * The ID of the element that initiates the drag operation. By default
  60230. * this is the linked element, but could be changed to be a child of this
  60231. * element. This lets us do things like only starting the drag when the
  60232. * header element within the linked html element is clicked.
  60233. * @private
  60234. */
  60235. handleElId: null,
  60236. /**
  60237. * @property {Object} invalidHandleTypes
  60238. * An object who's property names identify HTML tags to be considered invalid as drag handles.
  60239. * A non-null property value identifies the tag as invalid. Defaults to the
  60240. * following value which prevents drag operations from being initiated by `<a>` elements:
  60241. *
  60242. * {
  60243. * A: "A"
  60244. * }
  60245. */
  60246. invalidHandleTypes: null,
  60247. /**
  60248. * @property {Object} invalidHandleIds
  60249. * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
  60250. * A non-null property value identifies the ID as invalid. For example, to prevent
  60251. * dragging from being initiated on element ID "foo", use:
  60252. *
  60253. * {
  60254. * foo: true
  60255. * }
  60256. */
  60257. invalidHandleIds: null,
  60258. /**
  60259. * @property {String[]} invalidHandleClasses
  60260. * An Array of CSS class names for elements to be considered in valid as drag handles.
  60261. */
  60262. invalidHandleClasses: null,
  60263. /**
  60264. * @property {Number} startPageX
  60265. * The linked element's absolute X position at the time the drag was
  60266. * started
  60267. * @private
  60268. */
  60269. startPageX: 0,
  60270. /**
  60271. * @property {Number} startPageY
  60272. * The linked element's absolute X position at the time the drag was
  60273. * started
  60274. * @private
  60275. */
  60276. startPageY: 0,
  60277. /**
  60278. * @property {Object} groups
  60279. * The group defines a logical collection of DragDrop objects that are
  60280. * related. Instances only get events when interacting with other
  60281. * DragDrop object in the same group. This lets us define multiple
  60282. * groups using a single DragDrop subclass if we want.
  60283. *
  60284. * An object in the format {'group1':true, 'group2':true}
  60285. */
  60286. groups: null,
  60287. /**
  60288. * @property {Boolean} locked
  60289. * Individual drag/drop instances can be locked. This will prevent
  60290. * onmousedown start drag.
  60291. * @private
  60292. */
  60293. locked: false,
  60294. /**
  60295. * Locks this instance
  60296. */
  60297. lock: function() {
  60298. this.locked = true;
  60299. },
  60300. /**
  60301. * @property {Boolean} moveOnly
  60302. * When set to true, other DD objects in cooperating DDGroups do not receive
  60303. * notification events when this DD object is dragged over them.
  60304. */
  60305. moveOnly: false,
  60306. /**
  60307. * Unlocks this instace
  60308. */
  60309. unlock: function() {
  60310. this.locked = false;
  60311. },
  60312. /**
  60313. * @property {Boolean} isTarget
  60314. * By default, all instances can be a drop target. This can be disabled by
  60315. * setting isTarget to false.
  60316. */
  60317. isTarget: true,
  60318. /**
  60319. * @property {Number[]} padding
  60320. * The padding configured for this drag and drop object for calculating
  60321. * the drop zone intersection with this object.
  60322. * An array containing the 4 padding values: [top, right, bottom, left]
  60323. */
  60324. padding: null,
  60325. /**
  60326. * @property _domRef
  60327. * Cached reference to the linked element
  60328. * @private
  60329. */
  60330. _domRef: null,
  60331. /**
  60332. * @property __ygDragDrop
  60333. * Internal typeof flag
  60334. * @private
  60335. */
  60336. __ygDragDrop: true,
  60337. /**
  60338. * @property {Boolean} constrainX
  60339. * Set to true when horizontal contraints are applied
  60340. * @private
  60341. */
  60342. constrainX: false,
  60343. /**
  60344. * @property {Boolean} constrainY
  60345. * Set to true when vertical contraints are applied
  60346. * @private
  60347. */
  60348. constrainY: false,
  60349. /**
  60350. * @property {Number} minX
  60351. * The left constraint
  60352. * @private
  60353. */
  60354. minX: 0,
  60355. /**
  60356. * @property {Number} maxX
  60357. * The right constraint
  60358. * @private
  60359. */
  60360. maxX: 0,
  60361. /**
  60362. * @property {Number} minY
  60363. * The up constraint
  60364. * @private
  60365. */
  60366. minY: 0,
  60367. /**
  60368. * @property {Number} maxY
  60369. * The down constraint
  60370. * @private
  60371. */
  60372. maxY: 0,
  60373. /**
  60374. * @property {Boolean} maintainOffset
  60375. * Maintain offsets when we resetconstraints. Set to true when you want
  60376. * the position of the element relative to its parent to stay the same
  60377. * when the page changes
  60378. */
  60379. maintainOffset: false,
  60380. /**
  60381. * @property {Number[]} xTicks
  60382. * Array of pixel locations the element will snap to if we specified a
  60383. * horizontal graduation/interval. This array is generated automatically
  60384. * when you define a tick interval.
  60385. */
  60386. xTicks: null,
  60387. /**
  60388. * @property {Number[]} yTicks
  60389. * Array of pixel locations the element will snap to if we specified a
  60390. * vertical graduation/interval. This array is generated automatically
  60391. * when you define a tick interval.
  60392. */
  60393. yTicks: null,
  60394. /**
  60395. * @property {Boolean} primaryButtonOnly
  60396. * By default the drag and drop instance will only respond to the primary
  60397. * button click (left button for a right-handed mouse). Set to true to
  60398. * allow drag and drop to start with any mouse click that is propogated
  60399. * by the browser
  60400. */
  60401. primaryButtonOnly: true,
  60402. /**
  60403. * @property {Boolean} available
  60404. * The available property is false until the linked dom element is accessible.
  60405. */
  60406. available: false,
  60407. /**
  60408. * @property {Boolean} hasOuterHandles
  60409. * By default, drags can only be initiated if the mousedown occurs in the
  60410. * region the linked element is. This is done in part to work around a
  60411. * bug in some browsers that mis-report the mousedown if the previous
  60412. * mouseup happened outside of the window. This property is set to true
  60413. * if outer handles are defined. Defaults to false.
  60414. */
  60415. hasOuterHandles: false,
  60416. /**
  60417. * Code that executes immediately before the startDrag event
  60418. * @private
  60419. */
  60420. b4StartDrag: function(x, y) { },
  60421. /**
  60422. * Abstract method called after a drag/drop object is clicked
  60423. * and the drag or mousedown time thresholds have beeen met.
  60424. * @param {Number} X click location
  60425. * @param {Number} Y click location
  60426. */
  60427. startDrag: function(x, y) { /* override this */ },
  60428. /**
  60429. * Code that executes immediately before the onDrag event
  60430. * @private
  60431. */
  60432. b4Drag: function(e) { },
  60433. /**
  60434. * Abstract method called during the onMouseMove event while dragging an
  60435. * object.
  60436. * @param {Event} e the mousemove event
  60437. */
  60438. onDrag: function(e) { /* override this */ },
  60439. /**
  60440. * Abstract method called when this element fist begins hovering over
  60441. * another DragDrop obj
  60442. * @param {Event} e the mousemove event
  60443. * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
  60444. * id this is hovering over. In INTERSECT mode, an array of one or more
  60445. * dragdrop items being hovered over.
  60446. */
  60447. onDragEnter: function(e, id) { /* override this */ },
  60448. /**
  60449. * Code that executes immediately before the onDragOver event
  60450. * @private
  60451. */
  60452. b4DragOver: function(e) { },
  60453. /**
  60454. * Abstract method called when this element is hovering over another
  60455. * DragDrop obj
  60456. * @param {Event} e the mousemove event
  60457. * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
  60458. * id this is hovering over. In INTERSECT mode, an array of dd items
  60459. * being hovered over.
  60460. */
  60461. onDragOver: function(e, id) { /* override this */ },
  60462. /**
  60463. * Code that executes immediately before the onDragOut event
  60464. * @private
  60465. */
  60466. b4DragOut: function(e) { },
  60467. /**
  60468. * Abstract method called when we are no longer hovering over an element
  60469. * @param {Event} e the mousemove event
  60470. * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
  60471. * id this was hovering over. In INTERSECT mode, an array of dd items
  60472. * that the mouse is no longer over.
  60473. */
  60474. onDragOut: function(e, id) { /* override this */ },
  60475. /**
  60476. * Code that executes immediately before the onDragDrop event
  60477. * @private
  60478. */
  60479. b4DragDrop: function(e) { },
  60480. /**
  60481. * Abstract method called when this item is dropped on another DragDrop
  60482. * obj
  60483. * @param {Event} e the mouseup event
  60484. * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
  60485. * id this was dropped on. In INTERSECT mode, an array of dd items this
  60486. * was dropped on.
  60487. */
  60488. onDragDrop: function(e, id) { /* override this */ },
  60489. /**
  60490. * Abstract method called when this item is dropped on an area with no
  60491. * drop target
  60492. * @param {Event} e the mouseup event
  60493. */
  60494. onInvalidDrop: function(e) { /* override this */ },
  60495. /**
  60496. * Code that executes immediately before the endDrag event
  60497. * @private
  60498. */
  60499. b4EndDrag: function(e) { },
  60500. /**
  60501. * Called when we are done dragging the object
  60502. * @param {Event} e the mouseup event
  60503. */
  60504. endDrag: function(e) { /* override this */ },
  60505. /**
  60506. * Code executed immediately before the onMouseDown event
  60507. * @param {Event} e the mousedown event
  60508. * @private
  60509. */
  60510. b4MouseDown: function(e) { },
  60511. /**
  60512. * Called when a drag/drop obj gets a mousedown
  60513. * @param {Event} e the mousedown event
  60514. */
  60515. onMouseDown: function(e) { /* override this */ },
  60516. /**
  60517. * Called when a drag/drop obj gets a mouseup
  60518. * @param {Event} e the mouseup event
  60519. */
  60520. onMouseUp: function(e) { /* override this */ },
  60521. /**
  60522. * Override the onAvailable method to do what is needed after the initial
  60523. * position was determined.
  60524. */
  60525. onAvailable: function () {
  60526. },
  60527. /**
  60528. * @property {Object} defaultPadding
  60529. * Provides default constraint padding to "constrainTo" elements.
  60530. */
  60531. defaultPadding: {
  60532. left: 0,
  60533. right: 0,
  60534. top: 0,
  60535. bottom: 0
  60536. },
  60537. /**
  60538. * Initializes the drag drop object's constraints to restrict movement to a certain element.
  60539. *
  60540. * Usage:
  60541. *
  60542. * var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
  60543. * { dragElId: "existingProxyDiv" });
  60544. * dd.startDrag = function(){
  60545. * this.constrainTo("parent-id");
  60546. * };
  60547. *
  60548. * Or you can initalize it using the {@link Ext.Element} object:
  60549. *
  60550. * Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
  60551. * startDrag : function(){
  60552. * this.constrainTo("parent-id");
  60553. * }
  60554. * });
  60555. *
  60556. * @param {String/HTMLElement/Ext.Element} constrainTo The element or element ID to constrain to.
  60557. * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
  60558. * and can be either a number for symmetrical padding (4 would be equal to `{left:4, right:4, top:4, bottom:4}`) or
  60559. * an object containing the sides to pad. For example: `{right:10, bottom:10}`
  60560. * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
  60561. */
  60562. constrainTo : function(constrainTo, pad, inContent){
  60563. if(Ext.isNumber(pad)){
  60564. pad = {left: pad, right:pad, top:pad, bottom:pad};
  60565. }
  60566. pad = pad || this.defaultPadding;
  60567. var b = Ext.get(this.getEl()).getBox(),
  60568. ce = Ext.get(constrainTo),
  60569. s = ce.getScroll(),
  60570. c,
  60571. cd = ce.dom,
  60572. xy,
  60573. topSpace,
  60574. leftSpace;
  60575. if(cd == document.body){
  60576. c = { x: s.left, y: s.top, width: Ext.Element.getViewWidth(), height: Ext.Element.getViewHeight()};
  60577. }else{
  60578. xy = ce.getXY();
  60579. c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
  60580. }
  60581. topSpace = b.y - c.y;
  60582. leftSpace = b.x - c.x;
  60583. this.resetConstraints();
  60584. this.setXConstraint(leftSpace - (pad.left||0), // left
  60585. c.width - leftSpace - b.width - (pad.right||0), //right
  60586. this.xTickSize
  60587. );
  60588. this.setYConstraint(topSpace - (pad.top||0), //top
  60589. c.height - topSpace - b.height - (pad.bottom||0), //bottom
  60590. this.yTickSize
  60591. );
  60592. },
  60593. /**
  60594. * Returns a reference to the linked element
  60595. * @return {HTMLElement} the html element
  60596. */
  60597. getEl: function() {
  60598. if (!this._domRef) {
  60599. this._domRef = Ext.getDom(this.id);
  60600. }
  60601. return this._domRef;
  60602. },
  60603. /**
  60604. * Returns a reference to the actual element to drag. By default this is
  60605. * the same as the html element, but it can be assigned to another
  60606. * element. An example of this can be found in Ext.dd.DDProxy
  60607. * @return {HTMLElement} the html element
  60608. */
  60609. getDragEl: function() {
  60610. return Ext.getDom(this.dragElId);
  60611. },
  60612. /**
  60613. * Sets up the DragDrop object. Must be called in the constructor of any
  60614. * Ext.dd.DragDrop subclass
  60615. * @param {String} id the id of the linked element
  60616. * @param {String} sGroup the group of related items
  60617. * @param {Object} config configuration attributes
  60618. */
  60619. init: function(id, sGroup, config) {
  60620. this.initTarget(id, sGroup, config);
  60621. Ext.EventManager.on(this.id, "mousedown", this.handleMouseDown, this);
  60622. // Ext.EventManager.on(this.id, "selectstart", Event.preventDefault);
  60623. },
  60624. /**
  60625. * Initializes Targeting functionality only... the object does not
  60626. * get a mousedown handler.
  60627. * @param {String} id the id of the linked element
  60628. * @param {String} sGroup the group of related items
  60629. * @param {Object} config configuration attributes
  60630. */
  60631. initTarget: function(id, sGroup, config) {
  60632. // configuration attributes
  60633. this.config = config || {};
  60634. // create a local reference to the drag and drop manager
  60635. this.DDMInstance = Ext.dd.DragDropManager;
  60636. // initialize the groups array
  60637. this.groups = {};
  60638. // assume that we have an element reference instead of an id if the
  60639. // parameter is not a string
  60640. if (typeof id !== "string") {
  60641. id = Ext.id(id);
  60642. }
  60643. // set the id
  60644. this.id = id;
  60645. // add to an interaction group
  60646. this.addToGroup((sGroup) ? sGroup : "default");
  60647. // We don't want to register this as the handle with the manager
  60648. // so we just set the id rather than calling the setter.
  60649. this.handleElId = id;
  60650. // the linked element is the element that gets dragged by default
  60651. this.setDragElId(id);
  60652. // by default, clicked anchors will not start drag operations.
  60653. this.invalidHandleTypes = { A: "A" };
  60654. this.invalidHandleIds = {};
  60655. this.invalidHandleClasses = [];
  60656. this.applyConfig();
  60657. this.handleOnAvailable();
  60658. },
  60659. /**
  60660. * Applies the configuration parameters that were passed into the constructor.
  60661. * This is supposed to happen at each level through the inheritance chain. So
  60662. * a DDProxy implentation will execute apply config on DDProxy, DD, and
  60663. * DragDrop in order to get all of the parameters that are available in
  60664. * each object.
  60665. */
  60666. applyConfig: function() {
  60667. // configurable properties:
  60668. // padding, isTarget, maintainOffset, primaryButtonOnly
  60669. this.padding = this.config.padding || [0, 0, 0, 0];
  60670. this.isTarget = (this.config.isTarget !== false);
  60671. this.maintainOffset = (this.config.maintainOffset);
  60672. this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
  60673. },
  60674. /**
  60675. * Executed when the linked element is available
  60676. * @private
  60677. */
  60678. handleOnAvailable: function() {
  60679. this.available = true;
  60680. this.resetConstraints();
  60681. this.onAvailable();
  60682. },
  60683. /**
  60684. * Configures the padding for the target zone in px. Effectively expands
  60685. * (or reduces) the virtual object size for targeting calculations.
  60686. * Supports css-style shorthand; if only one parameter is passed, all sides
  60687. * will have that padding, and if only two are passed, the top and bottom
  60688. * will have the first param, the left and right the second.
  60689. * @param {Number} iTop Top pad
  60690. * @param {Number} iRight Right pad
  60691. * @param {Number} iBot Bot pad
  60692. * @param {Number} iLeft Left pad
  60693. */
  60694. setPadding: function(iTop, iRight, iBot, iLeft) {
  60695. // this.padding = [iLeft, iRight, iTop, iBot];
  60696. if (!iRight && 0 !== iRight) {
  60697. this.padding = [iTop, iTop, iTop, iTop];
  60698. } else if (!iBot && 0 !== iBot) {
  60699. this.padding = [iTop, iRight, iTop, iRight];
  60700. } else {
  60701. this.padding = [iTop, iRight, iBot, iLeft];
  60702. }
  60703. },
  60704. /**
  60705. * Stores the initial placement of the linked element.
  60706. * @param {Number} diffX the X offset, default 0
  60707. * @param {Number} diffY the Y offset, default 0
  60708. */
  60709. setInitPosition: function(diffX, diffY) {
  60710. var el = this.getEl(),
  60711. dx, dy, p;
  60712. if (!this.DDMInstance.verifyEl(el)) {
  60713. return;
  60714. }
  60715. dx = diffX || 0;
  60716. dy = diffY || 0;
  60717. p = Ext.Element.getXY( el );
  60718. this.initPageX = p[0] - dx;
  60719. this.initPageY = p[1] - dy;
  60720. this.lastPageX = p[0];
  60721. this.lastPageY = p[1];
  60722. this.setStartPosition(p);
  60723. },
  60724. /**
  60725. * Sets the start position of the element. This is set when the obj
  60726. * is initialized, the reset when a drag is started.
  60727. * @param pos current position (from previous lookup)
  60728. * @private
  60729. */
  60730. setStartPosition: function(pos) {
  60731. var p = pos || Ext.Element.getXY( this.getEl() );
  60732. this.deltaSetXY = null;
  60733. this.startPageX = p[0];
  60734. this.startPageY = p[1];
  60735. },
  60736. /**
  60737. * Adds this instance to a group of related drag/drop objects. All
  60738. * instances belong to at least one group, and can belong to as many
  60739. * groups as needed.
  60740. * @param {String} sGroup the name of the group
  60741. */
  60742. addToGroup: function(sGroup) {
  60743. this.groups[sGroup] = true;
  60744. this.DDMInstance.regDragDrop(this, sGroup);
  60745. },
  60746. /**
  60747. * Removes this instance from the supplied interaction group
  60748. * @param {String} sGroup The group to drop
  60749. */
  60750. removeFromGroup: function(sGroup) {
  60751. if (this.groups[sGroup]) {
  60752. delete this.groups[sGroup];
  60753. }
  60754. this.DDMInstance.removeDDFromGroup(this, sGroup);
  60755. },
  60756. /**
  60757. * Allows you to specify that an element other than the linked element
  60758. * will be moved with the cursor during a drag
  60759. * @param {String} id the id of the element that will be used to initiate the drag
  60760. */
  60761. setDragElId: function(id) {
  60762. this.dragElId = id;
  60763. },
  60764. /**
  60765. * Allows you to specify a child of the linked element that should be
  60766. * used to initiate the drag operation. An example of this would be if
  60767. * you have a content div with text and links. Clicking anywhere in the
  60768. * content area would normally start the drag operation. Use this method
  60769. * to specify that an element inside of the content div is the element
  60770. * that starts the drag operation.
  60771. * @param {String} id the id of the element that will be used to
  60772. * initiate the drag.
  60773. */
  60774. setHandleElId: function(id) {
  60775. if (typeof id !== "string") {
  60776. id = Ext.id(id);
  60777. }
  60778. this.handleElId = id;
  60779. this.DDMInstance.regHandle(this.id, id);
  60780. },
  60781. /**
  60782. * Allows you to set an element outside of the linked element as a drag
  60783. * handle
  60784. * @param {String} id the id of the element that will be used to initiate the drag
  60785. */
  60786. setOuterHandleElId: function(id) {
  60787. if (typeof id !== "string") {
  60788. id = Ext.id(id);
  60789. }
  60790. Ext.EventManager.on(id, "mousedown", this.handleMouseDown, this);
  60791. this.setHandleElId(id);
  60792. this.hasOuterHandles = true;
  60793. },
  60794. /**
  60795. * Removes all drag and drop hooks for this element
  60796. */
  60797. unreg: function() {
  60798. Ext.EventManager.un(this.id, "mousedown", this.handleMouseDown, this);
  60799. this._domRef = null;
  60800. this.DDMInstance._remove(this);
  60801. },
  60802. destroy : function(){
  60803. this.unreg();
  60804. },
  60805. /**
  60806. * Returns true if this instance is locked, or the drag drop mgr is locked
  60807. * (meaning that all drag/drop is disabled on the page.)
  60808. * @return {Boolean} true if this obj or all drag/drop is locked, else
  60809. * false
  60810. */
  60811. isLocked: function() {
  60812. return (this.DDMInstance.isLocked() || this.locked);
  60813. },
  60814. /**
  60815. * Called when this object is clicked
  60816. * @param {Event} e
  60817. * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
  60818. * @private
  60819. */
  60820. handleMouseDown: function(e, oDD){
  60821. if (this.primaryButtonOnly && e.button != 0) {
  60822. return;
  60823. }
  60824. if (this.isLocked()) {
  60825. return;
  60826. }
  60827. this.DDMInstance.refreshCache(this.groups);
  60828. if (this.hasOuterHandles || this.DDMInstance.isOverTarget(e.getPoint(), this) ) {
  60829. if (this.clickValidator(e)) {
  60830. // set the initial element position
  60831. this.setStartPosition();
  60832. this.b4MouseDown(e);
  60833. this.onMouseDown(e);
  60834. this.DDMInstance.handleMouseDown(e, this);
  60835. this.DDMInstance.stopEvent(e);
  60836. }
  60837. }
  60838. },
  60839. clickValidator: function(e) {
  60840. var target = e.getTarget();
  60841. return ( this.isValidHandleChild(target) &&
  60842. (this.id == this.handleElId ||
  60843. this.DDMInstance.handleWasClicked(target, this.id)) );
  60844. },
  60845. /**
  60846. * Allows you to specify a tag name that should not start a drag operation
  60847. * when clicked. This is designed to facilitate embedding links within a
  60848. * drag handle that do something other than start the drag.
  60849. * @method addInvalidHandleType
  60850. * @param {String} tagName the type of element to exclude
  60851. */
  60852. addInvalidHandleType: function(tagName) {
  60853. var type = tagName.toUpperCase();
  60854. this.invalidHandleTypes[type] = type;
  60855. },
  60856. /**
  60857. * Lets you to specify an element id for a child of a drag handle
  60858. * that should not initiate a drag
  60859. * @method addInvalidHandleId
  60860. * @param {String} id the element id of the element you wish to ignore
  60861. */
  60862. addInvalidHandleId: function(id) {
  60863. if (typeof id !== "string") {
  60864. id = Ext.id(id);
  60865. }
  60866. this.invalidHandleIds[id] = id;
  60867. },
  60868. /**
  60869. * Lets you specify a css class of elements that will not initiate a drag
  60870. * @param {String} cssClass the class of the elements you wish to ignore
  60871. */
  60872. addInvalidHandleClass: function(cssClass) {
  60873. this.invalidHandleClasses.push(cssClass);
  60874. },
  60875. /**
  60876. * Unsets an excluded tag name set by addInvalidHandleType
  60877. * @param {String} tagName the type of element to unexclude
  60878. */
  60879. removeInvalidHandleType: function(tagName) {
  60880. var type = tagName.toUpperCase();
  60881. // this.invalidHandleTypes[type] = null;
  60882. delete this.invalidHandleTypes[type];
  60883. },
  60884. /**
  60885. * Unsets an invalid handle id
  60886. * @param {String} id the id of the element to re-enable
  60887. */
  60888. removeInvalidHandleId: function(id) {
  60889. if (typeof id !== "string") {
  60890. id = Ext.id(id);
  60891. }
  60892. delete this.invalidHandleIds[id];
  60893. },
  60894. /**
  60895. * Unsets an invalid css class
  60896. * @param {String} cssClass the class of the element(s) you wish to
  60897. * re-enable
  60898. */
  60899. removeInvalidHandleClass: function(cssClass) {
  60900. for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
  60901. if (this.invalidHandleClasses[i] == cssClass) {
  60902. delete this.invalidHandleClasses[i];
  60903. }
  60904. }
  60905. },
  60906. /**
  60907. * Checks the tag exclusion list to see if this click should be ignored
  60908. * @param {HTMLElement} node the HTMLElement to evaluate
  60909. * @return {Boolean} true if this is a valid tag type, false if not
  60910. */
  60911. isValidHandleChild: function(node) {
  60912. var valid = true,
  60913. nodeName,
  60914. i, len;
  60915. // var n = (node.nodeName == "#text") ? node.parentNode : node;
  60916. try {
  60917. nodeName = node.nodeName.toUpperCase();
  60918. } catch(e) {
  60919. nodeName = node.nodeName;
  60920. }
  60921. valid = valid && !this.invalidHandleTypes[nodeName];
  60922. valid = valid && !this.invalidHandleIds[node.id];
  60923. for (i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
  60924. valid = !Ext.fly(node).hasCls(this.invalidHandleClasses[i]);
  60925. }
  60926. return valid;
  60927. },
  60928. /**
  60929. * Creates the array of horizontal tick marks if an interval was specified
  60930. * in setXConstraint().
  60931. * @private
  60932. */
  60933. setXTicks: function(iStartX, iTickSize) {
  60934. this.xTicks = [];
  60935. this.xTickSize = iTickSize;
  60936. var tickMap = {},
  60937. i;
  60938. for (i = this.initPageX; i >= this.minX; i = i - iTickSize) {
  60939. if (!tickMap[i]) {
  60940. this.xTicks[this.xTicks.length] = i;
  60941. tickMap[i] = true;
  60942. }
  60943. }
  60944. for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
  60945. if (!tickMap[i]) {
  60946. this.xTicks[this.xTicks.length] = i;
  60947. tickMap[i] = true;
  60948. }
  60949. }
  60950. Ext.Array.sort(this.xTicks, this.DDMInstance.numericSort);
  60951. },
  60952. /**
  60953. * Creates the array of vertical tick marks if an interval was specified in
  60954. * setYConstraint().
  60955. * @private
  60956. */
  60957. setYTicks: function(iStartY, iTickSize) {
  60958. this.yTicks = [];
  60959. this.yTickSize = iTickSize;
  60960. var tickMap = {},
  60961. i;
  60962. for (i = this.initPageY; i >= this.minY; i = i - iTickSize) {
  60963. if (!tickMap[i]) {
  60964. this.yTicks[this.yTicks.length] = i;
  60965. tickMap[i] = true;
  60966. }
  60967. }
  60968. for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
  60969. if (!tickMap[i]) {
  60970. this.yTicks[this.yTicks.length] = i;
  60971. tickMap[i] = true;
  60972. }
  60973. }
  60974. Ext.Array.sort(this.yTicks, this.DDMInstance.numericSort);
  60975. },
  60976. /**
  60977. * By default, the element can be dragged any place on the screen. Use
  60978. * this method to limit the horizontal travel of the element. Pass in
  60979. * 0,0 for the parameters if you want to lock the drag to the y axis.
  60980. * @param {Number} iLeft the number of pixels the element can move to the left
  60981. * @param {Number} iRight the number of pixels the element can move to the
  60982. * right
  60983. * @param {Number} iTickSize (optional) parameter for specifying that the
  60984. * element should move iTickSize pixels at a time.
  60985. */
  60986. setXConstraint: function(iLeft, iRight, iTickSize) {
  60987. this.leftConstraint = iLeft;
  60988. this.rightConstraint = iRight;
  60989. this.minX = this.initPageX - iLeft;
  60990. this.maxX = this.initPageX + iRight;
  60991. if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
  60992. this.constrainX = true;
  60993. },
  60994. /**
  60995. * Clears any constraints applied to this instance. Also clears ticks
  60996. * since they can't exist independent of a constraint at this time.
  60997. */
  60998. clearConstraints: function() {
  60999. this.constrainX = false;
  61000. this.constrainY = false;
  61001. this.clearTicks();
  61002. },
  61003. /**
  61004. * Clears any tick interval defined for this instance
  61005. */
  61006. clearTicks: function() {
  61007. this.xTicks = null;
  61008. this.yTicks = null;
  61009. this.xTickSize = 0;
  61010. this.yTickSize = 0;
  61011. },
  61012. /**
  61013. * By default, the element can be dragged any place on the screen. Set
  61014. * this to limit the vertical travel of the element. Pass in 0,0 for the
  61015. * parameters if you want to lock the drag to the x axis.
  61016. * @param {Number} iUp the number of pixels the element can move up
  61017. * @param {Number} iDown the number of pixels the element can move down
  61018. * @param {Number} iTickSize (optional) parameter for specifying that the
  61019. * element should move iTickSize pixels at a time.
  61020. */
  61021. setYConstraint: function(iUp, iDown, iTickSize) {
  61022. this.topConstraint = iUp;
  61023. this.bottomConstraint = iDown;
  61024. this.minY = this.initPageY - iUp;
  61025. this.maxY = this.initPageY + iDown;
  61026. if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
  61027. this.constrainY = true;
  61028. },
  61029. /**
  61030. * Must be called if you manually reposition a dd element.
  61031. * @param {Boolean} maintainOffset
  61032. */
  61033. resetConstraints: function() {
  61034. // Maintain offsets if necessary
  61035. if (this.initPageX || this.initPageX === 0) {
  61036. // figure out how much this thing has moved
  61037. var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0,
  61038. dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
  61039. this.setInitPosition(dx, dy);
  61040. // This is the first time we have detected the element's position
  61041. } else {
  61042. this.setInitPosition();
  61043. }
  61044. if (this.constrainX) {
  61045. this.setXConstraint( this.leftConstraint,
  61046. this.rightConstraint,
  61047. this.xTickSize );
  61048. }
  61049. if (this.constrainY) {
  61050. this.setYConstraint( this.topConstraint,
  61051. this.bottomConstraint,
  61052. this.yTickSize );
  61053. }
  61054. },
  61055. /**
  61056. * Normally the drag element is moved pixel by pixel, but we can specify
  61057. * that it move a number of pixels at a time. This method resolves the
  61058. * location when we have it set up like this.
  61059. * @param {Number} val where we want to place the object
  61060. * @param {Number[]} tickArray sorted array of valid points
  61061. * @return {Number} the closest tick
  61062. * @private
  61063. */
  61064. getTick: function(val, tickArray) {
  61065. if (!tickArray) {
  61066. // If tick interval is not defined, it is effectively 1 pixel,
  61067. // so we return the value passed to us.
  61068. return val;
  61069. } else if (tickArray[0] >= val) {
  61070. // The value is lower than the first tick, so we return the first
  61071. // tick.
  61072. return tickArray[0];
  61073. } else {
  61074. var i, len, next, diff1, diff2;
  61075. for (i=0, len=tickArray.length; i<len; ++i) {
  61076. next = i + 1;
  61077. if (tickArray[next] && tickArray[next] >= val) {
  61078. diff1 = val - tickArray[i];
  61079. diff2 = tickArray[next] - val;
  61080. return (diff2 > diff1) ? tickArray[i] : tickArray[next];
  61081. }
  61082. }
  61083. // The value is larger than the last tick, so we return the last
  61084. // tick.
  61085. return tickArray[tickArray.length - 1];
  61086. }
  61087. },
  61088. /**
  61089. * toString method
  61090. * @return {String} string representation of the dd obj
  61091. */
  61092. toString: function() {
  61093. return ("DragDrop " + this.id);
  61094. }
  61095. });
  61096. /*
  61097. * This is a derivative of the similarly named class in the YUI Library.
  61098. * The original license:
  61099. * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
  61100. * Code licensed under the BSD License:
  61101. * http://developer.yahoo.net/yui/license.txt
  61102. */
  61103. /**
  61104. * A DragDrop implementation where the linked element follows the
  61105. * mouse cursor during a drag.
  61106. */
  61107. Ext.define('Ext.dd.DD', {
  61108. extend: 'Ext.dd.DragDrop',
  61109. requires: ['Ext.dd.DragDropManager'],
  61110. /**
  61111. * Creates new DD instance.
  61112. * @param {String} id the id of the linked element
  61113. * @param {String} sGroup the group of related DragDrop items
  61114. * @param {Object} config an object containing configurable attributes.
  61115. * Valid properties for DD: scroll
  61116. */
  61117. constructor: function(id, sGroup, config) {
  61118. if (id) {
  61119. this.init(id, sGroup, config);
  61120. }
  61121. },
  61122. /**
  61123. * @property {Boolean} scroll
  61124. * When set to true, the utility automatically tries to scroll the browser
  61125. * window when a drag and drop element is dragged near the viewport boundary.
  61126. */
  61127. scroll: true,
  61128. /**
  61129. * Sets the pointer offset to the distance between the linked element's top
  61130. * left corner and the location the element was clicked.
  61131. * @param {Number} iPageX the X coordinate of the click
  61132. * @param {Number} iPageY the Y coordinate of the click
  61133. */
  61134. autoOffset: function(iPageX, iPageY) {
  61135. var x = iPageX - this.startPageX,
  61136. y = iPageY - this.startPageY;
  61137. this.setDelta(x, y);
  61138. },
  61139. /**
  61140. * Sets the pointer offset. You can call this directly to force the
  61141. * offset to be in a particular location (e.g., pass in 0,0 to set it
  61142. * to the center of the object)
  61143. * @param {Number} iDeltaX the distance from the left
  61144. * @param {Number} iDeltaY the distance from the top
  61145. */
  61146. setDelta: function(iDeltaX, iDeltaY) {
  61147. this.deltaX = iDeltaX;
  61148. this.deltaY = iDeltaY;
  61149. },
  61150. /**
  61151. * Sets the drag element to the location of the mousedown or click event,
  61152. * maintaining the cursor location relative to the location on the element
  61153. * that was clicked. Override this if you want to place the element in a
  61154. * location other than where the cursor is.
  61155. * @param {Number} iPageX the X coordinate of the mousedown or drag event
  61156. * @param {Number} iPageY the Y coordinate of the mousedown or drag event
  61157. */
  61158. setDragElPos: function(iPageX, iPageY) {
  61159. // the first time we do this, we are going to check to make sure
  61160. // the element has css positioning
  61161. var el = this.getDragEl();
  61162. this.alignElWithMouse(el, iPageX, iPageY);
  61163. },
  61164. /**
  61165. * Sets the element to the location of the mousedown or click event,
  61166. * maintaining the cursor location relative to the location on the element
  61167. * that was clicked. Override this if you want to place the element in a
  61168. * location other than where the cursor is.
  61169. * @param {HTMLElement} el the element to move
  61170. * @param {Number} iPageX the X coordinate of the mousedown or drag event
  61171. * @param {Number} iPageY the Y coordinate of the mousedown or drag event
  61172. */
  61173. alignElWithMouse: function(el, iPageX, iPageY) {
  61174. var oCoord = this.getTargetCoord(iPageX, iPageY),
  61175. fly = el.dom ? el : Ext.fly(el, '_dd'),
  61176. elSize = fly.getSize(),
  61177. EL = Ext.Element,
  61178. vpSize,
  61179. aCoord,
  61180. newLeft,
  61181. newTop;
  61182. if (!this.deltaSetXY) {
  61183. vpSize = this.cachedViewportSize = { width: EL.getDocumentWidth(), height: EL.getDocumentHeight() };
  61184. aCoord = [
  61185. Math.max(0, Math.min(oCoord.x, vpSize.width - elSize.width)),
  61186. Math.max(0, Math.min(oCoord.y, vpSize.height - elSize.height))
  61187. ];
  61188. fly.setXY(aCoord);
  61189. newLeft = fly.getLocalX();
  61190. newTop = fly.getLocalY();
  61191. this.deltaSetXY = [newLeft - oCoord.x, newTop - oCoord.y];
  61192. } else {
  61193. vpSize = this.cachedViewportSize;
  61194. fly.setLeftTop(
  61195. Math.max(0, Math.min(oCoord.x + this.deltaSetXY[0], vpSize.width - elSize.width)),
  61196. Math.max(0, Math.min(oCoord.y + this.deltaSetXY[1], vpSize.height - elSize.height))
  61197. );
  61198. }
  61199. this.cachePosition(oCoord.x, oCoord.y);
  61200. this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
  61201. return oCoord;
  61202. },
  61203. /**
  61204. * Saves the most recent position so that we can reset the constraints and
  61205. * tick marks on-demand. We need to know this so that we can calculate the
  61206. * number of pixels the element is offset from its original position.
  61207. *
  61208. * @param {Number} [iPageX] the current x position (this just makes it so we
  61209. * don't have to look it up again)
  61210. * @param {Number} [iPageY] the current y position (this just makes it so we
  61211. * don't have to look it up again)
  61212. */
  61213. cachePosition: function(iPageX, iPageY) {
  61214. if (iPageX) {
  61215. this.lastPageX = iPageX;
  61216. this.lastPageY = iPageY;
  61217. } else {
  61218. var aCoord = Ext.Element.getXY(this.getEl());
  61219. this.lastPageX = aCoord[0];
  61220. this.lastPageY = aCoord[1];
  61221. }
  61222. },
  61223. /**
  61224. * Auto-scroll the window if the dragged object has been moved beyond the
  61225. * visible window boundary.
  61226. * @param {Number} x the drag element's x position
  61227. * @param {Number} y the drag element's y position
  61228. * @param {Number} h the height of the drag element
  61229. * @param {Number} w the width of the drag element
  61230. * @private
  61231. */
  61232. autoScroll: function(x, y, h, w) {
  61233. if (this.scroll) {
  61234. // The client height
  61235. var clientH = Ext.Element.getViewHeight(),
  61236. // The client width
  61237. clientW = Ext.Element.getViewWidth(),
  61238. // The amt scrolled down
  61239. st = this.DDMInstance.getScrollTop(),
  61240. // The amt scrolled right
  61241. sl = this.DDMInstance.getScrollLeft(),
  61242. // Location of the bottom of the element
  61243. bot = h + y,
  61244. // Location of the right of the element
  61245. right = w + x,
  61246. // The distance from the cursor to the bottom of the visible area,
  61247. // adjusted so that we don't scroll if the cursor is beyond the
  61248. // element drag constraints
  61249. toBot = (clientH + st - y - this.deltaY),
  61250. // The distance from the cursor to the right of the visible area
  61251. toRight = (clientW + sl - x - this.deltaX),
  61252. // How close to the edge the cursor must be before we scroll
  61253. // var thresh = (document.all) ? 100 : 40;
  61254. thresh = 40,
  61255. // How many pixels to scroll per autoscroll op. This helps to reduce
  61256. // clunky scrolling. IE is more sensitive about this ... it needs this
  61257. // value to be higher.
  61258. scrAmt = (document.all) ? 80 : 30;
  61259. // Scroll down if we are near the bottom of the visible page and the
  61260. // obj extends below the crease
  61261. if ( bot > clientH && toBot < thresh ) {
  61262. window.scrollTo(sl, st + scrAmt);
  61263. }
  61264. // Scroll up if the window is scrolled down and the top of the object
  61265. // goes above the top border
  61266. if ( y < st && st > 0 && y - st < thresh ) {
  61267. window.scrollTo(sl, st - scrAmt);
  61268. }
  61269. // Scroll right if the obj is beyond the right border and the cursor is
  61270. // near the border.
  61271. if ( right > clientW && toRight < thresh ) {
  61272. window.scrollTo(sl + scrAmt, st);
  61273. }
  61274. // Scroll left if the window has been scrolled to the right and the obj
  61275. // extends past the left border
  61276. if ( x < sl && sl > 0 && x - sl < thresh ) {
  61277. window.scrollTo(sl - scrAmt, st);
  61278. }
  61279. }
  61280. },
  61281. /**
  61282. * Finds the location the element should be placed if we want to move
  61283. * it to where the mouse location less the click offset would place us.
  61284. * @param {Number} iPageX the X coordinate of the click
  61285. * @param {Number} iPageY the Y coordinate of the click
  61286. * @return an object that contains the coordinates (Object.x and Object.y)
  61287. * @private
  61288. */
  61289. getTargetCoord: function(iPageX, iPageY) {
  61290. var x = iPageX - this.deltaX,
  61291. y = iPageY - this.deltaY;
  61292. if (this.constrainX) {
  61293. if (x < this.minX) {
  61294. x = this.minX;
  61295. }
  61296. if (x > this.maxX) {
  61297. x = this.maxX;
  61298. }
  61299. }
  61300. if (this.constrainY) {
  61301. if (y < this.minY) {
  61302. y = this.minY;
  61303. }
  61304. if (y > this.maxY) {
  61305. y = this.maxY;
  61306. }
  61307. }
  61308. x = this.getTick(x, this.xTicks);
  61309. y = this.getTick(y, this.yTicks);
  61310. return {x: x, y: y};
  61311. },
  61312. /**
  61313. * Sets up config options specific to this class. Overrides
  61314. * Ext.dd.DragDrop, but all versions of this method through the
  61315. * inheritance chain are called
  61316. */
  61317. applyConfig: function() {
  61318. this.callParent();
  61319. this.scroll = (this.config.scroll !== false);
  61320. },
  61321. /**
  61322. * Event that fires prior to the onMouseDown event. Overrides
  61323. * Ext.dd.DragDrop.
  61324. */
  61325. b4MouseDown: function(e) {
  61326. // this.resetConstraints();
  61327. this.autoOffset(e.getPageX(), e.getPageY());
  61328. },
  61329. /**
  61330. * Event that fires prior to the onDrag event. Overrides
  61331. * Ext.dd.DragDrop.
  61332. */
  61333. b4Drag: function(e) {
  61334. this.setDragElPos(e.getPageX(), e.getPageY());
  61335. },
  61336. toString: function() {
  61337. return ("DD " + this.id);
  61338. }
  61339. //////////////////////////////////////////////////////////////////////////
  61340. // Debugging ygDragDrop events that can be overridden
  61341. //////////////////////////////////////////////////////////////////////////
  61342. /*
  61343. startDrag: function(x, y) {
  61344. },
  61345. onDrag: function(e) {
  61346. },
  61347. onDragEnter: function(e, id) {
  61348. },
  61349. onDragOver: function(e, id) {
  61350. },
  61351. onDragOut: function(e, id) {
  61352. },
  61353. onDragDrop: function(e, id) {
  61354. },
  61355. endDrag: function(e) {
  61356. }
  61357. */
  61358. });
  61359. /*
  61360. * This is a derivative of the similarly named class in the YUI Library.
  61361. * The original license:
  61362. * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
  61363. * Code licensed under the BSD License:
  61364. * http://developer.yahoo.net/yui/license.txt
  61365. */
  61366. /**
  61367. * A DragDrop implementation that inserts an empty, bordered div into
  61368. * the document that follows the cursor during drag operations. At the time of
  61369. * the click, the frame div is resized to the dimensions of the linked html
  61370. * element, and moved to the exact location of the linked element.
  61371. *
  61372. * References to the "frame" element refer to the single proxy element that
  61373. * was created to be dragged in place of all DDProxy elements on the
  61374. * page.
  61375. */
  61376. Ext.define('Ext.dd.DDProxy', {
  61377. extend: 'Ext.dd.DD',
  61378. statics: {
  61379. /**
  61380. * The default drag frame div id
  61381. * @static
  61382. */
  61383. dragElId: "ygddfdiv"
  61384. },
  61385. /**
  61386. * Creates new DDProxy.
  61387. * @param {String} id the id of the linked html element
  61388. * @param {String} sGroup the group of related DragDrop objects
  61389. * @param {Object} config an object containing configurable attributes.
  61390. * Valid properties for DDProxy in addition to those in DragDrop:
  61391. *
  61392. * - resizeFrame
  61393. * - centerFrame
  61394. * - dragElId
  61395. */
  61396. constructor: function(id, sGroup, config) {
  61397. if (id) {
  61398. this.init(id, sGroup, config);
  61399. this.initFrame();
  61400. }
  61401. },
  61402. /**
  61403. * @property {Boolean} resizeFrame
  61404. * By default we resize the drag frame to be the same size as the element
  61405. * we want to drag (this is to get the frame effect). We can turn it off
  61406. * if we want a different behavior.
  61407. */
  61408. resizeFrame: true,
  61409. /**
  61410. * @property {Boolean} centerFrame
  61411. * By default the frame is positioned exactly where the drag element is, so
  61412. * we use the cursor offset provided by Ext.dd.DD. Another option that works only if
  61413. * you do not have constraints on the obj is to have the drag frame centered
  61414. * around the cursor. Set centerFrame to true for this effect.
  61415. */
  61416. centerFrame: false,
  61417. /**
  61418. * Creates the proxy element if it does not yet exist
  61419. */
  61420. createFrame: function() {
  61421. var self = this,
  61422. body = document.body,
  61423. div,
  61424. s;
  61425. if (!body || !body.firstChild) {
  61426. setTimeout( function() { self.createFrame(); }, 50 );
  61427. return;
  61428. }
  61429. div = this.getDragEl();
  61430. if (!div) {
  61431. div = document.createElement("div");
  61432. div.id = this.dragElId;
  61433. s = div.style;
  61434. s.position = "absolute";
  61435. s.visibility = "hidden";
  61436. s.cursor = "move";
  61437. s.border = "2px solid #aaa";
  61438. s.zIndex = 999;
  61439. // appendChild can blow up IE if invoked prior to the window load event
  61440. // while rendering a table. It is possible there are other scenarios
  61441. // that would cause this to happen as well.
  61442. body.insertBefore(div, body.firstChild);
  61443. }
  61444. },
  61445. /**
  61446. * Initialization for the drag frame element. Must be called in the
  61447. * constructor of all subclasses
  61448. */
  61449. initFrame: function() {
  61450. this.createFrame();
  61451. },
  61452. applyConfig: function() {
  61453. this.callParent();
  61454. this.resizeFrame = (this.config.resizeFrame !== false);
  61455. this.centerFrame = (this.config.centerFrame);
  61456. this.setDragElId(this.config.dragElId || Ext.dd.DDProxy.dragElId);
  61457. },
  61458. /**
  61459. * Resizes the drag frame to the dimensions of the clicked object, positions
  61460. * it over the object, and finally displays it
  61461. * @param {Number} iPageX X click position
  61462. * @param {Number} iPageY Y click position
  61463. * @private
  61464. */
  61465. showFrame: function(iPageX, iPageY) {
  61466. var el = this.getEl(),
  61467. dragEl = this.getDragEl(),
  61468. s = dragEl.style;
  61469. this._resizeProxy();
  61470. if (this.centerFrame) {
  61471. this.setDelta( Math.round(parseInt(s.width, 10)/2),
  61472. Math.round(parseInt(s.height, 10)/2) );
  61473. }
  61474. this.setDragElPos(iPageX, iPageY);
  61475. Ext.fly(dragEl).show();
  61476. },
  61477. /**
  61478. * The proxy is automatically resized to the dimensions of the linked
  61479. * element when a drag is initiated, unless resizeFrame is set to false
  61480. * @private
  61481. */
  61482. _resizeProxy: function() {
  61483. if (this.resizeFrame) {
  61484. var el = this.getEl();
  61485. Ext.fly(this.getDragEl()).setSize(el.offsetWidth, el.offsetHeight);
  61486. }
  61487. },
  61488. // overrides Ext.dd.DragDrop
  61489. b4MouseDown: function(e) {
  61490. var x = e.getPageX(),
  61491. y = e.getPageY();
  61492. this.autoOffset(x, y);
  61493. this.setDragElPos(x, y);
  61494. },
  61495. // overrides Ext.dd.DragDrop
  61496. b4StartDrag: function(x, y) {
  61497. // show the drag frame
  61498. this.showFrame(x, y);
  61499. },
  61500. // overrides Ext.dd.DragDrop
  61501. b4EndDrag: function(e) {
  61502. Ext.fly(this.getDragEl()).hide();
  61503. },
  61504. // overrides Ext.dd.DragDrop
  61505. // By default we try to move the element to the last location of the frame.
  61506. // This is so that the default behavior mirrors that of Ext.dd.DD.
  61507. endDrag: function(e) {
  61508. var lel = this.getEl(),
  61509. del = this.getDragEl();
  61510. // Show the drag frame briefly so we can get its position
  61511. del.style.visibility = "";
  61512. this.beforeMove();
  61513. // Hide the linked element before the move to get around a Safari
  61514. // rendering bug.
  61515. lel.style.visibility = "hidden";
  61516. Ext.dd.DDM.moveToEl(lel, del);
  61517. del.style.visibility = "hidden";
  61518. lel.style.visibility = "";
  61519. this.afterDrag();
  61520. },
  61521. beforeMove : function(){
  61522. },
  61523. afterDrag : function(){
  61524. },
  61525. toString: function() {
  61526. return ("DDProxy " + this.id);
  61527. }
  61528. });
  61529. /**
  61530. * A specialized floating Component that supports a drop status icon, {@link Ext.Layer} styles
  61531. * and auto-repair. This is the default drag proxy used by all Ext.dd components.
  61532. */
  61533. Ext.define('Ext.dd.StatusProxy', {
  61534. extend: 'Ext.Component',
  61535. animRepair: false,
  61536. childEls: [
  61537. 'ghost'
  61538. ],
  61539. renderTpl: [
  61540. '<div class="' + Ext.baseCSSPrefix + 'dd-drop-icon"></div>' +
  61541. '<div id="{id}-ghost" class="' + Ext.baseCSSPrefix + 'dd-drag-ghost"></div>'
  61542. ],
  61543. /**
  61544. * Creates new StatusProxy.
  61545. * @param {Object} [config] Config object.
  61546. */
  61547. constructor: function(config) {
  61548. var me = this;
  61549. config = config || {};
  61550. Ext.apply(me, {
  61551. hideMode: 'visibility',
  61552. hidden: true,
  61553. floating: true,
  61554. id: me.id || Ext.id(),
  61555. cls: Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed,
  61556. shadow: config.shadow || false,
  61557. renderTo: Ext.getDetachedBody()
  61558. });
  61559. me.callParent(arguments);
  61560. this.dropStatus = this.dropNotAllowed;
  61561. },
  61562. /**
  61563. * @cfg {String} dropAllowed
  61564. * The CSS class to apply to the status element when drop is allowed.
  61565. */
  61566. dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
  61567. /**
  61568. * @cfg {String} dropNotAllowed
  61569. * The CSS class to apply to the status element when drop is not allowed.
  61570. */
  61571. dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
  61572. /**
  61573. * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
  61574. * over the current target element.
  61575. * @param {String} cssClass The css class for the new drop status indicator image
  61576. */
  61577. setStatus : function(cssClass){
  61578. cssClass = cssClass || this.dropNotAllowed;
  61579. if (this.dropStatus != cssClass) {
  61580. this.el.replaceCls(this.dropStatus, cssClass);
  61581. this.dropStatus = cssClass;
  61582. }
  61583. },
  61584. /**
  61585. * Resets the status indicator to the default dropNotAllowed value
  61586. * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
  61587. */
  61588. reset : function(clearGhost){
  61589. var me = this,
  61590. clsPrefix = Ext.baseCSSPrefix + 'dd-drag-proxy ';
  61591. me.el.replaceCls(clsPrefix + me.dropAllowed, clsPrefix + me.dropNotAllowed);
  61592. me.dropStatus = me.dropNotAllowed;
  61593. if (clearGhost) {
  61594. me.ghost.update('');
  61595. }
  61596. },
  61597. /**
  61598. * Updates the contents of the ghost element
  61599. * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
  61600. * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
  61601. */
  61602. update : function(html){
  61603. if (typeof html == "string") {
  61604. this.ghost.update(html);
  61605. } else {
  61606. this.ghost.update("");
  61607. html.style.margin = "0";
  61608. this.ghost.dom.appendChild(html);
  61609. }
  61610. var el = this.ghost.dom.firstChild;
  61611. if (el) {
  61612. Ext.fly(el).setStyle('float', 'none');
  61613. }
  61614. },
  61615. /**
  61616. * Returns the ghost element
  61617. * @return {Ext.Element} el
  61618. */
  61619. getGhost : function(){
  61620. return this.ghost;
  61621. },
  61622. /**
  61623. * Hides the proxy
  61624. * @param {Boolean} clear True to reset the status and clear the ghost contents,
  61625. * false to preserve them
  61626. */
  61627. hide : function(clear) {
  61628. this.callParent();
  61629. if (clear) {
  61630. this.reset(true);
  61631. }
  61632. },
  61633. /**
  61634. * Stops the repair animation if it's currently running
  61635. */
  61636. stop : function(){
  61637. if (this.anim && this.anim.isAnimated && this.anim.isAnimated()) {
  61638. this.anim.stop();
  61639. }
  61640. },
  61641. /**
  61642. * Force the Layer to sync its shadow and shim positions to the element
  61643. */
  61644. sync : function(){
  61645. this.el.sync();
  61646. },
  61647. /**
  61648. * Causes the proxy to return to its position of origin via an animation.
  61649. * Should be called after an invalid drop operation by the item being dragged.
  61650. * @param {Number[]} xy The XY position of the element ([x, y])
  61651. * @param {Function} callback The function to call after the repair is complete.
  61652. * @param {Object} scope The scope (`this` reference) in which the callback function is executed.
  61653. * Defaults to the browser window.
  61654. */
  61655. repair : function(xy, callback, scope) {
  61656. var me = this;
  61657. me.callback = callback;
  61658. me.scope = scope;
  61659. if (xy && me.animRepair !== false) {
  61660. me.el.addCls(Ext.baseCSSPrefix + 'dd-drag-repair');
  61661. me.el.hideUnders(true);
  61662. me.anim = me.el.animate({
  61663. duration: me.repairDuration || 500,
  61664. easing: 'ease-out',
  61665. to: {
  61666. x: xy[0],
  61667. y: xy[1]
  61668. },
  61669. stopAnimation: true,
  61670. callback: me.afterRepair,
  61671. scope: me
  61672. });
  61673. } else {
  61674. me.afterRepair();
  61675. }
  61676. },
  61677. // private
  61678. afterRepair : function() {
  61679. var me = this;
  61680. me.hide(true);
  61681. me.el.removeCls(Ext.baseCSSPrefix + 'dd-drag-repair');
  61682. if (typeof me.callback == "function") {
  61683. me.callback.call(me.scope || me);
  61684. }
  61685. delete me.callback;
  61686. delete me.scope;
  61687. }
  61688. });
  61689. /**
  61690. * A simple class that provides the basic implementation needed to make any element draggable.
  61691. */
  61692. Ext.define('Ext.dd.DragSource', {
  61693. extend: 'Ext.dd.DDProxy',
  61694. requires: [
  61695. 'Ext.dd.StatusProxy',
  61696. 'Ext.dd.DragDropManager'
  61697. ],
  61698. /**
  61699. * @cfg {String} ddGroup
  61700. * A named drag drop group to which this object belongs. If a group is specified, then this object will only
  61701. * interact with other drag drop objects in the same group.
  61702. */
  61703. /**
  61704. * @cfg {String} dropAllowed
  61705. * The CSS class returned to the drag source when drop is allowed.
  61706. */
  61707. dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
  61708. /**
  61709. * @cfg {String} dropNotAllowed
  61710. * The CSS class returned to the drag source when drop is not allowed.
  61711. */
  61712. dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
  61713. /**
  61714. * @cfg {Boolean} animRepair
  61715. * If true, animates the proxy element back to the position of the handle element used to trigger the drag.
  61716. */
  61717. animRepair: true,
  61718. /**
  61719. * @cfg {String} repairHighlightColor
  61720. * The color to use when visually highlighting the drag source in the afterRepair
  61721. * method after a failed drop (defaults to light blue). The color must be a 6 digit hex value, without
  61722. * a preceding '#'.
  61723. */
  61724. repairHighlightColor: 'c3daf9',
  61725. /**
  61726. * Creates new drag-source.
  61727. * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
  61728. * @param {Object} config (optional) Config object.
  61729. */
  61730. constructor: function(el, config) {
  61731. this.el = Ext.get(el);
  61732. if(!this.dragData){
  61733. this.dragData = {};
  61734. }
  61735. Ext.apply(this, config);
  61736. if(!this.proxy){
  61737. this.proxy = new Ext.dd.StatusProxy({
  61738. id: this.el.id + '-drag-status-proxy',
  61739. animRepair: this.animRepair
  61740. });
  61741. }
  61742. this.callParent([this.el.dom, this.ddGroup || this.group,
  61743. {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true}]);
  61744. this.dragging = false;
  61745. },
  61746. /**
  61747. * Returns the data object associated with this drag source
  61748. * @return {Object} data An object containing arbitrary data
  61749. */
  61750. getDragData : function(e){
  61751. return this.dragData;
  61752. },
  61753. // private
  61754. onDragEnter : function(e, id){
  61755. var target = Ext.dd.DragDropManager.getDDById(id),
  61756. status;
  61757. this.cachedTarget = target;
  61758. if (this.beforeDragEnter(target, e, id) !== false) {
  61759. if (target.isNotifyTarget) {
  61760. status = target.notifyEnter(this, e, this.dragData);
  61761. this.proxy.setStatus(status);
  61762. } else {
  61763. this.proxy.setStatus(this.dropAllowed);
  61764. }
  61765. if (this.afterDragEnter) {
  61766. /**
  61767. * An empty function by default, but provided so that you can perform a custom action
  61768. * when the dragged item enters the drop target by providing an implementation.
  61769. * @param {Ext.dd.DragDrop} target The drop target
  61770. * @param {Event} e The event object
  61771. * @param {String} id The id of the dragged element
  61772. * @method afterDragEnter
  61773. */
  61774. this.afterDragEnter(target, e, id);
  61775. }
  61776. }
  61777. },
  61778. /**
  61779. * An empty function by default, but provided so that you can perform a custom action
  61780. * before the dragged item enters the drop target and optionally cancel the onDragEnter.
  61781. * @param {Ext.dd.DragDrop} target The drop target
  61782. * @param {Event} e The event object
  61783. * @param {String} id The id of the dragged element
  61784. * @return {Boolean} isValid True if the drag event is valid, else false to cancel
  61785. * @template
  61786. */
  61787. beforeDragEnter: function(target, e, id) {
  61788. return true;
  61789. },
  61790. // private
  61791. onDragOver: function(e, id) {
  61792. var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id),
  61793. status;
  61794. if (this.beforeDragOver(target, e, id) !== false) {
  61795. if(target.isNotifyTarget){
  61796. status = target.notifyOver(this, e, this.dragData);
  61797. this.proxy.setStatus(status);
  61798. }
  61799. if (this.afterDragOver) {
  61800. /**
  61801. * An empty function by default, but provided so that you can perform a custom action
  61802. * while the dragged item is over the drop target by providing an implementation.
  61803. * @param {Ext.dd.DragDrop} target The drop target
  61804. * @param {Event} e The event object
  61805. * @param {String} id The id of the dragged element
  61806. * @method afterDragOver
  61807. */
  61808. this.afterDragOver(target, e, id);
  61809. }
  61810. }
  61811. },
  61812. /**
  61813. * An empty function by default, but provided so that you can perform a custom action
  61814. * while the dragged item is over the drop target and optionally cancel the onDragOver.
  61815. * @param {Ext.dd.DragDrop} target The drop target
  61816. * @param {Event} e The event object
  61817. * @param {String} id The id of the dragged element
  61818. * @return {Boolean} isValid True if the drag event is valid, else false to cancel
  61819. * @template
  61820. */
  61821. beforeDragOver: function(target, e, id) {
  61822. return true;
  61823. },
  61824. // private
  61825. onDragOut: function(e, id) {
  61826. var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
  61827. if (this.beforeDragOut(target, e, id) !== false) {
  61828. if (target.isNotifyTarget) {
  61829. target.notifyOut(this, e, this.dragData);
  61830. }
  61831. this.proxy.reset();
  61832. if (this.afterDragOut) {
  61833. /**
  61834. * An empty function by default, but provided so that you can perform a custom action
  61835. * after the dragged item is dragged out of the target without dropping.
  61836. * @param {Ext.dd.DragDrop} target The drop target
  61837. * @param {Event} e The event object
  61838. * @param {String} id The id of the dragged element
  61839. * @method afterDragOut
  61840. */
  61841. this.afterDragOut(target, e, id);
  61842. }
  61843. }
  61844. this.cachedTarget = null;
  61845. },
  61846. /**
  61847. * An empty function by default, but provided so that you can perform a custom action before the dragged
  61848. * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
  61849. * @param {Ext.dd.DragDrop} target The drop target
  61850. * @param {Event} e The event object
  61851. * @param {String} id The id of the dragged element
  61852. * @return {Boolean} isValid True if the drag event is valid, else false to cancel
  61853. * @template
  61854. */
  61855. beforeDragOut: function(target, e, id){
  61856. return true;
  61857. },
  61858. // private
  61859. onDragDrop: function(e, id){
  61860. var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
  61861. if (this.beforeDragDrop(target, e, id) !== false) {
  61862. if (target.isNotifyTarget) {
  61863. if (target.notifyDrop(this, e, this.dragData) !== false) { // valid drop?
  61864. this.onValidDrop(target, e, id);
  61865. } else {
  61866. this.onInvalidDrop(target, e, id);
  61867. }
  61868. } else {
  61869. this.onValidDrop(target, e, id);
  61870. }
  61871. if (this.afterDragDrop) {
  61872. /**
  61873. * An empty function by default, but provided so that you can perform a custom action
  61874. * after a valid drag drop has occurred by providing an implementation.
  61875. * @param {Ext.dd.DragDrop} target The drop target
  61876. * @param {Event} e The event object
  61877. * @param {String} id The id of the dropped element
  61878. * @method afterDragDrop
  61879. */
  61880. this.afterDragDrop(target, e, id);
  61881. }
  61882. }
  61883. delete this.cachedTarget;
  61884. },
  61885. /**
  61886. * An empty function by default, but provided so that you can perform a custom action before the dragged
  61887. * item is dropped onto the target and optionally cancel the onDragDrop.
  61888. * @param {Ext.dd.DragDrop} target The drop target
  61889. * @param {Event} e The event object
  61890. * @param {String} id The id of the dragged element
  61891. * @return {Boolean} isValid True if the drag drop event is valid, else false to cancel
  61892. * @template
  61893. */
  61894. beforeDragDrop: function(target, e, id){
  61895. return true;
  61896. },
  61897. // private
  61898. onValidDrop: function(target, e, id){
  61899. this.hideProxy();
  61900. if(this.afterValidDrop){
  61901. /**
  61902. * An empty function by default, but provided so that you can perform a custom action
  61903. * after a valid drop has occurred by providing an implementation.
  61904. * @param {Object} target The target DD
  61905. * @param {Event} e The event object
  61906. * @param {String} id The id of the dropped element
  61907. * @method afterValidDrop
  61908. */
  61909. this.afterValidDrop(target, e, id);
  61910. }
  61911. },
  61912. // private
  61913. getRepairXY: function(e, data){
  61914. return this.el.getXY();
  61915. },
  61916. // private
  61917. onInvalidDrop: function(target, e, id) {
  61918. // This method may be called by the DragDropManager.
  61919. // To preserve backwards compat, it only passes the event object
  61920. // Here we correct the arguments.
  61921. if (!e) {
  61922. e = target;
  61923. target = null;
  61924. id = e.getTarget().id;
  61925. }
  61926. this.beforeInvalidDrop(target, e, id);
  61927. if (this.cachedTarget) {
  61928. if(this.cachedTarget.isNotifyTarget){
  61929. this.cachedTarget.notifyOut(this, e, this.dragData);
  61930. }
  61931. this.cacheTarget = null;
  61932. }
  61933. this.proxy.repair(this.getRepairXY(e, this.dragData), this.afterRepair, this);
  61934. if (this.afterInvalidDrop) {
  61935. /**
  61936. * An empty function by default, but provided so that you can perform a custom action
  61937. * after an invalid drop has occurred by providing an implementation.
  61938. * @param {Event} e The event object
  61939. * @param {String} id The id of the dropped element
  61940. * @method afterInvalidDrop
  61941. */
  61942. this.afterInvalidDrop(e, id);
  61943. }
  61944. },
  61945. // private
  61946. afterRepair: function() {
  61947. var me = this;
  61948. if (Ext.enableFx) {
  61949. me.el.highlight(me.repairHighlightColor);
  61950. }
  61951. me.dragging = false;
  61952. },
  61953. /**
  61954. * An empty function by default, but provided so that you can perform a custom action after an invalid
  61955. * drop has occurred.
  61956. * @param {Ext.dd.DragDrop} target The drop target
  61957. * @param {Event} e The event object
  61958. * @param {String} id The id of the dragged element
  61959. * @return {Boolean} isValid True if the invalid drop should proceed, else false to cancel
  61960. * @template
  61961. */
  61962. beforeInvalidDrop: function(target, e, id) {
  61963. return true;
  61964. },
  61965. // private
  61966. handleMouseDown: function(e) {
  61967. if (this.dragging) {
  61968. return;
  61969. }
  61970. var data = this.getDragData(e);
  61971. if (data && this.onBeforeDrag(data, e) !== false) {
  61972. this.dragData = data;
  61973. this.proxy.stop();
  61974. this.callParent(arguments);
  61975. }
  61976. },
  61977. /**
  61978. * An empty function by default, but provided so that you can perform a custom action before the initial
  61979. * drag event begins and optionally cancel it.
  61980. * @param {Object} data An object containing arbitrary data to be shared with drop targets
  61981. * @param {Event} e The event object
  61982. * @return {Boolean} isValid True if the drag event is valid, else false to cancel
  61983. * @template
  61984. */
  61985. onBeforeDrag: function(data, e){
  61986. return true;
  61987. },
  61988. /**
  61989. * An empty function by default, but provided so that you can perform a custom action once the initial
  61990. * drag event has begun. The drag cannot be canceled from this function.
  61991. * @param {Number} x The x position of the click on the dragged object
  61992. * @param {Number} y The y position of the click on the dragged object
  61993. * @method
  61994. * @template
  61995. */
  61996. onStartDrag: Ext.emptyFn,
  61997. alignElWithMouse: function() {
  61998. this.proxy.ensureAttachedToBody(true);
  61999. return this.callParent(arguments);
  62000. },
  62001. // private override
  62002. startDrag: function(x, y) {
  62003. this.proxy.reset();
  62004. this.proxy.hidden = false;
  62005. this.dragging = true;
  62006. this.proxy.update("");
  62007. this.onInitDrag(x, y);
  62008. this.proxy.show();
  62009. },
  62010. // private
  62011. onInitDrag: function(x, y) {
  62012. var clone = this.el.dom.cloneNode(true);
  62013. clone.id = Ext.id(); // prevent duplicate ids
  62014. this.proxy.update(clone);
  62015. this.onStartDrag(x, y);
  62016. return true;
  62017. },
  62018. /**
  62019. * Returns the drag source's underlying {@link Ext.dd.StatusProxy}
  62020. * @return {Ext.dd.StatusProxy} proxy The StatusProxy
  62021. */
  62022. getProxy: function() {
  62023. return this.proxy;
  62024. },
  62025. /**
  62026. * Hides the drag source's {@link Ext.dd.StatusProxy}
  62027. */
  62028. hideProxy: function() {
  62029. this.proxy.hide();
  62030. this.proxy.reset(true);
  62031. this.dragging = false;
  62032. },
  62033. // private
  62034. triggerCacheRefresh: function() {
  62035. Ext.dd.DDM.refreshCache(this.groups);
  62036. },
  62037. // private - override to prevent hiding
  62038. b4EndDrag: function(e) {
  62039. },
  62040. // private - override to prevent moving
  62041. endDrag : function(e){
  62042. this.onEndDrag(this.dragData, e);
  62043. },
  62044. // private
  62045. onEndDrag : function(data, e){
  62046. },
  62047. // private - pin to cursor
  62048. autoOffset : function(x, y) {
  62049. this.setDelta(-12, -20);
  62050. },
  62051. destroy: function(){
  62052. this.callParent();
  62053. Ext.destroy(this.proxy);
  62054. }
  62055. });
  62056. /**
  62057. * A custom drag proxy implementation specific to {@link Ext.panel.Panel}s. This class
  62058. * is primarily used internally for the Panel's drag drop implementation, and
  62059. * should never need to be created directly.
  62060. * @private
  62061. */
  62062. Ext.define('Ext.panel.Proxy', {
  62063. alternateClassName: 'Ext.dd.PanelProxy',
  62064. /**
  62065. * @cfg {Boolean} [moveOnDrag=true]
  62066. * True to move the panel to the dragged position when dropped
  62067. */
  62068. moveOnDrag: true,
  62069. /**
  62070. * Creates new panel proxy.
  62071. * @param {Ext.panel.Panel} panel The {@link Ext.panel.Panel} to proxy for
  62072. * @param {Object} [config] Config object
  62073. */
  62074. constructor: function(panel, config){
  62075. var me = this;
  62076. /**
  62077. * @property panel
  62078. * @type Ext.panel.Panel
  62079. */
  62080. me.panel = panel;
  62081. me.id = me.panel.id +'-ddproxy';
  62082. Ext.apply(me, config);
  62083. },
  62084. /**
  62085. * @cfg {Boolean} insertProxy
  62086. * True to insert a placeholder proxy element while dragging the panel, false to drag with no proxy.
  62087. * Most Panels are not absolute positioned and therefore we need to reserve this space.
  62088. */
  62089. insertProxy: true,
  62090. // private overrides
  62091. setStatus: Ext.emptyFn,
  62092. reset: Ext.emptyFn,
  62093. update: Ext.emptyFn,
  62094. stop: Ext.emptyFn,
  62095. sync: Ext.emptyFn,
  62096. /**
  62097. * Gets the proxy's element
  62098. * @return {Ext.Element} The proxy's element
  62099. */
  62100. getEl: function(){
  62101. return this.ghost.el;
  62102. },
  62103. /**
  62104. * Gets the proxy's ghost Panel
  62105. * @return {Ext.panel.Panel} The proxy's ghost Panel
  62106. */
  62107. getGhost: function(){
  62108. return this.ghost;
  62109. },
  62110. /**
  62111. * Gets the proxy element. This is the element that represents where the
  62112. * Panel was before we started the drag operation.
  62113. * @return {Ext.Element} The proxy's element
  62114. */
  62115. getProxy: function(){
  62116. return this.proxy;
  62117. },
  62118. /**
  62119. * Hides the proxy
  62120. */
  62121. hide : function(){
  62122. var me = this;
  62123. if (me.ghost) {
  62124. if (me.proxy) {
  62125. me.proxy.remove();
  62126. delete me.proxy;
  62127. }
  62128. // Unghost the Panel, do not move the Panel to where the ghost was
  62129. me.panel.unghost(null, me.moveOnDrag);
  62130. delete me.ghost;
  62131. }
  62132. },
  62133. /**
  62134. * Shows the proxy
  62135. */
  62136. show: function(){
  62137. var me = this,
  62138. panelSize;
  62139. if (!me.ghost) {
  62140. panelSize = me.panel.getSize();
  62141. me.panel.el.setVisibilityMode(Ext.Element.DISPLAY);
  62142. me.ghost = me.panel.ghost();
  62143. if (me.insertProxy) {
  62144. // bc Panels aren't absolute positioned we need to take up the space
  62145. // of where the panel previously was
  62146. me.proxy = me.panel.el.insertSibling({cls: Ext.baseCSSPrefix + 'panel-dd-spacer'});
  62147. me.proxy.setSize(panelSize);
  62148. }
  62149. }
  62150. },
  62151. // private
  62152. repair: function(xy, callback, scope) {
  62153. this.hide();
  62154. Ext.callback(callback, scope || this);
  62155. },
  62156. /**
  62157. * Moves the proxy to a different position in the DOM. This is typically
  62158. * called while dragging the Panel to keep the proxy sync'd to the Panel's
  62159. * location.
  62160. * @param {HTMLElement} parentNode The proxy's parent DOM node
  62161. * @param {HTMLElement} [before] The sibling node before which the
  62162. * proxy should be inserted. Defaults to the parent's last child if not
  62163. * specified.
  62164. */
  62165. moveProxy : function(parentNode, before){
  62166. if (this.proxy) {
  62167. parentNode.insertBefore(this.proxy.dom, before);
  62168. }
  62169. }
  62170. });
  62171. /**
  62172. * DD implementation for Panels.
  62173. * @private
  62174. */
  62175. Ext.define('Ext.panel.DD', {
  62176. extend: 'Ext.dd.DragSource',
  62177. requires: ['Ext.panel.Proxy'],
  62178. constructor : function(panel, cfg){
  62179. var me = this;
  62180. me.panel = panel;
  62181. me.dragData = {panel: panel};
  62182. me.panelProxy = new Ext.panel.Proxy(panel, cfg);
  62183. me.proxy = me.panelProxy.proxy;
  62184. me.callParent([panel.el, cfg]);
  62185. me.setupEl(panel);
  62186. },
  62187. setupEl: function(panel){
  62188. var me = this,
  62189. header = panel.header,
  62190. el = panel.body;
  62191. if (header) {
  62192. me.setHandleElId(header.id);
  62193. el = header.el;
  62194. }
  62195. if (el) {
  62196. el.setStyle('cursor', 'move');
  62197. me.scroll = false;
  62198. } else {
  62199. // boxready fires after first layout, so we'll definitely be rendered
  62200. panel.on('boxready', me.setupEl, me, {single: true});
  62201. }
  62202. },
  62203. showFrame: Ext.emptyFn,
  62204. startDrag: Ext.emptyFn,
  62205. b4StartDrag: function(x, y) {
  62206. this.panelProxy.show();
  62207. },
  62208. b4MouseDown: function(e) {
  62209. var x = e.getPageX(),
  62210. y = e.getPageY();
  62211. this.autoOffset(x, y);
  62212. },
  62213. onInitDrag : function(x, y){
  62214. this.onStartDrag(x, y);
  62215. return true;
  62216. },
  62217. createFrame : Ext.emptyFn,
  62218. getDragEl : function(e){
  62219. return this.panelProxy.ghost.el.dom;
  62220. },
  62221. endDrag : function(e){
  62222. this.panelProxy.hide();
  62223. this.panel.saveState();
  62224. },
  62225. autoOffset : function(x, y) {
  62226. x -= this.startPageX;
  62227. y -= this.startPageY;
  62228. this.setDelta(x, y);
  62229. },
  62230. // Override this, we don't want to repair on an "invalid" drop, the panel
  62231. // should main it's position
  62232. onInvalidDrop: function(target, e, id) {
  62233. var me = this;
  62234. me.beforeInvalidDrop(target, e, id);
  62235. if (me.cachedTarget) {
  62236. if(me.cachedTarget.isNotifyTarget){
  62237. me.cachedTarget.notifyOut(me, e, me.dragData);
  62238. }
  62239. me.cacheTarget = null;
  62240. }
  62241. if (me.afterInvalidDrop) {
  62242. /**
  62243. * An empty function by default, but provided so that you can perform a custom action
  62244. * after an invalid drop has occurred by providing an implementation.
  62245. * @param {Event} e The event object
  62246. * @param {String} id The id of the dropped element
  62247. * @method afterInvalidDrop
  62248. */
  62249. me.afterInvalidDrop(e, id);
  62250. }
  62251. }
  62252. });
  62253. /**
  62254. * @class Ext.util.Memento
  62255. * This class manages a set of captured properties from an object. These captured properties
  62256. * can later be restored to an object.
  62257. */
  62258. Ext.define('Ext.util.Memento', (function () {
  62259. function captureOne (src, target, prop, prefix) {
  62260. src[prefix ? prefix + prop : prop] = target[prop];
  62261. }
  62262. function removeOne (src, target, prop) {
  62263. delete src[prop];
  62264. }
  62265. function restoreOne (src, target, prop, prefix) {
  62266. var name = prefix ? prefix + prop : prop,
  62267. value = src[name];
  62268. if (value || src.hasOwnProperty(name)) {
  62269. restoreValue(target, prop, value);
  62270. }
  62271. }
  62272. function restoreValue (target, prop, value) {
  62273. if (Ext.isDefined(value)) {
  62274. target[prop] = value;
  62275. } else {
  62276. delete target[prop];
  62277. }
  62278. }
  62279. function doMany (doOne, src, target, props, prefix) {
  62280. if (src) {
  62281. if (Ext.isArray(props)) {
  62282. var p, pLen = props.length;
  62283. for (p = 0; p < pLen; p++) {
  62284. doOne(src, target, props[p], prefix);
  62285. }
  62286. } else {
  62287. doOne(src, target, props, prefix);
  62288. }
  62289. }
  62290. }
  62291. return {
  62292. /**
  62293. * @property data
  62294. * The collection of captured properties.
  62295. * @private
  62296. */
  62297. data: null,
  62298. /**
  62299. * @property target
  62300. * The default target object for capture/restore (passed to the constructor).
  62301. */
  62302. target: null,
  62303. /**
  62304. * Creates a new memento and optionally captures properties from the target object.
  62305. * @param {Object} target The target from which to capture properties. If specified in the
  62306. * constructor, this target becomes the default target for all other operations.
  62307. * @param {String/String[]} props The property or array of properties to capture.
  62308. */
  62309. constructor: function (target, props) {
  62310. if (target) {
  62311. this.target = target;
  62312. if (props) {
  62313. this.capture(props);
  62314. }
  62315. }
  62316. },
  62317. /**
  62318. * Captures the specified properties from the target object in this memento.
  62319. * @param {String/String[]} props The property or array of properties to capture.
  62320. * @param {Object} target The object from which to capture properties.
  62321. */
  62322. capture: function (props, target, prefix) {
  62323. var me = this;
  62324. doMany(captureOne, me.data || (me.data = {}), target || me.target, props, prefix);
  62325. },
  62326. /**
  62327. * Removes the specified properties from this memento. These properties will not be
  62328. * restored later without re-capturing their values.
  62329. * @param {String/String[]} props The property or array of properties to remove.
  62330. */
  62331. remove: function (props) {
  62332. doMany(removeOne, this.data, null, props);
  62333. },
  62334. /**
  62335. * Restores the specified properties from this memento to the target object.
  62336. * @param {String/String[]} props The property or array of properties to restore.
  62337. * @param {Boolean} clear True to remove the restored properties from this memento or
  62338. * false to keep them (default is true).
  62339. * @param {Object} target The object to which to restore properties.
  62340. */
  62341. restore: function (props, clear, target, prefix) {
  62342. doMany(restoreOne, this.data, target || this.target, props, prefix);
  62343. if (clear !== false) {
  62344. this.remove(props);
  62345. }
  62346. },
  62347. /**
  62348. * Restores all captured properties in this memento to the target object.
  62349. * @param {Boolean} clear True to remove the restored properties from this memento or
  62350. * false to keep them (default is true).
  62351. * @param {Object} target The object to which to restore properties.
  62352. */
  62353. restoreAll: function (clear, target) {
  62354. var me = this,
  62355. t = target || this.target,
  62356. data = me.data,
  62357. prop;
  62358. for (prop in data) {
  62359. if (data.hasOwnProperty(prop)) {
  62360. restoreValue(t, prop, data[prop]);
  62361. }
  62362. }
  62363. if (clear !== false) {
  62364. delete me.data;
  62365. }
  62366. }
  62367. };
  62368. }()));
  62369. /**
  62370. * Panel is a container that has specific functionality and structural components that make it the perfect building
  62371. * block for application-oriented user interfaces.
  62372. *
  62373. * Panels are, by virtue of their inheritance from {@link Ext.container.Container}, capable of being configured with a
  62374. * {@link Ext.container.Container#layout layout}, and containing child Components.
  62375. *
  62376. * When either specifying child {@link #cfg-items} of a Panel, or dynamically {@link Ext.container.Container#method-add adding}
  62377. * Components to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether those
  62378. * child elements need to be sized using one of Ext's built-in `{@link Ext.container.Container#layout layout}`
  62379. * schemes. By default, Panels use the {@link Ext.layout.container.Auto Auto} scheme. This simply renders child
  62380. * components, appending them one after the other inside the Container, and **does not apply any sizing** at all.
  62381. *
  62382. * {@img Ext.panel.Panel/panel.png Panel components}
  62383. *
  62384. * A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate {@link
  62385. * Ext.panel.Header header}, {@link #fbar footer} and body sections.
  62386. *
  62387. * Panel also provides built-in {@link #collapsible collapsible, expandable} and {@link #closable} behavior. Panels can
  62388. * be easily dropped into any {@link Ext.container.Container Container} or layout, and the layout and rendering pipeline
  62389. * is {@link Ext.container.Container#method-add completely managed by the framework}.
  62390. *
  62391. * **Note:** By default, the `{@link #closable close}` header tool _destroys_ the Panel resulting in removal of the
  62392. * Panel and the destruction of any descendant Components. This makes the Panel object, and all its descendants
  62393. * **unusable**. To enable the close tool to simply _hide_ a Panel for later re-use, configure the Panel with
  62394. * `{@link #closeAction closeAction}: 'hide'`.
  62395. *
  62396. * Usually, Panels are used as constituents within an application, in which case, they would be used as child items of
  62397. * Containers, and would themselves use Ext.Components as child {@link #cfg-items}. However to illustrate simply rendering a
  62398. * Panel into the document, here's how to do it:
  62399. *
  62400. * @example
  62401. * Ext.create('Ext.panel.Panel', {
  62402. * title: 'Hello',
  62403. * width: 200,
  62404. * html: '<p>World!</p>',
  62405. * renderTo: Ext.getBody()
  62406. * });
  62407. *
  62408. * A more realistic scenario is a Panel created to house input fields which will not be rendered, but used as a
  62409. * constituent part of a Container:
  62410. *
  62411. * @example
  62412. * var filterPanel = Ext.create('Ext.panel.Panel', {
  62413. * bodyPadding: 5, // Don't want content to crunch against the borders
  62414. * width: 300,
  62415. * title: 'Filters',
  62416. * items: [{
  62417. * xtype: 'datefield',
  62418. * fieldLabel: 'Start date'
  62419. * }, {
  62420. * xtype: 'datefield',
  62421. * fieldLabel: 'End date'
  62422. * }],
  62423. * renderTo: Ext.getBody()
  62424. * });
  62425. *
  62426. * Note that the Panel above is configured to render into the document and assigned a size. In a real world scenario,
  62427. * the Panel will often be added inside a Container which will use a {@link #layout} to render, size and position its
  62428. * child Components.
  62429. *
  62430. * Panels will often use specific {@link #layout}s to provide an application with shape and structure by containing and
  62431. * arranging child Components:
  62432. *
  62433. * @example
  62434. * var resultsPanel = Ext.create('Ext.panel.Panel', {
  62435. * title: 'Results',
  62436. * width: 600,
  62437. * height: 400,
  62438. * renderTo: Ext.getBody(),
  62439. * layout: {
  62440. * type: 'vbox', // Arrange child items vertically
  62441. * align: 'stretch', // Each takes up full width
  62442. * padding: 5
  62443. * },
  62444. * items: [{ // Results grid specified as a config object with an xtype of 'grid'
  62445. * xtype: 'grid',
  62446. * columns: [{header: 'Column One'}], // One header just for show. There's no data,
  62447. * store: Ext.create('Ext.data.ArrayStore', {}), // A dummy empty data store
  62448. * flex: 1 // Use 1/3 of Container's height (hint to Box layout)
  62449. * }, {
  62450. * xtype: 'splitter' // A splitter between the two child items
  62451. * }, { // Details Panel specified as a config object (no xtype defaults to 'panel').
  62452. * title: 'Details',
  62453. * bodyPadding: 5,
  62454. * items: [{
  62455. * fieldLabel: 'Data item',
  62456. * xtype: 'textfield'
  62457. * }], // An array of form fields
  62458. * flex: 2 // Use 2/3 of Container's height (hint to Box layout)
  62459. * }]
  62460. * });
  62461. *
  62462. * The example illustrates one possible method of displaying search results. The Panel contains a grid with the
  62463. * resulting data arranged in rows. Each selected row may be displayed in detail in the Panel below. The {@link
  62464. * Ext.layout.container.VBox vbox} layout is used to arrange the two vertically. It is configured to stretch child items
  62465. * horizontally to full width. Child items may either be configured with a numeric height, or with a `flex` value to
  62466. * distribute available space proportionately.
  62467. *
  62468. * This Panel itself may be a child item of, for exaple, a {@link Ext.tab.Panel} which will size its child items to fit
  62469. * within its content area.
  62470. *
  62471. * Using these techniques, as long as the **layout** is chosen and configured correctly, an application may have any
  62472. * level of nested containment, all dynamically sized according to configuration, the user's preference and available
  62473. * browser size.
  62474. */
  62475. Ext.define('Ext.panel.Panel', {
  62476. extend: 'Ext.panel.AbstractPanel',
  62477. requires: [
  62478. 'Ext.panel.Header',
  62479. 'Ext.fx.Anim',
  62480. 'Ext.util.KeyMap',
  62481. 'Ext.panel.DD',
  62482. 'Ext.XTemplate',
  62483. 'Ext.layout.component.Dock',
  62484. 'Ext.util.Memento'
  62485. ],
  62486. alias: 'widget.panel',
  62487. alternateClassName: 'Ext.Panel',
  62488. /**
  62489. * @cfg {String} collapsedCls
  62490. * A CSS class to add to the panel's element after it has been collapsed.
  62491. */
  62492. collapsedCls: 'collapsed',
  62493. /**
  62494. * @cfg {Boolean} animCollapse
  62495. * `true` to animate the transition when the panel is collapsed, `false` to skip the animation (defaults to `true`
  62496. * if the {@link Ext.fx.Anim} class is available, otherwise `false`). May also be specified as the animation
  62497. * duration in milliseconds.
  62498. */
  62499. animCollapse: Ext.enableFx,
  62500. /**
  62501. * @cfg {Number} minButtonWidth
  62502. * Minimum width of all footer toolbar buttons in pixels. If set, this will be used as the default
  62503. * value for the {@link Ext.button.Button#minWidth} config of each Button added to the **footer toolbar** via the
  62504. * {@link #fbar} or {@link #buttons} configurations. It will be ignored for buttons that have a minWidth configured
  62505. * some other way, e.g. in their own config object or via the {@link Ext.container.Container#defaults defaults} of
  62506. * their parent container.
  62507. */
  62508. minButtonWidth: 75,
  62509. /**
  62510. * @cfg {Boolean} collapsed
  62511. * `true` to render the panel collapsed, `false` to render it expanded.
  62512. */
  62513. collapsed: false,
  62514. /**
  62515. * @cfg {Boolean} collapseFirst
  62516. * `true` to make sure the collapse/expand toggle button always renders first (to the left of) any other tools in
  62517. * the panel's title bar, `false` to render it last.
  62518. */
  62519. collapseFirst: true,
  62520. /**
  62521. * @cfg {Boolean} hideCollapseTool
  62522. * `true` to hide the expand/collapse toggle button when `{@link #collapsible} == true`, `false` to display it.
  62523. */
  62524. hideCollapseTool: false,
  62525. /**
  62526. * @cfg {Boolean} titleCollapse
  62527. * `true` to allow expanding and collapsing the panel (when `{@link #collapsible} = true`) by clicking anywhere in
  62528. * the header bar, `false`) to allow it only by clicking to tool button).
  62529. */
  62530. titleCollapse: false,
  62531. /**
  62532. * @cfg {String} collapseMode
  62533. * **Important: this config is only effective for {@link #collapsible} Panels which are direct child items of a
  62534. * {@link Ext.layout.container.Border border layout}.**
  62535. *
  62536. * When _not_ a direct child item of a {@link Ext.layout.container.Border border layout}, then the Panel's header
  62537. * remains visible, and the body is collapsed to zero dimensions. If the Panel has no header, then a new header
  62538. * (orientated correctly depending on the {@link #collapseDirection}) will be inserted to show a the title and a re-
  62539. * expand tool.
  62540. *
  62541. * When a child item of a {@link Ext.layout.container.Border border layout}, this config has three possible values:
  62542. *
  62543. * - `undefined` - When collapsed, a placeholder {@link Ext.panel.Header Header} is injected into the layout to
  62544. * represent the Panel and to provide a UI with a Tool to allow the user to re-expand the Panel.
  62545. *
  62546. * - `"header"` - The Panel collapses to leave its header visible as when not inside a
  62547. * {@link Ext.layout.container.Border border layout}.
  62548. *
  62549. * - `"mini"` - The Panel collapses without a visible header.
  62550. */
  62551. /**
  62552. * @cfg {Ext.Component/Object} placeholder
  62553. * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
  62554. * {@link Ext.layout.container.Border border layout} when not using the `'header'` {@link #collapseMode}.**
  62555. *
  62556. * **Optional.** A Component (or config object for a Component) to show in place of this Panel when this Panel is
  62557. * collapsed by a {@link Ext.layout.container.Border border layout}. Defaults to a generated {@link Ext.panel.Header
  62558. * Header} containing a {@link Ext.panel.Tool Tool} to re-expand the Panel.
  62559. */
  62560. /**
  62561. * @cfg {Boolean} floatable
  62562. * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
  62563. * {@link Ext.layout.container.Border border layout}.**
  62564. *
  62565. * true to allow clicking a collapsed Panel's {@link #placeholder} to display the Panel floated above the layout,
  62566. * false to force the user to fully expand a collapsed region by clicking the expand button to see it again.
  62567. */
  62568. floatable: true,
  62569. /**
  62570. * @cfg {Boolean} overlapHeader
  62571. * True to overlap the header in a panel over the framing of the panel itself. This is needed when frame:true (and
  62572. * is done automatically for you). Otherwise it is undefined. If you manually add rounded corners to a panel header
  62573. * which does not have frame:true, this will need to be set to true.
  62574. */
  62575. /**
  62576. * @cfg {Boolean} collapsible
  62577. * True to make the panel collapsible and have an expand/collapse toggle Tool added into the header tool button
  62578. * area. False to keep the panel sized either statically, or by an owning layout manager, with no toggle Tool.
  62579. *
  62580. * See {@link #collapseMode} and {@link #collapseDirection}
  62581. */
  62582. collapsible: false,
  62583. /**
  62584. * @cfg {String} collapseDirection
  62585. * The direction to collapse the Panel when the toggle button is clicked.
  62586. *
  62587. * Defaults to the {@link #headerPosition}
  62588. *
  62589. * **Important: This config is _ignored_ for {@link #collapsible} Panels which are direct child items of a {@link
  62590. * Ext.layout.container.Border border layout}.**
  62591. *
  62592. * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
  62593. */
  62594. /**
  62595. * @cfg {Boolean} closable
  62596. * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
  62597. * disallow closing the window.
  62598. *
  62599. * By default, when close is requested by clicking the close button in the header, the {@link #method-close} method will be
  62600. * called. This will _{@link Ext.Component#method-destroy destroy}_ the Panel and its content meaning that it may not be
  62601. * reused.
  62602. *
  62603. * To make closing a Panel _hide_ the Panel so that it may be reused, set {@link #closeAction} to 'hide'.
  62604. */
  62605. closable: false,
  62606. /**
  62607. * @cfg {String} closeAction
  62608. * The action to take when the close header tool is clicked:
  62609. *
  62610. * - **`'{@link #method-destroy}'`** :
  62611. *
  62612. * {@link #method-remove remove} the window from the DOM and {@link Ext.Component#method-destroy destroy} it and all descendant
  62613. * Components. The window will **not** be available to be redisplayed via the {@link #method-show} method.
  62614. *
  62615. * - **`'{@link #method-hide}'`** :
  62616. *
  62617. * {@link #method-hide} the window by setting visibility to hidden and applying negative offsets. The window will be
  62618. * available to be redisplayed via the {@link #method-show} method.
  62619. *
  62620. * **Note:** This behavior has changed! setting *does* affect the {@link #method-close} method which will invoke the
  62621. * approriate closeAction.
  62622. */
  62623. closeAction: 'destroy',
  62624. /**
  62625. * @cfg {Object/Object[]} dockedItems
  62626. * A component or series of components to be added as docked items to this panel. The docked items can be docked to
  62627. * either the top, right, left or bottom of a panel. This is typically used for things like toolbars or tab bars:
  62628. *
  62629. * var panel = new Ext.panel.Panel({
  62630. * dockedItems: [{
  62631. * xtype: 'toolbar',
  62632. * dock: 'top',
  62633. * items: [{
  62634. * text: 'Docked to the top'
  62635. * }]
  62636. * }]
  62637. * });
  62638. */
  62639. /**
  62640. * @cfg {Number} placeholderCollapseHideMode
  62641. * The {@link Ext.dom.Element#setVisibilityMode mode} for hiding collapsed panels when
  62642. * using {@link #collapseMode} "placeholder".
  62643. */
  62644. placeholderCollapseHideMode: Ext.Element.VISIBILITY,
  62645. /**
  62646. * @cfg {Boolean} preventHeader
  62647. * @deprecated 4.1.0 Use {@link #header} instead.
  62648. * Prevent a Header from being created and shown.
  62649. */
  62650. preventHeader: false,
  62651. /**
  62652. * @cfg {Boolean/Object} header
  62653. * Pass as `false` to prevent a Header from being created and shown.
  62654. *
  62655. * Pass as a config object (optionally containing an `xtype`) to custom-configure this Panel's header.
  62656. *
  62657. */
  62658. header: undefined,
  62659. /**
  62660. * @cfg {String} headerPosition
  62661. * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
  62662. */
  62663. headerPosition: 'top',
  62664. /**
  62665. * @cfg {Boolean} frame
  62666. * True to apply a frame to the panel.
  62667. */
  62668. frame: false,
  62669. /**
  62670. * @cfg {Boolean} frameHeader
  62671. * True to apply a frame to the panel panels header (if 'frame' is true).
  62672. */
  62673. frameHeader: true,
  62674. /**
  62675. * @cfg {Object[]/Ext.panel.Tool[]} tools
  62676. * An array of {@link Ext.panel.Tool} configs/instances to be added to the header tool area. The tools are stored as
  62677. * child components of the header container. They can be accessed using {@link #down} and {#query}, as well as the
  62678. * other component methods. The toggle tool is automatically created if {@link #collapsible} is set to true.
  62679. *
  62680. * Note that, apart from the toggle tool which is provided when a panel is collapsible, these tools only provide the
  62681. * visual button. Any required functionality must be provided by adding handlers that implement the necessary
  62682. * behavior.
  62683. *
  62684. * Example usage:
  62685. *
  62686. * tools:[{
  62687. * type:'refresh',
  62688. * tooltip: 'Refresh form Data',
  62689. * // hidden:true,
  62690. * handler: function(event, toolEl, panel){
  62691. * // refresh logic
  62692. * }
  62693. * },
  62694. * {
  62695. * type:'help',
  62696. * tooltip: 'Get Help',
  62697. * handler: function(event, toolEl, panel){
  62698. * // show help here
  62699. * }
  62700. * }]
  62701. */
  62702. /**
  62703. * @cfg {String} [title='']
  62704. * The title text to be used to display in the {@link Ext.panel.Header panel header}. When a
  62705. * `title` is specified the {@link Ext.panel.Header} will automatically be created and displayed unless
  62706. * {@link #header} is set to `false`.
  62707. */
  62708. /**
  62709. * @cfg {String} [titleAlign='left']
  62710. * May be `"left"`, `"right"` or `"center"`.
  62711. *
  62712. * The alignment of the title text within the available space between the icon and the tools.
  62713. */
  62714. titleAlign: 'left',
  62715. /**
  62716. * @cfg {Boolean} [manageHeight=true]: When true, the dock component layout writes
  62717. * height information to the panel's DOM elements based on its shrink wrap height
  62718. * calculation. This ensures that the browser respects the calculated height.
  62719. * When false, the dock component layout will not write heights on the panel or its
  62720. * body element. In some simple layout cases, not writing the heights to the DOM may
  62721. * be desired because this allows the browser to respond to direct DOM manipulations
  62722. * (like animations).
  62723. */
  62724. manageHeight: true,
  62725. /**
  62726. * @cfg {String} iconCls
  62727. * CSS class for an icon in the header. Used for displaying an icon to the left of a title.
  62728. */
  62729. /**
  62730. * @cfg {String} icon
  62731. * Path to image for an icon in the header. Used for displaying an icon to the left of a title.
  62732. */
  62733. initComponent: function() {
  62734. var me = this;
  62735. me.addEvents(
  62736. /**
  62737. * @event beforeclose
  62738. * Fires before the user closes the panel. Return false from any listener to stop the close event being
  62739. * fired
  62740. * @param {Ext.panel.Panel} panel The Panel object
  62741. */
  62742. 'beforeclose',
  62743. /**
  62744. * @event close
  62745. * Fires when the user closes the panel.
  62746. * @param {Ext.panel.Panel} panel The Panel object
  62747. */
  62748. 'close',
  62749. /**
  62750. * @event beforeexpand
  62751. * Fires before this panel is expanded. Return false to prevent the expand.
  62752. * @param {Ext.panel.Panel} p The Panel being expanded.
  62753. * @param {Boolean} animate True if the expand is animated, else false.
  62754. */
  62755. "beforeexpand",
  62756. /**
  62757. * @event beforecollapse
  62758. * Fires before this panel is collapsed. Return false to prevent the collapse.
  62759. * @param {Ext.panel.Panel} p The Panel being collapsed.
  62760. * @param {String} direction . The direction of the collapse. One of
  62761. *
  62762. * - Ext.Component.DIRECTION_TOP
  62763. * - Ext.Component.DIRECTION_RIGHT
  62764. * - Ext.Component.DIRECTION_BOTTOM
  62765. * - Ext.Component.DIRECTION_LEFT
  62766. *
  62767. * @param {Boolean} animate True if the collapse is animated, else false.
  62768. */
  62769. "beforecollapse",
  62770. /**
  62771. * @event expand
  62772. * Fires after this Panel has expanded.
  62773. * @param {Ext.panel.Panel} p The Panel that has been expanded.
  62774. */
  62775. "expand",
  62776. /**
  62777. * @event collapse
  62778. * Fires after this Panel hass collapsed.
  62779. * @param {Ext.panel.Panel} p The Panel that has been collapsed.
  62780. */
  62781. "collapse",
  62782. /**
  62783. * @event titlechange
  62784. * Fires after the Panel title has been set or changed.
  62785. * @param {Ext.panel.Panel} p the Panel which has been resized.
  62786. * @param {String} newTitle The new title.
  62787. * @param {String} oldTitle The previous panel title.
  62788. */
  62789. 'titlechange',
  62790. /**
  62791. * @event iconchange
  62792. * Fires after the Panel icon has been set or changed.
  62793. * @param {Ext.panel.Panel} p The Panel which has the icon changed.
  62794. * @param {String} newIcon The path to the new icon image.
  62795. * @param {String} oldIcon The path to the previous panel icon image.
  62796. */
  62797. 'iconchange',
  62798. /**
  62799. * @event iconclschange
  62800. * Fires after the Panel iconCls has been set or changed.
  62801. * @param {Ext.panel.Panel} p The Panel which has the iconCls changed.
  62802. * @param {String} newIconCls The new iconCls.
  62803. * @param {String} oldIconCls The previous panel iconCls.
  62804. */
  62805. 'iconclschange'
  62806. );
  62807. if (me.collapsible) {
  62808. // Save state on these two events.
  62809. this.addStateEvents(['expand', 'collapse']);
  62810. }
  62811. if (me.unstyled) {
  62812. me.setUI('plain');
  62813. }
  62814. if (me.frame) {
  62815. me.setUI(me.ui + '-framed');
  62816. }
  62817. // Backwards compatibility
  62818. me.bridgeToolbars();
  62819. me.callParent();
  62820. me.collapseDirection = me.collapseDirection || me.headerPosition || Ext.Component.DIRECTION_TOP;
  62821. // Used to track hidden content elements during collapsed state
  62822. me.hiddenOnCollapse = new Ext.dom.CompositeElement();
  62823. },
  62824. beforeDestroy: function() {
  62825. var me = this;
  62826. Ext.destroy(
  62827. me.placeholder,
  62828. me.ghostPanel,
  62829. me.dd
  62830. );
  62831. me.callParent();
  62832. },
  62833. initAria: function() {
  62834. this.callParent();
  62835. this.initHeaderAria();
  62836. },
  62837. getFocusEl: function() {
  62838. return this.el;
  62839. },
  62840. initHeaderAria: function() {
  62841. var me = this,
  62842. el = me.el,
  62843. header = me.header;
  62844. if (el && header) {
  62845. el.dom.setAttribute('aria-labelledby', header.titleCmp.id);
  62846. }
  62847. },
  62848. /**
  62849. * Gets the {@link Ext.panel.Header Header} for this panel.
  62850. */
  62851. getHeader: function() {
  62852. return this.header;
  62853. },
  62854. /**
  62855. * Set a title for the panel's header. See {@link Ext.panel.Header#title}.
  62856. * @param {String} newTitle
  62857. */
  62858. setTitle: function(newTitle) {
  62859. var me = this,
  62860. oldTitle = me.title,
  62861. header = me.header,
  62862. reExpander = me.reExpander,
  62863. placeholder = me.placeholder;
  62864. me.title = newTitle;
  62865. if (header) {
  62866. if (header.isHeader) {
  62867. header.setTitle(newTitle);
  62868. } else {
  62869. header.title = newTitle;
  62870. }
  62871. } else {
  62872. me.updateHeader();
  62873. }
  62874. if (reExpander) {
  62875. reExpander.setTitle(newTitle);
  62876. }
  62877. if (placeholder && placeholder.setTitle) {
  62878. placeholder.setTitle(newTitle);
  62879. }
  62880. me.fireEvent('titlechange', me, newTitle, oldTitle);
  62881. },
  62882. /**
  62883. * Set the iconCls for the panel's header. See {@link Ext.panel.Header#iconCls}. It will fire the
  62884. * {@link #iconclschange} event after completion.
  62885. * @param {String} newIconCls The new CSS class name
  62886. */
  62887. setIconCls: function(newIconCls) {
  62888. var me = this,
  62889. oldIconCls = me.iconCls,
  62890. header = me.header,
  62891. placeholder = me.placeholder;
  62892. me.iconCls = newIconCls;
  62893. if (header) {
  62894. if (header.isHeader) {
  62895. header.setIconCls(newIconCls);
  62896. } else {
  62897. header.iconCls = newIconCls;
  62898. }
  62899. } else {
  62900. me.updateHeader();
  62901. }
  62902. if (placeholder && placeholder.setIconCls) {
  62903. placeholder.setIconCls(newIconCls);
  62904. }
  62905. me.fireEvent('iconclschange', me, newIconCls, oldIconCls);
  62906. },
  62907. /**
  62908. * Set the icon for the panel's header. See {@link Ext.panel.Header#icon}. It will fire the
  62909. * {@link #iconchange} event after completion.
  62910. * @param {String} newIcon The new icon path
  62911. */
  62912. setIcon: function(newIcon) {
  62913. var me = this,
  62914. oldIcon = me.icon,
  62915. header = me.header,
  62916. placeholder = me.placeholder;
  62917. me.icon = newIcon;
  62918. if (header) {
  62919. if (header.isHeader) {
  62920. header.setIcon(newIcon);
  62921. } else {
  62922. header.icon = newIcon;
  62923. }
  62924. } else {
  62925. me.updateHeader();
  62926. }
  62927. if (placeholder && placeholder.setIcon) {
  62928. placeholder.setIcon(newIcon);
  62929. }
  62930. me.fireEvent('iconchange', me, newIcon, oldIcon);
  62931. },
  62932. bridgeToolbars: function() {
  62933. var me = this,
  62934. docked = [],
  62935. fbar,
  62936. fbarDefaults,
  62937. minButtonWidth = me.minButtonWidth;
  62938. function initToolbar (toolbar, pos, useButtonAlign) {
  62939. if (Ext.isArray(toolbar)) {
  62940. toolbar = {
  62941. xtype: 'toolbar',
  62942. items: toolbar
  62943. };
  62944. }
  62945. else if (!toolbar.xtype) {
  62946. toolbar.xtype = 'toolbar';
  62947. }
  62948. toolbar.dock = pos;
  62949. if (pos == 'left' || pos == 'right') {
  62950. toolbar.vertical = true;
  62951. }
  62952. // Legacy support for buttonAlign (only used by buttons/fbar)
  62953. if (useButtonAlign) {
  62954. toolbar.layout = Ext.applyIf(toolbar.layout || {}, {
  62955. // default to 'end' (right-aligned) if me.buttonAlign is undefined or invalid
  62956. pack: { left:'start', center:'center' }[me.buttonAlign] || 'end'
  62957. });
  62958. }
  62959. return toolbar;
  62960. }
  62961. // Short-hand toolbars (tbar, bbar and fbar plus new lbar and rbar):
  62962. /**
  62963. * @cfg {String} buttonAlign
  62964. * The alignment of any buttons added to this panel. Valid values are 'right', 'left' and 'center' (defaults to
  62965. * 'right' for buttons/fbar, 'left' for other toolbar types).
  62966. *
  62967. * **NOTE:** The prefered way to specify toolbars is to use the dockedItems config. Instead of buttonAlign you
  62968. * would add the layout: { pack: 'start' | 'center' | 'end' } option to the dockedItem config.
  62969. */
  62970. /**
  62971. * @cfg {Object/Object[]} tbar
  62972. * Convenience config. Short for 'Top Bar'.
  62973. *
  62974. * tbar: [
  62975. * { xtype: 'button', text: 'Button 1' }
  62976. * ]
  62977. *
  62978. * is equivalent to
  62979. *
  62980. * dockedItems: [{
  62981. * xtype: 'toolbar',
  62982. * dock: 'top',
  62983. * items: [
  62984. * { xtype: 'button', text: 'Button 1' }
  62985. * ]
  62986. * }]
  62987. */
  62988. if (me.tbar) {
  62989. docked.push(initToolbar(me.tbar, 'top'));
  62990. me.tbar = null;
  62991. }
  62992. /**
  62993. * @cfg {Object/Object[]} bbar
  62994. * Convenience config. Short for 'Bottom Bar'.
  62995. *
  62996. * bbar: [
  62997. * { xtype: 'button', text: 'Button 1' }
  62998. * ]
  62999. *
  63000. * is equivalent to
  63001. *
  63002. * dockedItems: [{
  63003. * xtype: 'toolbar',
  63004. * dock: 'bottom',
  63005. * items: [
  63006. * { xtype: 'button', text: 'Button 1' }
  63007. * ]
  63008. * }]
  63009. */
  63010. if (me.bbar) {
  63011. docked.push(initToolbar(me.bbar, 'bottom'));
  63012. me.bbar = null;
  63013. }
  63014. /**
  63015. * @cfg {Object/Object[]} buttons
  63016. * Convenience config used for adding buttons docked to the bottom of the panel. This is a
  63017. * synonym for the {@link #fbar} config.
  63018. *
  63019. * buttons: [
  63020. * { text: 'Button 1' }
  63021. * ]
  63022. *
  63023. * is equivalent to
  63024. *
  63025. * dockedItems: [{
  63026. * xtype: 'toolbar',
  63027. * dock: 'bottom',
  63028. * ui: 'footer',
  63029. * defaults: {minWidth: {@link #minButtonWidth}},
  63030. * items: [
  63031. * { xtype: 'component', flex: 1 },
  63032. * { xtype: 'button', text: 'Button 1' }
  63033. * ]
  63034. * }]
  63035. *
  63036. * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
  63037. * each of the buttons in the buttons toolbar.
  63038. */
  63039. if (me.buttons) {
  63040. me.fbar = me.buttons;
  63041. me.buttons = null;
  63042. }
  63043. /**
  63044. * @cfg {Object/Object[]} fbar
  63045. * Convenience config used for adding items to the bottom of the panel. Short for Footer Bar.
  63046. *
  63047. * fbar: [
  63048. * { type: 'button', text: 'Button 1' }
  63049. * ]
  63050. *
  63051. * is equivalent to
  63052. *
  63053. * dockedItems: [{
  63054. * xtype: 'toolbar',
  63055. * dock: 'bottom',
  63056. * ui: 'footer',
  63057. * defaults: {minWidth: {@link #minButtonWidth}},
  63058. * items: [
  63059. * { xtype: 'component', flex: 1 },
  63060. * { xtype: 'button', text: 'Button 1' }
  63061. * ]
  63062. * }]
  63063. *
  63064. * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
  63065. * each of the buttons in the fbar.
  63066. */
  63067. if (me.fbar) {
  63068. fbar = initToolbar(me.fbar, 'bottom', true); // only we useButtonAlign
  63069. fbar.ui = 'footer';
  63070. // Apply the minButtonWidth config to buttons in the toolbar
  63071. if (minButtonWidth) {
  63072. fbarDefaults = fbar.defaults;
  63073. fbar.defaults = function(config) {
  63074. var defaults = fbarDefaults || {};
  63075. if ((!config.xtype || config.xtype === 'button' || (config.isComponent && config.isXType('button'))) &&
  63076. !('minWidth' in defaults)) {
  63077. defaults = Ext.apply({minWidth: minButtonWidth}, defaults);
  63078. }
  63079. return defaults;
  63080. };
  63081. }
  63082. docked.push(fbar);
  63083. me.fbar = null;
  63084. }
  63085. /**
  63086. * @cfg {Object/Object[]} lbar
  63087. * Convenience config. Short for 'Left Bar' (left-docked, vertical toolbar).
  63088. *
  63089. * lbar: [
  63090. * { xtype: 'button', text: 'Button 1' }
  63091. * ]
  63092. *
  63093. * is equivalent to
  63094. *
  63095. * dockedItems: [{
  63096. * xtype: 'toolbar',
  63097. * dock: 'left',
  63098. * items: [
  63099. * { xtype: 'button', text: 'Button 1' }
  63100. * ]
  63101. * }]
  63102. */
  63103. if (me.lbar) {
  63104. docked.push(initToolbar(me.lbar, 'left'));
  63105. me.lbar = null;
  63106. }
  63107. /**
  63108. * @cfg {Object/Object[]} rbar
  63109. * Convenience config. Short for 'Right Bar' (right-docked, vertical toolbar).
  63110. *
  63111. * rbar: [
  63112. * { xtype: 'button', text: 'Button 1' }
  63113. * ]
  63114. *
  63115. * is equivalent to
  63116. *
  63117. * dockedItems: [{
  63118. * xtype: 'toolbar',
  63119. * dock: 'right',
  63120. * items: [
  63121. * { xtype: 'button', text: 'Button 1' }
  63122. * ]
  63123. * }]
  63124. */
  63125. if (me.rbar) {
  63126. docked.push(initToolbar(me.rbar, 'right'));
  63127. me.rbar = null;
  63128. }
  63129. if (me.dockedItems) {
  63130. if (!Ext.isArray(me.dockedItems)) {
  63131. me.dockedItems = [me.dockedItems];
  63132. }
  63133. me.dockedItems = me.dockedItems.concat(docked);
  63134. } else {
  63135. me.dockedItems = docked;
  63136. }
  63137. },
  63138. isPlaceHolderCollapse: function(){
  63139. return this.collapseMode == 'placeholder';
  63140. },
  63141. onBoxReady: function(){
  63142. this.callParent();
  63143. if (this.collapsed) {
  63144. this.setHiddenDocked();
  63145. }
  63146. },
  63147. beforeRender: function() {
  63148. var me = this,
  63149. wasCollapsed;
  63150. me.callParent();
  63151. // Add class-specific header tools.
  63152. // Panel adds collapsible and closable.
  63153. me.initTools();
  63154. // Dock the header/title unless we are configured specifically not to create a header
  63155. if (!(me.preventHeader || (me.header === false))) {
  63156. me.updateHeader();
  63157. }
  63158. // If we are rendering collapsed, we still need to save and modify various configs
  63159. if (me.collapsed) {
  63160. if (me.isPlaceHolderCollapse()) {
  63161. me.hidden = true;
  63162. // This will insert the placeholder Component into the ownerCt's child collection
  63163. // Its getRenderTree call which is calling this will then iterate again and
  63164. // recreate the child items array to include the new Component.
  63165. me.placeholderCollapse();
  63166. wasCollapsed = me.collapsed;
  63167. // Temporarily clear the flag so that the header is rendered with a collapse tool in it.
  63168. // Placeholder collapse panels never really collapse, they just hide. The tool is always
  63169. // a collapse tool.
  63170. me.collapsed = false;
  63171. } else {
  63172. me.beginCollapse();
  63173. me.addClsWithUI(me.collapsedCls);
  63174. }
  63175. }
  63176. // Restore the flag if we are being rendered initially placeholder collapsed.
  63177. if (wasCollapsed) {
  63178. me.collapsed = wasCollapsed;
  63179. }
  63180. },
  63181. /**
  63182. * @private
  63183. * Tools are a Panel-specific capabilty.
  63184. * Panel uses initTools. Subclasses may contribute tools by implementing addTools.
  63185. */
  63186. initTools: function() {
  63187. var me = this;
  63188. me.tools = me.tools ? Ext.Array.clone(me.tools) : [];
  63189. // Add a collapse tool unless configured to not show a collapse tool
  63190. // or to not even show a header.
  63191. if (me.collapsible && !(me.hideCollapseTool || me.header === false || me.preventHeader)) {
  63192. me.collapseDirection = me.collapseDirection || me.headerPosition || 'top';
  63193. me.collapseTool = me.expandTool = Ext.widget({
  63194. xtype: 'tool',
  63195. type: (me.collapsed && !me.isPlaceHolderCollapse()) ? ('expand-' + me.getOppositeDirection(me.collapseDirection)) : ('collapse-' + me.collapseDirection),
  63196. handler: me.toggleCollapse,
  63197. scope: me
  63198. });
  63199. // Prepend collapse tool is configured to do so.
  63200. if (me.collapseFirst) {
  63201. me.tools.unshift(me.collapseTool);
  63202. }
  63203. }
  63204. // Add subclass-specific tools.
  63205. me.addTools();
  63206. // Make Panel closable.
  63207. if (me.closable) {
  63208. me.addClsWithUI('closable');
  63209. me.addTool({
  63210. type: 'close',
  63211. handler: Ext.Function.bind(me.close, me, [])
  63212. });
  63213. }
  63214. // Append collapse tool if needed.
  63215. if (me.collapseTool && !me.collapseFirst) {
  63216. me.addTool(me.collapseTool);
  63217. }
  63218. },
  63219. /**
  63220. * @private
  63221. * @template
  63222. * Template method to be implemented in subclasses to add their tools after the collapsible tool.
  63223. */
  63224. addTools: Ext.emptyFn,
  63225. /**
  63226. * Closes the Panel. By default, this method, removes it from the DOM, {@link Ext.Component#method-destroy destroy}s the
  63227. * Panel object and all its descendant Components. The {@link #beforeclose beforeclose} event is fired before the
  63228. * close happens and will cancel the close action if it returns false.
  63229. *
  63230. * **Note:** This method is also affected by the {@link #closeAction} setting. For more explicit control use
  63231. * {@link #method-destroy} and {@link #method-hide} methods.
  63232. */
  63233. close: function() {
  63234. if (this.fireEvent('beforeclose', this) !== false) {
  63235. this.doClose();
  63236. }
  63237. },
  63238. // private
  63239. doClose: function() {
  63240. this.fireEvent('close', this);
  63241. this[this.closeAction]();
  63242. },
  63243. /**
  63244. * Create, hide, or show the header component as appropriate based on the current config.
  63245. * @private
  63246. * @param {Boolean} force True to force the header to be created
  63247. */
  63248. updateHeader: function(force) {
  63249. var me = this,
  63250. header = me.header,
  63251. title = me.title,
  63252. tools = me.tools,
  63253. icon = me.icon || me.iconCls,
  63254. vertical = me.headerPosition == 'left' || me.headerPosition == 'right';
  63255. if ((header !== false) && (force || (title || icon) || (tools && tools.length) || (me.collapsible && !me.titleCollapse))) {
  63256. if (header && header.isHeader) {
  63257. header.show();
  63258. } else {
  63259. // Apply the header property to the header config
  63260. header = me.header = Ext.widget(Ext.apply({
  63261. xtype : 'header',
  63262. title : title,
  63263. titleAlign : me.titleAlign,
  63264. orientation : vertical ? 'vertical' : 'horizontal',
  63265. dock : me.headerPosition || 'top',
  63266. textCls : me.headerTextCls,
  63267. iconCls : me.iconCls,
  63268. icon : me.icon,
  63269. baseCls : me.baseCls + '-header',
  63270. tools : tools,
  63271. ui : me.ui,
  63272. id : me.id + '_header',
  63273. indicateDrag: me.draggable,
  63274. frame : (me.frame || me.alwaysFramed) && me.frameHeader,
  63275. ignoreParentFrame : me.frame || me.overlapHeader,
  63276. ignoreBorderManagement: me.frame || me.ignoreHeaderBorderManagement,
  63277. listeners : me.collapsible && me.titleCollapse ? {
  63278. click: me.toggleCollapse,
  63279. scope: me
  63280. } : null
  63281. }, me.header));
  63282. me.addDocked(header, 0);
  63283. // Reference the Header's tool array.
  63284. // Header injects named references.
  63285. me.tools = header.tools;
  63286. }
  63287. me.initHeaderAria();
  63288. } else if (header) {
  63289. header.hide();
  63290. }
  63291. },
  63292. // inherit docs
  63293. setUI: function(ui) {
  63294. var me = this;
  63295. me.callParent(arguments);
  63296. if (me.header && me.header.rendered) {
  63297. me.header.setUI(ui);
  63298. }
  63299. },
  63300. // private
  63301. getContentTarget: function() {
  63302. return this.body;
  63303. },
  63304. getTargetEl: function() {
  63305. var me = this;
  63306. return me.body || me.protoBody || me.frameBody || me.el;
  63307. },
  63308. // the overrides below allow for collapsed regions inside the border layout to be hidden
  63309. // inherit docs
  63310. isVisible: function(deep){
  63311. var me = this;
  63312. if (me.collapsed && me.placeholder) {
  63313. return me.placeholder.isVisible(deep);
  63314. }
  63315. return me.callParent(arguments);
  63316. },
  63317. // inherit docs
  63318. onHide: function(){
  63319. var me = this;
  63320. if (me.collapsed && me.placeholder) {
  63321. me.placeholder.hide();
  63322. } else {
  63323. me.callParent(arguments);
  63324. }
  63325. },
  63326. // inherit docs
  63327. onShow: function(){
  63328. var me = this;
  63329. if (me.collapsed && me.placeholder) {
  63330. // force hidden back to true, since this gets set by the layout
  63331. me.hidden = true;
  63332. me.placeholder.show();
  63333. } else {
  63334. me.callParent(arguments);
  63335. }
  63336. },
  63337. onRemoved: function(destroying) {
  63338. var me = this;
  63339. me.callParent(arguments);
  63340. // If we are removed but not being destroyed, ensure our placeholder is also removed but not destroyed
  63341. // If we are being destroyed, our destroy processing will destroy the placeholder.
  63342. if (me.placeholder && !destroying) {
  63343. me.ownerCt.remove(me.placeholder, false);
  63344. }
  63345. },
  63346. addTool: function(tools) {
  63347. tools = [].concat(tools);
  63348. var me = this,
  63349. header = me.header,
  63350. t,
  63351. tLen = tools.length,
  63352. tool;
  63353. for (t = 0; t < tLen; t++) {
  63354. tool = tools[t];
  63355. me.tools.push(tool);
  63356. if (header && header.isHeader) {
  63357. header.addTool(tool);
  63358. }
  63359. }
  63360. me.updateHeader();
  63361. },
  63362. getOppositeDirection: function(d) {
  63363. var c = Ext.Component;
  63364. switch (d) {
  63365. case c.DIRECTION_TOP:
  63366. return c.DIRECTION_BOTTOM;
  63367. case c.DIRECTION_RIGHT:
  63368. return c.DIRECTION_LEFT;
  63369. case c.DIRECTION_BOTTOM:
  63370. return c.DIRECTION_TOP;
  63371. case c.DIRECTION_LEFT:
  63372. return c.DIRECTION_RIGHT;
  63373. }
  63374. },
  63375. getWidthAuthority: function() {
  63376. if (this.collapsed && this.collapsedHorizontal()) {
  63377. return 1; // the panel determine's its own width
  63378. }
  63379. return this.callParent();
  63380. },
  63381. getHeightAuthority: function() {
  63382. if (this.collapsed && this.collapsedVertical()) {
  63383. return 1; // the panel determine's its own height
  63384. }
  63385. return this.callParent();
  63386. },
  63387. collapsedHorizontal: function () {
  63388. var dir = this.getCollapsed();
  63389. return dir == 'left' || dir == 'right';
  63390. },
  63391. collapsedVertical: function () {
  63392. var dir = this.getCollapsed();
  63393. return dir == 'top' || dir == 'bottom';
  63394. },
  63395. restoreDimension: function(){
  63396. var dir = this.collapseDirection;
  63397. // If we're collapsing top/bottom, we want to restore the height
  63398. // If we're collapsing left/right, we want to restore the width
  63399. return (dir === 'top' || dir === 'bottom') ? 'height' : 'width';
  63400. },
  63401. /**
  63402. * Returns the current collapsed state of the panel.
  63403. * @return {Boolean/String} False when not collapsed, otherwise the value of {@link #collapseDirection}.
  63404. */
  63405. getCollapsed: function() {
  63406. var me = this;
  63407. // The collapsed flag, when the Panel is collapsed acts as the direction in which the collapse took
  63408. // place. It can still be tested as truthy/falsy if only a truth value is required.
  63409. if (me.collapsed === true) {
  63410. return me.collapseDirection;
  63411. }
  63412. return me.collapsed;
  63413. },
  63414. getState: function() {
  63415. var me = this,
  63416. state = me.callParent(),
  63417. memento;
  63418. state = me.addPropertyToState(state, 'collapsed');
  63419. // If a collapse has taken place, use remembered values as the dimensions.
  63420. if (me.collapsed) {
  63421. memento = me.collapseMemento;
  63422. memento = memento && memento.data;
  63423. if (me.collapsedVertical()) {
  63424. if (state) {
  63425. delete state.height;
  63426. }
  63427. if (memento) {
  63428. state = me.addPropertyToState(state, 'height', memento.height);
  63429. }
  63430. } else {
  63431. if (state) {
  63432. delete state.width;
  63433. }
  63434. if (memento) {
  63435. state = me.addPropertyToState(state, 'width', memento.width);
  63436. }
  63437. }
  63438. }
  63439. return state;
  63440. },
  63441. findReExpander: function (direction) {
  63442. var me = this,
  63443. c = Ext.Component,
  63444. dockedItems = me.dockedItems.items,
  63445. dockedItemCount = dockedItems.length,
  63446. comp, i;
  63447. // never use the header if we're in collapseMode mini
  63448. if (me.collapseMode == 'mini') {
  63449. return;
  63450. }
  63451. switch (direction) {
  63452. case c.DIRECTION_TOP:
  63453. case c.DIRECTION_BOTTOM:
  63454. // Attempt to find a reExpander Component (docked in a horizontal orientation)
  63455. // Also, collect all other docked items which we must hide after collapse.
  63456. for (i = 0; i < dockedItemCount; i++) {
  63457. comp = dockedItems[i];
  63458. if (!comp.hidden) {
  63459. if (comp.isHeader && (!comp.dock || comp.dock == 'top' || comp.dock == 'bottom')) {
  63460. return comp;
  63461. }
  63462. }
  63463. }
  63464. break;
  63465. case c.DIRECTION_LEFT:
  63466. case c.DIRECTION_RIGHT:
  63467. // Attempt to find a reExpander Component (docked in a vecrtical orientation)
  63468. // Also, collect all other docked items which we must hide after collapse.
  63469. for (i = 0; i < dockedItemCount; i++) {
  63470. comp = dockedItems[i];
  63471. if (!comp.hidden) {
  63472. if (comp.isHeader && (comp.dock == 'left' || comp.dock == 'right')) {
  63473. return comp;
  63474. }
  63475. }
  63476. }
  63477. break;
  63478. default:
  63479. throw('Panel#findReExpander must be passed a valid collapseDirection');
  63480. }
  63481. },
  63482. getReExpander: function (direction) {
  63483. var me = this,
  63484. collapseDir = direction || me.collapseDirection,
  63485. reExpander = me.reExpander || me.findReExpander(collapseDir);
  63486. me.expandDirection = me.getOppositeDirection(collapseDir);
  63487. if (!reExpander) {
  63488. // We did not find a Header of the required orientation: create one.
  63489. me.reExpander = reExpander = me.createReExpander(collapseDir, {
  63490. dock: collapseDir,
  63491. cls: Ext.baseCSSPrefix + 'docked ' + me.baseCls + '-' + me.ui + '-collapsed',
  63492. ownerCt: me,
  63493. ownerLayout: me.componentLayout
  63494. });
  63495. me.dockedItems.insert(0, reExpander);
  63496. }
  63497. return reExpander;
  63498. },
  63499. createReExpander: function(direction, defaults) {
  63500. var me = this,
  63501. isLeft = direction == 'left',
  63502. isRight = direction == 'right',
  63503. isVertical = isLeft || isRight,
  63504. toolAtTop,
  63505. result = Ext.apply({
  63506. hideMode: 'offsets',
  63507. title: me.title,
  63508. orientation: isVertical ? 'vertical' : 'horizontal',
  63509. textCls: me.headerTextCls,
  63510. icon: me.icon,
  63511. iconCls: me.iconCls,
  63512. baseCls: me.baseCls + '-header',
  63513. ui: me.ui,
  63514. frame: me.frame && me.frameHeader,
  63515. ignoreParentFrame: me.frame || me.overlapHeader,
  63516. indicateDrag: me.draggable
  63517. }, defaults);
  63518. // If we're in mini mode, set the placeholder size to only 1px since
  63519. // we don't need it to show up.
  63520. if (me.collapseMode == 'mini') {
  63521. if (isVertical) {
  63522. result.width = 1;
  63523. } else {
  63524. result.height = 1;
  63525. }
  63526. }
  63527. // Create the re expand tool
  63528. // For UI consistency reasons, collapse:left reExpanders, and region: 'west' placeHolders
  63529. // have the re expand tool at the *top* with a bit of space.
  63530. if (!me.hideCollapseTool) {
  63531. toolAtTop = isLeft || (isRight && me.isPlaceHolderCollapse());
  63532. result[toolAtTop ? 'items' : 'tools'] = [{
  63533. xtype: 'tool',
  63534. type: 'expand-' + me.getOppositeDirection(direction),
  63535. uiCls: ['top'],
  63536. handler: me.toggleCollapse,
  63537. scope: me
  63538. }];
  63539. }
  63540. result = new Ext.panel.Header(result);
  63541. result.addClsWithUI(me.getHeaderCollapsedClasses(result));
  63542. return result;
  63543. },
  63544. // private
  63545. // Create the class array to add to the Header when collpsed.
  63546. getHeaderCollapsedClasses: function(header) {
  63547. var me = this,
  63548. collapsedCls = me.collapsedCls,
  63549. collapsedClasses;
  63550. collapsedClasses = [ collapsedCls, collapsedCls + '-' + header.dock];
  63551. if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
  63552. collapsedClasses.push(collapsedCls + '-border-' + header.dock);
  63553. }
  63554. return collapsedClasses;
  63555. },
  63556. /**
  63557. * @private
  63558. * Called before the change from default, configured state into the collapsed state.
  63559. * This method may be called at render time to enable rendering in an initially collapsed state,
  63560. * or at runtime when an existing, fully layed out Panel may be collapsed.
  63561. * It basically saves configs which need to be clobbered for the duration of the collapsed state.
  63562. */
  63563. beginCollapse: function() {
  63564. var me = this,
  63565. lastBox = me.lastBox,
  63566. rendered = me.rendered,
  63567. collapseMemento = me.collapseMemento || (me.collapseMemento = new Ext.util.Memento(me)),
  63568. sizeModel = me.getSizeModel(),
  63569. reExpander;
  63570. // When we collapse a panel, the panel is in control of one dimension (depending on
  63571. // collapse direction) and sets that on the component. We must restore the user's
  63572. // original value (including non-existance) when we expand. Using this technique, we
  63573. // mimic setCalculatedSize for the dimension we do not control and setSize for the
  63574. // one we do (only while collapsed).
  63575. // Additionally, the panel may have a shrink wrapped width and/or height. For shrinkWrapped
  63576. // panels this can be problematic, since a collapsed, shrink-wrapped panel has no way
  63577. // of determining its width (or height if the collapse direction is horizontal). It is
  63578. // therefore necessary to capture both the width and height regardless of collapse direction.
  63579. // This allows us to set a configured width or height on the panel when it is collapsed,
  63580. // and it will be restored to an unconfigured-width shrinkWrapped state on expand.
  63581. collapseMemento.capture(['height', 'minHeight', 'width', 'minWidth']);
  63582. if (lastBox) {
  63583. collapseMemento.capture(me.restoreDimension(), lastBox, 'last.');
  63584. }
  63585. // If the panel has a shrinkWrapped height/width and is already rendered, configure its width/height as its calculated width/height,
  63586. // so that the collapsed header will have the same width or height as the panel did before it was collapsed.
  63587. // If the shrinkWrapped panel has not yet been rendered, as will be the case when a panel is initially configured with
  63588. // collapsed:true, we attempt to use the configured width/height, and fall back to minWidth or minHeight if
  63589. // width/height has not been configured, and fall back to a value of 100 if a minWidth/minHeight has not been configured.
  63590. if (me.collapsedVertical()) {
  63591. if (sizeModel.width.shrinkWrap) {
  63592. me.width = rendered ? me.getWidth() : me.width || me.minWidth || 100;
  63593. }
  63594. delete me.height;
  63595. me.minHeight = 0;
  63596. } else if (me.collapsedHorizontal()) {
  63597. if (sizeModel.height.shrinkWrap) {
  63598. me.height = rendered ? me.getHeight() : me.height || me.minHeight || 100;
  63599. }
  63600. delete me.width;
  63601. me.minWidth = 0;
  63602. }
  63603. if (me.ownerCt) {
  63604. me.ownerCt.getLayout().beginCollapse(me);
  63605. }
  63606. // Get a reExpander header. This will return the Panel Header if the Header is in the correct orientation
  63607. // If we are using the Header as the reExpander, change its UI to collapsed state
  63608. if (!me.isPlaceHolderCollapse()) {
  63609. if (me.header === (reExpander = me.getReExpander())) {
  63610. me.header.addClsWithUI(me.getHeaderCollapsedClasses(me.header));
  63611. // Ensure that the reExpander has the correct framing applied.
  63612. if (me.header.rendered) {
  63613. me.header.updateFrame();
  63614. }
  63615. }
  63616. // We're going to use a temporary reExpander: show it.
  63617. else {
  63618. if (reExpander.el) {
  63619. reExpander.el.show();
  63620. reExpander.hidden = false;
  63621. }
  63622. }
  63623. }
  63624. if (me.resizer) {
  63625. me.resizer.disable();
  63626. }
  63627. },
  63628. beginExpand: function() {
  63629. var me = this,
  63630. lastBox = me.lastBox,
  63631. collapseMemento = me.collapseMemento,
  63632. restoreDimension = this.restoreDimension(),
  63633. reExpander;
  63634. collapseMemento.restore(['minHeight', 'minWidth', restoreDimension]);
  63635. if (lastBox) {
  63636. collapseMemento.restore(restoreDimension, true, lastBox, 'last.');
  63637. }
  63638. if (me.ownerCt) {
  63639. me.ownerCt.getLayout().beginExpand(me);
  63640. }
  63641. if (!me.isPlaceHolderCollapse()) {
  63642. // If we have been using our Header as the reExpander then restore the Header to expanded UI
  63643. if (me.header === (reExpander = me.getReExpander())) {
  63644. me.header.removeClsWithUI(me.getHeaderCollapsedClasses(me.header));
  63645. // Ensure that the reExpander has the correct framing applied.
  63646. if (me.header.rendered) {
  63647. me.header.updateFrame();
  63648. }
  63649. }
  63650. // We've been using a temporary reExpander: hide it.
  63651. else {
  63652. reExpander.hidden = true;
  63653. reExpander.el.hide();
  63654. }
  63655. }
  63656. if (me.resizer) {
  63657. me.resizer.enable();
  63658. }
  63659. },
  63660. /**
  63661. * Collapses the panel body so that the body becomes hidden. Docked Components parallel to the border towards which
  63662. * the collapse takes place will remain visible. Fires the {@link #beforecollapse} event which will cancel the
  63663. * collapse action if it returns false.
  63664. *
  63665. * @param {String} [direction] The direction to collapse towards. Must be one of
  63666. *
  63667. * - Ext.Component.DIRECTION_TOP
  63668. * - Ext.Component.DIRECTION_RIGHT
  63669. * - Ext.Component.DIRECTION_BOTTOM
  63670. * - Ext.Component.DIRECTION_LEFT
  63671. *
  63672. * Defaults to {@link #collapseDirection}.
  63673. *
  63674. * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
  63675. * {@link #animCollapse} panel config)
  63676. * @return {Ext.panel.Panel} this
  63677. */
  63678. collapse: function(direction, animate) {
  63679. var me = this,
  63680. collapseDir = direction || me.collapseDirection,
  63681. ownerCt = me.ownerCt;
  63682. if (me.isCollapsingOrExpanding) {
  63683. return me;
  63684. }
  63685. if (arguments.length < 2) {
  63686. animate = me.animCollapse;
  63687. }
  63688. if (me.collapsed || me.fireEvent('beforecollapse', me, direction, animate) === false) {
  63689. return me;
  63690. }
  63691. if (ownerCt && me.isPlaceHolderCollapse()) {
  63692. return me.placeholderCollapse(direction, animate);
  63693. }
  63694. me.collapsed = collapseDir;
  63695. me.beginCollapse();
  63696. me.fireHierarchyEvent('collapse');
  63697. return me.doCollapseExpand(1, animate);
  63698. },
  63699. doCollapseExpand: function (flags, animate) {
  63700. var me = this,
  63701. originalAnimCollapse = me.animCollapse,
  63702. ownerLayout = me.ownerLayout;
  63703. // we need to temporarily set animCollapse to the animate value here because ContextItem
  63704. // uses the animCollapse property to determine if the collapse/expand should be animated
  63705. me.animCollapse = animate;
  63706. // Flag used by the layouy ContextItem to impose an animation policy based upon the
  63707. // collapse direction and the animCollapse setting.
  63708. me.isCollapsingOrExpanding = flags;
  63709. if (ownerLayout && !animate) {
  63710. ownerLayout.onContentChange(me);
  63711. } else {
  63712. me.updateLayout({ isRoot: true });
  63713. }
  63714. // set animCollapse back to its original value
  63715. me.animCollapse = originalAnimCollapse;
  63716. return me;
  63717. },
  63718. /**
  63719. * Invoked after the Panel is Collapsed.
  63720. *
  63721. * @param {Boolean} animated
  63722. *
  63723. * @template
  63724. * @protected
  63725. */
  63726. afterCollapse: function(animated) {
  63727. var me = this,
  63728. ownerLayout = me.ownerLayout;
  63729. me.isCollapsingOrExpanding = 0;
  63730. if (me.collapseTool) {
  63731. me.collapseTool.setType('expand-' + me.getOppositeDirection(me.collapseDirection));
  63732. }
  63733. if (ownerLayout && animated) {
  63734. ownerLayout.onContentChange(me);
  63735. }
  63736. me.setHiddenDocked();
  63737. me.fireEvent('collapse', me);
  63738. },
  63739. setHiddenDocked: function(){
  63740. // Hide Panel content except reExpander using visibility to prevent focusing of contained elements.
  63741. // Track what we hide to re-show on expand
  63742. var me = this,
  63743. toHide = me.hiddenOnCollapse,
  63744. reExpander = me.getReExpander(),
  63745. items = me.getDockedItems(),
  63746. len = items.length,
  63747. i = 0,
  63748. item;
  63749. toHide.add(me.body);
  63750. for (; i < len; i++) {
  63751. item = items[i];
  63752. if (item && item !== reExpander && item.el) {
  63753. toHide.add(item.el);
  63754. }
  63755. }
  63756. toHide.setStyle('visibility', 'hidden');
  63757. },
  63758. restoreHiddenDocked: function(){
  63759. var toShow = this.hiddenOnCollapse;
  63760. // Re-show Panel content which was hidden after collapse.
  63761. toShow.setStyle('visibility', '');
  63762. toShow.clear();
  63763. },
  63764. getPlaceholder: function(direction) {
  63765. var me = this,
  63766. collapseDir = direction || me.collapseDirection,
  63767. listeners = null,
  63768. placeholder = me.placeholder;
  63769. if (!placeholder) {
  63770. if (me.floatable || (me.collapsible && me.titleCollapse)) {
  63771. listeners = {
  63772. click: {
  63773. fn: me.floatable ? me.floatCollapsedPanel : me.toggleCollapse,
  63774. element: 'el',
  63775. scope: me
  63776. }
  63777. };
  63778. }
  63779. me.placeholder = placeholder = Ext.widget(me.createReExpander(collapseDir, {
  63780. id: me.id + '-placeholder',
  63781. listeners: listeners
  63782. }));
  63783. }
  63784. // User created placeholder was passed in
  63785. if (!placeholder.placeholderFor) {
  63786. // Handle the case of a placeholder config
  63787. if (!placeholder.isComponent) {
  63788. me.placeholder = placeholder = me.lookupComponent(placeholder);
  63789. }
  63790. Ext.applyIf(placeholder, {
  63791. margins: me.margins,
  63792. placeholderFor: me
  63793. });
  63794. placeholder.addCls([Ext.baseCSSPrefix + 'region-collapsed-placeholder', Ext.baseCSSPrefix + 'region-collapsed-' + collapseDir + '-placeholder', me.collapsedCls]);
  63795. }
  63796. return placeholder;
  63797. },
  63798. placeholderCollapse: function(direction, animate) {
  63799. var me = this,
  63800. ownerCt = me.ownerCt,
  63801. collapseDir = direction || me.collapseDirection,
  63802. floatCls = Ext.baseCSSPrefix + 'border-region-slide-in',
  63803. placeholder = me.getPlaceholder(direction);
  63804. me.isCollapsingOrExpanding = 1;
  63805. // Upcoming layout run will ignore this Component
  63806. me.hidden = true;
  63807. me.collapsed = collapseDir;
  63808. if (placeholder.rendered) {
  63809. // We may have been added to another Container from that in which we rendered the placeholder
  63810. if (placeholder.el.dom.parentNode !== me.el.dom.parentNode) {
  63811. me.el.dom.parentNode.insertBefore(placeholder.el.dom, me.el.dom);
  63812. }
  63813. placeholder.hidden = false;
  63814. placeholder.el.show();
  63815. ownerCt.updateLayout();
  63816. } else {
  63817. ownerCt.insert(ownerCt.items.indexOf(me), placeholder);
  63818. }
  63819. if (me.rendered) {
  63820. // We MUST NOT hide using display because that resets all scroll information.
  63821. me.el.setVisibilityMode(me.placeholderCollapseHideMode);
  63822. if (animate) {
  63823. me.el.addCls(floatCls);
  63824. placeholder.el.hide();
  63825. me.el.slideOut(collapseDir.substr(0, 1), {
  63826. preserveScroll: true,
  63827. duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration),
  63828. listeners: {
  63829. afteranimate: function() {
  63830. me.el.removeCls(floatCls);
  63831. /* We need to show the element so that slideIn will work correctly. However, if we leave it
  63832. visible then it can be seen before the animation starts, causing a flicker. The solution,
  63833. borrowed from date picker, is to hide it using display none. The slideIn effect includes
  63834. a call to fixDisplay() that will undo the display none at the appropriate time.
  63835. */
  63836. placeholder.el.show().setStyle('display', 'none').slideIn(collapseDir.substr(0, 1), {
  63837. easing: 'linear',
  63838. duration: 100,
  63839. listeners: {
  63840. afteranimate: function() {
  63841. placeholder.focus();
  63842. me.isCollapsingOrExpanding = 0;
  63843. me.fireEvent('collapse', me);
  63844. }
  63845. }
  63846. });
  63847. }
  63848. }
  63849. });
  63850. } else {
  63851. me.el.hide();
  63852. me.isCollapsingOrExpanding = 0;
  63853. me.fireEvent('collapse', me);
  63854. }
  63855. } else {
  63856. me.isCollapsingOrExpanding = 0;
  63857. me.fireEvent('collapse', me);
  63858. }
  63859. return me;
  63860. },
  63861. floatCollapsedPanel: function() {
  63862. var me = this,
  63863. placeholder = me.placeholder,
  63864. pb = placeholder.getBox(true),
  63865. myBox,
  63866. floatCls = Ext.baseCSSPrefix + 'border-region-slide-in',
  63867. collapsed = me.collapsed,
  63868. layoutOwner = me.ownerCt || me,
  63869. slideDirection;
  63870. // Already floated
  63871. if (me.el.hasCls(floatCls)) {
  63872. me.slideOutFloatedPanel();
  63873. return;
  63874. }
  63875. if (me.isSliding) {
  63876. return;
  63877. }
  63878. me.isSliding = true;
  63879. // Function to be called when the mouse leaves the floated Panel
  63880. // Slide out when the mouse leaves the region bounded by the slid Component and its placeholder.
  63881. function onMouseLeaveFloated(e) {
  63882. if (!me.isDestroyed) {
  63883. var slideRegion = me.el.getRegion().union(placeholder.el.getRegion()).adjust(1, -1, -1, 1);
  63884. // If mouse is not within slide Region, slide it out
  63885. if (!slideRegion.contains(e.getPoint())) {
  63886. me.slideOutFloatedPanel();
  63887. }
  63888. }
  63889. }
  63890. // Lay out in fully expanded mode to ensure we are at the correct size, and collect our expanded box
  63891. me.placeholder.el.hide();
  63892. me.placeholder.hidden = true;
  63893. me.el.show();
  63894. me.hidden = false;
  63895. me.collapsed = false;
  63896. layoutOwner.updateLayout();
  63897. myBox = me.getBox(true);
  63898. // Then go back immediately to collapsed state from which to initiate the float into view.
  63899. me.placeholder.el.show();
  63900. me.placeholder.hidden = false;
  63901. me.el.hide();
  63902. me.hidden = true;
  63903. me.collapsed = collapsed;
  63904. layoutOwner.updateLayout();
  63905. // Monitor for mouseouting of the placeholder. Hide it if they exit for half a second or more
  63906. me.placeholderMouseMon = placeholder.el.monitorMouseLeave(500, onMouseLeaveFloated);
  63907. me.panelMouseMon = me.el.monitorMouseLeave(500, onMouseLeaveFloated);
  63908. me.el.addCls(floatCls);
  63909. // Hide collapse tool in header if there is one (we might be headerless)
  63910. if (me.collapseTool) {
  63911. me.collapseTool.el.hide();
  63912. }
  63913. switch (me.collapsed) {
  63914. case 'top':
  63915. me.el.setLeftTop(pb.x, pb.y + pb.height - 1);
  63916. slideDirection = 't';
  63917. break;
  63918. case 'right':
  63919. me.el.setLeftTop(pb.x - myBox.width + 1, pb.y);
  63920. slideDirection = 'r';
  63921. break;
  63922. case 'bottom':
  63923. me.el.setLeftTop(pb.x, pb.y - myBox.height + 1);
  63924. slideDirection = 'b';
  63925. break;
  63926. case 'left':
  63927. me.el.setLeftTop(pb.x + pb.width - 1, pb.y);
  63928. slideDirection = 'l';
  63929. break;
  63930. }
  63931. // Remember how we are really collapsed so we can restore it, but also so we can
  63932. // become a layoutRoot while we are floated:
  63933. me.floatedFromCollapse = me.collapsed;
  63934. me.collapsed = me.hidden = false;
  63935. me.el.slideIn(slideDirection, {
  63936. preserveScroll: true,
  63937. listeners: {
  63938. afteranimate: function() {
  63939. me.isSliding = false;
  63940. }
  63941. }
  63942. });
  63943. },
  63944. isLayoutRoot: function() {
  63945. if (this.floatedFromCollapse) {
  63946. return true;
  63947. }
  63948. return this.callParent();
  63949. },
  63950. slideOutFloatedPanel: function() {
  63951. var me = this,
  63952. compEl = this.el,
  63953. collapseDirection;
  63954. if (me.isSliding) {
  63955. return;
  63956. }
  63957. me.isSliding = true;
  63958. me.slideOutFloatedPanelBegin();
  63959. if (typeof me.collapsed == 'string') {
  63960. collapseDirection = me.collapsed.charAt(0);
  63961. }
  63962. compEl.slideOut(collapseDirection, {
  63963. preserveScroll: true,
  63964. listeners: {
  63965. afteranimate: function() {
  63966. me.slideOutFloatedPanelEnd();
  63967. // this would be in slideOutFloatedPanelEnd except that the only other
  63968. // caller removes this cls later
  63969. me.el.removeCls(Ext.baseCSSPrefix + 'border-region-slide-in');
  63970. me.isSliding = false;
  63971. }
  63972. }
  63973. });
  63974. },
  63975. /**
  63976. * This method begins the slide out of the floated panel.
  63977. * @private
  63978. */
  63979. slideOutFloatedPanelBegin: function() {
  63980. var me = this,
  63981. compEl = this.el;
  63982. me.collapsed = me.floatedFromCollapse;
  63983. me.hidden = true;
  63984. me.floatedFromCollapse = null;
  63985. // Remove mouse leave monitors
  63986. compEl.un(me.panelMouseMon);
  63987. me.placeholder.el.un(me.placeholderMouseMon);
  63988. },
  63989. /**
  63990. * This method cleans up after the slide out of the floated panel.
  63991. * @private
  63992. */
  63993. slideOutFloatedPanelEnd: function() {
  63994. if (this.collapseTool) {
  63995. this.collapseTool.el.show();
  63996. }
  63997. },
  63998. /**
  63999. * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will
  64000. * cancel the expand action if it returns false.
  64001. * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
  64002. * {@link #animCollapse} panel config)
  64003. * @return {Ext.panel.Panel} this
  64004. */
  64005. expand: function(animate) {
  64006. var me = this;
  64007. if (me.isCollapsingOrExpanding) {
  64008. return me;
  64009. }
  64010. if (!arguments.length) {
  64011. animate = me.animCollapse;
  64012. }
  64013. if (!me.collapsed && !me.floatedFromCollapse) {
  64014. return me;
  64015. }
  64016. if (me.fireEvent('beforeexpand', me, animate) === false) {
  64017. return me;
  64018. }
  64019. if (me.isPlaceHolderCollapse()) {
  64020. return me.placeholderExpand(animate);
  64021. }
  64022. me.restoreHiddenDocked();
  64023. me.beginExpand();
  64024. me.collapsed = false;
  64025. me.fireHierarchyEvent('expand');
  64026. return me.doCollapseExpand(2, animate);
  64027. },
  64028. placeholderExpand: function(animate) {
  64029. var me = this,
  64030. collapseDir = me.collapsed,
  64031. floatCls = Ext.baseCSSPrefix + 'border-region-slide-in',
  64032. finalPos,
  64033. floatedPos,
  64034. slideInDirection;
  64035. if (me.floatedFromCollapse) {
  64036. floatedPos = me.getPosition(true);
  64037. // these are the same cleanups performed by the normal slideOut mechanism:
  64038. me.slideOutFloatedPanelBegin();
  64039. me.slideOutFloatedPanelEnd();
  64040. }
  64041. me.isCollapsingOrExpanding = 2;
  64042. // Expand me and hide the placeholder
  64043. me.placeholder.hidden = true;
  64044. me.placeholder.el.hide();
  64045. me.collapsed = false;
  64046. me.show();
  64047. if (animate) {
  64048. // Floated, move it back to the floated pos, and thence into the correct place
  64049. if (floatedPos) {
  64050. finalPos = me.el.getXY();
  64051. me.el.setLeftTop(floatedPos[0], floatedPos[1]);
  64052. me.el.moveTo(finalPos[0], finalPos[1], {
  64053. duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration),
  64054. listeners: {
  64055. afteranimate: function() {
  64056. me.el.removeCls(floatCls);
  64057. me.isCollapsingOrExpanding = 0;
  64058. me.fireEvent('expand', me);
  64059. }
  64060. }
  64061. });
  64062. }
  64063. // Not floated, slide it in to the correct place
  64064. else {
  64065. me.hidden = true;
  64066. me.el.addCls(floatCls);
  64067. me.el.hide();
  64068. me.collapsed = collapseDir;
  64069. me.placeholder.show();
  64070. slideInDirection = collapseDir.substr(0, 1);
  64071. // Slide this Component's el back into place, after which we lay out AGAIN
  64072. me.hidden = false;
  64073. me.el.slideIn(slideInDirection, {
  64074. preserveScroll: true,
  64075. duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration),
  64076. listeners: {
  64077. afteranimate: function() {
  64078. me.collapsed = false;
  64079. // the ordering of these two lines appears to be important in
  64080. // IE9. There is an odd expand issue in IE 9 in the border layout
  64081. // example that causes the index1 child of the south dock region
  64082. // to get 'hidden' after a collapse / expand cycle. See
  64083. // EXTJSIV-5318 for details
  64084. me.el.removeCls(floatCls);
  64085. me.placeholder.hide();
  64086. me.isCollapsingOrExpanding = 0;
  64087. me.fireEvent('expand', me);
  64088. }
  64089. }
  64090. });
  64091. }
  64092. } else {
  64093. me.isCollapsingOrExpanding = 0;
  64094. me.fireEvent('expand', me);
  64095. }
  64096. return me;
  64097. },
  64098. /**
  64099. * Invoked after the Panel is Expanded.
  64100. *
  64101. * @param {Boolean} animated
  64102. *
  64103. * @template
  64104. * @protected
  64105. */
  64106. afterExpand: function(animated) {
  64107. var me = this,
  64108. ownerLayout = me.ownerLayout;
  64109. me.isCollapsingOrExpanding = 0;
  64110. if (me.collapseTool) {
  64111. me.collapseTool.setType('collapse-' + me.collapseDirection);
  64112. }
  64113. if (ownerLayout && animated) {
  64114. ownerLayout.onContentChange(me);
  64115. }
  64116. me.fireEvent('expand', me);
  64117. },
  64118. // inherit docs
  64119. setBorder: function(border, targetEl) {
  64120. if (targetEl) {
  64121. // skip out here, the panel will set the border on the body/header during rendering
  64122. return;
  64123. }
  64124. var me = this,
  64125. header = me.header;
  64126. if (!border) {
  64127. border = 0;
  64128. } else {
  64129. border = Ext.Element.unitizeBox((border === true) ? 1 : border);
  64130. }
  64131. if (header) {
  64132. if (header.isHeader) {
  64133. header.setBorder(border);
  64134. } else {
  64135. header.border = border;
  64136. }
  64137. }
  64138. if (me.rendered && me.bodyBorder !== false) {
  64139. me.body.setStyle('border-width', border);
  64140. }
  64141. me.updateLayout();
  64142. me.border = border;
  64143. },
  64144. /**
  64145. * Shortcut for performing an {@link #method-expand} or {@link #method-collapse} based on the current state of the panel.
  64146. * @return {Ext.panel.Panel} this
  64147. */
  64148. toggleCollapse: function() {
  64149. return (this.collapsed || this.floatedFromCollapse) ? this.expand() : this.collapse();
  64150. },
  64151. // private
  64152. getKeyMap : function() {
  64153. return this.keyMap || (this.keyMap = new Ext.util.KeyMap(Ext.apply({
  64154. target: this.el
  64155. }, this.keys)));
  64156. },
  64157. // private
  64158. initDraggable : function(){
  64159. /**
  64160. * @property {Ext.dd.DragSource} dd
  64161. * If this Panel is configured {@link #cfg-draggable}, this property will contain an instance of {@link
  64162. * Ext.dd.DragSource} which handles dragging the Panel.
  64163. *
  64164. * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource} in order to
  64165. * supply behaviour for each stage of the drag/drop process. See {@link #cfg-draggable}.
  64166. */
  64167. this.dd = new Ext.panel.DD(this, Ext.isBoolean(this.draggable) ? null : this.draggable);
  64168. },
  64169. // private - helper function for ghost
  64170. ghostTools : function() {
  64171. var tools = [],
  64172. header = this.header,
  64173. headerTools = header ? header.query('tool[hidden=false]') : [],
  64174. t, tLen, tool;
  64175. if (headerTools.length) {
  64176. t = 0;
  64177. tLen = headerTools.length;
  64178. for (; t < tLen; t++) {
  64179. tool = headerTools[t];
  64180. // Some tools can be full components, and copying them into the ghost
  64181. // actually removes them from the owning panel. You could also potentially
  64182. // end up with duplicate DOM ids as well. To avoid any issues we just make
  64183. // a simple bare-minimum clone of each tool for ghosting purposes.
  64184. tools.push({
  64185. type: tool.type
  64186. });
  64187. }
  64188. } else {
  64189. tools = [{
  64190. type: 'placeholder'
  64191. }];
  64192. }
  64193. return tools;
  64194. },
  64195. // private - used for dragging
  64196. ghost: function(cls) {
  64197. var me = this,
  64198. ghostPanel = me.ghostPanel,
  64199. box = me.getBox(),
  64200. header;
  64201. if (!ghostPanel) {
  64202. ghostPanel = new Ext.panel.Panel({
  64203. renderTo: document.body,
  64204. floating: {
  64205. shadow: false
  64206. },
  64207. frame: me.frame && !me.alwaysFramed,
  64208. alwaysFramed: me.alwaysFramed,
  64209. overlapHeader: me.overlapHeader,
  64210. headerPosition: me.headerPosition,
  64211. baseCls: me.baseCls,
  64212. cls: me.baseCls + '-ghost ' + (cls ||'')
  64213. });
  64214. me.ghostPanel = ghostPanel;
  64215. } else {
  64216. ghostPanel.el.show();
  64217. }
  64218. ghostPanel.floatParent = me.floatParent;
  64219. if (me.floating) {
  64220. ghostPanel.setZIndex(Ext.Number.from(me.el.getStyle('zIndex'), 0));
  64221. } else {
  64222. ghostPanel.toFront();
  64223. }
  64224. if (!(me.preventHeader || (me.header === false))) {
  64225. header = ghostPanel.header;
  64226. // restore options
  64227. if (header) {
  64228. header.suspendLayouts();
  64229. Ext.Array.forEach(header.query('tool'), header.remove, header);
  64230. header.resumeLayouts();
  64231. }
  64232. ghostPanel.addTool(me.ghostTools());
  64233. ghostPanel.setTitle(me.title);
  64234. ghostPanel.setIconCls(me.iconCls);
  64235. }
  64236. ghostPanel.setPagePosition(box.x, box.y);
  64237. ghostPanel.setSize(box.width, box.height);
  64238. me.el.hide();
  64239. return ghostPanel;
  64240. },
  64241. // private
  64242. unghost: function(show, matchPosition) {
  64243. var me = this;
  64244. if (!me.ghostPanel) {
  64245. return;
  64246. }
  64247. if (show !== false) {
  64248. // Show el first, so that position adjustment in setPagePosition
  64249. // will work when relative positioned elements have their XY read.
  64250. me.el.show();
  64251. if (matchPosition !== false) {
  64252. me.setPagePosition(me.ghostPanel.el.getXY());
  64253. if (me.hideMode == 'offsets') {
  64254. // clear the hidden style because we just repositioned
  64255. delete me.el.hideModeStyles;
  64256. }
  64257. }
  64258. Ext.defer(me.focus, 10, me);
  64259. }
  64260. me.ghostPanel.el.hide();
  64261. },
  64262. beginDrag: function() {
  64263. if (this.floatingDescendants) {
  64264. this.floatingDescendants.hide();
  64265. }
  64266. },
  64267. endDrag: function() {
  64268. if (this.floatingDescendants) {
  64269. this.floatingDescendants.show();
  64270. }
  64271. },
  64272. initResizable: function(resizable) {
  64273. if (this.collapsed) {
  64274. resizable.disabled = true;
  64275. }
  64276. this.callParent([resizable]);
  64277. }
  64278. }, function() {
  64279. this.prototype.animCollapse = Ext.enableFx;
  64280. });
  64281. /**
  64282. * This is the base class for {@link Ext.tip.QuickTip} and {@link Ext.tip.ToolTip} that provides the basic layout and
  64283. * positioning that all tip-based classes require. This class can be used directly for simple, statically-positioned
  64284. * tips that are displayed programmatically, or it can be extended to provide custom tip implementations.
  64285. * @xtype tip
  64286. */
  64287. Ext.define('Ext.tip.Tip', {
  64288. extend: 'Ext.panel.Panel',
  64289. alternateClassName: 'Ext.Tip',
  64290. /**
  64291. * @cfg {Boolean} [closable=false]
  64292. * True to render a close tool button into the tooltip header.
  64293. */
  64294. /**
  64295. * @cfg {Number} width
  64296. * Width in pixels of the tip. Width will be ignored if it
  64297. * exceeds the bounds of {@link #minWidth} or {@link #maxWidth}. The maximum
  64298. * supported value is 500.
  64299. *
  64300. * Defaults to auto.
  64301. */
  64302. /**
  64303. * @cfg {Number} minWidth
  64304. * The minimum width of the tip in pixels.
  64305. */
  64306. minWidth : 40,
  64307. /**
  64308. * @cfg {Number} maxWidth
  64309. * The maximum width of the tip in pixels. The maximum supported value is 500.
  64310. */
  64311. maxWidth : 300,
  64312. /**
  64313. * @cfg {Boolean/String} shadow
  64314. * True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
  64315. * for bottom-right shadow.
  64316. */
  64317. shadow : "sides",
  64318. /**
  64319. * @cfg {String} defaultAlign
  64320. * **Experimental**. The default {@link Ext.Element#alignTo} anchor position value
  64321. * for this tip relative to its element of origin.
  64322. */
  64323. defaultAlign : "tl-bl?",
  64324. /**
  64325. * @cfg {Boolean} constrainPosition
  64326. * If true, then the tooltip will be automatically constrained to stay within
  64327. * the browser viewport.
  64328. */
  64329. constrainPosition : true,
  64330. // private panel overrides
  64331. autoRender: true,
  64332. hidden: true,
  64333. baseCls: Ext.baseCSSPrefix + 'tip',
  64334. floating: {
  64335. shadow: true,
  64336. shim: true,
  64337. constrain: true
  64338. },
  64339. focusOnToFront: false,
  64340. /**
  64341. * @cfg {String} closeAction
  64342. * The action to take when the close header tool is clicked:
  64343. *
  64344. * - **{@link #method-destroy}** : {@link #method-remove remove} the window from the DOM and
  64345. * {@link Ext.Component#method-destroy destroy} it and all descendant Components. The
  64346. * window will **not** be available to be redisplayed via the {@link #method-show} method.
  64347. *
  64348. * - **{@link #method-hide}** : **Default.** {@link #method-hide} the window by setting visibility
  64349. * to hidden and applying negative offsets. The window will be available to be
  64350. * redisplayed via the {@link #method-show} method.
  64351. *
  64352. * **Note:** This behavior has changed! setting *does* affect the {@link #method-close} method
  64353. * which will invoke the approriate closeAction.
  64354. */
  64355. closeAction: 'hide',
  64356. ariaRole: 'tooltip',
  64357. // Flag to Renderable to always look up the framing styles for this Component
  64358. alwaysFramed: true,
  64359. frameHeader: false,
  64360. initComponent: function() {
  64361. var me = this;
  64362. me.floating = Ext.apply({}, {shadow: me.shadow}, me.self.prototype.floating);
  64363. me.callParent(arguments);
  64364. // Or in the deprecated config. Floating.doConstrain only constrains if the constrain property is truthy.
  64365. me.constrain = me.constrain || me.constrainPosition;
  64366. },
  64367. /**
  64368. * Shows this tip at the specified XY position. Example usage:
  64369. *
  64370. * // Show the tip at x:50 and y:100
  64371. * tip.showAt([50,100]);
  64372. *
  64373. * @param {Number[]} xy An array containing the x and y coordinates
  64374. */
  64375. showAt : function(xy){
  64376. var me = this;
  64377. this.callParent(arguments);
  64378. // Show may have been vetoed.
  64379. if (me.isVisible()) {
  64380. me.setPagePosition(xy[0], xy[1]);
  64381. if (me.constrainPosition || me.constrain) {
  64382. me.doConstrain();
  64383. }
  64384. me.toFront(true);
  64385. }
  64386. },
  64387. /**
  64388. * **Experimental**. Shows this tip at a position relative to another element using
  64389. * a standard {@link Ext.Element#alignTo} anchor position value. Example usage:
  64390. *
  64391. * // Show the tip at the default position ('tl-br?')
  64392. * tip.showBy('my-el');
  64393. *
  64394. * // Show the tip's top-left corner anchored to the element's top-right corner
  64395. * tip.showBy('my-el', 'tl-tr');
  64396. *
  64397. * @param {String/HTMLElement/Ext.Element} el An HTMLElement, Ext.Element or string
  64398. * id of the target element to align to.
  64399. *
  64400. * @param {String} [position] A valid {@link Ext.Element#alignTo} anchor position.
  64401. *
  64402. * Defaults to 'tl-br?' or {@link #defaultAlign} if specified.
  64403. */
  64404. showBy : function(el, pos) {
  64405. this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign));
  64406. },
  64407. /**
  64408. * @private
  64409. * Set Tip draggable using base Component's draggability
  64410. */
  64411. initDraggable : function(){
  64412. var me = this;
  64413. me.draggable = {
  64414. el: me.getDragEl(),
  64415. delegate: me.header.el,
  64416. constrain: me,
  64417. constrainTo: me.el.getScopeParent()
  64418. };
  64419. // Important: Bypass Panel's initDraggable. Call direct to Component's implementation.
  64420. Ext.Component.prototype.initDraggable.call(me);
  64421. },
  64422. // Tip does not ghost. Drag is "live"
  64423. ghost: undefined,
  64424. unghost: undefined
  64425. });
  64426. /**
  64427. * ToolTip is a {@link Ext.tip.Tip} implementation that handles the common case of displaying a
  64428. * tooltip when hovering over a certain element or elements on the page. It allows fine-grained
  64429. * control over the tooltip's alignment relative to the target element or mouse, and the timing
  64430. * of when it is automatically shown and hidden.
  64431. *
  64432. * This implementation does **not** have a built-in method of automatically populating the tooltip's
  64433. * text based on the target element; you must either configure a fixed {@link #html} value for each
  64434. * ToolTip instance, or implement custom logic (e.g. in a {@link #beforeshow} event listener) to
  64435. * generate the appropriate tooltip content on the fly. See {@link Ext.tip.QuickTip} for a more
  64436. * convenient way of automatically populating and configuring a tooltip based on specific DOM
  64437. * attributes of each target element.
  64438. *
  64439. * # Basic Example
  64440. *
  64441. * var tip = Ext.create('Ext.tip.ToolTip', {
  64442. * target: 'clearButton',
  64443. * html: 'Press this button to clear the form'
  64444. * });
  64445. *
  64446. * {@img Ext.tip.ToolTip/Ext.tip.ToolTip1.png Basic Ext.tip.ToolTip}
  64447. *
  64448. * # Delegation
  64449. *
  64450. * In addition to attaching a ToolTip to a single element, you can also use delegation to attach
  64451. * one ToolTip to many elements under a common parent. This is more efficient than creating many
  64452. * ToolTip instances. To do this, point the {@link #target} config to a common ancestor of all the
  64453. * elements, and then set the {@link #delegate} config to a CSS selector that will select all the
  64454. * appropriate sub-elements.
  64455. *
  64456. * When using delegation, it is likely that you will want to programmatically change the content
  64457. * of the ToolTip based on each delegate element; you can do this by implementing a custom
  64458. * listener for the {@link #beforeshow} event. Example:
  64459. *
  64460. * var store = Ext.create('Ext.data.ArrayStore', {
  64461. * fields: ['company', 'price', 'change'],
  64462. * data: [
  64463. * ['3m Co', 71.72, 0.02],
  64464. * ['Alcoa Inc', 29.01, 0.42],
  64465. * ['Altria Group Inc', 83.81, 0.28],
  64466. * ['American Express Company', 52.55, 0.01],
  64467. * ['American International Group, Inc.', 64.13, 0.31],
  64468. * ['AT&T Inc.', 31.61, -0.48]
  64469. * ]
  64470. * });
  64471. *
  64472. * var grid = Ext.create('Ext.grid.Panel', {
  64473. * title: 'Array Grid',
  64474. * store: store,
  64475. * columns: [
  64476. * {text: 'Company', flex: 1, dataIndex: 'company'},
  64477. * {text: 'Price', width: 75, dataIndex: 'price'},
  64478. * {text: 'Change', width: 75, dataIndex: 'change'}
  64479. * ],
  64480. * height: 200,
  64481. * width: 400,
  64482. * renderTo: Ext.getBody()
  64483. * });
  64484. *
  64485. * grid.getView().on('render', function(view) {
  64486. * view.tip = Ext.create('Ext.tip.ToolTip', {
  64487. * // The overall target element.
  64488. * target: view.el,
  64489. * // Each grid row causes its own separate show and hide.
  64490. * delegate: view.itemSelector,
  64491. * // Moving within the row should not hide the tip.
  64492. * trackMouse: true,
  64493. * // Render immediately so that tip.body can be referenced prior to the first show.
  64494. * renderTo: Ext.getBody(),
  64495. * listeners: {
  64496. * // Change content dynamically depending on which element triggered the show.
  64497. * beforeshow: function updateTipBody(tip) {
  64498. * tip.update('Over company "' + view.getRecord(tip.triggerElement).get('company') + '"');
  64499. * }
  64500. * }
  64501. * });
  64502. * });
  64503. *
  64504. * {@img Ext.tip.ToolTip/Ext.tip.ToolTip2.png Ext.tip.ToolTip with delegation}
  64505. *
  64506. * # Alignment
  64507. *
  64508. * The following configuration properties allow control over how the ToolTip is aligned relative to
  64509. * the target element and/or mouse pointer:
  64510. *
  64511. * - {@link #anchor}
  64512. * - {@link #anchorToTarget}
  64513. * - {@link #anchorOffset}
  64514. * - {@link #trackMouse}
  64515. * - {@link #mouseOffset}
  64516. *
  64517. * # Showing/Hiding
  64518. *
  64519. * The following configuration properties allow control over how and when the ToolTip is automatically
  64520. * shown and hidden:
  64521. *
  64522. * - {@link #autoHide}
  64523. * - {@link #showDelay}
  64524. * - {@link #hideDelay}
  64525. * - {@link #dismissDelay}
  64526. *
  64527. * @docauthor Jason Johnston <jason@sencha.com>
  64528. */
  64529. Ext.define('Ext.tip.ToolTip', {
  64530. extend: 'Ext.tip.Tip',
  64531. alias: 'widget.tooltip',
  64532. alternateClassName: 'Ext.ToolTip',
  64533. /**
  64534. * @property {HTMLElement} triggerElement
  64535. * When a ToolTip is configured with the `{@link #delegate}`
  64536. * option to cause selected child elements of the `{@link #target}`
  64537. * Element to each trigger a separate show event, this property is set to
  64538. * the DOM element which triggered the show.
  64539. */
  64540. /**
  64541. * @cfg {HTMLElement/Ext.Element/String} target
  64542. * The target element or string id to monitor for mouseover events to trigger
  64543. * showing this ToolTip.
  64544. */
  64545. /**
  64546. * @cfg {Boolean} [autoHide=true]
  64547. * True to automatically hide the tooltip after the
  64548. * mouse exits the target element or after the `{@link #dismissDelay}`
  64549. * has expired if set. If `{@link #closable} = true`
  64550. * a close tool button will be rendered into the tooltip header.
  64551. */
  64552. autoHide: true,
  64553. /**
  64554. * @cfg {Number} showDelay
  64555. * Delay in milliseconds before the tooltip displays after the mouse enters the target element.
  64556. */
  64557. showDelay: 500,
  64558. /**
  64559. * @cfg {Number} hideDelay
  64560. * Delay in milliseconds after the mouse exits the target element but before the tooltip actually hides.
  64561. * Set to 0 for the tooltip to hide immediately.
  64562. */
  64563. hideDelay: 200,
  64564. /**
  64565. * @cfg {Number} dismissDelay
  64566. * Delay in milliseconds before the tooltip automatically hides. To disable automatic hiding, set
  64567. * dismissDelay = 0.
  64568. */
  64569. dismissDelay: 5000,
  64570. /**
  64571. * @cfg {Number[]} [mouseOffset=[15,18]]
  64572. * An XY offset from the mouse position where the tooltip should be shown.
  64573. */
  64574. /**
  64575. * @cfg {Boolean} trackMouse
  64576. * True to have the tooltip follow the mouse as it moves over the target element.
  64577. */
  64578. trackMouse: false,
  64579. /**
  64580. * @cfg {String} anchor
  64581. * If specified, indicates that the tip should be anchored to a
  64582. * particular side of the target element or mouse pointer ("top", "right", "bottom",
  64583. * or "left"), with an arrow pointing back at the target or mouse pointer. If
  64584. * {@link #constrainPosition} is enabled, this will be used as a preferred value
  64585. * only and may be flipped as needed.
  64586. */
  64587. /**
  64588. * @cfg {Boolean} anchorToTarget
  64589. * True to anchor the tooltip to the target element, false to anchor it relative to the mouse coordinates.
  64590. * When `anchorToTarget` is true, use `{@link #defaultAlign}` to control tooltip alignment to the
  64591. * target element. When `anchorToTarget` is false, use `{@link #anchor}` instead to control alignment.
  64592. */
  64593. anchorToTarget: true,
  64594. /**
  64595. * @cfg {Number} anchorOffset
  64596. * A numeric pixel value used to offset the default position of the anchor arrow. When the anchor
  64597. * position is on the top or bottom of the tooltip, `anchorOffset` will be used as a horizontal offset.
  64598. * Likewise, when the anchor position is on the left or right side, `anchorOffset` will be used as
  64599. * a vertical offset.
  64600. */
  64601. anchorOffset: 0,
  64602. /**
  64603. * @cfg {String} delegate
  64604. *
  64605. * A {@link Ext.DomQuery DomQuery} selector which allows selection of individual elements within the
  64606. * `{@link #target}` element to trigger showing and hiding the ToolTip as the mouse moves within the
  64607. * target.
  64608. *
  64609. * When specified, the child element of the target which caused a show event is placed into the
  64610. * `{@link #triggerElement}` property before the ToolTip is shown.
  64611. *
  64612. * This may be useful when a Component has regular, repeating elements in it, each of which need a
  64613. * ToolTip which contains information specific to that element.
  64614. *
  64615. * See the delegate example in class documentation of {@link Ext.tip.ToolTip}.
  64616. */
  64617. // private
  64618. targetCounter: 0,
  64619. quickShowInterval: 250,
  64620. // private
  64621. initComponent: function() {
  64622. var me = this;
  64623. me.callParent(arguments);
  64624. me.lastActive = new Date();
  64625. me.setTarget(me.target);
  64626. me.origAnchor = me.anchor;
  64627. },
  64628. // private
  64629. onRender: function(ct, position) {
  64630. var me = this;
  64631. me.callParent(arguments);
  64632. me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
  64633. me.anchorEl = me.el.createChild({
  64634. cls: Ext.baseCSSPrefix + 'tip-anchor ' + me.anchorCls
  64635. });
  64636. },
  64637. /**
  64638. * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
  64639. * @param {String/HTMLElement/Ext.Element} t The Element, HtmlElement, or ID of an element to bind to
  64640. */
  64641. setTarget: function(target) {
  64642. var me = this,
  64643. t = Ext.get(target),
  64644. tg;
  64645. if (me.target) {
  64646. tg = Ext.get(me.target);
  64647. me.mun(tg, 'mouseover', me.onTargetOver, me);
  64648. me.mun(tg, 'mouseout', me.onTargetOut, me);
  64649. me.mun(tg, 'mousemove', me.onMouseMove, me);
  64650. }
  64651. me.target = t;
  64652. if (t) {
  64653. me.mon(t, {
  64654. // TODO - investigate why IE6/7 seem to fire recursive resize in e.getXY
  64655. // breaking QuickTip#onTargetOver (EXTJSIV-1608)
  64656. freezeEvent: true,
  64657. mouseover: me.onTargetOver,
  64658. mouseout: me.onTargetOut,
  64659. mousemove: me.onMouseMove,
  64660. scope: me
  64661. });
  64662. }
  64663. if (me.anchor) {
  64664. me.anchorTarget = me.target;
  64665. }
  64666. },
  64667. // private
  64668. onMouseMove: function(e) {
  64669. var me = this,
  64670. t = me.delegate ? e.getTarget(me.delegate) : me.triggerElement = true,
  64671. xy;
  64672. if (t) {
  64673. me.targetXY = e.getXY();
  64674. if (t === me.triggerElement) {
  64675. if (!me.hidden && me.trackMouse) {
  64676. xy = me.getTargetXY();
  64677. if (me.constrainPosition) {
  64678. xy = me.el.adjustForConstraints(xy, me.el.getScopeParent());
  64679. }
  64680. me.setPagePosition(xy);
  64681. }
  64682. } else {
  64683. me.hide();
  64684. me.lastActive = new Date(0);
  64685. me.onTargetOver(e);
  64686. }
  64687. } else if ((!me.closable && me.isVisible()) && me.autoHide !== false) {
  64688. me.hide();
  64689. }
  64690. },
  64691. // private
  64692. getTargetXY: function() {
  64693. var me = this,
  64694. mouseOffset,
  64695. offsets, xy, dw, dh, de, bd, scrollX, scrollY, axy, sz, constrainPosition;
  64696. if (me.delegate) {
  64697. me.anchorTarget = me.triggerElement;
  64698. }
  64699. if (me.anchor) {
  64700. me.targetCounter++;
  64701. offsets = me.getOffsets();
  64702. xy = (me.anchorToTarget && !me.trackMouse) ? me.el.getAlignToXY(me.anchorTarget, me.getAnchorAlign()) : me.targetXY;
  64703. dw = Ext.Element.getViewWidth() - 5;
  64704. dh = Ext.Element.getViewHeight() - 5;
  64705. de = document.documentElement;
  64706. bd = document.body;
  64707. scrollX = (de.scrollLeft || bd.scrollLeft || 0) + 5;
  64708. scrollY = (de.scrollTop || bd.scrollTop || 0) + 5;
  64709. axy = [xy[0] + offsets[0], xy[1] + offsets[1]];
  64710. sz = me.getSize();
  64711. constrainPosition = me.constrainPosition;
  64712. me.anchorEl.removeCls(me.anchorCls);
  64713. if (me.targetCounter < 2 && constrainPosition) {
  64714. if (axy[0] < scrollX) {
  64715. if (me.anchorToTarget) {
  64716. me.defaultAlign = 'l-r';
  64717. if (me.mouseOffset) {
  64718. me.mouseOffset[0] *= -1;
  64719. }
  64720. }
  64721. me.anchor = 'left';
  64722. return me.getTargetXY();
  64723. }
  64724. if (axy[0] + sz.width > dw) {
  64725. if (me.anchorToTarget) {
  64726. me.defaultAlign = 'r-l';
  64727. if (me.mouseOffset) {
  64728. me.mouseOffset[0] *= -1;
  64729. }
  64730. }
  64731. me.anchor = 'right';
  64732. return me.getTargetXY();
  64733. }
  64734. if (axy[1] < scrollY) {
  64735. if (me.anchorToTarget) {
  64736. me.defaultAlign = 't-b';
  64737. if (me.mouseOffset) {
  64738. me.mouseOffset[1] *= -1;
  64739. }
  64740. }
  64741. me.anchor = 'top';
  64742. return me.getTargetXY();
  64743. }
  64744. if (axy[1] + sz.height > dh) {
  64745. if (me.anchorToTarget) {
  64746. me.defaultAlign = 'b-t';
  64747. if (me.mouseOffset) {
  64748. me.mouseOffset[1] *= -1;
  64749. }
  64750. }
  64751. me.anchor = 'bottom';
  64752. return me.getTargetXY();
  64753. }
  64754. }
  64755. me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
  64756. me.anchorEl.addCls(me.anchorCls);
  64757. me.targetCounter = 0;
  64758. return axy;
  64759. } else {
  64760. mouseOffset = me.getMouseOffset();
  64761. return (me.targetXY) ? [me.targetXY[0] + mouseOffset[0], me.targetXY[1] + mouseOffset[1]] : mouseOffset;
  64762. }
  64763. },
  64764. getMouseOffset: function() {
  64765. var me = this,
  64766. offset = me.anchor ? [0, 0] : [15, 18];
  64767. if (me.mouseOffset) {
  64768. offset[0] += me.mouseOffset[0];
  64769. offset[1] += me.mouseOffset[1];
  64770. }
  64771. return offset;
  64772. },
  64773. // private
  64774. getAnchorPosition: function() {
  64775. var me = this,
  64776. m;
  64777. if (me.anchor) {
  64778. me.tipAnchor = me.anchor.charAt(0);
  64779. } else {
  64780. m = me.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
  64781. if (!m) {
  64782. Ext.Error.raise('The AnchorTip.defaultAlign value "' + me.defaultAlign + '" is invalid.');
  64783. }
  64784. me.tipAnchor = m[1].charAt(0);
  64785. }
  64786. switch (me.tipAnchor) {
  64787. case 't':
  64788. return 'top';
  64789. case 'b':
  64790. return 'bottom';
  64791. case 'r':
  64792. return 'right';
  64793. }
  64794. return 'left';
  64795. },
  64796. // private
  64797. getAnchorAlign: function() {
  64798. switch (this.anchor) {
  64799. case 'top':
  64800. return 'tl-bl';
  64801. case 'left':
  64802. return 'tl-tr';
  64803. case 'right':
  64804. return 'tr-tl';
  64805. default:
  64806. return 'bl-tl';
  64807. }
  64808. },
  64809. // private
  64810. getOffsets: function() {
  64811. var me = this,
  64812. mouseOffset,
  64813. offsets,
  64814. ap = me.getAnchorPosition().charAt(0);
  64815. if (me.anchorToTarget && !me.trackMouse) {
  64816. switch (ap) {
  64817. case 't':
  64818. offsets = [0, 9];
  64819. break;
  64820. case 'b':
  64821. offsets = [0, -13];
  64822. break;
  64823. case 'r':
  64824. offsets = [ - 13, 0];
  64825. break;
  64826. default:
  64827. offsets = [9, 0];
  64828. break;
  64829. }
  64830. } else {
  64831. switch (ap) {
  64832. case 't':
  64833. offsets = [ - 15 - me.anchorOffset, 30];
  64834. break;
  64835. case 'b':
  64836. offsets = [ - 19 - me.anchorOffset, -13 - me.el.dom.offsetHeight];
  64837. break;
  64838. case 'r':
  64839. offsets = [ - 15 - me.el.dom.offsetWidth, -13 - me.anchorOffset];
  64840. break;
  64841. default:
  64842. offsets = [25, -13 - me.anchorOffset];
  64843. break;
  64844. }
  64845. }
  64846. mouseOffset = me.getMouseOffset();
  64847. offsets[0] += mouseOffset[0];
  64848. offsets[1] += mouseOffset[1];
  64849. return offsets;
  64850. },
  64851. // private
  64852. onTargetOver: function(e) {
  64853. var me = this,
  64854. t;
  64855. if (me.disabled || e.within(me.target.dom, true)) {
  64856. return;
  64857. }
  64858. t = e.getTarget(me.delegate);
  64859. if (t) {
  64860. me.triggerElement = t;
  64861. me.clearTimer('hide');
  64862. me.targetXY = e.getXY();
  64863. me.delayShow();
  64864. }
  64865. },
  64866. // private
  64867. delayShow: function() {
  64868. var me = this;
  64869. if (me.hidden && !me.showTimer) {
  64870. if (Ext.Date.getElapsed(me.lastActive) < me.quickShowInterval) {
  64871. me.show();
  64872. } else {
  64873. me.showTimer = Ext.defer(me.show, me.showDelay, me);
  64874. }
  64875. }
  64876. else if (!me.hidden && me.autoHide !== false) {
  64877. me.show();
  64878. }
  64879. },
  64880. onShowVeto: function(){
  64881. this.callParent();
  64882. this.clearTimer('show');
  64883. },
  64884. // private
  64885. onTargetOut: function(e) {
  64886. var me = this;
  64887. // If disabled, moving within the current target, ignore the mouseout
  64888. // EventObject.within is the only correct way to determine this.
  64889. if (me.disabled || e.within(me.target.dom, true)) {
  64890. return;
  64891. }
  64892. me.clearTimer('show');
  64893. if (me.autoHide !== false) {
  64894. me.delayHide();
  64895. }
  64896. },
  64897. // private
  64898. delayHide: function() {
  64899. var me = this;
  64900. if (!me.hidden && !me.hideTimer) {
  64901. me.hideTimer = Ext.defer(me.hide, me.hideDelay, me);
  64902. }
  64903. },
  64904. /**
  64905. * Hides this tooltip if visible.
  64906. */
  64907. hide: function() {
  64908. var me = this;
  64909. me.clearTimer('dismiss');
  64910. me.lastActive = new Date();
  64911. if (me.anchorEl) {
  64912. me.anchorEl.hide();
  64913. }
  64914. me.callParent(arguments);
  64915. delete me.triggerElement;
  64916. },
  64917. /**
  64918. * Shows this tooltip at the current event target XY position.
  64919. */
  64920. show: function() {
  64921. var me = this;
  64922. // Show this Component first, so that sizing can be calculated
  64923. // pre-show it off screen so that the el will have dimensions
  64924. this.callParent();
  64925. if (this.hidden === false) {
  64926. me.setPagePosition(-10000, -10000);
  64927. if (me.anchor) {
  64928. me.anchor = me.origAnchor;
  64929. }
  64930. if (!me.calledFromShowAt) {
  64931. me.showAt(me.getTargetXY());
  64932. }
  64933. if (me.anchor) {
  64934. me.syncAnchor();
  64935. me.anchorEl.show();
  64936. } else {
  64937. me.anchorEl.hide();
  64938. }
  64939. }
  64940. },
  64941. // inherit docs
  64942. showAt: function(xy) {
  64943. var me = this;
  64944. me.lastActive = new Date();
  64945. me.clearTimers();
  64946. me.calledFromShowAt = true;
  64947. // Only call if this is hidden. May have been called from show above.
  64948. if (!me.isVisible()) {
  64949. this.callParent(arguments);
  64950. }
  64951. // Show may have been vetoed.
  64952. if (me.isVisible()) {
  64953. me.setPagePosition(xy[0], xy[1]);
  64954. if (me.constrainPosition || me.constrain) {
  64955. me.doConstrain();
  64956. }
  64957. me.toFront(true);
  64958. me.el.sync(true);
  64959. if (me.dismissDelay && me.autoHide !== false) {
  64960. me.dismissTimer = Ext.defer(me.hide, me.dismissDelay, me);
  64961. }
  64962. if (me.anchor) {
  64963. me.syncAnchor();
  64964. if (!me.anchorEl.isVisible()) {
  64965. me.anchorEl.show();
  64966. }
  64967. } else {
  64968. me.anchorEl.hide();
  64969. }
  64970. }
  64971. delete me.calledFromShowAt;
  64972. },
  64973. // private
  64974. syncAnchor: function() {
  64975. var me = this,
  64976. anchorPos,
  64977. targetPos,
  64978. offset;
  64979. switch (me.tipAnchor.charAt(0)) {
  64980. case 't':
  64981. anchorPos = 'b';
  64982. targetPos = 'tl';
  64983. offset = [20 + me.anchorOffset, 1];
  64984. break;
  64985. case 'r':
  64986. anchorPos = 'l';
  64987. targetPos = 'tr';
  64988. offset = [ - 1, 12 + me.anchorOffset];
  64989. break;
  64990. case 'b':
  64991. anchorPos = 't';
  64992. targetPos = 'bl';
  64993. offset = [20 + me.anchorOffset, -1];
  64994. break;
  64995. default:
  64996. anchorPos = 'r';
  64997. targetPos = 'tl';
  64998. offset = [1, 12 + me.anchorOffset];
  64999. break;
  65000. }
  65001. me.anchorEl.alignTo(me.el, anchorPos + '-' + targetPos, offset);
  65002. me.anchorEl.setStyle('z-index', parseInt(me.el.getZIndex(), 10) || 0 + 1).setVisibilityMode(Ext.Element.DISPLAY);
  65003. },
  65004. // private
  65005. setPagePosition: function(x, y) {
  65006. var me = this;
  65007. me.callParent(arguments);
  65008. if (me.anchor) {
  65009. me.syncAnchor();
  65010. }
  65011. },
  65012. // private
  65013. clearTimer: function(name) {
  65014. name = name + 'Timer';
  65015. clearTimeout(this[name]);
  65016. delete this[name];
  65017. },
  65018. // private
  65019. clearTimers: function() {
  65020. var me = this;
  65021. me.clearTimer('show');
  65022. me.clearTimer('dismiss');
  65023. me.clearTimer('hide');
  65024. },
  65025. // private
  65026. onShow: function() {
  65027. var me = this;
  65028. me.callParent();
  65029. me.mon(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
  65030. },
  65031. // private
  65032. onHide: function() {
  65033. var me = this;
  65034. me.callParent();
  65035. me.mun(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
  65036. },
  65037. // private
  65038. onDocMouseDown: function(e) {
  65039. var me = this;
  65040. if (!me.closable && !e.within(me.el.dom)) {
  65041. me.disable();
  65042. Ext.defer(me.doEnable, 100, me);
  65043. }
  65044. },
  65045. // private
  65046. doEnable: function() {
  65047. if (!this.isDestroyed) {
  65048. this.enable();
  65049. }
  65050. },
  65051. // private
  65052. onDisable: function() {
  65053. this.callParent();
  65054. this.clearTimers();
  65055. this.hide();
  65056. },
  65057. beforeDestroy: function() {
  65058. var me = this;
  65059. me.clearTimers();
  65060. Ext.destroy(me.anchorEl);
  65061. delete me.anchorEl;
  65062. delete me.target;
  65063. delete me.anchorTarget;
  65064. delete me.triggerElement;
  65065. me.callParent();
  65066. },
  65067. // private
  65068. onDestroy: function() {
  65069. Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
  65070. this.callParent();
  65071. }
  65072. });
  65073. /**
  65074. * A specialized tooltip class for tooltips that can be specified in markup and automatically managed
  65075. * by the global {@link Ext.tip.QuickTipManager} instance. See the QuickTipManager documentation for
  65076. * additional usage details and examples.
  65077. */
  65078. Ext.define('Ext.tip.QuickTip', {
  65079. extend: 'Ext.tip.ToolTip',
  65080. alias: 'widget.quicktip',
  65081. alternateClassName: 'Ext.QuickTip',
  65082. /**
  65083. * @cfg {String/HTMLElement/Ext.Element} target
  65084. * The target HTMLElement, Ext.Element or id to associate with this Quicktip.
  65085. *
  65086. * Defaults to the document.
  65087. */
  65088. /**
  65089. * @cfg {Boolean} interceptTitles
  65090. * True to automatically use the element's DOM title value if available.
  65091. */
  65092. interceptTitles : false,
  65093. // Force creation of header Component
  65094. title: '&#160;',
  65095. // private
  65096. tagConfig : {
  65097. namespace : "data-",
  65098. attribute : "qtip",
  65099. width : "qwidth",
  65100. target : "target",
  65101. title : "qtitle",
  65102. hide : "hide",
  65103. cls : "qclass",
  65104. align : "qalign",
  65105. anchor : "anchor"
  65106. },
  65107. // private
  65108. initComponent : function(){
  65109. var me = this;
  65110. me.target = me.target || Ext.getDoc();
  65111. me.targets = me.targets || {};
  65112. me.callParent();
  65113. },
  65114. /**
  65115. * Configures a new quick tip instance and assigns it to a target element.
  65116. *
  65117. * For example usage, see the {@link Ext.tip.QuickTipManager} class header.
  65118. *
  65119. * @param {Object} config The config object with the following properties:
  65120. * @param config.autoHide
  65121. * @param config.cls
  65122. * @param config.dismissDelay overrides the singleton value
  65123. * @param config.target required
  65124. * @param config.text required
  65125. * @param config.title
  65126. * @param config.width
  65127. */
  65128. register : function(config){
  65129. var configs = Ext.isArray(config) ? config : arguments,
  65130. i = 0,
  65131. len = configs.length,
  65132. target, j, targetLen;
  65133. for (; i < len; i++) {
  65134. config = configs[i];
  65135. target = config.target;
  65136. if (target) {
  65137. if (Ext.isArray(target)) {
  65138. for (j = 0, targetLen = target.length; j < targetLen; j++) {
  65139. this.targets[Ext.id(target[j])] = config;
  65140. }
  65141. } else{
  65142. this.targets[Ext.id(target)] = config;
  65143. }
  65144. }
  65145. }
  65146. },
  65147. /**
  65148. * Removes this quick tip from its element and destroys it.
  65149. * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip
  65150. * is to be removed or ID of the element.
  65151. */
  65152. unregister : function(el){
  65153. delete this.targets[Ext.id(el)];
  65154. },
  65155. /**
  65156. * Hides a visible tip or cancels an impending show for a particular element.
  65157. * @param {String/HTMLElement/Ext.Element} el The element that is the target of
  65158. * the tip or ID of the element.
  65159. */
  65160. cancelShow: function(el){
  65161. var me = this,
  65162. activeTarget = me.activeTarget;
  65163. el = Ext.get(el).dom;
  65164. if (me.isVisible()) {
  65165. if (activeTarget && activeTarget.el == el) {
  65166. me.hide();
  65167. }
  65168. } else if (activeTarget && activeTarget.el == el) {
  65169. me.clearTimer('show');
  65170. }
  65171. },
  65172. /**
  65173. * @private
  65174. * Reads the tip text from the closest node to the event target which contains the
  65175. * attribute we are configured to look for. Returns an object containing the text
  65176. * from the attribute, and the target element from which the text was read.
  65177. */
  65178. getTipCfg: function(e) {
  65179. var t = e.getTarget(),
  65180. titleText = t.title,
  65181. cfg;
  65182. if (this.interceptTitles && titleText && Ext.isString(titleText)) {
  65183. t.qtip = titleText;
  65184. t.removeAttribute("title");
  65185. e.preventDefault();
  65186. return {
  65187. text: titleText
  65188. };
  65189. }
  65190. else {
  65191. cfg = this.tagConfig;
  65192. t = e.getTarget('[' + cfg.namespace + cfg.attribute + ']');
  65193. if (t) {
  65194. return {
  65195. target: t,
  65196. text: t.getAttribute(cfg.namespace + cfg.attribute)
  65197. };
  65198. }
  65199. }
  65200. },
  65201. // private
  65202. onTargetOver : function(e){
  65203. var me = this,
  65204. target = e.getTarget(me.delegate),
  65205. hasShowDelay,
  65206. delay,
  65207. elTarget,
  65208. cfg,
  65209. ns,
  65210. tipConfig,
  65211. autoHide,
  65212. targets, targetEl, value, key;
  65213. if (me.disabled) {
  65214. return;
  65215. }
  65216. // TODO - this causes "e" to be recycled in IE6/7 (EXTJSIV-1608) so ToolTip#setTarget
  65217. // was changed to include freezeEvent. The issue seems to be a nested 'resize' event
  65218. // that smashed Ext.EventObject.
  65219. me.targetXY = e.getXY();
  65220. // If the over target was filtered out by the delegate selector, or is not an HTMLElement, or is the <html> or the <body>, then return
  65221. if(!target || target.nodeType !== 1 || target == document.documentElement || target == document.body){
  65222. return;
  65223. }
  65224. if (me.activeTarget && ((target == me.activeTarget.el) || Ext.fly(me.activeTarget.el).contains(target))) {
  65225. me.clearTimer('hide');
  65226. me.show();
  65227. return;
  65228. }
  65229. if (target) {
  65230. targets = me.targets;
  65231. for (key in targets) {
  65232. if (targets.hasOwnProperty(key)) {
  65233. value = targets[key];
  65234. targetEl = Ext.fly(value.target);
  65235. if (targetEl && (targetEl.dom === target || targetEl.contains(target))) {
  65236. elTarget = targetEl.dom;
  65237. break;
  65238. }
  65239. }
  65240. }
  65241. if (elTarget) {
  65242. me.activeTarget = me.targets[elTarget.id];
  65243. me.activeTarget.el = target;
  65244. me.anchor = me.activeTarget.anchor;
  65245. if (me.anchor) {
  65246. me.anchorTarget = target;
  65247. }
  65248. hasShowDelay = Ext.isDefined(me.activeTarget.showDelay);
  65249. if (hasShowDelay) {
  65250. delay = me.showDelay;
  65251. me.showDelay = me.activeTarget.showDelay;
  65252. }
  65253. me.delayShow();
  65254. if (hasShowDelay) {
  65255. me.showDelay = delay;
  65256. }
  65257. return;
  65258. }
  65259. }
  65260. // Should be a fly.
  65261. elTarget = Ext.fly(target, '_quicktip-target');
  65262. cfg = me.tagConfig;
  65263. ns = cfg.namespace;
  65264. tipConfig = me.getTipCfg(e);
  65265. if (tipConfig) {
  65266. // getTipCfg may look up the parentNode axis for a tip text attribute and will return the new target node.
  65267. // Change our target element to match that from which the tip text attribute was read.
  65268. if (tipConfig.target) {
  65269. target = tipConfig.target;
  65270. elTarget = Ext.fly(target, '_quicktip-target');
  65271. }
  65272. autoHide = elTarget.getAttribute(ns + cfg.hide);
  65273. me.activeTarget = {
  65274. el: target,
  65275. text: tipConfig.text,
  65276. width: +elTarget.getAttribute(ns + cfg.width) || null,
  65277. autoHide: autoHide != "user" && autoHide !== 'false',
  65278. title: elTarget.getAttribute(ns + cfg.title),
  65279. cls: elTarget.getAttribute(ns + cfg.cls),
  65280. align: elTarget.getAttribute(ns + cfg.align)
  65281. };
  65282. me.anchor = elTarget.getAttribute(ns + cfg.anchor);
  65283. if (me.anchor) {
  65284. me.anchorTarget = target;
  65285. }
  65286. hasShowDelay = Ext.isDefined(me.activeTarget.showDelay);
  65287. if (hasShowDelay) {
  65288. delay = me.showDelay;
  65289. me.showDelay = me.activeTarget.showDelay;
  65290. }
  65291. me.delayShow();
  65292. if (hasShowDelay) {
  65293. me.showDelay = delay;
  65294. }
  65295. }
  65296. },
  65297. // private
  65298. onTargetOut : function(e){
  65299. var me = this,
  65300. active = me.activeTarget,
  65301. hasHideDelay,
  65302. delay;
  65303. // If moving within the current target, and it does not have a new tip, ignore the mouseout
  65304. // EventObject.within is the only correct way to determine this.
  65305. if (active && e.within(me.activeTarget.el) && !me.getTipCfg(e)) {
  65306. return;
  65307. }
  65308. me.clearTimer('show');
  65309. delete me.activeTarget;
  65310. if (me.autoHide !== false) {
  65311. hasHideDelay = active && Ext.isDefined(active.hideDelay);
  65312. if (hasHideDelay) {
  65313. delay = me.hideDelay;
  65314. me.hideDelay = active.hideDelay;
  65315. }
  65316. me.delayHide();
  65317. if (hasHideDelay) {
  65318. me.hideDelay = delay;
  65319. }
  65320. }
  65321. },
  65322. // inherit docs
  65323. showAt : function(xy){
  65324. var me = this,
  65325. target = me.activeTarget,
  65326. cls;
  65327. if (target) {
  65328. if (!me.rendered) {
  65329. me.render(Ext.getBody());
  65330. me.activeTarget = target;
  65331. }
  65332. me.suspendLayouts();
  65333. if (target.title) {
  65334. me.setTitle(target.title);
  65335. me.header.show();
  65336. } else {
  65337. me.header.hide();
  65338. }
  65339. me.update(target.text);
  65340. me.autoHide = target.autoHide;
  65341. me.dismissDelay = target.dismissDelay || me.dismissDelay;
  65342. if (target.mouseOffset) {
  65343. xy[0] += target.mouseOffset[0];
  65344. xy[1] += target.mouseOffset[1];
  65345. }
  65346. cls = me.lastCls;
  65347. if (cls) {
  65348. me.removeCls(cls);
  65349. delete me.lastCls;
  65350. }
  65351. cls = target.cls;
  65352. if (cls) {
  65353. me.addCls(cls);
  65354. me.lastCls = cls;
  65355. }
  65356. me.setWidth(target.width);
  65357. if (me.anchor) {
  65358. me.constrainPosition = false;
  65359. } else if (target.align) { // TODO: this doesn't seem to work consistently
  65360. xy = me.el.getAlignToXY(target.el, target.align);
  65361. me.constrainPosition = false;
  65362. }else{
  65363. me.constrainPosition = true;
  65364. }
  65365. me.resumeLayouts(true);
  65366. }
  65367. me.callParent([xy]);
  65368. },
  65369. // inherit docs
  65370. hide: function(){
  65371. delete this.activeTarget;
  65372. this.callParent();
  65373. }
  65374. });
  65375. /**
  65376. * Provides attractive and customizable tooltips for any element. The QuickTips
  65377. * singleton is used to configure and manage tooltips globally for multiple elements
  65378. * in a generic manner. To create individual tooltips with maximum customizability,
  65379. * you should consider either {@link Ext.tip.Tip} or {@link Ext.tip.ToolTip}.
  65380. *
  65381. * Quicktips can be configured via tag attributes directly in markup, or by
  65382. * registering quick tips programmatically via the {@link #register} method.
  65383. *
  65384. * The singleton's instance of {@link Ext.tip.QuickTip} is available via
  65385. * {@link #getQuickTip}, and supports all the methods, and all the all the
  65386. * configuration properties of Ext.tip.QuickTip. These settings will apply to all
  65387. * tooltips shown by the singleton.
  65388. *
  65389. * Below is the summary of the configuration properties which can be used.
  65390. * For detailed descriptions see the config options for the
  65391. * {@link Ext.tip.QuickTip QuickTip} class
  65392. *
  65393. * ## QuickTips singleton configs (all are optional)
  65394. *
  65395. * - `dismissDelay`
  65396. * - `hideDelay`
  65397. * - `maxWidth`
  65398. * - `minWidth`
  65399. * - `showDelay`
  65400. * - `trackMouse`
  65401. *
  65402. * ## Target element configs (optional unless otherwise noted)
  65403. *
  65404. * - `autoHide`
  65405. * - `cls`
  65406. * - `dismissDelay` (overrides singleton value)
  65407. * - `target` (required)
  65408. * - `text` (required)
  65409. * - `title`
  65410. * - `width`
  65411. *
  65412. * Here is an example showing how some of these config options could be used:
  65413. *
  65414. * @example
  65415. * // Init the singleton. Any tag-based quick tips will start working.
  65416. * Ext.tip.QuickTipManager.init();
  65417. *
  65418. * // Apply a set of config properties to the singleton
  65419. * Ext.apply(Ext.tip.QuickTipManager.getQuickTip(), {
  65420. * maxWidth: 200,
  65421. * minWidth: 100,
  65422. * showDelay: 50 // Show 50ms after entering target
  65423. * });
  65424. *
  65425. * // Create a small panel to add a quick tip to
  65426. * Ext.create('Ext.container.Container', {
  65427. * id: 'quickTipContainer',
  65428. * width: 200,
  65429. * height: 150,
  65430. * style: {
  65431. * backgroundColor:'#000000'
  65432. * },
  65433. * renderTo: Ext.getBody()
  65434. * });
  65435. *
  65436. *
  65437. * // Manually register a quick tip for a specific element
  65438. * Ext.tip.QuickTipManager.register({
  65439. * target: 'quickTipContainer',
  65440. * title: 'My Tooltip',
  65441. * text: 'This tooltip was added in code',
  65442. * width: 100,
  65443. * dismissDelay: 10000 // Hide after 10 seconds hover
  65444. * });
  65445. *
  65446. * To register a quick tip in markup, you simply add one or more of the valid QuickTip
  65447. * attributes prefixed with the **data-** namespace. The HTML element itself is
  65448. * automatically set as the quick tip target. Here is the summary of supported attributes
  65449. * (optional unless otherwise noted):
  65450. *
  65451. * - `hide`: Specifying "user" is equivalent to setting autoHide = false.
  65452. * Any other value will be the same as autoHide = true.
  65453. * - `qclass`: A CSS class to be applied to the quick tip
  65454. * (equivalent to the 'cls' target element config).
  65455. * - `qtip (required)`: The quick tip text (equivalent to the 'text' target element config).
  65456. * - `qtitle`: The quick tip title (equivalent to the 'title' target element config).
  65457. * - `qwidth`: The quick tip width (equivalent to the 'width' target element config).
  65458. *
  65459. * Here is an example of configuring an HTML element to display a tooltip from markup:
  65460. *
  65461. * // Add a quick tip to an HTML button
  65462. * <input type="button" value="OK" data-qtitle="OK Button" data-qwidth="100"
  65463. * data-qtip="This is a quick tip from markup!"></input>
  65464. *
  65465. * @singleton
  65466. */
  65467. Ext.define('Ext.tip.QuickTipManager', (function() {
  65468. var tip,
  65469. disabled = false;
  65470. return {
  65471. requires: ['Ext.tip.QuickTip'],
  65472. singleton: true,
  65473. alternateClassName: 'Ext.QuickTips',
  65474. /**
  65475. * Initializes the global QuickTips instance and prepare any quick tips.
  65476. * @param {Boolean} [autoRender=true] True to render the QuickTips container
  65477. * immediately to preload images.
  65478. * @param {Object} [config] config object for the created QuickTip. By
  65479. * default, the {@link Ext.tip.QuickTip QuickTip} class is instantiated, but this can
  65480. * be changed by supplying an xtype property or a className property in this object.
  65481. * All other properties on this object are configuration for the created component.
  65482. */
  65483. init : function (autoRender, config) {
  65484. if (!tip) {
  65485. if (!Ext.isReady) {
  65486. Ext.onReady(function(){
  65487. Ext.tip.QuickTipManager.init(autoRender, config);
  65488. });
  65489. return;
  65490. }
  65491. var tipConfig = Ext.apply({ disabled: disabled, id: 'ext-quicktips-tip' }, config),
  65492. className = tipConfig.className,
  65493. xtype = tipConfig.xtype;
  65494. if (className) {
  65495. delete tipConfig.className;
  65496. } else if (xtype) {
  65497. className = 'widget.' + xtype;
  65498. delete tipConfig.xtype;
  65499. }
  65500. if (autoRender !== false) {
  65501. tipConfig.renderTo = document.body;
  65502. if (tipConfig.renderTo.tagName.toUpperCase() != 'BODY') { // e.g., == 'FRAMESET'
  65503. Ext.Error.raise({
  65504. sourceClass: 'Ext.tip.QuickTipManager',
  65505. sourceMethod: 'init',
  65506. msg: 'Cannot init QuickTipManager: no document body'
  65507. });
  65508. }
  65509. }
  65510. tip = Ext.create(className || 'Ext.tip.QuickTip', tipConfig);
  65511. }
  65512. },
  65513. /**
  65514. * Destroys the QuickTips instance.
  65515. */
  65516. destroy: function() {
  65517. if (tip) {
  65518. var undef;
  65519. tip.destroy();
  65520. tip = undef;
  65521. }
  65522. },
  65523. // Protected method called by the dd classes
  65524. ddDisable : function(){
  65525. // don't disable it if we don't need to
  65526. if(tip && !disabled){
  65527. tip.disable();
  65528. }
  65529. },
  65530. // Protected method called by the dd classes
  65531. ddEnable : function(){
  65532. // only enable it if it hasn't been disabled
  65533. if(tip && !disabled){
  65534. tip.enable();
  65535. }
  65536. },
  65537. /**
  65538. * Enables quick tips globally.
  65539. */
  65540. enable : function(){
  65541. if(tip){
  65542. tip.enable();
  65543. }
  65544. disabled = false;
  65545. },
  65546. /**
  65547. * Disables quick tips globally.
  65548. */
  65549. disable : function(){
  65550. if(tip){
  65551. tip.disable();
  65552. }
  65553. disabled = true;
  65554. },
  65555. /**
  65556. * Returns true if quick tips are enabled, else false.
  65557. * @return {Boolean}
  65558. */
  65559. isEnabled : function(){
  65560. return tip !== undefined && !tip.disabled;
  65561. },
  65562. /**
  65563. * Gets the single {@link Ext.tip.QuickTip QuickTip} instance used to show tips
  65564. * from all registered elements.
  65565. * @return {Ext.tip.QuickTip}
  65566. */
  65567. getQuickTip : function(){
  65568. return tip;
  65569. },
  65570. /**
  65571. * Configures a new quick tip instance and assigns it to a target element. See
  65572. * {@link Ext.tip.QuickTip#register} for details.
  65573. * @param {Object} config The config object
  65574. */
  65575. register : function(){
  65576. tip.register.apply(tip, arguments);
  65577. },
  65578. /**
  65579. * Removes any registered quick tip from the target element and destroys it.
  65580. * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip
  65581. * is to be removed or ID of the element.
  65582. */
  65583. unregister : function(){
  65584. tip.unregister.apply(tip, arguments);
  65585. },
  65586. /**
  65587. * Alias of {@link #register}.
  65588. * @inheritdoc Ext.tip.QuickTipManager#register
  65589. */
  65590. tips : function(){
  65591. tip.register.apply(tip, arguments);
  65592. }
  65593. };
  65594. }()));
  65595. /**
  65596. * @class Ext.app.EventBus
  65597. * @private
  65598. */
  65599. Ext.define('Ext.app.EventBus', {
  65600. requires: [
  65601. 'Ext.util.Event',
  65602. 'Ext.Component'
  65603. ],
  65604. mixins: {
  65605. observable: 'Ext.util.Observable'
  65606. },
  65607. constructor: function() {
  65608. this.mixins.observable.constructor.call(this);
  65609. this.bus = {};
  65610. var me = this;
  65611. Ext.override(Ext.Component, {
  65612. fireEvent: function(ev) {
  65613. if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
  65614. return me.dispatch.call(me, ev, this, arguments);
  65615. }
  65616. return false;
  65617. }
  65618. });
  65619. },
  65620. dispatch: function(ev, target, args) {
  65621. var bus = this.bus,
  65622. selectors = bus[ev],
  65623. selector, controllers, id, events, event, i, ln;
  65624. if (selectors) {
  65625. // Loop over all the selectors that are bound to this event
  65626. for (selector in selectors) {
  65627. // Check if the target matches the selector
  65628. if (selectors.hasOwnProperty(selector) && target.is(selector)) {
  65629. // Loop over all the controllers that are bound to this selector
  65630. controllers = selectors[selector];
  65631. for (id in controllers) {
  65632. if (controllers.hasOwnProperty(id)) {
  65633. // Loop over all the events that are bound to this selector on this controller
  65634. events = controllers[id];
  65635. for (i = 0, ln = events.length; i < ln; i++) {
  65636. event = events[i];
  65637. // Fire the event!
  65638. if (event.fire.apply(event, Array.prototype.slice.call(args, 1)) === false) {
  65639. return false;
  65640. }
  65641. }
  65642. }
  65643. }
  65644. }
  65645. }
  65646. }
  65647. return true;
  65648. },
  65649. control: function(selectors, listeners, controller) {
  65650. var bus = this.bus,
  65651. hasListeners, tree, list,
  65652. selector, options, listener, scope, event, listenerList, ev;
  65653. if (Ext.isString(selectors)) {
  65654. selector = selectors;
  65655. selectors = {};
  65656. selectors[selector] = listeners;
  65657. this.control(selectors, null, controller);
  65658. return;
  65659. }
  65660. hasListeners = Ext.util.Observable.HasListeners.prototype;
  65661. for (selector in selectors) {
  65662. if (selectors.hasOwnProperty(selector)) {
  65663. listenerList = selectors[selector] || {};
  65664. for (ev in listenerList) {
  65665. if (listenerList.hasOwnProperty(ev)) {
  65666. options = {};
  65667. listener = listenerList[ev];
  65668. scope = controller;
  65669. event = new Ext.util.Event(controller, ev);
  65670. // Normalize the listener
  65671. if (Ext.isObject(listener)) {
  65672. options = listener;
  65673. listener = options.fn;
  65674. scope = options.scope || controller;
  65675. delete options.fn;
  65676. delete options.scope;
  65677. }
  65678. event.addListener(listener, scope, options);
  65679. hasListeners[ev] = 1;
  65680. // Create the bus tree if it is not there yet
  65681. tree = bus[ev] || (bus[ev] = {});
  65682. tree = tree[selector] || (tree[selector] = {});
  65683. list = tree[controller.id] || (tree[controller.id] = []);
  65684. // Push our listener in our bus
  65685. list.push(event);
  65686. }
  65687. } //end inner loop
  65688. }
  65689. } //end outer loop
  65690. }
  65691. });
  65692. /**
  65693. * Represents an Ext JS 4 application, which is typically a single page app using a {@link Ext.container.Viewport Viewport}.
  65694. * A typical Ext.app.Application might look like this:
  65695. *
  65696. * Ext.application({
  65697. * name: 'MyApp',
  65698. * launch: function() {
  65699. * Ext.create('Ext.container.Viewport', {
  65700. * items: {
  65701. * html: 'My App'
  65702. * }
  65703. * });
  65704. * }
  65705. * });
  65706. *
  65707. * This does several things. First it creates a global variable called 'MyApp' - all of your Application's classes (such
  65708. * as its Models, Views and Controllers) will reside under this single namespace, which drastically lowers the chances
  65709. * of colliding global variables.
  65710. *
  65711. * When the page is ready and all of your JavaScript has loaded, your Application's {@link #launch} function is called,
  65712. * at which time you can run the code that starts your app. Usually this consists of creating a Viewport, as we do in
  65713. * the example above.
  65714. *
  65715. * # Telling Application about the rest of the app
  65716. *
  65717. * Because an Ext.app.Application represents an entire app, we should tell it about the other parts of the app - namely
  65718. * the Models, Views and Controllers that are bundled with the application. Let's say we have a blog management app; we
  65719. * might have Models and Controllers for Posts and Comments, and Views for listing, adding and editing Posts and Comments.
  65720. * Here's how we'd tell our Application about all these things:
  65721. *
  65722. * Ext.application({
  65723. * name: 'Blog',
  65724. * models: ['Post', 'Comment'],
  65725. * controllers: ['Posts', 'Comments'],
  65726. *
  65727. * launch: function() {
  65728. * ...
  65729. * }
  65730. * });
  65731. *
  65732. * Note that we didn't actually list the Views directly in the Application itself. This is because Views are managed by
  65733. * Controllers, so it makes sense to keep those dependencies there. The Application will load each of the specified
  65734. * Controllers using the pathing conventions laid out in the [application architecture guide][mvc] - in this case
  65735. * expecting the controllers to reside in app/controller/Posts.js and app/controller/Comments.js. In turn, each
  65736. * Controller simply needs to list the Views it uses and they will be automatically loaded. Here's how our Posts
  65737. * controller like be defined:
  65738. *
  65739. * Ext.define('MyApp.controller.Posts', {
  65740. * extend: 'Ext.app.Controller',
  65741. * views: ['posts.List', 'posts.Edit'],
  65742. *
  65743. * //the rest of the Controller here
  65744. * });
  65745. *
  65746. * Because we told our Application about our Models and Controllers, and our Controllers about their Views, Ext JS will
  65747. * automatically load all of our app files for us. This means we don't have to manually add script tags into our html
  65748. * files whenever we add a new class, but more importantly it enables us to create a minimized build of our entire
  65749. * application using the Ext JS 4 SDK Tools.
  65750. *
  65751. * For more information about writing Ext JS 4 applications, please see the [application architecture guide][mvc].
  65752. *
  65753. * [mvc]: #/guide/application_architecture
  65754. *
  65755. * @docauthor Ed Spencer
  65756. */
  65757. Ext.define('Ext.app.Application', {
  65758. extend: 'Ext.app.Controller',
  65759. requires: [
  65760. 'Ext.ModelManager',
  65761. 'Ext.data.Model',
  65762. 'Ext.data.StoreManager',
  65763. 'Ext.tip.QuickTipManager',
  65764. 'Ext.ComponentManager',
  65765. 'Ext.app.EventBus'
  65766. ],
  65767. /**
  65768. * @cfg {String} name
  65769. * The name of your application. This will also be the namespace for your views, controllers
  65770. * models and stores. Don't use spaces or special characters in the name.
  65771. */
  65772. /**
  65773. * @cfg {String[]} controllers
  65774. * Names of controllers that the app uses.
  65775. */
  65776. /**
  65777. * @cfg {Object} scope
  65778. * The scope to execute the {@link #launch} function in. Defaults to the Application instance.
  65779. */
  65780. scope: undefined,
  65781. /**
  65782. * @cfg {Boolean} enableQuickTips
  65783. * True to automatically set up Ext.tip.QuickTip support.
  65784. */
  65785. enableQuickTips: true,
  65786. /**
  65787. * @cfg {String} appFolder
  65788. * The path to the directory which contains all application's classes.
  65789. * This path will be registered via {@link Ext.Loader#setPath} for the namespace specified
  65790. * in the {@link #name name} config.
  65791. */
  65792. appFolder: 'app',
  65793. /**
  65794. * @cfg {Boolean} autoCreateViewport
  65795. * True to automatically load and instantiate AppName.view.Viewport before firing the launch function.
  65796. */
  65797. autoCreateViewport: false,
  65798. /**
  65799. * @cfg {Object} paths
  65800. * Additional load paths to add to Ext.Loader.
  65801. * See {@link Ext.Loader#paths} config for more details.
  65802. */
  65803. /**
  65804. * Creates new Application.
  65805. * @param {Object} [config] Config object.
  65806. */
  65807. constructor: function(config) {
  65808. config = config || {};
  65809. Ext.apply(this, config);
  65810. var me = this,
  65811. requires = config.requires || [],
  65812. controllers, ln, i, controller,
  65813. paths, path, ns;
  65814. Ext.Loader.setPath(me.name, me.appFolder);
  65815. if (me.paths) {
  65816. paths = me.paths;
  65817. for (ns in paths) {
  65818. if (paths.hasOwnProperty(ns)) {
  65819. path = paths[ns];
  65820. Ext.Loader.setPath(ns, path);
  65821. }
  65822. }
  65823. }
  65824. me.callParent(arguments);
  65825. me.eventbus = new Ext.app.EventBus;
  65826. controllers = Ext.Array.from(me.controllers);
  65827. ln = controllers && controllers.length;
  65828. me.controllers = new Ext.util.MixedCollection();
  65829. if (me.autoCreateViewport) {
  65830. requires.push(me.getModuleClassName('Viewport', 'view'));
  65831. }
  65832. for (i = 0; i < ln; i++) {
  65833. requires.push(me.getModuleClassName(controllers[i], 'controller'));
  65834. }
  65835. Ext.require(requires);
  65836. Ext.onReady(function() {
  65837. me.init(me);
  65838. for (i = 0; i < ln; i++) {
  65839. controller = me.getController(controllers[i]);
  65840. controller.init(me);
  65841. }
  65842. me.onBeforeLaunch.call(me);
  65843. }, me);
  65844. },
  65845. control: function(selectors, listeners, controller) {
  65846. this.eventbus.control(selectors, listeners, controller);
  65847. },
  65848. /**
  65849. * @method
  65850. * @template
  65851. * Called automatically when the page has completely loaded. This is an empty function that should be
  65852. * overridden by each application that needs to take action on page load.
  65853. * @param {String} profile The detected application profile
  65854. * @return {Boolean} By default, the Application will dispatch to the configured startup controller and
  65855. * action immediately after running the launch function. Return false to prevent this behavior.
  65856. */
  65857. launch: Ext.emptyFn,
  65858. /**
  65859. * @private
  65860. */
  65861. onBeforeLaunch: function() {
  65862. var me = this,
  65863. controllers, c, cLen, controller;
  65864. if (me.enableQuickTips) {
  65865. Ext.tip.QuickTipManager.init();
  65866. }
  65867. if (me.autoCreateViewport) {
  65868. me.getView('Viewport').create();
  65869. }
  65870. me.launch.call(this.scope || this);
  65871. me.launched = true;
  65872. me.fireEvent('launch', this);
  65873. controllers = me.controllers.items;
  65874. cLen = controllers.length;
  65875. for (c = 0; c < cLen; c++) {
  65876. controller = controllers[c];
  65877. controller.onLaunch(this);
  65878. }
  65879. },
  65880. getModuleClassName: function(name, module) {
  65881. // Deciding if a class name must be qualified:
  65882. // 1 - if the name doesn't contains at least one dot, we must definitely qualify it
  65883. // 2 - the name may be a qualified name of a known class, but:
  65884. // 2.1 - in runtime, the loader may not know the class - specially in production - so we must check the class manager
  65885. // 2.2 - in build time, the class manager may not know the class, but the loader does, so we check the second one
  65886. // (the loader check assures it's really a class, and not a namespace, so we can have 'Books.controller.Books',
  65887. // and request for a controller called Books will not be underqualified)
  65888. if (name.indexOf('.') !== -1 && (Ext.ClassManager.isCreated(name) || Ext.Loader.isAClassNameWithAKnownPrefix(name))) {
  65889. return name;
  65890. } else {
  65891. return this.name + '.' + module + '.' + name;
  65892. }
  65893. },
  65894. getController: function(name) {
  65895. var controller = this.controllers.get(name);
  65896. if (!controller) {
  65897. controller = Ext.create(this.getModuleClassName(name, 'controller'), {
  65898. application: this,
  65899. id: name
  65900. });
  65901. this.controllers.add(controller);
  65902. }
  65903. return controller;
  65904. },
  65905. getStore: function(name) {
  65906. var store = Ext.StoreManager.get(name);
  65907. if (!store) {
  65908. store = Ext.create(this.getModuleClassName(name, 'store'), {
  65909. storeId: name
  65910. });
  65911. }
  65912. return store;
  65913. },
  65914. getModel: function(model) {
  65915. model = this.getModuleClassName(model, 'model');
  65916. return Ext.ModelManager.getModel(model);
  65917. },
  65918. getView: function(view) {
  65919. view = this.getModuleClassName(view, 'view');
  65920. return Ext.ClassManager.get(view);
  65921. }
  65922. });
  65923. /**
  65924. * A split button that provides a built-in dropdown arrow that can fire an event separately from the default click event
  65925. * of the button. Typically this would be used to display a dropdown menu that provides additional options to the
  65926. * primary button action, but any custom handler can provide the arrowclick implementation. Example usage:
  65927. *
  65928. * @example
  65929. * // display a dropdown menu:
  65930. * Ext.create('Ext.button.Split', {
  65931. * renderTo: Ext.getBody(),
  65932. * text: 'Options',
  65933. * // handle a click on the button itself
  65934. * handler: function() {
  65935. * alert("The button was clicked");
  65936. * },
  65937. * menu: new Ext.menu.Menu({
  65938. * items: [
  65939. * // these will render as dropdown menu items when the arrow is clicked:
  65940. * {text: 'Item 1', handler: function(){ alert("Item 1 clicked"); }},
  65941. * {text: 'Item 2', handler: function(){ alert("Item 2 clicked"); }}
  65942. * ]
  65943. * })
  65944. * });
  65945. *
  65946. * Instead of showing a menu, you can provide any type of custom functionality you want when the dropdown
  65947. * arrow is clicked:
  65948. *
  65949. * Ext.create('Ext.button.Split', {
  65950. * renderTo: 'button-ct',
  65951. * text: 'Options',
  65952. * handler: optionsHandler,
  65953. * arrowHandler: myCustomHandler
  65954. * });
  65955. *
  65956. */
  65957. Ext.define('Ext.button.Split', {
  65958. /* Begin Definitions */
  65959. alias: 'widget.splitbutton',
  65960. extend: 'Ext.button.Button',
  65961. alternateClassName: 'Ext.SplitButton',
  65962. /* End Definitions */
  65963. /**
  65964. * @cfg {Function} arrowHandler
  65965. * A function called when the arrow button is clicked (can be used instead of click event)
  65966. * @cfg {Ext.button.Split} arrowHandler.this
  65967. * @cfg {Event} arrowHandler.e The click event
  65968. */
  65969. /**
  65970. * @cfg {String} arrowTooltip
  65971. * The title attribute of the arrow
  65972. */
  65973. // private
  65974. arrowCls : 'split',
  65975. split : true,
  65976. // private
  65977. initComponent : function(){
  65978. this.callParent();
  65979. /**
  65980. * @event arrowclick
  65981. * Fires when this button's arrow is clicked.
  65982. * @param {Ext.button.Split} this
  65983. * @param {Event} e The click event
  65984. */
  65985. this.addEvents("arrowclick");
  65986. },
  65987. /**
  65988. * Sets this button's arrow click handler.
  65989. * @param {Function} handler The function to call when the arrow is clicked
  65990. * @param {Object} scope (optional) Scope for the function passed above
  65991. */
  65992. setArrowHandler : function(handler, scope){
  65993. this.arrowHandler = handler;
  65994. this.scope = scope;
  65995. },
  65996. // private
  65997. onClick : function(e, t) {
  65998. var me = this;
  65999. e.preventDefault();
  66000. if (!me.disabled) {
  66001. if (me.overMenuTrigger) {
  66002. me.maybeShowMenu();
  66003. me.fireEvent("arrowclick", me, e);
  66004. if (me.arrowHandler) {
  66005. me.arrowHandler.call(me.scope || me, me, e);
  66006. }
  66007. } else {
  66008. me.doToggle();
  66009. me.fireHandler(e);
  66010. }
  66011. }
  66012. }
  66013. });
  66014. /**
  66015. * A specialized SplitButton that contains a menu of {@link Ext.menu.CheckItem} elements. The button automatically
  66016. * cycles through each menu item on click, raising the button's {@link #change} event (or calling the button's
  66017. * {@link #changeHandler} function, if supplied) for the active menu item. Clicking on the arrow section of the
  66018. * button displays the dropdown menu just like a normal SplitButton. Example usage:
  66019. *
  66020. * @example
  66021. * Ext.create('Ext.button.Cycle', {
  66022. * showText: true,
  66023. * prependText: 'View as ',
  66024. * renderTo: Ext.getBody(),
  66025. * menu: {
  66026. * id: 'view-type-menu',
  66027. * items: [{
  66028. * text: 'text only',
  66029. * iconCls: 'view-text',
  66030. * checked: true
  66031. * },{
  66032. * text: 'HTML',
  66033. * iconCls: 'view-html'
  66034. * }]
  66035. * },
  66036. * changeHandler: function(cycleBtn, activeItem) {
  66037. * Ext.Msg.alert('Change View', activeItem.text);
  66038. * }
  66039. * });
  66040. */
  66041. Ext.define('Ext.button.Cycle', {
  66042. /* Begin Definitions */
  66043. alias: 'widget.cycle',
  66044. extend: 'Ext.button.Split',
  66045. alternateClassName: 'Ext.CycleButton',
  66046. /* End Definitions */
  66047. /**
  66048. * @cfg {Object[]} items
  66049. * An array of {@link Ext.menu.CheckItem} **config** objects to be used when creating the button's menu items (e.g.,
  66050. * `{text:'Foo', iconCls:'foo-icon'}`)
  66051. *
  66052. * @deprecated 4.0 Use the {@link #cfg-menu} config instead. All menu items will be created as
  66053. * {@link Ext.menu.CheckItem CheckItems}.
  66054. */
  66055. /**
  66056. * @cfg {Boolean} [showText=false]
  66057. * True to display the active item's text as the button text. The Button will show its
  66058. * configured {@link #text} if this config is omitted.
  66059. */
  66060. /**
  66061. * @cfg {String} [prependText='']
  66062. * A static string to prepend before the active item's text when displayed as the button's text (only applies when
  66063. * showText = true).
  66064. */
  66065. /**
  66066. * @cfg {Function} changeHandler
  66067. * A callback function that will be invoked each time the active menu item in the button's menu has changed. If this
  66068. * callback is not supplied, the SplitButton will instead fire the {@link #change} event on active item change. The
  66069. * changeHandler function will be called with the following argument list: (SplitButton this, Ext.menu.CheckItem
  66070. * item)
  66071. */
  66072. /**
  66073. * @cfg {String} forceIcon
  66074. * A css class which sets an image to be used as the static icon for this button. This icon will always be displayed
  66075. * regardless of which item is selected in the dropdown list. This overrides the default behavior of changing the
  66076. * button's icon to match the selected item's icon on change.
  66077. */
  66078. /**
  66079. * @property {Ext.menu.Menu} menu
  66080. * The {@link Ext.menu.Menu Menu} object used to display the {@link Ext.menu.CheckItem CheckItems} representing the
  66081. * available choices.
  66082. */
  66083. // private
  66084. getButtonText: function(item) {
  66085. var me = this,
  66086. text = '';
  66087. if (item && me.showText === true) {
  66088. if (me.prependText) {
  66089. text += me.prependText;
  66090. }
  66091. text += item.text;
  66092. return text;
  66093. }
  66094. return me.text;
  66095. },
  66096. /**
  66097. * Sets the button's active menu item.
  66098. * @param {Ext.menu.CheckItem} item The item to activate
  66099. * @param {Boolean} [suppressEvent=false] True to prevent the button's change event from firing.
  66100. */
  66101. setActiveItem: function(item, suppressEvent) {
  66102. var me = this;
  66103. if (!Ext.isObject(item)) {
  66104. item = me.menu.getComponent(item);
  66105. }
  66106. if (item) {
  66107. if (!me.rendered) {
  66108. me.text = me.getButtonText(item);
  66109. me.iconCls = item.iconCls;
  66110. } else {
  66111. me.setText(me.getButtonText(item));
  66112. me.setIconCls(item.iconCls);
  66113. }
  66114. me.activeItem = item;
  66115. if (!item.checked) {
  66116. item.setChecked(true, false);
  66117. }
  66118. if (me.forceIcon) {
  66119. me.setIconCls(me.forceIcon);
  66120. }
  66121. if (!suppressEvent) {
  66122. me.fireEvent('change', me, item);
  66123. }
  66124. }
  66125. },
  66126. /**
  66127. * Gets the currently active menu item.
  66128. * @return {Ext.menu.CheckItem} The active item
  66129. */
  66130. getActiveItem: function() {
  66131. return this.activeItem;
  66132. },
  66133. // private
  66134. initComponent: function() {
  66135. var me = this,
  66136. checked = 0,
  66137. items,
  66138. i, iLen, item;
  66139. me.addEvents(
  66140. /**
  66141. * @event change
  66142. * Fires after the button's active menu item has changed. Note that if a {@link #changeHandler} function is
  66143. * set on this CycleButton, it will be called instead on active item change and this change event will not
  66144. * be fired.
  66145. * @param {Ext.button.Cycle} this
  66146. * @param {Ext.menu.CheckItem} item The menu item that was selected
  66147. */
  66148. "change"
  66149. );
  66150. if (me.changeHandler) {
  66151. me.on('change', me.changeHandler, me.scope || me);
  66152. delete me.changeHandler;
  66153. }
  66154. // Allow them to specify a menu config which is a standard Button config.
  66155. // Remove direct use of "items" in 5.0.
  66156. items = (me.menu.items || []).concat(me.items || []);
  66157. me.menu = Ext.applyIf({
  66158. cls: Ext.baseCSSPrefix + 'cycle-menu',
  66159. items: []
  66160. }, me.menu);
  66161. iLen = items.length;
  66162. // Convert all items to CheckItems
  66163. for (i = 0; i < iLen; i++) {
  66164. item = items[i];
  66165. item = Ext.applyIf({
  66166. group : me.id,
  66167. itemIndex : i,
  66168. checkHandler : me.checkHandler,
  66169. scope : me,
  66170. checked : item.checked || false
  66171. }, item);
  66172. me.menu.items.push(item);
  66173. if (item.checked) {
  66174. checked = i;
  66175. }
  66176. }
  66177. me.itemCount = me.menu.items.length;
  66178. me.callParent(arguments);
  66179. me.on('click', me.toggleSelected, me);
  66180. me.setActiveItem(checked, me);
  66181. // If configured with a fixed width, the cycling will center a different child item's text each click. Prevent this.
  66182. if (me.width && me.showText) {
  66183. me.addCls(Ext.baseCSSPrefix + 'cycle-fixed-width');
  66184. }
  66185. },
  66186. // private
  66187. checkHandler: function(item, pressed) {
  66188. if (pressed) {
  66189. this.setActiveItem(item);
  66190. }
  66191. },
  66192. /**
  66193. * This is normally called internally on button click, but can be called externally to advance the button's active
  66194. * item programmatically to the next one in the menu. If the current item is the last one in the menu the active
  66195. * item will be set to the first item in the menu.
  66196. */
  66197. toggleSelected: function() {
  66198. var me = this,
  66199. m = me.menu,
  66200. checkItem;
  66201. checkItem = me.activeItem.next(':not([disabled])') || m.items.getAt(0);
  66202. checkItem.setChecked(true);
  66203. }
  66204. });
  66205. /**
  66206. * @class Ext.chart.Callout
  66207. * A mixin providing callout functionality for Ext.chart.series.Series.
  66208. */
  66209. Ext.define('Ext.chart.Callout', {
  66210. /* Begin Definitions */
  66211. /* End Definitions */
  66212. constructor: function(config) {
  66213. if (config.callouts) {
  66214. config.callouts.styles = Ext.applyIf(config.callouts.styles || {}, {
  66215. color: "#000",
  66216. font: "11px Helvetica, sans-serif"
  66217. });
  66218. this.callouts = Ext.apply(this.callouts || {}, config.callouts);
  66219. this.calloutsArray = [];
  66220. }
  66221. },
  66222. renderCallouts: function() {
  66223. if (!this.callouts) {
  66224. return;
  66225. }
  66226. var me = this,
  66227. items = me.items,
  66228. animate = me.chart.animate,
  66229. config = me.callouts,
  66230. styles = config.styles,
  66231. group = me.calloutsArray,
  66232. store = me.chart.store,
  66233. len = store.getCount(),
  66234. ratio = items.length / len,
  66235. previouslyPlacedCallouts = [],
  66236. i,
  66237. count,
  66238. j,
  66239. p,
  66240. item,
  66241. label,
  66242. storeItem,
  66243. display;
  66244. for (i = 0, count = 0; i < len; i++) {
  66245. for (j = 0; j < ratio; j++) {
  66246. item = items[count];
  66247. label = group[count];
  66248. storeItem = store.getAt(i);
  66249. display = config.filter(storeItem);
  66250. if (!display && !label) {
  66251. count++;
  66252. continue;
  66253. }
  66254. if (!label) {
  66255. group[count] = label = me.onCreateCallout(storeItem, item, i, display, j, count);
  66256. }
  66257. for (p in label) {
  66258. if (label[p] && label[p].setAttributes) {
  66259. label[p].setAttributes(styles, true);
  66260. }
  66261. }
  66262. if (!display) {
  66263. for (p in label) {
  66264. if (label[p]) {
  66265. if (label[p].setAttributes) {
  66266. label[p].setAttributes({
  66267. hidden: true
  66268. }, true);
  66269. } else if(label[p].setVisible) {
  66270. label[p].setVisible(false);
  66271. }
  66272. }
  66273. }
  66274. }
  66275. config.renderer(label, storeItem);
  66276. me.onPlaceCallout(label, storeItem, item, i, display, animate,
  66277. j, count, previouslyPlacedCallouts);
  66278. previouslyPlacedCallouts.push(label);
  66279. count++;
  66280. }
  66281. }
  66282. this.hideCallouts(count);
  66283. },
  66284. onCreateCallout: function(storeItem, item, i, display) {
  66285. var me = this,
  66286. group = me.calloutsGroup,
  66287. config = me.callouts,
  66288. styles = config.styles,
  66289. width = styles.width,
  66290. height = styles.height,
  66291. chart = me.chart,
  66292. surface = chart.surface,
  66293. calloutObj = {
  66294. //label: false,
  66295. //box: false,
  66296. lines: false
  66297. };
  66298. calloutObj.lines = surface.add(Ext.apply({},
  66299. {
  66300. type: 'path',
  66301. path: 'M0,0',
  66302. stroke: me.getLegendColor() || '#555'
  66303. },
  66304. styles));
  66305. if (config.items) {
  66306. calloutObj.panel = new Ext.Panel({
  66307. style: "position: absolute;",
  66308. width: width,
  66309. height: height,
  66310. items: config.items,
  66311. renderTo: chart.el
  66312. });
  66313. }
  66314. return calloutObj;
  66315. },
  66316. hideCallouts: function(index) {
  66317. var calloutsArray = this.calloutsArray,
  66318. len = calloutsArray.length,
  66319. co,
  66320. p;
  66321. while (len-->index) {
  66322. co = calloutsArray[len];
  66323. for (p in co) {
  66324. if (co[p]) {
  66325. co[p].hide(true);
  66326. }
  66327. }
  66328. }
  66329. }
  66330. });
  66331. /**
  66332. * @class Ext.layout.component.Draw
  66333. * @private
  66334. *
  66335. */
  66336. Ext.define('Ext.layout.component.Draw', {
  66337. /* Begin Definitions */
  66338. alias: 'layout.draw',
  66339. extend: 'Ext.layout.component.Auto',
  66340. /* End Definitions */
  66341. type: 'draw',
  66342. measureContentWidth : function (ownerContext) {
  66343. var target = ownerContext.target,
  66344. paddingInfo = ownerContext.getPaddingInfo(),
  66345. bbox = this.getBBox(ownerContext);
  66346. if (!target.viewBox) {
  66347. if (target.autoSize) {
  66348. return bbox.width + paddingInfo.width;
  66349. } else {
  66350. return bbox.x + bbox.width + paddingInfo.width;
  66351. }
  66352. } else {
  66353. if (ownerContext.heightModel.shrinkWrap) {
  66354. return paddingInfo.width;
  66355. } else {
  66356. return bbox.width / bbox.height * (ownerContext.getProp('contentHeight') - paddingInfo.height) + paddingInfo.width;
  66357. }
  66358. }
  66359. },
  66360. measureContentHeight : function (ownerContext) {
  66361. var target = ownerContext.target,
  66362. paddingInfo = ownerContext.getPaddingInfo(),
  66363. bbox = this.getBBox(ownerContext);
  66364. if (!ownerContext.target.viewBox) {
  66365. if (target.autoSize) {
  66366. return bbox.height + paddingInfo.height;
  66367. } else {
  66368. return bbox.y + bbox.height + paddingInfo.height;
  66369. }
  66370. } else {
  66371. if (ownerContext.widthModel.shrinkWrap) {
  66372. return paddingInfo.height;
  66373. } else {
  66374. return bbox.height / bbox.width * (ownerContext.getProp('contentWidth') - paddingInfo.width) + paddingInfo.height;
  66375. }
  66376. }
  66377. },
  66378. getBBox: function(ownerContext) {
  66379. var bbox = ownerContext.surfaceBBox;
  66380. if (!bbox) {
  66381. bbox = ownerContext.target.surface.items.getBBox();
  66382. // If the surface is empty, we'll get these values, normalize them
  66383. if (bbox.width === -Infinity && bbox.height === -Infinity) {
  66384. bbox.width = bbox.height = bbox.x = bbox.y = 0;
  66385. }
  66386. ownerContext.surfaceBBox = bbox;
  66387. }
  66388. return bbox;
  66389. },
  66390. publishInnerWidth: function (ownerContext, width) {
  66391. ownerContext.setContentWidth(width - ownerContext.getFrameInfo().width, true);
  66392. },
  66393. publishInnerHeight: function (ownerContext, height) {
  66394. ownerContext.setContentHeight(height - ownerContext.getFrameInfo().height, true);
  66395. },
  66396. finishedLayout: function (ownerContext) {
  66397. // TODO: Is there a better way doing this?
  66398. var props = ownerContext.props,
  66399. paddingInfo = ownerContext.getPaddingInfo();
  66400. // We don't want the cost of getProps, so we just use the props data... this is ok
  66401. // because all the props have been calculated by this time
  66402. this.owner.setSurfaceSize(props.contentWidth - paddingInfo.width, props.contentHeight - paddingInfo.height);
  66403. // calls afterComponentLayout, so we want the surface to be sized before that:
  66404. this.callParent(arguments);
  66405. }
  66406. });
  66407. /**
  66408. * A composite Sprite handles a group of sprites with common methods to a sprite
  66409. * such as `hide`, `show`, `setAttributes`. These methods are applied to the set of sprites
  66410. * added to the group.
  66411. *
  66412. * CompositeSprite extends {@link Ext.util.MixedCollection} so you can use the same methods
  66413. * in `MixedCollection` to iterate through sprites, add and remove elements, etc.
  66414. *
  66415. * In order to create a CompositeSprite, one has to provide a handle to the surface where it is
  66416. * rendered:
  66417. *
  66418. * var group = Ext.create('Ext.draw.CompositeSprite', {
  66419. * surface: drawComponent.surface
  66420. * });
  66421. *
  66422. * Then just by using `MixedCollection` methods it's possible to add {@link Ext.draw.Sprite}s:
  66423. *
  66424. * group.add(sprite1);
  66425. * group.add(sprite2);
  66426. * group.add(sprite3);
  66427. *
  66428. * And then apply common Sprite methods to them:
  66429. *
  66430. * group.setAttributes({
  66431. * fill: '#f00'
  66432. * }, true);
  66433. */
  66434. Ext.define('Ext.draw.CompositeSprite', {
  66435. /* Begin Definitions */
  66436. extend: 'Ext.util.MixedCollection',
  66437. mixins: {
  66438. animate: 'Ext.util.Animate'
  66439. },
  66440. autoDestroy: false,
  66441. /* End Definitions */
  66442. isCompositeSprite: true,
  66443. constructor: function(config) {
  66444. var me = this;
  66445. config = config || {};
  66446. Ext.apply(me, config);
  66447. me.addEvents(
  66448. /**
  66449. * @event
  66450. * @inheritdoc Ext.draw.Sprite#mousedown
  66451. */
  66452. 'mousedown',
  66453. /**
  66454. * @event
  66455. * @inheritdoc Ext.draw.Sprite#mouseup
  66456. */
  66457. 'mouseup',
  66458. /**
  66459. * @event
  66460. * @inheritdoc Ext.draw.Sprite#mouseover
  66461. */
  66462. 'mouseover',
  66463. /**
  66464. * @event
  66465. * @inheritdoc Ext.draw.Sprite#mouseout
  66466. */
  66467. 'mouseout',
  66468. /**
  66469. * @event
  66470. * @inheritdoc Ext.draw.Sprite#click
  66471. */
  66472. 'click'
  66473. );
  66474. me.id = Ext.id(null, 'ext-sprite-group-');
  66475. me.callParent();
  66476. },
  66477. // @private
  66478. onClick: function(e) {
  66479. this.fireEvent('click', e);
  66480. },
  66481. // @private
  66482. onMouseUp: function(e) {
  66483. this.fireEvent('mouseup', e);
  66484. },
  66485. // @private
  66486. onMouseDown: function(e) {
  66487. this.fireEvent('mousedown', e);
  66488. },
  66489. // @private
  66490. onMouseOver: function(e) {
  66491. this.fireEvent('mouseover', e);
  66492. },
  66493. // @private
  66494. onMouseOut: function(e) {
  66495. this.fireEvent('mouseout', e);
  66496. },
  66497. attachEvents: function(o) {
  66498. var me = this;
  66499. o.on({
  66500. scope: me,
  66501. mousedown: me.onMouseDown,
  66502. mouseup: me.onMouseUp,
  66503. mouseover: me.onMouseOver,
  66504. mouseout: me.onMouseOut,
  66505. click: me.onClick
  66506. });
  66507. },
  66508. // Inherit docs from MixedCollection
  66509. add: function(key, o) {
  66510. var result = this.callParent(arguments);
  66511. this.attachEvents(result);
  66512. return result;
  66513. },
  66514. insert: function(index, key, o) {
  66515. return this.callParent(arguments);
  66516. },
  66517. // Inherit docs from MixedCollection
  66518. remove: function(o) {
  66519. var me = this;
  66520. o.un({
  66521. scope: me,
  66522. mousedown: me.onMouseDown,
  66523. mouseup: me.onMouseUp,
  66524. mouseover: me.onMouseOver,
  66525. mouseout: me.onMouseOut,
  66526. click: me.onClick
  66527. });
  66528. return me.callParent(arguments);
  66529. },
  66530. /**
  66531. * Returns the group bounding box.
  66532. * Behaves like {@link Ext.draw.Sprite#getBBox} method.
  66533. * @return {Object} an object with x, y, width, and height properties.
  66534. */
  66535. getBBox: function() {
  66536. var i = 0,
  66537. sprite,
  66538. bb,
  66539. items = this.items,
  66540. len = this.length,
  66541. infinity = Infinity,
  66542. minX = infinity,
  66543. maxHeight = -infinity,
  66544. minY = infinity,
  66545. maxWidth = -infinity,
  66546. maxWidthBBox, maxHeightBBox;
  66547. for (; i < len; i++) {
  66548. sprite = items[i];
  66549. if (sprite.el && ! sprite.bboxExcluded) {
  66550. bb = sprite.getBBox();
  66551. minX = Math.min(minX, bb.x);
  66552. minY = Math.min(minY, bb.y);
  66553. maxHeight = Math.max(maxHeight, bb.height + bb.y);
  66554. maxWidth = Math.max(maxWidth, bb.width + bb.x);
  66555. }
  66556. }
  66557. return {
  66558. x: minX,
  66559. y: minY,
  66560. height: maxHeight - minY,
  66561. width: maxWidth - minX
  66562. };
  66563. },
  66564. /**
  66565. * Iterates through all sprites calling `setAttributes` on each one. For more information {@link Ext.draw.Sprite}
  66566. * provides a description of the attributes that can be set with this method.
  66567. * @param {Object} attrs Attributes to be changed on the sprite.
  66568. * @param {Boolean} redraw Flag to immediately draw the change.
  66569. * @return {Ext.draw.CompositeSprite} this
  66570. */
  66571. setAttributes: function(attrs, redraw) {
  66572. var i = 0,
  66573. items = this.items,
  66574. len = this.length;
  66575. for (; i < len; i++) {
  66576. items[i].setAttributes(attrs, redraw);
  66577. }
  66578. return this;
  66579. },
  66580. /**
  66581. * Hides all sprites. If `true` is passed then a redraw will be forced for each sprite.
  66582. * @param {Boolean} redraw Flag to immediately draw the change.
  66583. * @return {Ext.draw.CompositeSprite} this
  66584. */
  66585. hide: function(redraw) {
  66586. var i = 0,
  66587. items = this.items,
  66588. len = this.length;
  66589. for (; i < len; i++) {
  66590. items[i].hide(redraw);
  66591. }
  66592. return this;
  66593. },
  66594. /**
  66595. * Shows all sprites. If `true` is passed then a redraw will be forced for each sprite.
  66596. * @param {Boolean} redraw Flag to immediately draw the change.
  66597. * @return {Ext.draw.CompositeSprite} this
  66598. */
  66599. show: function(redraw) {
  66600. var i = 0,
  66601. items = this.items,
  66602. len = this.length;
  66603. for (; i < len; i++) {
  66604. items[i].show(redraw);
  66605. }
  66606. return this;
  66607. },
  66608. /**
  66609. * Force redraw of all sprites.
  66610. */
  66611. redraw: function() {
  66612. var me = this,
  66613. i = 0,
  66614. items = me.items,
  66615. surface = me.getSurface(),
  66616. len = me.length;
  66617. if (surface) {
  66618. for (; i < len; i++) {
  66619. surface.renderItem(items[i]);
  66620. }
  66621. }
  66622. return me;
  66623. },
  66624. /**
  66625. * Sets style for all sprites.
  66626. * @param {String} style CSS Style definition.
  66627. */
  66628. setStyle: function(obj) {
  66629. var i = 0,
  66630. items = this.items,
  66631. len = this.length,
  66632. item, el;
  66633. for (; i < len; i++) {
  66634. item = items[i];
  66635. el = item.el;
  66636. if (el) {
  66637. el.setStyle(obj);
  66638. }
  66639. }
  66640. },
  66641. /**
  66642. * Adds class to all sprites.
  66643. * @param {String} cls CSS class name
  66644. */
  66645. addCls: function(obj) {
  66646. var i = 0,
  66647. items = this.items,
  66648. surface = this.getSurface(),
  66649. len = this.length;
  66650. if (surface) {
  66651. for (; i < len; i++) {
  66652. surface.addCls(items[i], obj);
  66653. }
  66654. }
  66655. },
  66656. /**
  66657. * Removes class from all sprites.
  66658. * @param {String} cls CSS class name
  66659. */
  66660. removeCls: function(obj) {
  66661. var i = 0,
  66662. items = this.items,
  66663. surface = this.getSurface(),
  66664. len = this.length;
  66665. if (surface) {
  66666. for (; i < len; i++) {
  66667. surface.removeCls(items[i], obj);
  66668. }
  66669. }
  66670. },
  66671. /**
  66672. * Grab the surface from the items
  66673. * @private
  66674. * @return {Ext.draw.Surface} The surface, null if not found
  66675. */
  66676. getSurface: function(){
  66677. var first = this.first();
  66678. if (first) {
  66679. return first.surface;
  66680. }
  66681. return null;
  66682. },
  66683. /**
  66684. * Destroys this CompositeSprite.
  66685. */
  66686. destroy: function(){
  66687. var me = this,
  66688. surface = me.getSurface(),
  66689. destroySprites = me.autoDestroy,
  66690. item;
  66691. if (surface) {
  66692. while (me.getCount() > 0) {
  66693. item = me.first();
  66694. me.remove(item);
  66695. surface.remove(item, destroySprites);
  66696. }
  66697. }
  66698. me.clearListeners();
  66699. }
  66700. });
  66701. /**
  66702. * A Surface is an interface to render methods inside {@link Ext.draw.Component}.
  66703. *
  66704. * Most of the Surface methods are abstract and they have a concrete implementation
  66705. * in {@link Ext.draw.engine.Vml VML} or {@link Ext.draw.engine.Svg SVG} engines.
  66706. *
  66707. * A Surface contains methods to render {@link Ext.draw.Sprite sprites}, get bounding
  66708. * boxes of sprites, add sprites to the canvas, initialize other graphic components, etc.
  66709. *
  66710. * ## Adding sprites to surface
  66711. *
  66712. * One of the most used methods for this class is the {@link #add} method, to add Sprites to
  66713. * the surface. For example:
  66714. *
  66715. * drawComponent.surface.add({
  66716. * type: 'circle',
  66717. * fill: '#ffc',
  66718. * radius: 100,
  66719. * x: 100,
  66720. * y: 100
  66721. * });
  66722. *
  66723. * The configuration object passed in the `add` method is the same as described in the
  66724. * {@link Ext.draw.Sprite} class documentation.
  66725. *
  66726. * Sprites can also be added to surface by setting their surface config at creation time:
  66727. *
  66728. * var sprite = Ext.create('Ext.draw.Sprite', {
  66729. * type: 'circle',
  66730. * fill: '#ff0',
  66731. * surface: drawComponent.surface,
  66732. * radius: 5
  66733. * });
  66734. *
  66735. * In order to properly apply properties and render the sprite we have to
  66736. * `show` the sprite setting the option `redraw` to `true`:
  66737. *
  66738. * sprite.show(true);
  66739. *
  66740. */
  66741. Ext.define('Ext.draw.Surface', {
  66742. /* Begin Definitions */
  66743. mixins: {
  66744. observable: 'Ext.util.Observable'
  66745. },
  66746. requires: ['Ext.draw.CompositeSprite'],
  66747. uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml', 'Ext.draw.engine.SvgExporter', 'Ext.draw.engine.ImageExporter'],
  66748. separatorRe: /[, ]+/,
  66749. statics: {
  66750. /**
  66751. * Creates and returns a new concrete Surface instance appropriate for the current environment.
  66752. * @param {Object} config Initial configuration for the Surface instance
  66753. * @param {String[]} enginePriority (Optional) order of implementations to use; the first one that is
  66754. * available in the current environment will be used. Defaults to `['Svg', 'Vml']`.
  66755. * @return {Object} The created Surface or false.
  66756. * @static
  66757. */
  66758. create: function(config, enginePriority) {
  66759. enginePriority = enginePriority || ['Svg', 'Vml'];
  66760. var i = 0,
  66761. len = enginePriority.length,
  66762. surfaceClass;
  66763. for (; i < len; i++) {
  66764. if (Ext.supports[enginePriority[i]] !== false) {
  66765. return Ext.create('Ext.draw.engine.' + enginePriority[i], config);
  66766. }
  66767. }
  66768. return false;
  66769. },
  66770. /**
  66771. * Exports a {@link Ext.draw.Surface surface} in a different format.
  66772. * The surface may be exported to an SVG string, using the
  66773. * {@link Ext.draw.engine.SvgExporter}. It may also be exported
  66774. * as an image using the {@link Ext.draw.engine.ImageExporter ImageExporter}.
  66775. * Note that this requires sending data to a remote server to process
  66776. * the SVG into an image, see the {@link Ext.draw.engine.ImageExporter} for
  66777. * more details.
  66778. * @param {Ext.draw.Surface} surface The surface to export.
  66779. * @param {Object} [config] The configuration to be passed to the exporter.
  66780. * See the export method for the appropriate exporter for the relevant
  66781. * configuration options
  66782. * @return {Object} See the return types for the appropriate exporter
  66783. * @static
  66784. */
  66785. save: function(surface, config) {
  66786. config = config || {};
  66787. var exportTypes = {
  66788. 'image/png': 'Image',
  66789. 'image/jpeg': 'Image',
  66790. 'image/svg+xml': 'Svg'
  66791. },
  66792. prefix = exportTypes[config.type] || 'Svg',
  66793. exporter = Ext.draw.engine[prefix + 'Exporter'];
  66794. return exporter.generate(surface, config);
  66795. }
  66796. },
  66797. /* End Definitions */
  66798. // @private
  66799. availableAttrs: {
  66800. blur: 0,
  66801. "clip-rect": "0 0 1e9 1e9",
  66802. cursor: "default",
  66803. cx: 0,
  66804. cy: 0,
  66805. 'dominant-baseline': 'auto',
  66806. fill: "none",
  66807. "fill-opacity": 1,
  66808. font: '10px "Arial"',
  66809. "font-family": '"Arial"',
  66810. "font-size": "10",
  66811. "font-style": "normal",
  66812. "font-weight": 400,
  66813. gradient: "",
  66814. height: 0,
  66815. hidden: false,
  66816. href: "http://sencha.com/",
  66817. opacity: 1,
  66818. path: "M0,0",
  66819. radius: 0,
  66820. rx: 0,
  66821. ry: 0,
  66822. scale: "1 1",
  66823. src: "",
  66824. stroke: "none",
  66825. "stroke-dasharray": "",
  66826. "stroke-linecap": "butt",
  66827. "stroke-linejoin": "butt",
  66828. "stroke-miterlimit": 0,
  66829. "stroke-opacity": 1,
  66830. "stroke-width": 1,
  66831. target: "_blank",
  66832. text: "",
  66833. "text-anchor": "middle",
  66834. title: "Ext Draw",
  66835. width: 0,
  66836. x: 0,
  66837. y: 0,
  66838. zIndex: 0
  66839. },
  66840. /**
  66841. * @cfg {Number} height
  66842. * The height of this component in pixels (defaults to auto).
  66843. */
  66844. /**
  66845. * @cfg {Number} width
  66846. * The width of this component in pixels (defaults to auto).
  66847. */
  66848. container: undefined,
  66849. height: 352,
  66850. width: 512,
  66851. x: 0,
  66852. y: 0,
  66853. /**
  66854. * @cfg {Ext.draw.Sprite[]} items
  66855. * Array of sprites or sprite config objects to add initially to the surface.
  66856. */
  66857. /**
  66858. * @private Flag indicating that the surface implementation requires sprites to be maintained
  66859. * in order of their zIndex. Impls that don't require this can set it to false.
  66860. */
  66861. orderSpritesByZIndex: true,
  66862. /**
  66863. * Creates new Surface.
  66864. * @param {Object} config (optional) Config object.
  66865. */
  66866. constructor: function(config) {
  66867. var me = this;
  66868. config = config || {};
  66869. Ext.apply(me, config);
  66870. me.domRef = Ext.getDoc().dom;
  66871. me.customAttributes = {};
  66872. me.addEvents(
  66873. /**
  66874. * @event
  66875. * Fires when a mousedown is detected within the surface.
  66876. * @param {Ext.EventObject} e An object encapsulating the DOM event.
  66877. */
  66878. 'mousedown',
  66879. /**
  66880. * @event
  66881. * Fires when a mouseup is detected within the surface.
  66882. * @param {Ext.EventObject} e An object encapsulating the DOM event.
  66883. */
  66884. 'mouseup',
  66885. /**
  66886. * @event
  66887. * Fires when a mouseover is detected within the surface.
  66888. * @param {Ext.EventObject} e An object encapsulating the DOM event.
  66889. */
  66890. 'mouseover',
  66891. /**
  66892. * @event
  66893. * Fires when a mouseout is detected within the surface.
  66894. * @param {Ext.EventObject} e An object encapsulating the DOM event.
  66895. */
  66896. 'mouseout',
  66897. /**
  66898. * @event
  66899. * Fires when a mousemove is detected within the surface.
  66900. * @param {Ext.EventObject} e An object encapsulating the DOM event.
  66901. */
  66902. 'mousemove',
  66903. /**
  66904. * @event
  66905. * Fires when a mouseenter is detected within the surface.
  66906. * @param {Ext.EventObject} e An object encapsulating the DOM event.
  66907. */
  66908. 'mouseenter',
  66909. /**
  66910. * @event
  66911. * Fires when a mouseleave is detected within the surface.
  66912. * @param {Ext.EventObject} e An object encapsulating the DOM event.
  66913. */
  66914. 'mouseleave',
  66915. /**
  66916. * @event
  66917. * Fires when a click is detected within the surface.
  66918. * @param {Ext.EventObject} e An object encapsulating the DOM event.
  66919. */
  66920. 'click',
  66921. /**
  66922. * @event
  66923. * Fires when a dblclick is detected within the surface.
  66924. * @param {Ext.EventObject} e An object encapsulating the DOM event.
  66925. */
  66926. 'dblclick'
  66927. );
  66928. me.mixins.observable.constructor.call(me);
  66929. me.getId();
  66930. me.initGradients();
  66931. me.initItems();
  66932. if (me.renderTo) {
  66933. me.render(me.renderTo);
  66934. delete me.renderTo;
  66935. }
  66936. me.initBackground(config.background);
  66937. },
  66938. // @private called to initialize components in the surface
  66939. // this is dependent on the underlying implementation.
  66940. initSurface: Ext.emptyFn,
  66941. // @private called to setup the surface to render an item
  66942. //this is dependent on the underlying implementation.
  66943. renderItem: Ext.emptyFn,
  66944. // @private
  66945. renderItems: Ext.emptyFn,
  66946. // @private
  66947. setViewBox: function (x, y, width, height) {
  66948. if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
  66949. this.viewBox = {x: x, y: y, width: width, height: height};
  66950. this.applyViewBox();
  66951. }
  66952. },
  66953. /**
  66954. * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
  66955. *
  66956. * For example:
  66957. *
  66958. * drawComponent.surface.addCls(sprite, 'x-visible');
  66959. *
  66960. * @param {Object} sprite The sprite to add the class to.
  66961. * @param {String/String[]} className The CSS class to add, or an array of classes
  66962. * @method
  66963. */
  66964. addCls: Ext.emptyFn,
  66965. /**
  66966. * Removes one or more CSS classes from the element.
  66967. *
  66968. * For example:
  66969. *
  66970. * drawComponent.surface.removeCls(sprite, 'x-visible');
  66971. *
  66972. * @param {Object} sprite The sprite to remove the class from.
  66973. * @param {String/String[]} className The CSS class to remove, or an array of classes
  66974. * @method
  66975. */
  66976. removeCls: Ext.emptyFn,
  66977. /**
  66978. * Sets CSS style attributes to an element.
  66979. *
  66980. * For example:
  66981. *
  66982. * drawComponent.surface.setStyle(sprite, {
  66983. * 'cursor': 'pointer'
  66984. * });
  66985. *
  66986. * @param {Object} sprite The sprite to add, or an array of classes to
  66987. * @param {Object} styles An Object with CSS styles.
  66988. * @method
  66989. */
  66990. setStyle: Ext.emptyFn,
  66991. // @private
  66992. initGradients: function() {
  66993. if (this.hasOwnProperty('gradients')) {
  66994. var gradients = this.gradients,
  66995. gLen = gradients.length,
  66996. fn = this.addGradient,
  66997. g;
  66998. if (gradients) {
  66999. for (g = 0; g < gLen; g++) {
  67000. if (fn.call(this, gradients[g], g, gLen) === false) {
  67001. break;
  67002. }
  67003. }
  67004. }
  67005. }
  67006. },
  67007. // @private
  67008. initItems: function() {
  67009. var items = this.items;
  67010. this.items = new Ext.draw.CompositeSprite();
  67011. this.items.autoDestroy = true;
  67012. this.groups = new Ext.draw.CompositeSprite();
  67013. if (items) {
  67014. this.add(items);
  67015. }
  67016. },
  67017. // @private
  67018. initBackground: function(config) {
  67019. var me = this,
  67020. width = me.width,
  67021. height = me.height,
  67022. gradientId, gradient, backgroundSprite;
  67023. if (Ext.isString(config)) {
  67024. config = {
  67025. fill : config
  67026. };
  67027. }
  67028. if (config) {
  67029. if (config.gradient) {
  67030. gradient = config.gradient;
  67031. gradientId = gradient.id;
  67032. me.addGradient(gradient);
  67033. me.background = me.add({
  67034. type: 'rect',
  67035. x: 0,
  67036. y: 0,
  67037. width: width,
  67038. height: height,
  67039. fill: 'url(#' + gradientId + ')',
  67040. zIndex: -1
  67041. });
  67042. } else if (config.fill) {
  67043. me.background = me.add({
  67044. type: 'rect',
  67045. x: 0,
  67046. y: 0,
  67047. width: width,
  67048. height: height,
  67049. fill: config.fill,
  67050. zIndex: -1
  67051. });
  67052. } else if (config.image) {
  67053. me.background = me.add({
  67054. type: 'image',
  67055. x: 0,
  67056. y: 0,
  67057. width: width,
  67058. height: height,
  67059. src: config.image,
  67060. zIndex: -1
  67061. });
  67062. }
  67063. // prevent me.background to jeopardize me.items.getBBox
  67064. me.background.bboxExcluded = true;
  67065. }
  67066. },
  67067. /**
  67068. * Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
  67069. *
  67070. * For example:
  67071. *
  67072. * drawComponent.surface.setSize(500, 500);
  67073. *
  67074. * This method is generally called when also setting the size of the draw Component.
  67075. *
  67076. * @param {Number} w The new width of the canvas.
  67077. * @param {Number} h The new height of the canvas.
  67078. */
  67079. setSize: function(w, h) {
  67080. this.applyViewBox();
  67081. },
  67082. // @private
  67083. scrubAttrs: function(sprite) {
  67084. var i,
  67085. attrs = {},
  67086. exclude = {},
  67087. sattr = sprite.attr;
  67088. for (i in sattr) {
  67089. // Narrow down attributes to the main set
  67090. if (this.translateAttrs.hasOwnProperty(i)) {
  67091. // Translated attr
  67092. attrs[this.translateAttrs[i]] = sattr[i];
  67093. exclude[this.translateAttrs[i]] = true;
  67094. }
  67095. else if (this.availableAttrs.hasOwnProperty(i) && !exclude[i]) {
  67096. // Passtrhough attr
  67097. attrs[i] = sattr[i];
  67098. }
  67099. }
  67100. return attrs;
  67101. },
  67102. // @private
  67103. onClick: function(e) {
  67104. this.processEvent('click', e);
  67105. },
  67106. // @private
  67107. onDblClick: function(e) {
  67108. this.processEvent('dblclick', e);
  67109. },
  67110. // @private
  67111. onMouseUp: function(e) {
  67112. this.processEvent('mouseup', e);
  67113. },
  67114. // @private
  67115. onMouseDown: function(e) {
  67116. this.processEvent('mousedown', e);
  67117. },
  67118. // @private
  67119. onMouseOver: function(e) {
  67120. this.processEvent('mouseover', e);
  67121. },
  67122. // @private
  67123. onMouseOut: function(e) {
  67124. this.processEvent('mouseout', e);
  67125. },
  67126. // @private
  67127. onMouseMove: function(e) {
  67128. this.fireEvent('mousemove', e);
  67129. },
  67130. // @private
  67131. onMouseEnter: Ext.emptyFn,
  67132. // @private
  67133. onMouseLeave: Ext.emptyFn,
  67134. /**
  67135. * Adds a gradient definition to the Surface. Note that in some surface engines, adding
  67136. * a gradient via this method will not take effect if the surface has already been rendered.
  67137. * Therefore, it is preferred to pass the gradients as an item to the surface config, rather
  67138. * than calling this method, especially if the surface is rendered immediately (e.g. due to
  67139. * 'renderTo' in its config). For more information on how to create gradients in the Chart
  67140. * configuration object please refer to {@link Ext.chart.Chart}.
  67141. *
  67142. * The gradient object to be passed into this method is composed by:
  67143. *
  67144. * - **id** - string - The unique name of the gradient.
  67145. * - **angle** - number, optional - The angle of the gradient in degrees.
  67146. * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
  67147. *
  67148. * For example:
  67149. *
  67150. * drawComponent.surface.addGradient({
  67151. * id: 'gradientId',
  67152. * angle: 45,
  67153. * stops: {
  67154. * 0: {
  67155. * color: '#555'
  67156. * },
  67157. * 100: {
  67158. * color: '#ddd'
  67159. * }
  67160. * }
  67161. * });
  67162. *
  67163. * @param {Object} gradient A gradient config.
  67164. * @method
  67165. */
  67166. addGradient: Ext.emptyFn,
  67167. /**
  67168. * Adds a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be
  67169. * passed into this method.
  67170. *
  67171. * For example:
  67172. *
  67173. * drawComponent.surface.add({
  67174. * type: 'circle',
  67175. * fill: '#ffc',
  67176. * radius: 100,
  67177. * x: 100,
  67178. * y: 100
  67179. * });
  67180. *
  67181. * @param {Ext.draw.Sprite[]/Ext.draw.Sprite...} args One or more Sprite objects or configs.
  67182. * @return {Ext.draw.Sprite[]/Ext.draw.Sprite} The sprites added.
  67183. */
  67184. add: function() {
  67185. var args = Array.prototype.slice.call(arguments),
  67186. sprite,
  67187. index,
  67188. hasMultipleArgs = args.length > 1,
  67189. items,
  67190. results,
  67191. i,
  67192. ln,
  67193. item;
  67194. if (hasMultipleArgs || Ext.isArray(args[0])) {
  67195. items = hasMultipleArgs ? args : args[0];
  67196. results = [];
  67197. for (i = 0, ln = items.length; i < ln; i++) {
  67198. item = items[i];
  67199. item = this.add(item);
  67200. results.push(item);
  67201. }
  67202. return results;
  67203. }
  67204. sprite = this.prepareItems(args[0], true)[0];
  67205. this.insertByZIndex(sprite);
  67206. this.onAdd(sprite);
  67207. return sprite;
  67208. },
  67209. /**
  67210. * @private
  67211. * Inserts a given sprite into the correct position in the items collection, according to
  67212. * its zIndex. It will be inserted at the end of an existing series of sprites with the same or
  67213. * lower zIndex. By ensuring sprites are always ordered, this allows surface subclasses to render
  67214. * the sprites in the correct order for proper z-index stacking.
  67215. * @param {Ext.draw.Sprite} sprite
  67216. * @return {Number} the sprite's new index in the list
  67217. */
  67218. insertByZIndex: function(sprite) {
  67219. var me = this,
  67220. sprites = me.items.items,
  67221. len = sprites.length,
  67222. ceil = Math.ceil,
  67223. zIndex = sprite.attr.zIndex,
  67224. idx = len,
  67225. high = idx - 1,
  67226. low = 0,
  67227. otherZIndex;
  67228. if (me.orderSpritesByZIndex && len && zIndex < sprites[high].attr.zIndex) {
  67229. // Find the target index via a binary search for speed
  67230. while (low <= high) {
  67231. idx = ceil((low + high) / 2);
  67232. otherZIndex = sprites[idx].attr.zIndex;
  67233. if (otherZIndex > zIndex) {
  67234. high = idx - 1;
  67235. }
  67236. else if (otherZIndex < zIndex) {
  67237. low = idx + 1;
  67238. }
  67239. else {
  67240. break;
  67241. }
  67242. }
  67243. // Step forward to the end of a sequence of the same or lower z-index
  67244. while (idx < len && sprites[idx].attr.zIndex <= zIndex) {
  67245. idx++;
  67246. }
  67247. }
  67248. me.items.insert(idx, sprite);
  67249. return idx;
  67250. },
  67251. onAdd: function(sprite) {
  67252. var group = sprite.group,
  67253. draggable = sprite.draggable,
  67254. groups, ln, i;
  67255. if (group) {
  67256. groups = [].concat(group);
  67257. ln = groups.length;
  67258. for (i = 0; i < ln; i++) {
  67259. group = groups[i];
  67260. this.getGroup(group).add(sprite);
  67261. }
  67262. delete sprite.group;
  67263. }
  67264. if (draggable) {
  67265. sprite.initDraggable();
  67266. }
  67267. },
  67268. /**
  67269. * Removes a given sprite from the surface, optionally destroying the sprite in the process.
  67270. * You can also call the sprite own `remove` method.
  67271. *
  67272. * For example:
  67273. *
  67274. * drawComponent.surface.remove(sprite);
  67275. * //or...
  67276. * sprite.remove();
  67277. *
  67278. * @param {Ext.draw.Sprite} sprite
  67279. * @param {Boolean} destroySprite
  67280. */
  67281. remove: function(sprite, destroySprite) {
  67282. if (sprite) {
  67283. this.items.remove(sprite);
  67284. var groups = [].concat(this.groups.items),
  67285. gLen = groups.length,
  67286. g;
  67287. for (g = 0; g < gLen; g++) {
  67288. groups[g].remove(sprite);
  67289. }
  67290. sprite.onRemove();
  67291. if (destroySprite === true) {
  67292. sprite.destroy();
  67293. }
  67294. }
  67295. },
  67296. /**
  67297. * Removes all sprites from the surface, optionally destroying the sprites in the process.
  67298. *
  67299. * For example:
  67300. *
  67301. * drawComponent.surface.removeAll();
  67302. *
  67303. * @param {Boolean} destroySprites Whether to destroy all sprites when removing them.
  67304. */
  67305. removeAll: function(destroySprites) {
  67306. var items = this.items.items,
  67307. ln = items.length,
  67308. i;
  67309. for (i = ln - 1; i > -1; i--) {
  67310. this.remove(items[i], destroySprites);
  67311. }
  67312. },
  67313. onRemove: Ext.emptyFn,
  67314. onDestroy: Ext.emptyFn,
  67315. /**
  67316. * @private Using the current viewBox property and the surface's width and height, calculate the
  67317. * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
  67318. */
  67319. applyViewBox: function() {
  67320. var me = this,
  67321. viewBox = me.viewBox,
  67322. width = me.width || 1, // Avoid problems in division
  67323. height = me.height || 1,
  67324. viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
  67325. relativeHeight, relativeWidth, size;
  67326. if (viewBox && (width || height)) {
  67327. viewBoxX = viewBox.x;
  67328. viewBoxY = viewBox.y;
  67329. viewBoxWidth = viewBox.width;
  67330. viewBoxHeight = viewBox.height;
  67331. relativeHeight = height / viewBoxHeight;
  67332. relativeWidth = width / viewBoxWidth;
  67333. size = Math.min(relativeWidth, relativeHeight);
  67334. if (viewBoxWidth * size < width) {
  67335. viewBoxX -= (width - viewBoxWidth * size) / 2 / size;
  67336. }
  67337. if (viewBoxHeight * size < height) {
  67338. viewBoxY -= (height - viewBoxHeight * size) / 2 / size;
  67339. }
  67340. me.viewBoxShift = {
  67341. dx: -viewBoxX,
  67342. dy: -viewBoxY,
  67343. scale: size
  67344. };
  67345. if (me.background) {
  67346. me.background.setAttributes(Ext.apply({}, {
  67347. x: viewBoxX,
  67348. y: viewBoxY,
  67349. width: width / size,
  67350. height: height / size
  67351. }, { hidden: false }), true);
  67352. }
  67353. } else {
  67354. if (me.background && width && height) {
  67355. me.background.setAttributes(Ext.apply({x: 0, y: 0, width: width, height: height}, { hidden: false }), true);
  67356. }
  67357. }
  67358. },
  67359. getBBox: function (sprite, isWithoutTransform) {
  67360. var realPath = this["getPath" + sprite.type](sprite);
  67361. if (isWithoutTransform) {
  67362. sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
  67363. return sprite.bbox.plain;
  67364. }
  67365. if (sprite.dirtyTransform) {
  67366. this.applyTransformations(sprite, true);
  67367. }
  67368. sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
  67369. return sprite.bbox.transform;
  67370. },
  67371. transformToViewBox: function (x, y) {
  67372. if (this.viewBoxShift) {
  67373. var me = this, shift = me.viewBoxShift;
  67374. return [x / shift.scale - shift.dx, y / shift.scale - shift.dy];
  67375. } else {
  67376. return [x, y];
  67377. }
  67378. },
  67379. // @private
  67380. applyTransformations: function(sprite, onlyMatrix) {
  67381. if (sprite.type == 'text') {
  67382. // TODO: getTextBBox function always take matrix into account no matter whether `isWithoutTransform` is true. Fix that.
  67383. sprite.bbox.transform = 0;
  67384. this.transform(sprite, false);
  67385. }
  67386. sprite.dirtyTransform = false;
  67387. var me = this,
  67388. attr = sprite.attr;
  67389. if (attr.translation.x != null || attr.translation.y != null) {
  67390. me.translate(sprite);
  67391. }
  67392. if (attr.scaling.x != null || attr.scaling.y != null) {
  67393. me.scale(sprite);
  67394. }
  67395. if (attr.rotation.degrees != null) {
  67396. me.rotate(sprite);
  67397. }
  67398. sprite.bbox.transform = 0;
  67399. this.transform(sprite, onlyMatrix);
  67400. sprite.transformations = [];
  67401. },
  67402. // @private
  67403. rotate: function (sprite) {
  67404. var bbox,
  67405. deg = sprite.attr.rotation.degrees,
  67406. centerX = sprite.attr.rotation.x,
  67407. centerY = sprite.attr.rotation.y;
  67408. if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
  67409. bbox = this.getBBox(sprite, true);
  67410. centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
  67411. centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
  67412. }
  67413. sprite.transformations.push({
  67414. type: "rotate",
  67415. degrees: deg,
  67416. x: centerX,
  67417. y: centerY
  67418. });
  67419. },
  67420. // @private
  67421. translate: function(sprite) {
  67422. var x = sprite.attr.translation.x || 0,
  67423. y = sprite.attr.translation.y || 0;
  67424. sprite.transformations.push({
  67425. type: "translate",
  67426. x: x,
  67427. y: y
  67428. });
  67429. },
  67430. // @private
  67431. scale: function(sprite) {
  67432. var bbox,
  67433. x = sprite.attr.scaling.x || 1,
  67434. y = sprite.attr.scaling.y || 1,
  67435. centerX = sprite.attr.scaling.centerX,
  67436. centerY = sprite.attr.scaling.centerY;
  67437. if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
  67438. bbox = this.getBBox(sprite, true);
  67439. centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
  67440. centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
  67441. }
  67442. sprite.transformations.push({
  67443. type: "scale",
  67444. x: x,
  67445. y: y,
  67446. centerX: centerX,
  67447. centerY: centerY
  67448. });
  67449. },
  67450. // @private
  67451. rectPath: function (x, y, w, h, r) {
  67452. if (r) {
  67453. return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]];
  67454. }
  67455. return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
  67456. },
  67457. // @private
  67458. ellipsePath: function (x, y, rx, ry) {
  67459. if (ry == null) {
  67460. ry = rx;
  67461. }
  67462. return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]];
  67463. },
  67464. // @private
  67465. getPathpath: function (el) {
  67466. return el.attr.path;
  67467. },
  67468. // @private
  67469. getPathcircle: function (el) {
  67470. var a = el.attr;
  67471. return this.ellipsePath(a.x, a.y, a.radius, a.radius);
  67472. },
  67473. // @private
  67474. getPathellipse: function (el) {
  67475. var a = el.attr;
  67476. return this.ellipsePath(a.x, a.y,
  67477. a.radiusX || (a.width / 2) || 0,
  67478. a.radiusY || (a.height / 2) || 0);
  67479. },
  67480. // @private
  67481. getPathrect: function (el) {
  67482. var a = el.attr;
  67483. return this.rectPath(a.x || 0, a.y || 0, a.width || 0, a.height || 0, a.r || 0);
  67484. },
  67485. // @private
  67486. getPathimage: function (el) {
  67487. var a = el.attr;
  67488. return this.rectPath(a.x || 0, a.y || 0, a.width, a.height);
  67489. },
  67490. // @private
  67491. getPathtext: function (el) {
  67492. var bbox = this.getBBoxText(el);
  67493. return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
  67494. },
  67495. createGroup: function(id) {
  67496. var group = this.groups.get(id);
  67497. if (!group) {
  67498. group = new Ext.draw.CompositeSprite({
  67499. surface: this
  67500. });
  67501. group.id = id || Ext.id(null, 'ext-surface-group-');
  67502. this.groups.add(group);
  67503. }
  67504. return group;
  67505. },
  67506. /**
  67507. * Returns a new group or an existent group associated with the current surface.
  67508. * The group returned is a {@link Ext.draw.CompositeSprite} group.
  67509. *
  67510. * For example:
  67511. *
  67512. * var spriteGroup = drawComponent.surface.getGroup('someGroupId');
  67513. *
  67514. * @param {String} id The unique identifier of the group.
  67515. * @return {Object} The {@link Ext.draw.CompositeSprite}.
  67516. */
  67517. getGroup: function(id) {
  67518. var group;
  67519. if (typeof id == "string") {
  67520. group = this.groups.get(id);
  67521. if (!group) {
  67522. group = this.createGroup(id);
  67523. }
  67524. } else {
  67525. group = id;
  67526. }
  67527. return group;
  67528. },
  67529. // @private
  67530. prepareItems: function(items, applyDefaults) {
  67531. items = [].concat(items);
  67532. // Make sure defaults are applied and item is initialized
  67533. var item, i, ln;
  67534. for (i = 0, ln = items.length; i < ln; i++) {
  67535. item = items[i];
  67536. if (!(item instanceof Ext.draw.Sprite)) {
  67537. // Temporary, just take in configs...
  67538. item.surface = this;
  67539. items[i] = this.createItem(item);
  67540. } else {
  67541. item.surface = this;
  67542. }
  67543. }
  67544. return items;
  67545. },
  67546. /**
  67547. * Changes the text in the sprite element. The sprite must be a `text` sprite.
  67548. * This method can also be called from {@link Ext.draw.Sprite}.
  67549. *
  67550. * For example:
  67551. *
  67552. * var spriteGroup = drawComponent.surface.setText(sprite, 'my new text');
  67553. *
  67554. * @param {Object} sprite The Sprite to change the text.
  67555. * @param {String} text The new text to be set.
  67556. * @method
  67557. */
  67558. setText: Ext.emptyFn,
  67559. // @private Creates an item and appends it to the surface. Called
  67560. // as an internal method when calling `add`.
  67561. createItem: Ext.emptyFn,
  67562. /**
  67563. * Retrieves the id of this component.
  67564. * Will autogenerate an id if one has not already been set.
  67565. */
  67566. getId: function() {
  67567. return this.id || (this.id = Ext.id(null, 'ext-surface-'));
  67568. },
  67569. /**
  67570. * Destroys the surface. This is done by removing all components from it and
  67571. * also removing its reference to a DOM element.
  67572. *
  67573. * For example:
  67574. *
  67575. * drawComponent.surface.destroy();
  67576. */
  67577. destroy: function() {
  67578. var me = this;
  67579. delete me.domRef;
  67580. if (me.background) {
  67581. me.background.destroy();
  67582. }
  67583. me.removeAll(true);
  67584. Ext.destroy(me.groups.items);
  67585. }
  67586. });
  67587. /**
  67588. * The Draw Component is a surface in which sprites can be rendered. The Draw Component
  67589. * manages and holds an {@link Ext.draw.Surface} instance where
  67590. * {@link Ext.draw.Sprite Sprites} can be appended.
  67591. *
  67592. * One way to create a draw component is:
  67593. *
  67594. * @example
  67595. * var drawComponent = Ext.create('Ext.draw.Component', {
  67596. * viewBox: false,
  67597. * items: [{
  67598. * type: 'circle',
  67599. * fill: '#79BB3F',
  67600. * radius: 100,
  67601. * x: 100,
  67602. * y: 100
  67603. * }]
  67604. * });
  67605. *
  67606. * Ext.create('Ext.Window', {
  67607. * width: 215,
  67608. * height: 235,
  67609. * layout: 'fit',
  67610. * items: [drawComponent]
  67611. * }).show();
  67612. *
  67613. * In this case we created a draw component and added a {@link Ext.draw.Sprite sprite} to it.
  67614. * The {@link Ext.draw.Sprite#type type} of the sprite is `circle` so if you run this code you'll see a yellow-ish
  67615. * circle in a Window. When setting `viewBox` to `false` we are responsible for setting the object's position and
  67616. * dimensions accordingly.
  67617. *
  67618. * You can also add sprites by using the surface's add method:
  67619. *
  67620. * drawComponent.surface.add({
  67621. * type: 'circle',
  67622. * fill: '#79BB3F',
  67623. * radius: 100,
  67624. * x: 100,
  67625. * y: 100
  67626. * });
  67627. *
  67628. * ## Larger example
  67629. *
  67630. * @example
  67631. * var drawComponent = Ext.create('Ext.draw.Component', {
  67632. * width: 800,
  67633. * height: 600,
  67634. * renderTo: document.body
  67635. * }), surface = drawComponent.surface;
  67636. *
  67637. * surface.add([{
  67638. * type: 'circle',
  67639. * radius: 10,
  67640. * fill: '#f00',
  67641. * x: 10,
  67642. * y: 10,
  67643. * group: 'circles'
  67644. * }, {
  67645. * type: 'circle',
  67646. * radius: 10,
  67647. * fill: '#0f0',
  67648. * x: 50,
  67649. * y: 50,
  67650. * group: 'circles'
  67651. * }, {
  67652. * type: 'circle',
  67653. * radius: 10,
  67654. * fill: '#00f',
  67655. * x: 100,
  67656. * y: 100,
  67657. * group: 'circles'
  67658. * }, {
  67659. * type: 'rect',
  67660. * width: 20,
  67661. * height: 20,
  67662. * fill: '#f00',
  67663. * x: 10,
  67664. * y: 10,
  67665. * group: 'rectangles'
  67666. * }, {
  67667. * type: 'rect',
  67668. * width: 20,
  67669. * height: 20,
  67670. * fill: '#0f0',
  67671. * x: 50,
  67672. * y: 50,
  67673. * group: 'rectangles'
  67674. * }, {
  67675. * type: 'rect',
  67676. * width: 20,
  67677. * height: 20,
  67678. * fill: '#00f',
  67679. * x: 100,
  67680. * y: 100,
  67681. * group: 'rectangles'
  67682. * }]);
  67683. *
  67684. * // Get references to my groups
  67685. * circles = surface.getGroup('circles');
  67686. * rectangles = surface.getGroup('rectangles');
  67687. *
  67688. * // Animate the circles down
  67689. * circles.animate({
  67690. * duration: 1000,
  67691. * to: {
  67692. * translate: {
  67693. * y: 200
  67694. * }
  67695. * }
  67696. * });
  67697. *
  67698. * // Animate the rectangles across
  67699. * rectangles.animate({
  67700. * duration: 1000,
  67701. * to: {
  67702. * translate: {
  67703. * x: 200
  67704. * }
  67705. * }
  67706. * });
  67707. *
  67708. * For more information on Sprites, the core elements added to a draw component's surface,
  67709. * refer to the {@link Ext.draw.Sprite} documentation.
  67710. */
  67711. Ext.define('Ext.draw.Component', {
  67712. /* Begin Definitions */
  67713. alias: 'widget.draw',
  67714. extend: 'Ext.Component',
  67715. requires: [
  67716. 'Ext.draw.Surface',
  67717. 'Ext.layout.component.Draw'
  67718. ],
  67719. /* End Definitions */
  67720. /**
  67721. * @cfg {String[]} enginePriority
  67722. * Defines the priority order for which Surface implementation to use. The first
  67723. * one supported by the current environment will be used.
  67724. */
  67725. enginePriority: ['Svg', 'Vml'],
  67726. baseCls: Ext.baseCSSPrefix + 'surface',
  67727. componentLayout: 'draw',
  67728. /**
  67729. * @cfg {Boolean} viewBox
  67730. * Turn on view box support which will scale and position items in the draw component to fit to the component while
  67731. * maintaining aspect ratio. Note that this scaling can override other sizing settings on your items.
  67732. */
  67733. viewBox: true,
  67734. shrinkWrap: 3,
  67735. /**
  67736. * @cfg {Boolean} autoSize
  67737. * Turn on autoSize support which will set the bounding div's size to the natural size of the contents.
  67738. */
  67739. autoSize: false,
  67740. /**
  67741. * @cfg {Object[]} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
  67742. * The gradients array is an array of objects with the following properties:
  67743. *
  67744. * - `id` - string - The unique name of the gradient.
  67745. * - `angle` - number, optional - The angle of the gradient in degrees.
  67746. * - `stops` - object - An object with numbers as keys (from 0 to 100) and style objects as values
  67747. *
  67748. * ## Example
  67749. *
  67750. * gradients: [{
  67751. * id: 'gradientId',
  67752. * angle: 45,
  67753. * stops: {
  67754. * 0: {
  67755. * color: '#555'
  67756. * },
  67757. * 100: {
  67758. * color: '#ddd'
  67759. * }
  67760. * }
  67761. * }, {
  67762. * id: 'gradientId2',
  67763. * angle: 0,
  67764. * stops: {
  67765. * 0: {
  67766. * color: '#590'
  67767. * },
  67768. * 20: {
  67769. * color: '#599'
  67770. * },
  67771. * 100: {
  67772. * color: '#ddd'
  67773. * }
  67774. * }
  67775. * }]
  67776. *
  67777. * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
  67778. *
  67779. * sprite.setAttributes({
  67780. * fill: 'url(#gradientId)'
  67781. * }, true);
  67782. */
  67783. /**
  67784. * @cfg {Ext.draw.Sprite[]} items
  67785. * Array of sprites or sprite config objects to add initially to the surface.
  67786. */
  67787. /**
  67788. * @property {Ext.draw.Surface} surface
  67789. * The Surface instance managed by this component.
  67790. */
  67791. initComponent: function() {
  67792. this.callParent(arguments);
  67793. this.addEvents(
  67794. /**
  67795. * @event
  67796. * Event forwarded from {@link Ext.draw.Surface surface}.
  67797. * @inheritdoc Ext.draw.Surface#mousedown
  67798. */
  67799. 'mousedown',
  67800. /**
  67801. * @event
  67802. * Event forwarded from {@link Ext.draw.Surface surface}.
  67803. * @inheritdoc Ext.draw.Surface#mouseup
  67804. */
  67805. 'mouseup',
  67806. /**
  67807. * @event
  67808. * Event forwarded from {@link Ext.draw.Surface surface}.
  67809. * @inheritdoc Ext.draw.Surface#mousemove
  67810. */
  67811. 'mousemove',
  67812. /**
  67813. * @event
  67814. * Event forwarded from {@link Ext.draw.Surface surface}.
  67815. * @inheritdoc Ext.draw.Surface#mouseenter
  67816. */
  67817. 'mouseenter',
  67818. /**
  67819. * @event
  67820. * Event forwarded from {@link Ext.draw.Surface surface}.
  67821. * @inheritdoc Ext.draw.Surface#mouseleave
  67822. */
  67823. 'mouseleave',
  67824. /**
  67825. * @event
  67826. * Event forwarded from {@link Ext.draw.Surface surface}.
  67827. * @inheritdoc Ext.draw.Surface#click
  67828. */
  67829. 'click',
  67830. /**
  67831. * @event
  67832. * Event forwarded from {@link Ext.draw.Surface surface}.
  67833. * @inheritdoc Ext.draw.Surface#dblclick
  67834. */
  67835. 'dblclick'
  67836. );
  67837. },
  67838. /**
  67839. * @private
  67840. *
  67841. * Create the Surface on initial render
  67842. */
  67843. onRender: function() {
  67844. var me = this,
  67845. viewBox = me.viewBox,
  67846. autoSize = me.autoSize,
  67847. bbox, items, width, height, x, y;
  67848. me.callParent(arguments);
  67849. if (me.createSurface() !== false) {
  67850. items = me.surface.items;
  67851. if (viewBox || autoSize) {
  67852. bbox = items.getBBox();
  67853. width = bbox.width;
  67854. height = bbox.height;
  67855. x = bbox.x;
  67856. y = bbox.y;
  67857. if (me.viewBox) {
  67858. me.surface.setViewBox(x, y, width, height);
  67859. } else {
  67860. me.autoSizeSurface();
  67861. }
  67862. }
  67863. }
  67864. },
  67865. // @private
  67866. autoSizeSurface: function() {
  67867. var bbox = this.surface.items.getBBox();
  67868. this.setSurfaceSize(bbox.width, bbox.height);
  67869. },
  67870. setSurfaceSize: function (width, height) {
  67871. this.surface.setSize(width, height);
  67872. if (this.autoSize) {
  67873. var bbox = this.surface.items.getBBox();
  67874. this.surface.setViewBox(bbox.x, bbox.y - (+Ext.isOpera), width, height);
  67875. }
  67876. },
  67877. /**
  67878. * Create the Surface instance. Resolves the correct Surface implementation to
  67879. * instantiate based on the 'enginePriority' config. Once the Surface instance is
  67880. * created you can use the handle to that instance to add sprites. For example:
  67881. *
  67882. * drawComponent.surface.add(sprite);
  67883. *
  67884. * @private
  67885. */
  67886. createSurface: function() {
  67887. var me = this,
  67888. cfg = Ext.applyIf({
  67889. renderTo: me.el,
  67890. height: me.height,
  67891. width: me.width,
  67892. items: me.items
  67893. }, me.initialConfig), surface;
  67894. // ensure we remove any listeners to prevent duplicate events since we refire them below
  67895. delete cfg.listeners;
  67896. surface = Ext.draw.Surface.create(cfg);
  67897. if (!surface) {
  67898. // In case we cannot create a surface, return false so we can stop
  67899. return false;
  67900. }
  67901. me.surface = surface;
  67902. function refire(eventName) {
  67903. return function(e) {
  67904. me.fireEvent(eventName, e);
  67905. };
  67906. }
  67907. surface.on({
  67908. scope: me,
  67909. mouseup: refire('mouseup'),
  67910. mousedown: refire('mousedown'),
  67911. mousemove: refire('mousemove'),
  67912. mouseenter: refire('mouseenter'),
  67913. mouseleave: refire('mouseleave'),
  67914. click: refire('click'),
  67915. dblclick: refire('dblclick')
  67916. });
  67917. },
  67918. /**
  67919. * @private
  67920. *
  67921. * Clean up the Surface instance on component destruction
  67922. */
  67923. onDestroy: function() {
  67924. Ext.destroy(this.surface);
  67925. this.callParent(arguments);
  67926. }
  67927. });
  67928. /**
  67929. * @private
  67930. */
  67931. Ext.define('Ext.chart.Shape', {
  67932. /* Begin Definitions */
  67933. singleton: true,
  67934. /* End Definitions */
  67935. circle: function (surface, opts) {
  67936. return surface.add(Ext.apply({
  67937. type: 'circle',
  67938. x: opts.x,
  67939. y: opts.y,
  67940. stroke: null,
  67941. radius: opts.radius
  67942. }, opts));
  67943. },
  67944. line: function (surface, opts) {
  67945. return surface.add(Ext.apply({
  67946. type: 'rect',
  67947. x: opts.x - opts.radius,
  67948. y: opts.y - opts.radius,
  67949. height: 2 * opts.radius,
  67950. width: 2 * opts.radius / 5
  67951. }, opts));
  67952. },
  67953. square: function (surface, opts) {
  67954. return surface.add(Ext.applyIf({
  67955. type: 'rect',
  67956. x: opts.x - opts.radius,
  67957. y: opts.y - opts.radius,
  67958. height: 2 * opts.radius,
  67959. width: 2 * opts.radius,
  67960. radius: null
  67961. }, opts));
  67962. },
  67963. triangle: function (surface, opts) {
  67964. opts.radius *= 1.75;
  67965. return surface.add(Ext.apply({
  67966. type: 'path',
  67967. stroke: null,
  67968. path: "M".concat(opts.x, ",", opts.y, "m0-", opts.radius * 0.58, "l", opts.radius * 0.5, ",", opts.radius * 0.87, "-", opts.radius, ",0z")
  67969. }, opts));
  67970. },
  67971. diamond: function (surface, opts) {
  67972. var r = opts.radius;
  67973. r *= 1.5;
  67974. return surface.add(Ext.apply({
  67975. type: 'path',
  67976. stroke: null,
  67977. path: ["M", opts.x, opts.y - r, "l", r, r, -r, r, -r, -r, r, -r, "z"]
  67978. }, opts));
  67979. },
  67980. cross: function (surface, opts) {
  67981. var r = opts.radius;
  67982. r = r / 1.7;
  67983. return surface.add(Ext.apply({
  67984. type: 'path',
  67985. stroke: null,
  67986. path: "M".concat(opts.x - r, ",", opts.y, "l", [-r, -r, r, -r, r, r, r, -r, r, r, -r, r, r, r, -r, r, -r, -r, -r, r, -r, -r, "z"])
  67987. }, opts));
  67988. },
  67989. plus: function (surface, opts) {
  67990. var r = opts.radius / 1.3;
  67991. return surface.add(Ext.apply({
  67992. type: 'path',
  67993. stroke: null,
  67994. path: "M".concat(opts.x - r / 2, ",", opts.y - r / 2, "l", [0, -r, r, 0, 0, r, r, 0, 0, r, -r, 0, 0, r, -r, 0, 0, -r, -r, 0, 0, -r, "z"])
  67995. }, opts));
  67996. },
  67997. arrow: function (surface, opts) {
  67998. var r = opts.radius;
  67999. return surface.add(Ext.apply({
  68000. type: 'path',
  68001. path: "M".concat(opts.x - r * 0.7, ",", opts.y - r * 0.4, "l", [r * 0.6, 0, 0, -r * 0.4, r, r * 0.8, -r, r * 0.8, 0, -r * 0.4, -r * 0.6, 0], "z")
  68002. }, opts));
  68003. },
  68004. drop: function (surface, x, y, text, size, angle) {
  68005. size = size || 30;
  68006. angle = angle || 0;
  68007. surface.add({
  68008. type: 'path',
  68009. path: ['M', x, y, 'l', size, 0, 'A', size * 0.4, size * 0.4, 0, 1, 0, x + size * 0.7, y - size * 0.7, 'z'],
  68010. fill: '#000',
  68011. stroke: 'none',
  68012. rotate: {
  68013. degrees: 22.5 - angle,
  68014. x: x,
  68015. y: y
  68016. }
  68017. });
  68018. angle = (angle + 90) * Math.PI / 180;
  68019. surface.add({
  68020. type: 'text',
  68021. x: x + size * Math.sin(angle) - 10, // Shift here, Not sure why.
  68022. y: y + size * Math.cos(angle) + 5,
  68023. text: text,
  68024. 'font-size': size * 12 / 40,
  68025. stroke: 'none',
  68026. fill: '#fff'
  68027. });
  68028. }
  68029. });
  68030. /**
  68031. * @class Ext.chart.LegendItem
  68032. * A single item of a legend (marker plus label)
  68033. */
  68034. Ext.define('Ext.chart.LegendItem', {
  68035. /* Begin Definitions */
  68036. extend: 'Ext.draw.CompositeSprite',
  68037. requires: ['Ext.chart.Shape'],
  68038. /* End Definitions */
  68039. // Position of the item, relative to the upper-left corner of the legend box
  68040. x: 0,
  68041. y: 0,
  68042. zIndex: 500,
  68043. // checks to make sure that a unit size follows the bold keyword in the font style value
  68044. boldRe: /bold\s\d{1,}.*/i,
  68045. constructor: function(config) {
  68046. this.callParent(arguments);
  68047. this.createLegend(config);
  68048. },
  68049. /**
  68050. * Creates all the individual sprites for this legend item
  68051. */
  68052. createLegend: function(config) {
  68053. var me = this,
  68054. index = config.yFieldIndex,
  68055. series = me.series,
  68056. seriesType = series.type,
  68057. idx = me.yFieldIndex,
  68058. legend = me.legend,
  68059. surface = me.surface,
  68060. refX = legend.x + me.x,
  68061. refY = legend.y + me.y,
  68062. bbox, z = me.zIndex,
  68063. markerConfig, label, mask,
  68064. radius, toggle = false,
  68065. seriesStyle = Ext.apply(series.seriesStyle, series.style);
  68066. function getSeriesProp(name) {
  68067. var val = series[name];
  68068. return (Ext.isArray(val) ? val[idx] : val);
  68069. }
  68070. label = me.add('label', surface.add({
  68071. type: 'text',
  68072. x: 20,
  68073. y: 0,
  68074. zIndex: (z || 0) + 2,
  68075. fill: legend.labelColor,
  68076. font: legend.labelFont,
  68077. text: getSeriesProp('title') || getSeriesProp('yField'),
  68078. style: {
  68079. 'cursor': 'pointer'
  68080. }
  68081. }));
  68082. // Line series - display as short line with optional marker in the middle
  68083. if (seriesType === 'line' || seriesType === 'scatter') {
  68084. if(seriesType === 'line') {
  68085. me.add('line', surface.add({
  68086. type: 'path',
  68087. path: 'M0.5,0.5L16.5,0.5',
  68088. zIndex: (z || 0) + 2,
  68089. "stroke-width": series.lineWidth,
  68090. "stroke-linejoin": "round",
  68091. "stroke-dasharray": series.dash,
  68092. stroke: seriesStyle.stroke || series.getLegendColor(index) || '#000',
  68093. style: {
  68094. cursor: 'pointer'
  68095. }
  68096. }));
  68097. }
  68098. if (series.showMarkers || seriesType === 'scatter') {
  68099. markerConfig = Ext.apply(series.markerStyle, series.markerConfig || {}, {
  68100. fill: series.getLegendColor(index)
  68101. });
  68102. me.add('marker', Ext.chart.Shape[markerConfig.type](surface, {
  68103. fill: markerConfig.fill,
  68104. x: 8.5,
  68105. y: 0.5,
  68106. zIndex: (z || 0) + 2,
  68107. radius: markerConfig.radius || markerConfig.size,
  68108. style: {
  68109. cursor: 'pointer'
  68110. }
  68111. }));
  68112. }
  68113. }
  68114. // All other series types - display as filled box
  68115. else {
  68116. me.add('box', surface.add({
  68117. type: 'rect',
  68118. zIndex: (z || 0) + 2,
  68119. x: 0,
  68120. y: 0,
  68121. width: 12,
  68122. height: 12,
  68123. fill: series.getLegendColor(index),
  68124. style: {
  68125. cursor: 'pointer'
  68126. }
  68127. }));
  68128. }
  68129. me.setAttributes({
  68130. hidden: false
  68131. }, true);
  68132. bbox = me.getBBox();
  68133. mask = me.add('mask', surface.add({
  68134. type: 'rect',
  68135. x: bbox.x,
  68136. y: bbox.y,
  68137. width: bbox.width || 20,
  68138. height: bbox.height || 20,
  68139. zIndex: (z || 0) + 1,
  68140. fill: me.legend.boxFill,
  68141. style: {
  68142. 'cursor': 'pointer'
  68143. }
  68144. }));
  68145. //add toggle listener
  68146. me.on('mouseover', function() {
  68147. label.setStyle({
  68148. 'font-weight': 'bold'
  68149. });
  68150. mask.setStyle({
  68151. 'cursor': 'pointer'
  68152. });
  68153. series._index = index;
  68154. series.highlightItem();
  68155. }, me);
  68156. me.on('mouseout', function() {
  68157. label.setStyle({
  68158. 'font-weight': legend.labelFont && me.boldRe.test(legend.labelFont) ? 'bold' : 'normal'
  68159. });
  68160. series._index = index;
  68161. series.unHighlightItem();
  68162. }, me);
  68163. if (!series.visibleInLegend(index)) {
  68164. toggle = true;
  68165. label.setAttributes({
  68166. opacity: 0.5
  68167. }, true);
  68168. }
  68169. me.on('mousedown', function() {
  68170. if (!toggle) {
  68171. series.hideAll(index);
  68172. label.setAttributes({
  68173. opacity: 0.5
  68174. }, true);
  68175. } else {
  68176. series.showAll(index);
  68177. label.setAttributes({
  68178. opacity: 1
  68179. }, true);
  68180. }
  68181. toggle = !toggle;
  68182. me.legend.chart.redraw();
  68183. }, me);
  68184. me.updatePosition({x:0, y:0}); //Relative to 0,0 at first so that the bbox is calculated correctly
  68185. },
  68186. /**
  68187. * Update the positions of all this item's sprites to match the root position
  68188. * of the legend box.
  68189. * @param {Object} relativeTo (optional) If specified, this object's 'x' and 'y' values will be used
  68190. * as the reference point for the relative positioning. Defaults to the Legend.
  68191. */
  68192. updatePosition: function(relativeTo) {
  68193. var me = this,
  68194. items = me.items,
  68195. ln = items.length,
  68196. i = 0,
  68197. item;
  68198. if (!relativeTo) {
  68199. relativeTo = me.legend;
  68200. }
  68201. for (; i < ln; i++) {
  68202. item = items[i];
  68203. switch (item.type) {
  68204. case 'text':
  68205. item.setAttributes({
  68206. x: 20 + relativeTo.x + me.x,
  68207. y: relativeTo.y + me.y
  68208. }, true);
  68209. break;
  68210. case 'rect':
  68211. item.setAttributes({
  68212. translate: {
  68213. x: relativeTo.x + me.x,
  68214. y: relativeTo.y + me.y - 6
  68215. }
  68216. }, true);
  68217. break;
  68218. default:
  68219. item.setAttributes({
  68220. translate: {
  68221. x: relativeTo.x + me.x,
  68222. y: relativeTo.y + me.y
  68223. }
  68224. }, true);
  68225. }
  68226. }
  68227. }
  68228. });
  68229. /**
  68230. * @class Ext.chart.Legend
  68231. *
  68232. * Defines a legend for a chart's series.
  68233. * The 'chart' member must be set prior to rendering.
  68234. * The legend class displays a list of legend items each of them related with a
  68235. * series being rendered. In order to render the legend item of the proper series
  68236. * the series configuration object must have `showInLegend` set to true.
  68237. *
  68238. * The legend configuration object accepts a `position` as parameter.
  68239. * The `position` parameter can be `left`, `right`
  68240. * `top` or `bottom`. For example:
  68241. *
  68242. * legend: {
  68243. * position: 'right'
  68244. * },
  68245. *
  68246. * ## Example
  68247. *
  68248. * @example
  68249. * var store = Ext.create('Ext.data.JsonStore', {
  68250. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  68251. * data: [
  68252. * { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
  68253. * { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
  68254. * { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
  68255. * { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
  68256. * { 'name': 'metric five', 'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
  68257. * ]
  68258. * });
  68259. *
  68260. * Ext.create('Ext.chart.Chart', {
  68261. * renderTo: Ext.getBody(),
  68262. * width: 500,
  68263. * height: 300,
  68264. * animate: true,
  68265. * store: store,
  68266. * shadow: true,
  68267. * theme: 'Category1',
  68268. * legend: {
  68269. * position: 'top'
  68270. * },
  68271. * axes: [
  68272. * {
  68273. * type: 'Numeric',
  68274. * grid: true,
  68275. * position: 'left',
  68276. * fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
  68277. * title: 'Sample Values',
  68278. * grid: {
  68279. * odd: {
  68280. * opacity: 1,
  68281. * fill: '#ddd',
  68282. * stroke: '#bbb',
  68283. * 'stroke-width': 1
  68284. * }
  68285. * },
  68286. * minimum: 0,
  68287. * adjustMinimumByMajorUnit: 0
  68288. * },
  68289. * {
  68290. * type: 'Category',
  68291. * position: 'bottom',
  68292. * fields: ['name'],
  68293. * title: 'Sample Metrics',
  68294. * grid: true,
  68295. * label: {
  68296. * rotate: {
  68297. * degrees: 315
  68298. * }
  68299. * }
  68300. * }
  68301. * ],
  68302. * series: [{
  68303. * type: 'area',
  68304. * highlight: false,
  68305. * axis: 'left',
  68306. * xField: 'name',
  68307. * yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
  68308. * style: {
  68309. * opacity: 0.93
  68310. * }
  68311. * }]
  68312. * });
  68313. */
  68314. Ext.define('Ext.chart.Legend', {
  68315. /* Begin Definitions */
  68316. requires: ['Ext.chart.LegendItem'],
  68317. /* End Definitions */
  68318. /**
  68319. * @cfg {Boolean} visible
  68320. * Whether or not the legend should be displayed.
  68321. */
  68322. visible: true,
  68323. /**
  68324. * @cfg {Boolean} update
  68325. * If set to true the legend will be refreshed when the chart is.
  68326. * This is useful to update the legend items if series are
  68327. * added/removed/updated from the chart. Default is true.
  68328. */
  68329. update: true,
  68330. /**
  68331. * @cfg {String} position
  68332. * The position of the legend in relation to the chart. One of: "top",
  68333. * "bottom", "left", "right", or "float". If set to "float", then the legend
  68334. * box will be positioned at the point denoted by the x and y parameters.
  68335. */
  68336. position: 'bottom',
  68337. /**
  68338. * @cfg {Number} x
  68339. * X-position of the legend box. Used directly if position is set to "float", otherwise
  68340. * it will be calculated dynamically.
  68341. */
  68342. x: 0,
  68343. /**
  68344. * @cfg {Number} y
  68345. * Y-position of the legend box. Used directly if position is set to "float", otherwise
  68346. * it will be calculated dynamically.
  68347. */
  68348. y: 0,
  68349. /**
  68350. * @cfg {String} labelColor
  68351. * Color to be used for the legend labels, eg '#000'
  68352. */
  68353. labelColor: '#000',
  68354. /**
  68355. * @cfg {String} labelFont
  68356. * Font to be used for the legend labels, eg '12px Helvetica'
  68357. */
  68358. labelFont: '12px Helvetica, sans-serif',
  68359. /**
  68360. * @cfg {String} boxStroke
  68361. * Style of the stroke for the legend box
  68362. */
  68363. boxStroke: '#000',
  68364. /**
  68365. * @cfg {String} boxStrokeWidth
  68366. * Width of the stroke for the legend box
  68367. */
  68368. boxStrokeWidth: 1,
  68369. /**
  68370. * @cfg {String} boxFill
  68371. * Fill style for the legend box
  68372. */
  68373. boxFill: '#FFF',
  68374. /**
  68375. * @cfg {Number} itemSpacing
  68376. * Amount of space between legend items
  68377. */
  68378. itemSpacing: 10,
  68379. /**
  68380. * @cfg {Number} padding
  68381. * Amount of padding between the legend box's border and its items
  68382. */
  68383. padding: 5,
  68384. // @private
  68385. width: 0,
  68386. // @private
  68387. height: 0,
  68388. /**
  68389. * @cfg {Number} boxZIndex
  68390. * Sets the z-index for the legend. Defaults to 100.
  68391. */
  68392. boxZIndex: 100,
  68393. /**
  68394. * Creates new Legend.
  68395. * @param {Object} config (optional) Config object.
  68396. */
  68397. constructor: function(config) {
  68398. var me = this;
  68399. if (config) {
  68400. Ext.apply(me, config);
  68401. }
  68402. me.items = [];
  68403. /**
  68404. * Whether the legend box is oriented vertically, i.e. if it is on the left or right side or floating.
  68405. * @type {Boolean}
  68406. */
  68407. me.isVertical = ("left|right|float".indexOf(me.position) !== -1);
  68408. // cache these here since they may get modified later on
  68409. me.origX = me.x;
  68410. me.origY = me.y;
  68411. },
  68412. /**
  68413. * @private Create all the sprites for the legend
  68414. */
  68415. create: function() {
  68416. var me = this,
  68417. seriesItems = me.chart.series.items,
  68418. i, ln, series;
  68419. me.createBox();
  68420. if (me.rebuild !== false) {
  68421. me.createItems();
  68422. }
  68423. if (!me.created && me.isDisplayed()) {
  68424. me.created = true;
  68425. // Listen for changes to series titles to trigger regeneration of the legend
  68426. for (i = 0, ln = seriesItems.length; i < ln; i++) {
  68427. series = seriesItems[i];
  68428. series.on('titlechange', function() {
  68429. me.create();
  68430. me.updatePosition();
  68431. });
  68432. }
  68433. }
  68434. },
  68435. /**
  68436. * @private Determine whether the legend should be displayed. Looks at the legend's 'visible' config,
  68437. * and also the 'showInLegend' config for each of the series.
  68438. */
  68439. isDisplayed: function() {
  68440. return this.visible && this.chart.series.findIndex('showInLegend', true) !== -1;
  68441. },
  68442. /**
  68443. * @private Create the series markers and labels
  68444. */
  68445. createItems: function() {
  68446. var me = this,
  68447. chart = me.chart,
  68448. seriesItems = chart.series.items,
  68449. ln, series,
  68450. surface = chart.surface,
  68451. items = me.items,
  68452. padding = me.padding,
  68453. itemSpacing = me.itemSpacing,
  68454. spacingOffset = 2,
  68455. maxWidth = 0,
  68456. maxHeight = 0,
  68457. totalWidth = 0,
  68458. totalHeight = 0,
  68459. vertical = me.isVertical,
  68460. math = Math,
  68461. mfloor = math.floor,
  68462. mmax = math.max,
  68463. index = 0,
  68464. i = 0,
  68465. len = items ? items.length : 0,
  68466. x, y, spacing, item, bbox, height, width,
  68467. fields, field, nFields, j;
  68468. //remove all legend items
  68469. if (len) {
  68470. for (; i < len; i++) {
  68471. items[i].destroy();
  68472. }
  68473. }
  68474. //empty array
  68475. items.length = [];
  68476. // Create all the item labels, collecting their dimensions and positioning each one
  68477. // properly in relation to the previous item
  68478. for (i = 0, ln = seriesItems.length; i < ln; i++) {
  68479. series = seriesItems[i];
  68480. if (series.showInLegend) {
  68481. fields = [].concat(series.yField);
  68482. for (j = 0, nFields = fields.length; j < nFields; j++) {
  68483. field = fields[j];
  68484. item = new Ext.chart.LegendItem({
  68485. legend: this,
  68486. series: series,
  68487. surface: chart.surface,
  68488. yFieldIndex: j
  68489. });
  68490. bbox = item.getBBox();
  68491. //always measure from x=0, since not all markers go all the way to the left
  68492. width = bbox.width;
  68493. height = bbox.height;
  68494. if (i + j === 0) {
  68495. spacing = vertical ? padding + height / 2 : padding;
  68496. }
  68497. else {
  68498. spacing = itemSpacing / (vertical ? 2 : 1);
  68499. }
  68500. // Set the item's position relative to the legend box
  68501. item.x = mfloor(vertical ? padding : totalWidth + spacing);
  68502. item.y = mfloor(vertical ? totalHeight + spacing : padding + height / 2);
  68503. // Collect cumulative dimensions
  68504. totalWidth += width + spacing;
  68505. totalHeight += height + spacing;
  68506. maxWidth = mmax(maxWidth, width);
  68507. maxHeight = mmax(maxHeight, height);
  68508. items.push(item);
  68509. }
  68510. }
  68511. }
  68512. // Store the collected dimensions for later
  68513. me.width = mfloor((vertical ? maxWidth : totalWidth) + padding * 2);
  68514. if (vertical && items.length === 1) {
  68515. spacingOffset = 1;
  68516. }
  68517. me.height = mfloor((vertical ? totalHeight - spacingOffset * spacing : maxHeight) + (padding * 2));
  68518. me.itemHeight = maxHeight;
  68519. },
  68520. /**
  68521. * @private Get the bounds for the legend's outer box
  68522. */
  68523. getBBox: function() {
  68524. var me = this;
  68525. return {
  68526. x: Math.round(me.x) - me.boxStrokeWidth / 2,
  68527. y: Math.round(me.y) - me.boxStrokeWidth / 2,
  68528. width: me.width,
  68529. height: me.height
  68530. };
  68531. },
  68532. /**
  68533. * @private Create the box around the legend items
  68534. */
  68535. createBox: function() {
  68536. var me = this,
  68537. box, bbox;
  68538. if (me.boxSprite) {
  68539. me.boxSprite.destroy();
  68540. }
  68541. bbox = me.getBBox();
  68542. //if some of the dimensions are NaN this means that we
  68543. //cannot set a specific width/height for the legend
  68544. //container. One possibility for this is that there are
  68545. //actually no items to show in the legend, and the legend
  68546. //should be hidden.
  68547. if (isNaN(bbox.width) || isNaN(bbox.height)) {
  68548. me.boxSprite = false;
  68549. return;
  68550. }
  68551. box = me.boxSprite = me.chart.surface.add(Ext.apply({
  68552. type: 'rect',
  68553. stroke: me.boxStroke,
  68554. "stroke-width": me.boxStrokeWidth,
  68555. fill: me.boxFill,
  68556. zIndex: me.boxZIndex
  68557. }, bbox));
  68558. box.redraw();
  68559. },
  68560. /**
  68561. * @private Update the position of all the legend's sprites to match its current x/y values
  68562. */
  68563. updatePosition: function() {
  68564. var me = this,
  68565. items = me.items,
  68566. i, ln,
  68567. x, y,
  68568. legendWidth = me.width || 0,
  68569. legendHeight = me.height || 0,
  68570. padding = me.padding,
  68571. chart = me.chart,
  68572. chartBBox = chart.chartBBox,
  68573. insets = chart.insetPadding,
  68574. chartWidth = chartBBox.width - (insets * 2),
  68575. chartHeight = chartBBox.height - (insets * 2),
  68576. chartX = chartBBox.x + insets,
  68577. chartY = chartBBox.y + insets,
  68578. surface = chart.surface,
  68579. mfloor = Math.floor,
  68580. bbox;
  68581. if (me.isDisplayed()) {
  68582. // Find the position based on the dimensions
  68583. switch(me.position) {
  68584. case "left":
  68585. x = insets;
  68586. y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
  68587. break;
  68588. case "right":
  68589. x = mfloor(surface.width - legendWidth) - insets;
  68590. y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
  68591. break;
  68592. case "top":
  68593. x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
  68594. y = insets;
  68595. break;
  68596. case "bottom":
  68597. x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
  68598. y = mfloor(surface.height - legendHeight) - insets;
  68599. break;
  68600. default:
  68601. x = mfloor(me.origX) + insets;
  68602. y = mfloor(me.origY) + insets;
  68603. }
  68604. me.x = x;
  68605. me.y = y;
  68606. // Update the position of each item
  68607. for (i = 0, ln = items.length; i < ln; i++) {
  68608. items[i].updatePosition();
  68609. }
  68610. bbox = me.getBBox();
  68611. //if some of the dimensions are NaN this means that we
  68612. //cannot set a specific width/height for the legend
  68613. //container. One possibility for this is that there are
  68614. //actually no items to show in the legend, and the legend
  68615. //should be hidden.
  68616. if (isNaN(bbox.width) || isNaN(bbox.height)) {
  68617. if (me.boxSprite) {
  68618. me.boxSprite.hide(true);
  68619. }
  68620. } else {
  68621. if (!me.boxSprite) {
  68622. me.createBox();
  68623. }
  68624. // Update the position of the outer box
  68625. me.boxSprite.setAttributes(bbox, true);
  68626. me.boxSprite.show(true);
  68627. }
  68628. }
  68629. },
  68630. /** toggle
  68631. * @param {Boolean} Whether to show or hide the legend.
  68632. *
  68633. */
  68634. toggle: function(show) {
  68635. var me = this,
  68636. i = 0,
  68637. items = me.items,
  68638. len = items.length;
  68639. if (me.boxSprite) {
  68640. if (show) {
  68641. me.boxSprite.show(true);
  68642. } else {
  68643. me.boxSprite.hide(true);
  68644. }
  68645. }
  68646. for (; i < len; ++i) {
  68647. if (show) {
  68648. items[i].show(true);
  68649. } else {
  68650. items[i].hide(true);
  68651. }
  68652. }
  68653. me.visible = show;
  68654. }
  68655. });
  68656. /**
  68657. * @class Ext.chart.theme.Theme
  68658. *
  68659. * Provides chart theming.
  68660. *
  68661. * Used as mixins by Ext.chart.Chart.
  68662. */
  68663. Ext.define('Ext.chart.theme.Theme', {
  68664. /* Begin Definitions */
  68665. requires: ['Ext.draw.Color'],
  68666. /* End Definitions */
  68667. theme: 'Base',
  68668. themeAttrs: false,
  68669. initTheme: function(theme) {
  68670. var me = this,
  68671. themes = Ext.chart.theme,
  68672. key, gradients;
  68673. if (theme) {
  68674. theme = theme.split(':');
  68675. for (key in themes) {
  68676. if (key == theme[0]) {
  68677. gradients = theme[1] == 'gradients';
  68678. me.themeAttrs = new themes[key]({
  68679. useGradients: gradients
  68680. });
  68681. if (gradients) {
  68682. me.gradients = me.themeAttrs.gradients;
  68683. }
  68684. if (me.themeAttrs.background) {
  68685. me.background = me.themeAttrs.background;
  68686. }
  68687. return;
  68688. }
  68689. }
  68690. Ext.Error.raise('No theme found named "' + theme + '"');
  68691. }
  68692. }
  68693. },
  68694. // This callback is executed right after when the class is created. This scope refers to the newly created class itself
  68695. function() {
  68696. /* Theme constructor: takes either a complex object with styles like:
  68697. {
  68698. axis: {
  68699. fill: '#000',
  68700. 'stroke-width': 1
  68701. },
  68702. axisLabelTop: {
  68703. fill: '#000',
  68704. font: '11px Arial'
  68705. },
  68706. axisLabelLeft: {
  68707. fill: '#000',
  68708. font: '11px Arial'
  68709. },
  68710. axisLabelRight: {
  68711. fill: '#000',
  68712. font: '11px Arial'
  68713. },
  68714. axisLabelBottom: {
  68715. fill: '#000',
  68716. font: '11px Arial'
  68717. },
  68718. axisTitleTop: {
  68719. fill: '#000',
  68720. font: '11px Arial'
  68721. },
  68722. axisTitleLeft: {
  68723. fill: '#000',
  68724. font: '11px Arial'
  68725. },
  68726. axisTitleRight: {
  68727. fill: '#000',
  68728. font: '11px Arial'
  68729. },
  68730. axisTitleBottom: {
  68731. fill: '#000',
  68732. font: '11px Arial'
  68733. },
  68734. series: {
  68735. 'stroke-width': 1
  68736. },
  68737. seriesLabel: {
  68738. font: '12px Arial',
  68739. fill: '#333'
  68740. },
  68741. marker: {
  68742. stroke: '#555',
  68743. fill: '#000',
  68744. radius: 3,
  68745. size: 3
  68746. },
  68747. seriesThemes: [{
  68748. fill: '#C6DBEF'
  68749. }, {
  68750. fill: '#9ECAE1'
  68751. }, {
  68752. fill: '#6BAED6'
  68753. }, {
  68754. fill: '#4292C6'
  68755. }, {
  68756. fill: '#2171B5'
  68757. }, {
  68758. fill: '#084594'
  68759. }],
  68760. markerThemes: [{
  68761. fill: '#084594',
  68762. type: 'circle'
  68763. }, {
  68764. fill: '#2171B5',
  68765. type: 'cross'
  68766. }, {
  68767. fill: '#4292C6',
  68768. type: 'plus'
  68769. }]
  68770. }
  68771. ...or also takes just an array of colors and creates the complex object:
  68772. {
  68773. colors: ['#aaa', '#bcd', '#eee']
  68774. }
  68775. ...or takes just a base color and makes a theme from it
  68776. {
  68777. baseColor: '#bce'
  68778. }
  68779. To create a new theme you may add it to the Themes object:
  68780. Ext.chart.theme.MyNewTheme = Ext.extend(Object, {
  68781. constructor: function(config) {
  68782. Ext.chart.theme.call(this, config, {
  68783. baseColor: '#mybasecolor'
  68784. });
  68785. }
  68786. });
  68787. //Proposal:
  68788. Ext.chart.theme.MyNewTheme = Ext.chart.createTheme('#basecolor');
  68789. ...and then to use it provide the name of the theme (as a lower case string) in the chart config.
  68790. {
  68791. theme: 'mynewtheme'
  68792. }
  68793. */
  68794. (function() {
  68795. Ext.chart.theme = function(config, base) {
  68796. config = config || {};
  68797. var i = 0, d = +new Date(), l, colors, color,
  68798. seriesThemes, markerThemes,
  68799. seriesTheme, markerTheme,
  68800. key, gradients = [],
  68801. midColor, midL;
  68802. if (config.baseColor) {
  68803. midColor = Ext.draw.Color.fromString(config.baseColor);
  68804. midL = midColor.getHSL()[2];
  68805. if (midL < 0.15) {
  68806. midColor = midColor.getLighter(0.3);
  68807. } else if (midL < 0.3) {
  68808. midColor = midColor.getLighter(0.15);
  68809. } else if (midL > 0.85) {
  68810. midColor = midColor.getDarker(0.3);
  68811. } else if (midL > 0.7) {
  68812. midColor = midColor.getDarker(0.15);
  68813. }
  68814. config.colors = [ midColor.getDarker(0.3).toString(),
  68815. midColor.getDarker(0.15).toString(),
  68816. midColor.toString(),
  68817. midColor.getLighter(0.15).toString(),
  68818. midColor.getLighter(0.3).toString()];
  68819. delete config.baseColor;
  68820. }
  68821. if (config.colors) {
  68822. colors = config.colors.slice();
  68823. markerThemes = base.markerThemes;
  68824. seriesThemes = base.seriesThemes;
  68825. l = colors.length;
  68826. base.colors = colors;
  68827. for (; i < l; i++) {
  68828. color = colors[i];
  68829. markerTheme = markerThemes[i] || {};
  68830. seriesTheme = seriesThemes[i] || {};
  68831. markerTheme.fill = seriesTheme.fill = markerTheme.stroke = seriesTheme.stroke = color;
  68832. markerThemes[i] = markerTheme;
  68833. seriesThemes[i] = seriesTheme;
  68834. }
  68835. base.markerThemes = markerThemes.slice(0, l);
  68836. base.seriesThemes = seriesThemes.slice(0, l);
  68837. //the user is configuring something in particular (either markers, series or pie slices)
  68838. }
  68839. for (key in base) {
  68840. if (key in config) {
  68841. if (Ext.isObject(config[key]) && Ext.isObject(base[key])) {
  68842. Ext.apply(base[key], config[key]);
  68843. } else {
  68844. base[key] = config[key];
  68845. }
  68846. }
  68847. }
  68848. if (config.useGradients) {
  68849. colors = base.colors || (function () {
  68850. var ans = [];
  68851. for (i = 0, seriesThemes = base.seriesThemes, l = seriesThemes.length; i < l; i++) {
  68852. ans.push(seriesThemes[i].fill || seriesThemes[i].stroke);
  68853. }
  68854. return ans;
  68855. }());
  68856. for (i = 0, l = colors.length; i < l; i++) {
  68857. midColor = Ext.draw.Color.fromString(colors[i]);
  68858. if (midColor) {
  68859. color = midColor.getDarker(0.1).toString();
  68860. midColor = midColor.toString();
  68861. key = 'theme-' + midColor.substr(1) + '-' + color.substr(1) + '-' + d;
  68862. gradients.push({
  68863. id: key,
  68864. angle: 45,
  68865. stops: {
  68866. 0: {
  68867. color: midColor.toString()
  68868. },
  68869. 100: {
  68870. color: color.toString()
  68871. }
  68872. }
  68873. });
  68874. colors[i] = 'url(#' + key + ')';
  68875. }
  68876. }
  68877. base.gradients = gradients;
  68878. base.colors = colors;
  68879. }
  68880. /*
  68881. base.axis = Ext.apply(base.axis || {}, config.axis || {});
  68882. base.axisLabel = Ext.apply(base.axisLabel || {}, config.axisLabel || {});
  68883. base.axisTitle = Ext.apply(base.axisTitle || {}, config.axisTitle || {});
  68884. */
  68885. Ext.apply(this, base);
  68886. };
  68887. }());
  68888. });
  68889. /**
  68890. * Provides default colors for non-specified things. Should be sub-classed when creating new themes.
  68891. * @private
  68892. */
  68893. Ext.define('Ext.chart.theme.Base', {
  68894. /* Begin Definitions */
  68895. requires: ['Ext.chart.theme.Theme'],
  68896. /* End Definitions */
  68897. constructor: function(config) {
  68898. Ext.chart.theme.call(this, config, {
  68899. background: false,
  68900. axis: {
  68901. stroke: '#444',
  68902. 'stroke-width': 1
  68903. },
  68904. axisLabelTop: {
  68905. fill: '#444',
  68906. font: '12px Arial, Helvetica, sans-serif',
  68907. spacing: 2,
  68908. padding: 5,
  68909. renderer: function(v) { return v; }
  68910. },
  68911. axisLabelRight: {
  68912. fill: '#444',
  68913. font: '12px Arial, Helvetica, sans-serif',
  68914. spacing: 2,
  68915. padding: 5,
  68916. renderer: function(v) { return v; }
  68917. },
  68918. axisLabelBottom: {
  68919. fill: '#444',
  68920. font: '12px Arial, Helvetica, sans-serif',
  68921. spacing: 2,
  68922. padding: 5,
  68923. renderer: function(v) { return v; }
  68924. },
  68925. axisLabelLeft: {
  68926. fill: '#444',
  68927. font: '12px Arial, Helvetica, sans-serif',
  68928. spacing: 2,
  68929. padding: 5,
  68930. renderer: function(v) { return v; }
  68931. },
  68932. axisTitleTop: {
  68933. font: 'bold 18px Arial',
  68934. fill: '#444'
  68935. },
  68936. axisTitleRight: {
  68937. font: 'bold 18px Arial',
  68938. fill: '#444',
  68939. rotate: {
  68940. x:0, y:0,
  68941. degrees: 270
  68942. }
  68943. },
  68944. axisTitleBottom: {
  68945. font: 'bold 18px Arial',
  68946. fill: '#444'
  68947. },
  68948. axisTitleLeft: {
  68949. font: 'bold 18px Arial',
  68950. fill: '#444',
  68951. rotate: {
  68952. x:0, y:0,
  68953. degrees: 270
  68954. }
  68955. },
  68956. series: {
  68957. 'stroke-width': 0
  68958. },
  68959. seriesLabel: {
  68960. font: '12px Arial',
  68961. fill: '#333'
  68962. },
  68963. marker: {
  68964. stroke: '#555',
  68965. radius: 3,
  68966. size: 3
  68967. },
  68968. colors: [ "#94ae0a", "#115fa6","#a61120", "#ff8809", "#ffd13e", "#a61187", "#24ad9a", "#7c7474", "#a66111"],
  68969. seriesThemes: [{
  68970. fill: "#115fa6"
  68971. }, {
  68972. fill: "#94ae0a"
  68973. }, {
  68974. fill: "#a61120"
  68975. }, {
  68976. fill: "#ff8809"
  68977. }, {
  68978. fill: "#ffd13e"
  68979. }, {
  68980. fill: "#a61187"
  68981. }, {
  68982. fill: "#24ad9a"
  68983. }, {
  68984. fill: "#7c7474"
  68985. }, {
  68986. fill: "#115fa6"
  68987. }, {
  68988. fill: "#94ae0a"
  68989. }, {
  68990. fill: "#a61120"
  68991. }, {
  68992. fill: "#ff8809"
  68993. }, {
  68994. fill: "#ffd13e"
  68995. }, {
  68996. fill: "#a61187"
  68997. }, {
  68998. fill: "#24ad9a"
  68999. }, {
  69000. fill: "#7c7474"
  69001. }, {
  69002. fill: "#a66111"
  69003. }],
  69004. markerThemes: [{
  69005. fill: "#115fa6",
  69006. type: 'circle'
  69007. }, {
  69008. fill: "#94ae0a",
  69009. type: 'cross'
  69010. }, {
  69011. fill: "#115fa6",
  69012. type: 'plus'
  69013. }, {
  69014. fill: "#94ae0a",
  69015. type: 'circle'
  69016. }, {
  69017. fill: "#a61120",
  69018. type: 'cross'
  69019. }]
  69020. });
  69021. }
  69022. }, function() {
  69023. var palette = ['#b1da5a', '#4ce0e7', '#e84b67', '#da5abd', '#4d7fe6', '#fec935'],
  69024. names = ['Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow'],
  69025. i = 0, j = 0, l = palette.length, themes = Ext.chart.theme,
  69026. categories = [['#f0a50a', '#c20024', '#2044ba', '#810065', '#7eae29'],
  69027. ['#6d9824', '#87146e', '#2a9196', '#d39006', '#1e40ac'],
  69028. ['#fbbc29', '#ce2e4e', '#7e0062', '#158b90', '#57880e'],
  69029. ['#ef5773', '#fcbd2a', '#4f770d', '#1d3eaa', '#9b001f'],
  69030. ['#7eae29', '#fdbe2a', '#910019', '#27b4bc', '#d74dbc'],
  69031. ['#44dce1', '#0b2592', '#996e05', '#7fb325', '#b821a1']],
  69032. cats = categories.length;
  69033. //Create themes from base colors
  69034. for (; i < l; i++) {
  69035. themes[names[i]] = (function(color) {
  69036. return Ext.extend(themes.Base, {
  69037. constructor: function(config) {
  69038. themes.Base.prototype.constructor.call(this, Ext.apply({
  69039. baseColor: color
  69040. }, config));
  69041. }
  69042. });
  69043. }(palette[i]));
  69044. }
  69045. //Create theme from color array
  69046. for (i = 0; i < cats; i++) {
  69047. themes['Category' + (i + 1)] = (function(category) {
  69048. return Ext.extend(themes.Base, {
  69049. constructor: function(config) {
  69050. themes.Base.prototype.constructor.call(this, Ext.apply({
  69051. colors: category
  69052. }, config));
  69053. }
  69054. });
  69055. }(categories[i]));
  69056. }
  69057. });
  69058. /**
  69059. * @private
  69060. */
  69061. Ext.define('Ext.chart.MaskLayer', {
  69062. extend: 'Ext.Component',
  69063. constructor: function(config) {
  69064. config = Ext.apply(config || {}, {
  69065. style: 'position:absolute;background-color:#888;cursor:move;opacity:0.6;border:1px solid #222;'
  69066. });
  69067. this.callParent([config]);
  69068. },
  69069. initComponent: function() {
  69070. var me = this;
  69071. me.callParent(arguments);
  69072. me.addEvents(
  69073. 'mousedown',
  69074. 'mouseup',
  69075. 'mousemove',
  69076. 'mouseenter',
  69077. 'mouseleave'
  69078. );
  69079. },
  69080. initDraggable: function() {
  69081. this.callParent(arguments);
  69082. this.dd.onStart = function (e) {
  69083. var me = this,
  69084. comp = me.comp;
  69085. // Cache the start [X, Y] array
  69086. this.startPosition = comp.getPosition(true);
  69087. // If client Component has a ghost method to show a lightweight version of itself
  69088. // then use that as a drag proxy unless configured to liveDrag.
  69089. if (comp.ghost && !comp.liveDrag) {
  69090. me.proxy = comp.ghost();
  69091. me.dragTarget = me.proxy.header.el;
  69092. }
  69093. // Set the constrainTo Region before we start dragging.
  69094. if (me.constrain || me.constrainDelegate) {
  69095. me.constrainTo = me.calculateConstrainRegion();
  69096. }
  69097. };
  69098. }
  69099. });
  69100. /**
  69101. * Defines a mask for a chart's series.
  69102. * The 'chart' member must be set prior to rendering.
  69103. *
  69104. * A Mask can be used to select a certain region in a chart.
  69105. * When enabled, the `select` event will be triggered when a
  69106. * region is selected by the mask, allowing the user to perform
  69107. * other tasks like zooming on that region, etc.
  69108. *
  69109. * In order to use the mask one has to set the Chart `mask` option to
  69110. * `true`, `vertical` or `horizontal`. Then a possible configuration for the
  69111. * listener could be:
  69112. *
  69113. * items: {
  69114. * xtype: 'chart',
  69115. * animate: true,
  69116. * store: store1,
  69117. * mask: 'horizontal',
  69118. * listeners: {
  69119. * select: {
  69120. * fn: function(me, selection) {
  69121. * me.setZoom(selection);
  69122. * me.mask.hide();
  69123. * }
  69124. * }
  69125. * }
  69126. * }
  69127. *
  69128. * In this example we zoom the chart to that particular region. You can also get
  69129. * a handle to a mask instance from the chart object. The `chart.mask` element is a
  69130. * `Ext.Panel`.
  69131. *
  69132. */
  69133. Ext.define('Ext.chart.Mask', {
  69134. requires: [
  69135. 'Ext.chart.MaskLayer'
  69136. ],
  69137. /**
  69138. * @cfg {Boolean/String} mask
  69139. * Enables selecting a region on chart. True to enable any selection,
  69140. * 'horizontal' or 'vertical' to restrict the selection to X or Y axis.
  69141. *
  69142. * The mask in itself will do nothing but fire 'select' event.
  69143. * See {@link Ext.chart.Mask} for example.
  69144. */
  69145. /**
  69146. * Creates new Mask.
  69147. * @param {Object} [config] Config object.
  69148. */
  69149. constructor: function(config) {
  69150. var me = this,
  69151. resizeHandler;
  69152. me.addEvents('select');
  69153. if (config) {
  69154. Ext.apply(me, config);
  69155. }
  69156. if (me.enableMask) {
  69157. me.on('afterrender', function() {
  69158. //create a mask layer component
  69159. var comp = new Ext.chart.MaskLayer({
  69160. renderTo: me.el,
  69161. hidden: true
  69162. });
  69163. comp.el.on({
  69164. 'mousemove': function(e) {
  69165. me.onMouseMove(e);
  69166. },
  69167. 'mouseup': function(e) {
  69168. me.resized(e);
  69169. }
  69170. });
  69171. //create a resize handler for the component
  69172. resizeHandler = new Ext.resizer.Resizer({
  69173. el: comp.el,
  69174. handles: 'all',
  69175. pinned: true
  69176. });
  69177. resizeHandler.on({
  69178. 'resize': function(e) {
  69179. me.resized(e);
  69180. }
  69181. });
  69182. comp.initDraggable();
  69183. me.maskType = me.mask;
  69184. me.mask = comp;
  69185. me.maskSprite = me.surface.add({
  69186. type: 'path',
  69187. path: ['M', 0, 0],
  69188. zIndex: 1001,
  69189. opacity: 0.7,
  69190. hidden: true,
  69191. stroke: '#444'
  69192. });
  69193. }, me, { single: true });
  69194. }
  69195. },
  69196. resized: function(e) {
  69197. var me = this,
  69198. bbox = me.bbox || me.chartBBox,
  69199. x = bbox.x,
  69200. y = bbox.y,
  69201. width = bbox.width,
  69202. height = bbox.height,
  69203. box = me.mask.getBox(true),
  69204. max = Math.max,
  69205. min = Math.min,
  69206. staticX = box.x - x,
  69207. staticY = box.y - y;
  69208. staticX = max(staticX, x);
  69209. staticY = max(staticY, y);
  69210. staticX = min(staticX, width);
  69211. staticY = min(staticY, height);
  69212. box.x = staticX;
  69213. box.y = staticY;
  69214. me.fireEvent('select', me, box);
  69215. },
  69216. onMouseUp: function(e) {
  69217. var me = this,
  69218. bbox = me.bbox || me.chartBBox,
  69219. sel = me.maskSelection;
  69220. me.maskMouseDown = false;
  69221. me.mouseDown = false;
  69222. if (me.mouseMoved) {
  69223. me.onMouseMove(e);
  69224. me.mouseMoved = false;
  69225. me.fireEvent('select', me, {
  69226. x: sel.x - bbox.x,
  69227. y: sel.y - bbox.y,
  69228. width: sel.width,
  69229. height: sel.height
  69230. });
  69231. }
  69232. },
  69233. onMouseDown: function(e) {
  69234. var me = this;
  69235. me.mouseDown = true;
  69236. me.mouseMoved = false;
  69237. me.maskMouseDown = {
  69238. x: e.getPageX() - me.el.getX(),
  69239. y: e.getPageY() - me.el.getY()
  69240. };
  69241. },
  69242. onMouseMove: function(e) {
  69243. var me = this,
  69244. mask = me.maskType,
  69245. bbox = me.bbox || me.chartBBox,
  69246. x = bbox.x,
  69247. y = bbox.y,
  69248. math = Math,
  69249. floor = math.floor,
  69250. abs = math.abs,
  69251. min = math.min,
  69252. max = math.max,
  69253. height = floor(y + bbox.height),
  69254. width = floor(x + bbox.width),
  69255. posX = e.getPageX(),
  69256. posY = e.getPageY(),
  69257. staticX = posX - me.el.getX(),
  69258. staticY = posY - me.el.getY(),
  69259. maskMouseDown = me.maskMouseDown,
  69260. path;
  69261. me.mouseMoved = me.mouseDown;
  69262. staticX = max(staticX, x);
  69263. staticY = max(staticY, y);
  69264. staticX = min(staticX, width);
  69265. staticY = min(staticY, height);
  69266. if (maskMouseDown && me.mouseDown) {
  69267. if (mask == 'horizontal') {
  69268. staticY = y;
  69269. maskMouseDown.y = height;
  69270. posY = me.el.getY() + bbox.height + me.insetPadding;
  69271. }
  69272. else if (mask == 'vertical') {
  69273. staticX = x;
  69274. maskMouseDown.x = width;
  69275. }
  69276. width = maskMouseDown.x - staticX;
  69277. height = maskMouseDown.y - staticY;
  69278. path = ['M', staticX, staticY, 'l', width, 0, 0, height, -width, 0, 'z'];
  69279. me.maskSelection = {
  69280. x: width > 0 ? staticX : staticX + width,
  69281. y: height > 0 ? staticY : staticY + height,
  69282. width: abs(width),
  69283. height: abs(height)
  69284. };
  69285. me.mask.updateBox(me.maskSelection);
  69286. me.mask.show();
  69287. me.maskSprite.setAttributes({
  69288. hidden: true
  69289. }, true);
  69290. }
  69291. else {
  69292. if (mask == 'horizontal') {
  69293. path = ['M', staticX, y, 'L', staticX, height];
  69294. }
  69295. else if (mask == 'vertical') {
  69296. path = ['M', x, staticY, 'L', width, staticY];
  69297. }
  69298. else {
  69299. path = ['M', staticX, y, 'L', staticX, height, 'M', x, staticY, 'L', width, staticY];
  69300. }
  69301. me.maskSprite.setAttributes({
  69302. path: path,
  69303. fill: me.maskMouseDown ? me.maskSprite.stroke : false,
  69304. 'stroke-width': mask === true ? 1 : 3,
  69305. hidden: false
  69306. }, true);
  69307. }
  69308. },
  69309. onMouseLeave: function(e) {
  69310. var me = this;
  69311. me.mouseMoved = false;
  69312. me.mouseDown = false;
  69313. me.maskMouseDown = false;
  69314. me.mask.hide();
  69315. me.maskSprite.hide(true);
  69316. }
  69317. });
  69318. /**
  69319. * @class Ext.chart.Navigation
  69320. *
  69321. * Handles panning and zooming capabilities.
  69322. *
  69323. * Used as mixin by Ext.chart.Chart.
  69324. */
  69325. Ext.define('Ext.chart.Navigation', {
  69326. constructor: function() {
  69327. this.originalStore = this.store;
  69328. },
  69329. /**
  69330. * Zooms the chart to the specified selection range.
  69331. * Can be used with a selection mask. For example:
  69332. *
  69333. * items: {
  69334. * xtype: 'chart',
  69335. * animate: true,
  69336. * store: store1,
  69337. * mask: 'horizontal',
  69338. * listeners: {
  69339. * select: {
  69340. * fn: function(me, selection) {
  69341. * me.setZoom(selection);
  69342. * me.mask.hide();
  69343. * }
  69344. * }
  69345. * }
  69346. * }
  69347. */
  69348. setZoom: function(zoomConfig) {
  69349. var me = this,
  69350. axes = me.axes,
  69351. axesItems = axes.items,
  69352. i, ln, axis,
  69353. bbox = me.chartBBox,
  69354. xScale = 1 / bbox.width,
  69355. yScale = 1 / bbox.height,
  69356. zoomer = {
  69357. x : zoomConfig.x * xScale,
  69358. y : zoomConfig.y * yScale,
  69359. width : zoomConfig.width * xScale,
  69360. height : zoomConfig.height * yScale
  69361. },
  69362. ends, from, to;
  69363. for (i = 0, ln = axesItems.length; i < ln; i++) {
  69364. axis = axesItems[i];
  69365. ends = axis.calcEnds();
  69366. if (axis.position == 'bottom' || axis.position == 'top') {
  69367. from = (ends.to - ends.from) * zoomer.x + ends.from;
  69368. to = (ends.to - ends.from) * zoomer.width + from;
  69369. axis.minimum = from;
  69370. axis.maximum = to;
  69371. } else {
  69372. to = (ends.to - ends.from) * (1 - zoomer.y) + ends.from;
  69373. from = to - (ends.to - ends.from) * zoomer.height;
  69374. axis.minimum = from;
  69375. axis.maximum = to;
  69376. }
  69377. }
  69378. me.redraw(false);
  69379. },
  69380. /**
  69381. * Restores the zoom to the original value. This can be used to reset
  69382. * the previous zoom state set by `setZoom`. For example:
  69383. *
  69384. * myChart.restoreZoom();
  69385. */
  69386. restoreZoom: function() {
  69387. if (this.originalStore) {
  69388. this.store = this.substore = this.originalStore;
  69389. this.redraw(true);
  69390. }
  69391. }
  69392. });
  69393. /**
  69394. * Charts provide a flexible way to achieve a wide range of data visualization capablitities.
  69395. * Each Chart gets its data directly from a {@link Ext.data.Store Store}, and automatically
  69396. * updates its display whenever data in the Store changes. In addition, the look and feel
  69397. * of a Chart can be customized using {@link Ext.chart.theme.Theme Theme}s.
  69398. *
  69399. * ## Creating a Simple Chart
  69400. *
  69401. * Every Chart has three key parts - a {@link Ext.data.Store Store} that contains the data,
  69402. * an array of {@link Ext.chart.axis.Axis Axes} which define the boundaries of the Chart,
  69403. * and one or more {@link Ext.chart.series.Series Series} to handle the visual rendering of the data points.
  69404. *
  69405. * ### 1. Creating a Store
  69406. *
  69407. * The first step is to create a {@link Ext.data.Model Model} that represents the type of
  69408. * data that will be displayed in the Chart. For example the data for a chart that displays
  69409. * a weather forecast could be represented as a series of "WeatherPoint" data points with
  69410. * two fields - "temperature", and "date":
  69411. *
  69412. * Ext.define('WeatherPoint', {
  69413. * extend: 'Ext.data.Model',
  69414. * fields: ['temperature', 'date']
  69415. * });
  69416. *
  69417. * Next a {@link Ext.data.Store Store} must be created. The store contains a collection of "WeatherPoint" Model instances.
  69418. * The data could be loaded dynamically, but for sake of ease this example uses inline data:
  69419. *
  69420. * var store = Ext.create('Ext.data.Store', {
  69421. * model: 'WeatherPoint',
  69422. * data: [
  69423. * { temperature: 58, date: new Date(2011, 1, 1, 8) },
  69424. * { temperature: 63, date: new Date(2011, 1, 1, 9) },
  69425. * { temperature: 73, date: new Date(2011, 1, 1, 10) },
  69426. * { temperature: 78, date: new Date(2011, 1, 1, 11) },
  69427. * { temperature: 81, date: new Date(2011, 1, 1, 12) }
  69428. * ]
  69429. * });
  69430. *
  69431. * For additional information on Models and Stores please refer to the [Data Guide](#/guide/data).
  69432. *
  69433. * ### 2. Creating the Chart object
  69434. *
  69435. * Now that a Store has been created it can be used in a Chart:
  69436. *
  69437. * Ext.create('Ext.chart.Chart', {
  69438. * renderTo: Ext.getBody(),
  69439. * width: 400,
  69440. * height: 300,
  69441. * store: store
  69442. * });
  69443. *
  69444. * That's all it takes to create a Chart instance that is backed by a Store.
  69445. * However, if the above code is run in a browser, a blank screen will be displayed.
  69446. * This is because the two pieces that are responsible for the visual display,
  69447. * the Chart's {@link #cfg-axes axes} and {@link #cfg-series series}, have not yet been defined.
  69448. *
  69449. * ### 3. Configuring the Axes
  69450. *
  69451. * {@link Ext.chart.axis.Axis Axes} are the lines that define the boundaries of the data points that a Chart can display.
  69452. * This example uses one of the most common Axes configurations - a horizontal "x" axis, and a vertical "y" axis:
  69453. *
  69454. * Ext.create('Ext.chart.Chart', {
  69455. * ...
  69456. * axes: [
  69457. * {
  69458. * title: 'Temperature',
  69459. * type: 'Numeric',
  69460. * position: 'left',
  69461. * fields: ['temperature'],
  69462. * minimum: 0,
  69463. * maximum: 100
  69464. * },
  69465. * {
  69466. * title: 'Time',
  69467. * type: 'Time',
  69468. * position: 'bottom',
  69469. * fields: ['date'],
  69470. * dateFormat: 'ga'
  69471. * }
  69472. * ]
  69473. * });
  69474. *
  69475. * The "Temperature" axis is a vertical {@link Ext.chart.axis.Numeric Numeric Axis} and is positioned on the left edge of the Chart.
  69476. * It represents the bounds of the data contained in the "WeatherPoint" Model's "temperature" field that was
  69477. * defined above. The minimum value for this axis is "0", and the maximum is "100".
  69478. *
  69479. * The horizontal axis is a {@link Ext.chart.axis.Time Time Axis} and is positioned on the bottom edge of the Chart.
  69480. * It represents the bounds of the data contained in the "WeatherPoint" Model's "date" field.
  69481. * The {@link Ext.chart.axis.Time#cfg-dateFormat dateFormat}
  69482. * configuration tells the Time Axis how to format it's labels.
  69483. *
  69484. * Here's what the Chart looks like now that it has its Axes configured:
  69485. *
  69486. * {@img Ext.chart.Chart/Ext.chart.Chart1.png Chart Axes}
  69487. *
  69488. * ### 4. Configuring the Series
  69489. *
  69490. * The final step in creating a simple Chart is to configure one or more {@link Ext.chart.series.Series Series}.
  69491. * Series are responsible for the visual representation of the data points contained in the Store.
  69492. * This example only has one Series:
  69493. *
  69494. * Ext.create('Ext.chart.Chart', {
  69495. * ...
  69496. * axes: [
  69497. * ...
  69498. * ],
  69499. * series: [
  69500. * {
  69501. * type: 'line',
  69502. * xField: 'date',
  69503. * yField: 'temperature'
  69504. * }
  69505. * ]
  69506. * });
  69507. *
  69508. * This Series is a {@link Ext.chart.series.Line Line Series}, and it uses the "date" and "temperature" fields
  69509. * from the "WeatherPoint" Models in the Store to plot its data points:
  69510. *
  69511. * {@img Ext.chart.Chart/Ext.chart.Chart2.png Line Series}
  69512. *
  69513. * See the [Line Charts Example](#!/example/charts/Charts.html) for a live demo.
  69514. *
  69515. * ## Themes
  69516. *
  69517. * The color scheme for a Chart can be easily changed using the {@link #cfg-theme theme} configuration option:
  69518. *
  69519. * Ext.create('Ext.chart.Chart', {
  69520. * ...
  69521. * theme: 'Green',
  69522. * ...
  69523. * });
  69524. *
  69525. * {@img Ext.chart.Chart/Ext.chart.Chart3.png Green Theme}
  69526. *
  69527. * For more information on Charts please refer to the [Drawing and Charting Guide](#/guide/drawing_and_charting).
  69528. *
  69529. */
  69530. Ext.define('Ext.chart.Chart', {
  69531. /* Begin Definitions */
  69532. alias: 'widget.chart',
  69533. extend: 'Ext.draw.Component',
  69534. mixins: {
  69535. themeManager: 'Ext.chart.theme.Theme',
  69536. mask: 'Ext.chart.Mask',
  69537. navigation: 'Ext.chart.Navigation',
  69538. bindable: 'Ext.util.Bindable',
  69539. observable: 'Ext.util.Observable'
  69540. },
  69541. uses: [
  69542. 'Ext.chart.series.Series'
  69543. ],
  69544. requires: [
  69545. 'Ext.util.MixedCollection',
  69546. 'Ext.data.StoreManager',
  69547. 'Ext.chart.Legend',
  69548. 'Ext.chart.theme.Base',
  69549. 'Ext.chart.theme.Theme',
  69550. 'Ext.util.DelayedTask'
  69551. ],
  69552. /* End Definitions */
  69553. // @private
  69554. viewBox: false,
  69555. /**
  69556. * @cfg {String} theme
  69557. * The name of the theme to be used. A theme defines the colors and other visual displays of tick marks
  69558. * on axis, text, title text, line colors, marker colors and styles, etc. Possible theme values are 'Base', 'Green',
  69559. * 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes 'Category1' to 'Category6'. Default value
  69560. * is 'Base'.
  69561. */
  69562. /**
  69563. * @cfg {Boolean/Object} animate
  69564. * True for the default animation (easing: 'ease' and duration: 500) or a standard animation config
  69565. * object to be used for default chart animations. Defaults to false.
  69566. */
  69567. animate: false,
  69568. /**
  69569. * @cfg {Boolean/Object} legend
  69570. * True for the default legend display or a legend config object. Defaults to false.
  69571. */
  69572. legend: false,
  69573. /**
  69574. * @cfg {Number} insetPadding
  69575. * The amount of inset padding in pixels for the chart. Defaults to 10.
  69576. */
  69577. insetPadding: 10,
  69578. /**
  69579. * @cfg {String[]} enginePriority
  69580. * Defines the priority order for which Surface implementation to use. The first one supported by the current
  69581. * environment will be used. Defaults to `['Svg', 'Vml']`.
  69582. */
  69583. enginePriority: ['Svg', 'Vml'],
  69584. /**
  69585. * @cfg {Object/Boolean} background
  69586. * The chart background. This can be a gradient object, image, or color. Defaults to false for no
  69587. * background. For example, if `background` were to be a color we could set the object as
  69588. *
  69589. * background: {
  69590. * //color string
  69591. * fill: '#ccc'
  69592. * }
  69593. *
  69594. * You can specify an image by using:
  69595. *
  69596. * background: {
  69597. * image: 'http://path.to.image/'
  69598. * }
  69599. *
  69600. * Also you can specify a gradient by using the gradient object syntax:
  69601. *
  69602. * background: {
  69603. * gradient: {
  69604. * id: 'gradientId',
  69605. * angle: 45,
  69606. * stops: {
  69607. * 0: {
  69608. * color: '#555'
  69609. * }
  69610. * 100: {
  69611. * color: '#ddd'
  69612. * }
  69613. * }
  69614. * }
  69615. * }
  69616. */
  69617. background: false,
  69618. /**
  69619. * @cfg {Object[]} gradients
  69620. * Define a set of gradients that can be used as `fill` property in sprites. The gradients array is an
  69621. * array of objects with the following properties:
  69622. *
  69623. * - **id** - string - The unique name of the gradient.
  69624. * - **angle** - number, optional - The angle of the gradient in degrees.
  69625. * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values
  69626. *
  69627. * For example:
  69628. *
  69629. * gradients: [{
  69630. * id: 'gradientId',
  69631. * angle: 45,
  69632. * stops: {
  69633. * 0: {
  69634. * color: '#555'
  69635. * },
  69636. * 100: {
  69637. * color: '#ddd'
  69638. * }
  69639. * }
  69640. * }, {
  69641. * id: 'gradientId2',
  69642. * angle: 0,
  69643. * stops: {
  69644. * 0: {
  69645. * color: '#590'
  69646. * },
  69647. * 20: {
  69648. * color: '#599'
  69649. * },
  69650. * 100: {
  69651. * color: '#ddd'
  69652. * }
  69653. * }
  69654. * }]
  69655. *
  69656. * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
  69657. *
  69658. * sprite.setAttributes({
  69659. * fill: 'url(#gradientId)'
  69660. * }, true);
  69661. */
  69662. /**
  69663. * @cfg {Ext.data.Store} store
  69664. * The store that supplies data to this chart.
  69665. */
  69666. /**
  69667. * @cfg {Ext.chart.series.Series[]} series
  69668. * Array of {@link Ext.chart.series.Series Series} instances or config objects. For example:
  69669. *
  69670. * series: [{
  69671. * type: 'column',
  69672. * axis: 'left',
  69673. * listeners: {
  69674. * 'afterrender': function() {
  69675. * console('afterrender');
  69676. * }
  69677. * },
  69678. * xField: 'category',
  69679. * yField: 'data1'
  69680. * }]
  69681. */
  69682. /**
  69683. * @cfg {Ext.chart.axis.Axis[]} axes
  69684. * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects. For example:
  69685. *
  69686. * axes: [{
  69687. * type: 'Numeric',
  69688. * position: 'left',
  69689. * fields: ['data1'],
  69690. * title: 'Number of Hits',
  69691. * minimum: 0,
  69692. * //one minor tick between two major ticks
  69693. * minorTickSteps: 1
  69694. * }, {
  69695. * type: 'Category',
  69696. * position: 'bottom',
  69697. * fields: ['name'],
  69698. * title: 'Month of the Year'
  69699. * }]
  69700. */
  69701. constructor: function(config) {
  69702. var me = this,
  69703. defaultAnim;
  69704. config = Ext.apply({}, config);
  69705. me.initTheme(config.theme || me.theme);
  69706. if (me.gradients) {
  69707. Ext.apply(config, { gradients: me.gradients });
  69708. }
  69709. if (me.background) {
  69710. Ext.apply(config, { background: me.background });
  69711. }
  69712. if (config.animate) {
  69713. defaultAnim = {
  69714. easing: 'ease',
  69715. duration: 500
  69716. };
  69717. if (Ext.isObject(config.animate)) {
  69718. config.animate = Ext.applyIf(config.animate, defaultAnim);
  69719. }
  69720. else {
  69721. config.animate = defaultAnim;
  69722. }
  69723. }
  69724. me.mixins.observable.constructor.call(me, config);
  69725. if (config.enableMask) {
  69726. me.mixins.mask.constructor.call(me);
  69727. }
  69728. me.mixins.navigation.constructor.call(me);
  69729. me.callParent([config]);
  69730. },
  69731. getChartStore: function(){
  69732. return this.substore || this.store;
  69733. },
  69734. initComponent: function() {
  69735. var me = this,
  69736. axes,
  69737. series;
  69738. me.callParent();
  69739. me.addEvents(
  69740. 'itemmousedown',
  69741. 'itemmouseup',
  69742. 'itemmouseover',
  69743. 'itemmouseout',
  69744. 'itemclick',
  69745. 'itemdblclick',
  69746. 'itemdragstart',
  69747. 'itemdrag',
  69748. 'itemdragend',
  69749. /**
  69750. * @event beforerefresh
  69751. * Fires before a refresh to the chart data is called. If the beforerefresh handler returns false the
  69752. * {@link #event-refresh} action will be cancelled.
  69753. * @param {Ext.chart.Chart} this
  69754. */
  69755. 'beforerefresh',
  69756. /**
  69757. * @event refresh
  69758. * Fires after the chart data has been refreshed.
  69759. * @param {Ext.chart.Chart} this
  69760. */
  69761. 'refresh'
  69762. );
  69763. Ext.applyIf(me, {
  69764. zoom: {
  69765. width: 1,
  69766. height: 1,
  69767. x: 0,
  69768. y: 0
  69769. }
  69770. });
  69771. me.maxGutter = [0, 0];
  69772. me.store = Ext.data.StoreManager.lookup(me.store);
  69773. axes = me.axes;
  69774. me.axes = new Ext.util.MixedCollection(false, function(a) { return a.position; });
  69775. if (axes) {
  69776. me.axes.addAll(axes);
  69777. }
  69778. series = me.series;
  69779. me.series = new Ext.util.MixedCollection(false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
  69780. if (series) {
  69781. me.series.addAll(series);
  69782. }
  69783. if (me.legend !== false) {
  69784. me.legend = new Ext.chart.Legend(Ext.applyIf({chart:me}, me.legend));
  69785. }
  69786. me.on({
  69787. mousemove: me.onMouseMove,
  69788. mouseleave: me.onMouseLeave,
  69789. mousedown: me.onMouseDown,
  69790. mouseup: me.onMouseUp,
  69791. click: me.onClick,
  69792. dblclick: me.onDblClick,
  69793. scope: me
  69794. });
  69795. },
  69796. // @private overrides the component method to set the correct dimensions to the chart.
  69797. afterComponentLayout: function(width, height) {
  69798. var me = this;
  69799. if (Ext.isNumber(width) && Ext.isNumber(height)) {
  69800. if (width !== me.curWidth || height !== me.curHeight) {
  69801. me.curWidth = width;
  69802. me.curHeight = height;
  69803. me.redraw(true);
  69804. } else if (me.needsRedraw) {
  69805. delete me.needsRedraw;
  69806. me.redraw();
  69807. }
  69808. }
  69809. this.callParent(arguments);
  69810. },
  69811. /**
  69812. * Redraws the chart. If animations are set this will animate the chart too.
  69813. * @param {Boolean} resize (optional) flag which changes the default origin points of the chart for animations.
  69814. */
  69815. redraw: function(resize) {
  69816. var me = this,
  69817. seriesItems = me.series.items,
  69818. seriesLen = seriesItems.length,
  69819. axesItems = me.axes.items,
  69820. axesLen = axesItems.length,
  69821. i,
  69822. chartBBox = me.chartBBox = {
  69823. x: 0,
  69824. y: 0,
  69825. height: me.curHeight,
  69826. width: me.curWidth
  69827. },
  69828. legend = me.legend;
  69829. me.surface.setSize(chartBBox.width, chartBBox.height);
  69830. // Instantiate Series and Axes
  69831. for (i = 0; i < seriesLen; i++) {
  69832. me.initializeSeries(seriesItems[i],i);
  69833. }
  69834. for (i = 0; i < axesLen; i++) {
  69835. me.initializeAxis(axesItems[i]);
  69836. }
  69837. //process all views (aggregated data etc) on stores
  69838. //before rendering.
  69839. for (i = 0; i < axesLen; i++) {
  69840. axesItems[i].processView();
  69841. }
  69842. for (i = 0; i < axesLen; i++) {
  69843. axesItems[i].drawAxis(true);
  69844. }
  69845. // Create legend if not already created
  69846. if (legend !== false && legend.visible) {
  69847. if (legend.update || !legend.created) {
  69848. legend.create();
  69849. }
  69850. }
  69851. // Place axes properly, including influence from each other
  69852. me.alignAxes();
  69853. // Reposition legend based on new axis alignment
  69854. if (legend !== false && legend.visible) {
  69855. legend.updatePosition();
  69856. }
  69857. // Find the max gutter
  69858. me.getMaxGutter();
  69859. // Draw axes and series
  69860. me.resizing = !!resize;
  69861. for (i = 0; i < axesLen; i++) {
  69862. axesItems[i].drawAxis();
  69863. }
  69864. for (i = 0; i < seriesLen; i++) {
  69865. me.drawCharts(seriesItems[i]);
  69866. }
  69867. me.resizing = false;
  69868. },
  69869. // @private set the store after rendering the chart.
  69870. afterRender: function() {
  69871. var ref,
  69872. me = this;
  69873. this.callParent();
  69874. if (me.categoryNames) {
  69875. me.setCategoryNames(me.categoryNames);
  69876. }
  69877. if (me.tipRenderer) {
  69878. ref = me.getFunctionRef(me.tipRenderer);
  69879. me.setTipRenderer(ref.fn, ref.scope);
  69880. }
  69881. me.bindStore(me.store, true);
  69882. me.refresh();
  69883. if (me.surface.engine === 'Vml') {
  69884. me.on('added', me.onAddedVml, me);
  69885. me.mon(Ext.container.Container.hierarchyEventSource, 'added', me.onContainerAddedVml, me);
  69886. }
  69887. },
  69888. // When using a vml surface we need to redraw when this chart or one of its ancestors
  69889. // is moved to a new container after render, because moving the vml chart causes the
  69890. // vml elements to go haywire, some displaing incorrectly or not displaying at all.
  69891. // This appears to be caused by the component being moved to the detached body element
  69892. // before being added to the new container.
  69893. onAddedVml: function() {
  69894. this.needsRedraw = true; // redraw after component layout
  69895. },
  69896. onContainerAddedVml: function(container) {
  69897. if (this.isDescendantOf(container)) {
  69898. this.needsRedraw = true; // redraw after component layout
  69899. }
  69900. },
  69901. // @private get x and y position of the mouse cursor.
  69902. getEventXY: function(e) {
  69903. var me = this,
  69904. box = this.surface.getRegion(),
  69905. pageXY = e.getXY(),
  69906. x = pageXY[0] - box.left,
  69907. y = pageXY[1] - box.top;
  69908. return [x, y];
  69909. },
  69910. onClick: function(e) {
  69911. this.handleClick('itemclick', e);
  69912. },
  69913. onDblClick: function(e) {
  69914. this.handleClick('itemdblclick', e);
  69915. },
  69916. // @private wrap the mouse down position to delegate the event to the series.
  69917. handleClick: function(name, e) {
  69918. var me = this,
  69919. position = me.getEventXY(e),
  69920. seriesItems = me.series.items,
  69921. i, ln, series,
  69922. item;
  69923. // Ask each series if it has an item corresponding to (not necessarily exactly
  69924. // on top of) the current mouse coords. Fire itemclick event.
  69925. for (i = 0, ln = seriesItems.length; i < ln; i++) {
  69926. series = seriesItems[i];
  69927. if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
  69928. if (series.getItemForPoint) {
  69929. item = series.getItemForPoint(position[0], position[1]);
  69930. if (item) {
  69931. series.fireEvent(name, item);
  69932. }
  69933. }
  69934. }
  69935. }
  69936. },
  69937. // @private wrap the mouse down position to delegate the event to the series.
  69938. onMouseDown: function(e) {
  69939. var me = this,
  69940. position = me.getEventXY(e),
  69941. seriesItems = me.series.items,
  69942. i, ln, series,
  69943. item;
  69944. if (me.enableMask) {
  69945. me.mixins.mask.onMouseDown.call(me, e);
  69946. }
  69947. // Ask each series if it has an item corresponding to (not necessarily exactly
  69948. // on top of) the current mouse coords. Fire itemmousedown event.
  69949. for (i = 0, ln = seriesItems.length; i < ln; i++) {
  69950. series = seriesItems[i];
  69951. if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
  69952. if (series.getItemForPoint) {
  69953. item = series.getItemForPoint(position[0], position[1]);
  69954. if (item) {
  69955. series.fireEvent('itemmousedown', item);
  69956. }
  69957. }
  69958. }
  69959. }
  69960. },
  69961. // @private wrap the mouse up event to delegate it to the series.
  69962. onMouseUp: function(e) {
  69963. var me = this,
  69964. position = me.getEventXY(e),
  69965. seriesItems = me.series.items,
  69966. i, ln, series,
  69967. item;
  69968. if (me.enableMask) {
  69969. me.mixins.mask.onMouseUp.call(me, e);
  69970. }
  69971. // Ask each series if it has an item corresponding to (not necessarily exactly
  69972. // on top of) the current mouse coords. Fire itemmouseup event.
  69973. for (i = 0, ln = seriesItems.length; i < ln; i++) {
  69974. series = seriesItems[i];
  69975. if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
  69976. if (series.getItemForPoint) {
  69977. item = series.getItemForPoint(position[0], position[1]);
  69978. if (item) {
  69979. series.fireEvent('itemmouseup', item);
  69980. }
  69981. }
  69982. }
  69983. }
  69984. },
  69985. // @private wrap the mouse move event so it can be delegated to the series.
  69986. onMouseMove: function(e) {
  69987. var me = this,
  69988. position = me.getEventXY(e),
  69989. seriesItems = me.series.items,
  69990. i, ln, series,
  69991. item, last, storeItem, storeField;
  69992. if (me.enableMask) {
  69993. me.mixins.mask.onMouseMove.call(me, e);
  69994. }
  69995. // Ask each series if it has an item corresponding to (not necessarily exactly
  69996. // on top of) the current mouse coords. Fire itemmouseover/out events.
  69997. for (i = 0, ln = seriesItems.length; i < ln; i++) {
  69998. series = seriesItems[i];
  69999. if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
  70000. if (series.getItemForPoint) {
  70001. item = series.getItemForPoint(position[0], position[1]);
  70002. last = series._lastItemForPoint;
  70003. storeItem = series._lastStoreItem;
  70004. storeField = series._lastStoreField;
  70005. if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
  70006. if (last) {
  70007. series.fireEvent('itemmouseout', last);
  70008. delete series._lastItemForPoint;
  70009. delete series._lastStoreField;
  70010. delete series._lastStoreItem;
  70011. }
  70012. if (item) {
  70013. series.fireEvent('itemmouseover', item);
  70014. series._lastItemForPoint = item;
  70015. series._lastStoreItem = item.storeItem;
  70016. series._lastStoreField = item.storeField;
  70017. }
  70018. }
  70019. }
  70020. } else {
  70021. last = series._lastItemForPoint;
  70022. if (last) {
  70023. series.fireEvent('itemmouseout', last);
  70024. delete series._lastItemForPoint;
  70025. delete series._lastStoreField;
  70026. delete series._lastStoreItem;
  70027. }
  70028. }
  70029. }
  70030. },
  70031. // @private handle mouse leave event.
  70032. onMouseLeave: function(e) {
  70033. var me = this,
  70034. seriesItems = me.series.items,
  70035. i, ln, series;
  70036. if (me.enableMask) {
  70037. me.mixins.mask.onMouseLeave.call(me, e);
  70038. }
  70039. for (i = 0, ln = seriesItems.length; i < ln; i++) {
  70040. series = seriesItems[i];
  70041. delete series._lastItemForPoint;
  70042. }
  70043. },
  70044. // @private buffered refresh for when we update the store
  70045. delayRefresh: function() {
  70046. var me = this;
  70047. if (!me.refreshTask) {
  70048. me.refreshTask = new Ext.util.DelayedTask(me.refresh, me);
  70049. }
  70050. me.refreshTask.delay(me.refreshBuffer);
  70051. },
  70052. // @private
  70053. refresh: function() {
  70054. var me = this;
  70055. if (me.rendered && me.curWidth !== undefined && me.curHeight !== undefined) {
  70056. if (!me.isVisible(true) && !me.refreshPending) {
  70057. me.setShowListeners('mon');
  70058. me.refreshPending = true;
  70059. return;
  70060. }
  70061. if (me.fireEvent('beforerefresh', me) !== false) {
  70062. me.redraw();
  70063. me.fireEvent('refresh', me);
  70064. }
  70065. }
  70066. },
  70067. onShow: function(){
  70068. var me = this;
  70069. me.callParent(arguments);
  70070. if (me.refreshPending) {
  70071. me.delayRefresh();
  70072. me.setShowListeners('mun');
  70073. }
  70074. delete me.refreshPending;
  70075. },
  70076. setShowListeners: function(method){
  70077. var me = this;
  70078. me[method](Ext.container.Container.hierarchyEventSource, {
  70079. scope: me,
  70080. single: true,
  70081. show: me.forceRefresh,
  70082. expand: me.forceRefresh
  70083. });
  70084. },
  70085. forceRefresh: function(container) {
  70086. var me = this;
  70087. if (me.isDescendantOf(container) && me.refreshPending) {
  70088. // Add unbind here, because either expand/show could be fired,
  70089. // so be sure to unbind the listener that didn't
  70090. me.setShowListeners('mun');
  70091. me.delayRefresh();
  70092. }
  70093. delete me.refreshPending;
  70094. },
  70095. bindStore: function(store, initial) {
  70096. var me = this;
  70097. me.mixins.bindable.bindStore.apply(me, arguments);
  70098. if (me.store && !initial) {
  70099. me.refresh();
  70100. }
  70101. },
  70102. getStoreListeners: function() {
  70103. var refresh = this.refresh,
  70104. delayRefresh = this.delayRefresh;
  70105. return {
  70106. refresh: refresh,
  70107. add: delayRefresh,
  70108. remove: delayRefresh,
  70109. update: delayRefresh,
  70110. clear: refresh
  70111. };
  70112. },
  70113. // @private Create Axis
  70114. initializeAxis: function(axis) {
  70115. var me = this,
  70116. chartBBox = me.chartBBox,
  70117. w = chartBBox.width,
  70118. h = chartBBox.height,
  70119. x = chartBBox.x,
  70120. y = chartBBox.y,
  70121. themeAttrs = me.themeAttrs,
  70122. config = {
  70123. chart: me
  70124. };
  70125. if (themeAttrs) {
  70126. config.axisStyle = Ext.apply({}, themeAttrs.axis);
  70127. config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
  70128. config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
  70129. config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
  70130. config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
  70131. config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
  70132. config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
  70133. config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
  70134. config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
  70135. }
  70136. switch (axis.position) {
  70137. case 'top':
  70138. Ext.apply(config, {
  70139. length: w,
  70140. width: h,
  70141. x: x,
  70142. y: y
  70143. });
  70144. break;
  70145. case 'bottom':
  70146. Ext.apply(config, {
  70147. length: w,
  70148. width: h,
  70149. x: x,
  70150. y: h
  70151. });
  70152. break;
  70153. case 'left':
  70154. Ext.apply(config, {
  70155. length: h,
  70156. width: w,
  70157. x: x,
  70158. y: h
  70159. });
  70160. break;
  70161. case 'right':
  70162. Ext.apply(config, {
  70163. length: h,
  70164. width: w,
  70165. x: w,
  70166. y: h
  70167. });
  70168. break;
  70169. }
  70170. if (!axis.chart) {
  70171. Ext.apply(config, axis);
  70172. axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
  70173. }
  70174. else {
  70175. Ext.apply(axis, config);
  70176. }
  70177. },
  70178. /**
  70179. * @private Adjust the dimensions and positions of each axis and the chart body area after accounting
  70180. * for the space taken up on each side by the axes and legend.
  70181. */
  70182. alignAxes: function() {
  70183. var me = this,
  70184. axes = me.axes,
  70185. axesItems = axes.items,
  70186. axis,
  70187. legend = me.legend,
  70188. edges = ['top', 'right', 'bottom', 'left'],
  70189. edge,
  70190. i, ln,
  70191. chartBBox,
  70192. insetPadding = me.insetPadding,
  70193. insets = {
  70194. top: insetPadding,
  70195. right: insetPadding,
  70196. bottom: insetPadding,
  70197. left: insetPadding
  70198. },
  70199. isVertical, bbox, pos;
  70200. function getAxis(edge) {
  70201. var i = axes.findIndex('position', edge);
  70202. return (i < 0) ? null : axes.getAt(i);
  70203. }
  70204. // Find the space needed by axes and legend as a positive inset from each edge
  70205. for (i = 0, ln = edges.length; i < ln; i++) {
  70206. edge = edges[i];
  70207. isVertical = (edge === 'left' || edge === 'right');
  70208. axis = getAxis(edge);
  70209. // Add legend size if it's on this edge
  70210. if (legend !== false) {
  70211. if (legend.position === edge) {
  70212. bbox = legend.getBBox();
  70213. insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
  70214. }
  70215. }
  70216. // Add axis size if there's one on this edge only if it has been
  70217. //drawn before.
  70218. if (axis && axis.bbox) {
  70219. bbox = axis.bbox;
  70220. insets[edge] += (isVertical ? bbox.width : bbox.height);
  70221. }
  70222. }
  70223. // Build the chart bbox based on the collected inset values
  70224. chartBBox = {
  70225. x: insets.left,
  70226. y: insets.top,
  70227. width: me.curWidth - insets.left - insets.right,
  70228. height: me.curHeight - insets.top - insets.bottom
  70229. };
  70230. me.chartBBox = chartBBox;
  70231. // Go back through each axis and set its length and position based on the
  70232. // corresponding edge of the chartBBox
  70233. for (i = 0, ln = axesItems.length; i < ln; i++) {
  70234. axis = axesItems[i];
  70235. pos = axis.position;
  70236. isVertical = (pos === 'left' || pos === 'right');
  70237. axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
  70238. axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
  70239. axis.width = (isVertical ? chartBBox.width : chartBBox.height);
  70240. axis.length = (isVertical ? chartBBox.height : chartBBox.width);
  70241. }
  70242. },
  70243. // @private initialize the series.
  70244. initializeSeries: function(series, idx) {
  70245. var me = this,
  70246. themeAttrs = me.themeAttrs,
  70247. seriesObj, markerObj, seriesThemes, st,
  70248. markerThemes, colorArrayStyle = [],
  70249. i = 0, l,
  70250. config = {
  70251. chart: me,
  70252. seriesId: series.seriesId
  70253. };
  70254. if (themeAttrs) {
  70255. seriesThemes = themeAttrs.seriesThemes;
  70256. markerThemes = themeAttrs.markerThemes;
  70257. seriesObj = Ext.apply({}, themeAttrs.series);
  70258. markerObj = Ext.apply({}, themeAttrs.marker);
  70259. config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
  70260. config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
  70261. config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
  70262. if (themeAttrs.colors) {
  70263. config.colorArrayStyle = themeAttrs.colors;
  70264. } else {
  70265. colorArrayStyle = [];
  70266. for (l = seriesThemes.length; i < l; i++) {
  70267. st = seriesThemes[i];
  70268. if (st.fill || st.stroke) {
  70269. colorArrayStyle.push(st.fill || st.stroke);
  70270. }
  70271. }
  70272. if (colorArrayStyle.length) {
  70273. config.colorArrayStyle = colorArrayStyle;
  70274. }
  70275. }
  70276. config.seriesIdx = idx;
  70277. }
  70278. if (series instanceof Ext.chart.series.Series) {
  70279. Ext.apply(series, config);
  70280. } else {
  70281. Ext.applyIf(config, series);
  70282. series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
  70283. }
  70284. if (series.initialize) {
  70285. series.initialize();
  70286. }
  70287. },
  70288. // @private
  70289. getMaxGutter: function() {
  70290. var me = this,
  70291. seriesItems = me.series.items,
  70292. i, ln, series,
  70293. maxGutter = [0, 0],
  70294. gutter;
  70295. for (i = 0, ln = seriesItems.length; i < ln; i++) {
  70296. series = seriesItems[i];
  70297. gutter = series.getGutters && series.getGutters() || [0, 0];
  70298. maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
  70299. maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
  70300. }
  70301. me.maxGutter = maxGutter;
  70302. },
  70303. // @private draw axis.
  70304. drawAxis: function(axis) {
  70305. axis.drawAxis();
  70306. },
  70307. // @private draw series.
  70308. drawCharts: function(series) {
  70309. series.triggerafterrender = false;
  70310. series.drawSeries();
  70311. if (!this.animate) {
  70312. series.fireEvent('afterrender');
  70313. }
  70314. },
  70315. /**
  70316. * Saves the chart by either triggering a download or returning a string containing the chart data
  70317. * as SVG. The action depends on the export type specified in the passed configuration. The chart
  70318. * will be exported using either the {@link Ext.draw.engine.SvgExporter} or the {@link Ext.draw.engine.ImageExporter}
  70319. * classes.
  70320. *
  70321. * Possible export types:
  70322. *
  70323. * - 'image/png'
  70324. * - 'image/jpeg',
  70325. * - 'image/svg+xml'
  70326. *
  70327. * If 'image/svg+xml' is specified, the SvgExporter will be used.
  70328. * If 'image/png' or 'image/jpeg' are specified, the ImageExporter will be used. This exporter
  70329. * must post the SVG data to a remote server to have the data processed, see the {@link Ext.draw.engine.ImageExporter}
  70330. * for more details.
  70331. *
  70332. * Example usage:
  70333. *
  70334. * chart.save({
  70335. * type: 'image/png'
  70336. * });
  70337. *
  70338. * @param {Object} [config] The configuration to be passed to the exporter.
  70339. * See the export method for the appropriate exporter for the relevant
  70340. * configuration options
  70341. * @return {Object} See the return types for the appropriate exporter
  70342. */
  70343. save: function(config){
  70344. return Ext.draw.Surface.save(this.surface, config);
  70345. },
  70346. // @private remove gently.
  70347. destroy: function() {
  70348. Ext.destroy(this.surface);
  70349. this.bindStore(null);
  70350. this.callParent(arguments);
  70351. }
  70352. });
  70353. /**
  70354. * @class Ext.chart.Highlight
  70355. * A mixin providing highlight functionality for Ext.chart.series.Series.
  70356. */
  70357. Ext.define('Ext.chart.Highlight', {
  70358. /* Begin Definitions */
  70359. requires: ['Ext.fx.Anim'],
  70360. /* End Definitions */
  70361. /**
  70362. * Highlight the given series item.
  70363. * @param {Boolean/Object} Default's false. Can also be an object width style properties (i.e fill, stroke, radius)
  70364. * or just use default styles per series by setting highlight = true.
  70365. */
  70366. highlight: false,
  70367. highlightCfg : {
  70368. fill: '#fdd',
  70369. "stroke-width": 5,
  70370. stroke: '#f55'
  70371. },
  70372. constructor: function(config) {
  70373. if (config.highlight) {
  70374. if (config.highlight !== true) { //is an object
  70375. this.highlightCfg = Ext.merge(this.highlightCfg, config.highlight);
  70376. }
  70377. }
  70378. },
  70379. /**
  70380. * Highlight the given series item.
  70381. * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
  70382. */
  70383. highlightItem: function(item) {
  70384. if (!item) {
  70385. return;
  70386. }
  70387. var me = this,
  70388. sprite = item.sprite,
  70389. opts = Ext.merge({}, me.highlightCfg, me.highlight),
  70390. surface = me.chart.surface,
  70391. animate = me.chart.animate,
  70392. p, from, to, pi;
  70393. if (!me.highlight || !sprite || sprite._highlighted) {
  70394. return;
  70395. }
  70396. if (sprite._anim) {
  70397. sprite._anim.paused = true;
  70398. }
  70399. sprite._highlighted = true;
  70400. if (!sprite._defaults) {
  70401. sprite._defaults = Ext.apply({}, sprite.attr);
  70402. from = {};
  70403. to = {};
  70404. for (p in opts) {
  70405. if (! (p in sprite._defaults)) {
  70406. sprite._defaults[p] = surface.availableAttrs[p];
  70407. }
  70408. from[p] = sprite._defaults[p];
  70409. to[p] = opts[p];
  70410. if (Ext.isObject(opts[p])) {
  70411. from[p] = {};
  70412. to[p] = {};
  70413. Ext.apply(sprite._defaults[p], sprite.attr[p]);
  70414. Ext.apply(from[p], sprite._defaults[p]);
  70415. for (pi in sprite._defaults[p]) {
  70416. if (! (pi in opts[p])) {
  70417. to[p][pi] = from[p][pi];
  70418. } else {
  70419. to[p][pi] = opts[p][pi];
  70420. }
  70421. }
  70422. for (pi in opts[p]) {
  70423. if (! (pi in to[p])) {
  70424. to[p][pi] = opts[p][pi];
  70425. }
  70426. }
  70427. }
  70428. }
  70429. sprite._from = from;
  70430. sprite._to = to;
  70431. sprite._endStyle = to;
  70432. }
  70433. if (animate) {
  70434. sprite._anim = new Ext.fx.Anim({
  70435. target: sprite,
  70436. from: sprite._from,
  70437. to: sprite._to,
  70438. duration: 150
  70439. });
  70440. } else {
  70441. sprite.setAttributes(sprite._to, true);
  70442. }
  70443. },
  70444. /**
  70445. * Un-highlight any existing highlights
  70446. */
  70447. unHighlightItem: function() {
  70448. if (!this.highlight || !this.items) {
  70449. return;
  70450. }
  70451. var me = this,
  70452. items = me.items,
  70453. len = items.length,
  70454. opts = Ext.merge({}, me.highlightCfg, me.highlight),
  70455. animate = me.chart.animate,
  70456. i = 0,
  70457. obj, p, sprite;
  70458. for (; i < len; i++) {
  70459. if (!items[i]) {
  70460. continue;
  70461. }
  70462. sprite = items[i].sprite;
  70463. if (sprite && sprite._highlighted) {
  70464. if (sprite._anim) {
  70465. sprite._anim.paused = true;
  70466. }
  70467. obj = {};
  70468. for (p in opts) {
  70469. if (Ext.isObject(sprite._defaults[p])) {
  70470. obj[p] = {};
  70471. Ext.apply(obj[p], sprite._defaults[p]);
  70472. }
  70473. else {
  70474. obj[p] = sprite._defaults[p];
  70475. }
  70476. }
  70477. if (animate) {
  70478. //sprite._to = obj;
  70479. sprite._endStyle = obj;
  70480. sprite._anim = new Ext.fx.Anim({
  70481. target: sprite,
  70482. to: obj,
  70483. duration: 150
  70484. });
  70485. }
  70486. else {
  70487. sprite.setAttributes(obj, true);
  70488. }
  70489. delete sprite._highlighted;
  70490. //delete sprite._defaults;
  70491. }
  70492. }
  70493. },
  70494. cleanHighlights: function() {
  70495. if (!this.highlight) {
  70496. return;
  70497. }
  70498. var group = this.group,
  70499. markerGroup = this.markerGroup,
  70500. i = 0,
  70501. l;
  70502. for (l = group.getCount(); i < l; i++) {
  70503. delete group.getAt(i)._defaults;
  70504. }
  70505. if (markerGroup) {
  70506. for (l = markerGroup.getCount(); i < l; i++) {
  70507. delete markerGroup.getAt(i)._defaults;
  70508. }
  70509. }
  70510. }
  70511. });
  70512. /**
  70513. * @class Ext.chart.Label
  70514. *
  70515. * Labels is a mixin to the Series class. Labels methods are implemented
  70516. * in each of the Series (Pie, Bar, etc) for label creation and placement.
  70517. *
  70518. * The methods implemented by the Series are:
  70519. *
  70520. * - **`onCreateLabel(storeItem, item, i, display)`** Called each time a new label is created.
  70521. * The arguments of the method are:
  70522. * - *`storeItem`* The element of the store that is related to the label sprite.
  70523. * - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
  70524. * used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
  70525. * - *`i`* The index of the element created (i.e the first created label, second created label, etc)
  70526. * - *`display`* The display type. May be <b>false</b> if the label is hidden
  70527. *
  70528. * - **`onPlaceLabel(label, storeItem, item, i, display, animate)`** Called for updating the position of the label.
  70529. * The arguments of the method are:
  70530. * - *`label`* The sprite label.</li>
  70531. * - *`storeItem`* The element of the store that is related to the label sprite</li>
  70532. * - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
  70533. * used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
  70534. * - *`i`* The index of the element to be updated (i.e. whether it is the first, second, third from the labelGroup)
  70535. * - *`display`* The display type. May be <b>false</b> if the label is hidden.
  70536. * - *`animate`* A boolean value to set or unset animations for the labels.
  70537. */
  70538. Ext.define('Ext.chart.Label', {
  70539. /* Begin Definitions */
  70540. requires: ['Ext.draw.Color'],
  70541. /* End Definitions */
  70542. /**
  70543. * @cfg {Object} label
  70544. * Object with the following properties:
  70545. *
  70546. * - **display** : String
  70547. *
  70548. * Specifies the presence and position of labels for each pie slice. Either "rotate", "middle", "insideStart",
  70549. * "insideEnd", "outside", "over", "under", or "none" to prevent label rendering.
  70550. * Default value: 'none'.
  70551. *
  70552. * - **color** : String
  70553. *
  70554. * The color of the label text.
  70555. * Default value: '#000' (black).
  70556. *
  70557. * - **contrast** : Boolean
  70558. *
  70559. * True to render the label in contrasting color with the backround.
  70560. * Default value: false.
  70561. *
  70562. * - **field** : String
  70563. *
  70564. * The name of the field to be displayed in the label.
  70565. * Default value: 'name'.
  70566. *
  70567. * - **minMargin** : Number
  70568. *
  70569. * Specifies the minimum distance from a label to the origin of the visualization.
  70570. * This parameter is useful when using PieSeries width variable pie slice lengths.
  70571. * Default value: 50.
  70572. *
  70573. * - **font** : String
  70574. *
  70575. * The font used for the labels.
  70576. * Default value: "11px Helvetica, sans-serif".
  70577. *
  70578. * - **orientation** : String
  70579. *
  70580. * Either "horizontal" or "vertical".
  70581. * Dafault value: "horizontal".
  70582. *
  70583. * - **renderer** : Function
  70584. *
  70585. * Optional function for formatting the label into a displayable value.
  70586. * Default value: function(v) { return v; }
  70587. */
  70588. // @private a regex to parse url type colors.
  70589. colorStringRe: /url\s*\(\s*#([^\/)]+)\s*\)/,
  70590. // @private the mixin constructor. Used internally by Series.
  70591. constructor: function(config) {
  70592. var me = this;
  70593. me.label = Ext.applyIf(me.label || {},
  70594. {
  70595. display: "none",
  70596. color: "#000",
  70597. field: "name",
  70598. minMargin: 50,
  70599. font: "11px Helvetica, sans-serif",
  70600. orientation: "horizontal",
  70601. renderer: function(v) {
  70602. return v;
  70603. }
  70604. });
  70605. if (me.label.display !== 'none') {
  70606. me.labelsGroup = me.chart.surface.getGroup(me.seriesId + '-labels');
  70607. }
  70608. },
  70609. // @private a method to render all labels in the labelGroup
  70610. renderLabels: function() {
  70611. var me = this,
  70612. chart = me.chart,
  70613. gradients = chart.gradients,
  70614. items = me.items,
  70615. animate = chart.animate,
  70616. config = me.label,
  70617. display = config.display,
  70618. color = config.color,
  70619. field = [].concat(config.field),
  70620. group = me.labelsGroup,
  70621. groupLength = (group || 0) && group.length,
  70622. store = me.chart.getChartStore(),
  70623. len = store.getCount(),
  70624. itemLength = (items || 0) && items.length,
  70625. ratio = itemLength / len,
  70626. gradientsCount = (gradients || 0) && gradients.length,
  70627. Color = Ext.draw.Color,
  70628. hides = [],
  70629. gradient, i, count, groupIndex, index, j, k, colorStopTotal, colorStopIndex, colorStop, item, label,
  70630. storeItem, sprite, spriteColor, spriteBrightness, labelColor, colorString;
  70631. if (display == 'none') {
  70632. return;
  70633. }
  70634. // no items displayed, hide all labels
  70635. if(itemLength == 0){
  70636. while(groupLength--) {
  70637. hides.push(groupLength);
  70638. }
  70639. } else {
  70640. for (i = 0, count = 0, groupIndex = 0; i < len; i++) {
  70641. index = 0;
  70642. for (j = 0; j < ratio; j++) {
  70643. item = items[count];
  70644. label = group.getAt(groupIndex);
  70645. storeItem = store.getAt(i);
  70646. //check the excludes
  70647. while(this.__excludes && this.__excludes[index]) {
  70648. index++;
  70649. }
  70650. if (!item && label) {
  70651. label.hide(true);
  70652. groupIndex++;
  70653. }
  70654. if (item && field[j]) {
  70655. if (!label) {
  70656. label = me.onCreateLabel(storeItem, item, i, display, j, index);
  70657. }
  70658. me.onPlaceLabel(label, storeItem, item, i, display, animate, j, index);
  70659. groupIndex++;
  70660. //set contrast
  70661. if (config.contrast && item.sprite) {
  70662. sprite = item.sprite;
  70663. //set the color string to the color to be set.
  70664. if (sprite._endStyle) {
  70665. colorString = sprite._endStyle.fill;
  70666. }
  70667. else if (sprite._to) {
  70668. colorString = sprite._to.fill;
  70669. }
  70670. else {
  70671. colorString = sprite.attr.fill;
  70672. }
  70673. colorString = colorString || sprite.attr.fill;
  70674. spriteColor = Color.fromString(colorString);
  70675. //color wasn't parsed property maybe because it's a gradient id
  70676. if (colorString && !spriteColor) {
  70677. colorString = colorString.match(me.colorStringRe)[1];
  70678. for (k = 0; k < gradientsCount; k++) {
  70679. gradient = gradients[k];
  70680. if (gradient.id == colorString) {
  70681. //avg color stops
  70682. colorStop = 0; colorStopTotal = 0;
  70683. for (colorStopIndex in gradient.stops) {
  70684. colorStop++;
  70685. colorStopTotal += Color.fromString(gradient.stops[colorStopIndex].color).getGrayscale();
  70686. }
  70687. spriteBrightness = (colorStopTotal / colorStop) / 255;
  70688. break;
  70689. }
  70690. }
  70691. }
  70692. else {
  70693. spriteBrightness = spriteColor.getGrayscale() / 255;
  70694. }
  70695. if (label.isOutside) {
  70696. spriteBrightness = 1;
  70697. }
  70698. labelColor = Color.fromString(label.attr.color || label.attr.fill).getHSL();
  70699. labelColor[2] = spriteBrightness > 0.5 ? 0.2 : 0.8;
  70700. label.setAttributes({
  70701. fill: String(Color.fromHSL.apply({}, labelColor))
  70702. }, true);
  70703. }
  70704. }
  70705. count++;
  70706. index++;
  70707. }
  70708. }
  70709. groupLength = group.length;
  70710. while(groupLength > groupIndex){
  70711. hides.push(groupIndex);
  70712. groupIndex++;
  70713. }
  70714. }
  70715. me.hideLabels(hides);
  70716. },
  70717. hideLabels: function(hides){
  70718. var labelsGroup = this.labelsGroup,
  70719. hlen = !!hides && hides.length;
  70720. if (!labelsGroup) {
  70721. return;
  70722. }
  70723. if (hlen === false) {
  70724. hlen = labelsGroup.getCount();
  70725. while (hlen--) {
  70726. labelsGroup.getAt(hlen).hide(true);
  70727. }
  70728. } else {
  70729. while(hlen--) {
  70730. labelsGroup.getAt(hides[hlen]).hide(true);
  70731. }
  70732. }
  70733. }
  70734. });
  70735. /**
  70736. * @private
  70737. */
  70738. Ext.define('Ext.chart.TipSurface', {
  70739. /* Begin Definitions */
  70740. extend: 'Ext.draw.Component',
  70741. /* End Definitions */
  70742. spriteArray: false,
  70743. renderFirst: true,
  70744. constructor: function(config) {
  70745. this.callParent([config]);
  70746. if (config.sprites) {
  70747. this.spriteArray = [].concat(config.sprites);
  70748. delete config.sprites;
  70749. }
  70750. },
  70751. onRender: function() {
  70752. var me = this,
  70753. i = 0,
  70754. l = 0,
  70755. sp,
  70756. sprites;
  70757. this.callParent(arguments);
  70758. sprites = me.spriteArray;
  70759. if (me.renderFirst && sprites) {
  70760. me.renderFirst = false;
  70761. for (l = sprites.length; i < l; i++) {
  70762. sp = me.surface.add(sprites[i]);
  70763. sp.setAttributes({
  70764. hidden: false
  70765. },
  70766. true);
  70767. }
  70768. }
  70769. }
  70770. });
  70771. /**
  70772. * @class Ext.chart.Tip
  70773. * Provides tips for Ext.chart.series.Series.
  70774. */
  70775. Ext.define('Ext.chart.Tip', {
  70776. /* Begin Definitions */
  70777. requires: ['Ext.tip.ToolTip', 'Ext.chart.TipSurface'],
  70778. /* End Definitions */
  70779. constructor: function(config) {
  70780. var me = this,
  70781. surface,
  70782. sprites,
  70783. tipSurface;
  70784. if (config.tips) {
  70785. me.tipTimeout = null;
  70786. me.tipConfig = Ext.apply({}, config.tips, {
  70787. renderer: Ext.emptyFn,
  70788. constrainPosition: true,
  70789. autoHide: true
  70790. });
  70791. me.tooltip = new Ext.tip.ToolTip(me.tipConfig);
  70792. me.chart.surface.on('mousemove', me.tooltip.onMouseMove, me.tooltip);
  70793. me.chart.surface.on('mouseleave', function() {
  70794. me.hideTip();
  70795. });
  70796. if (me.tipConfig.surface) {
  70797. //initialize a surface
  70798. surface = me.tipConfig.surface;
  70799. sprites = surface.sprites;
  70800. tipSurface = new Ext.chart.TipSurface({
  70801. id: 'tipSurfaceComponent',
  70802. sprites: sprites
  70803. });
  70804. if (surface.width && surface.height) {
  70805. tipSurface.setSize(surface.width, surface.height);
  70806. }
  70807. me.tooltip.add(tipSurface);
  70808. me.spriteTip = tipSurface;
  70809. }
  70810. }
  70811. },
  70812. showTip: function(item) {
  70813. var me = this,
  70814. tooltip,
  70815. spriteTip,
  70816. tipConfig,
  70817. trackMouse,
  70818. sprite,
  70819. surface,
  70820. surfaceExt,
  70821. pos,
  70822. x,
  70823. y;
  70824. if (!me.tooltip) {
  70825. return;
  70826. }
  70827. clearTimeout(me.tipTimeout);
  70828. tooltip = me.tooltip;
  70829. spriteTip = me.spriteTip;
  70830. tipConfig = me.tipConfig;
  70831. trackMouse = tooltip.trackMouse;
  70832. if (!trackMouse) {
  70833. tooltip.trackMouse = true;
  70834. sprite = item.sprite;
  70835. surface = sprite.surface;
  70836. surfaceExt = Ext.get(surface.getId());
  70837. if (surfaceExt) {
  70838. pos = surfaceExt.getXY();
  70839. x = pos[0] + (sprite.attr.x || 0) + (sprite.attr.translation && sprite.attr.translation.x || 0);
  70840. y = pos[1] + (sprite.attr.y || 0) + (sprite.attr.translation && sprite.attr.translation.y || 0);
  70841. tooltip.targetXY = [x, y];
  70842. }
  70843. }
  70844. if (spriteTip) {
  70845. tipConfig.renderer.call(tooltip, item.storeItem, item, spriteTip.surface);
  70846. } else {
  70847. tipConfig.renderer.call(tooltip, item.storeItem, item);
  70848. }
  70849. tooltip.show();
  70850. tooltip.trackMouse = trackMouse;
  70851. },
  70852. hideTip: function(item) {
  70853. var tooltip = this.tooltip;
  70854. if (!tooltip) {
  70855. return;
  70856. }
  70857. clearTimeout(this.tipTimeout);
  70858. this.tipTimeout = setTimeout(function() {
  70859. tooltip.hide();
  70860. }, 0);
  70861. }
  70862. });
  70863. /**
  70864. * @class Ext.chart.axis.Abstract
  70865. * Base class for all axis classes.
  70866. * @private
  70867. */
  70868. Ext.define('Ext.chart.axis.Abstract', {
  70869. /* Begin Definitions */
  70870. requires: ['Ext.chart.Chart'],
  70871. /* End Definitions */
  70872. /**
  70873. * @cfg {Ext.chart.Label} label
  70874. * The config for chart label.
  70875. */
  70876. /**
  70877. * @cfg {String[]} fields
  70878. * The fields of model to bind to this axis.
  70879. *
  70880. * For example if you have a data set of lap times per car, each having the fields:
  70881. * `'carName'`, `'avgSpeed'`, `'maxSpeed'`. Then you might want to show the data on chart
  70882. * with `['carName']` on Name axis and `['avgSpeed', 'maxSpeed']` on Speed axis.
  70883. */
  70884. /**
  70885. * Creates new Axis.
  70886. * @param {Object} config (optional) Config options.
  70887. */
  70888. constructor: function(config) {
  70889. config = config || {};
  70890. var me = this,
  70891. pos = config.position || 'left';
  70892. pos = pos.charAt(0).toUpperCase() + pos.substring(1);
  70893. //axisLabel(Top|Bottom|Right|Left)Style
  70894. config.label = Ext.apply(config['axisLabel' + pos + 'Style'] || {}, config.label || {});
  70895. config.axisTitleStyle = Ext.apply(config['axisTitle' + pos + 'Style'] || {}, config.labelTitle || {});
  70896. Ext.apply(me, config);
  70897. me.fields = Ext.Array.from(me.fields);
  70898. this.callParent();
  70899. me.labels = [];
  70900. me.getId();
  70901. me.labelGroup = me.chart.surface.getGroup(me.axisId + "-labels");
  70902. },
  70903. alignment: null,
  70904. grid: false,
  70905. steps: 10,
  70906. x: 0,
  70907. y: 0,
  70908. minValue: 0,
  70909. maxValue: 0,
  70910. getId: function() {
  70911. return this.axisId || (this.axisId = Ext.id(null, 'ext-axis-'));
  70912. },
  70913. /*
  70914. Called to process a view i.e to make aggregation and filtering over
  70915. a store creating a substore to be used to render the axis. Since many axes
  70916. may do different things on the data and we want the final result of all these
  70917. operations to be rendered we need to call processView on all axes before drawing
  70918. them.
  70919. */
  70920. processView: Ext.emptyFn,
  70921. drawAxis: Ext.emptyFn,
  70922. addDisplayAndLabels: Ext.emptyFn
  70923. });
  70924. /**
  70925. * @class Ext.chart.axis.Axis
  70926. *
  70927. * Defines axis for charts. The axis position, type, style can be configured.
  70928. * The axes are defined in an axes array of configuration objects where the type,
  70929. * field, grid and other configuration options can be set. To know more about how
  70930. * to create a Chart please check the Chart class documentation. Here's an example for the axes part:
  70931. * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
  70932. *
  70933. * axes: [{
  70934. * type: 'Numeric',
  70935. * position: 'left',
  70936. * fields: ['data1', 'data2', 'data3'],
  70937. * title: 'Number of Hits',
  70938. * grid: {
  70939. * odd: {
  70940. * opacity: 1,
  70941. * fill: '#ddd',
  70942. * stroke: '#bbb',
  70943. * 'stroke-width': 1
  70944. * }
  70945. * },
  70946. * minimum: 0
  70947. * }, {
  70948. * type: 'Category',
  70949. * position: 'bottom',
  70950. * fields: ['name'],
  70951. * title: 'Month of the Year',
  70952. * grid: true,
  70953. * label: {
  70954. * rotate: {
  70955. * degrees: 315
  70956. * }
  70957. * }
  70958. * }]
  70959. *
  70960. * In this case we use a `Numeric` axis for displaying the values of the Area series and a `Category` axis for displaying the names of
  70961. * the store elements. The numeric axis is placed on the left of the screen, while the category axis is placed at the bottom of the chart.
  70962. * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the
  70963. * category axis the labels will be rotated so they can fit the space better.
  70964. */
  70965. Ext.define('Ext.chart.axis.Axis', {
  70966. /* Begin Definitions */
  70967. extend: 'Ext.chart.axis.Abstract',
  70968. alternateClassName: 'Ext.chart.Axis',
  70969. requires: ['Ext.draw.Draw'],
  70970. /* End Definitions */
  70971. /**
  70972. * @cfg {Boolean/Object} grid
  70973. * The grid configuration enables you to set a background grid for an axis.
  70974. * If set to *true* on a vertical axis, vertical lines will be drawn.
  70975. * If set to *true* on a horizontal axis, horizontal lines will be drawn.
  70976. * If both are set, a proper grid with horizontal and vertical lines will be drawn.
  70977. *
  70978. * You can set specific options for the grid configuration for odd and/or even lines/rows.
  70979. * Since the rows being drawn are rectangle sprites, you can set to an odd or even property
  70980. * all styles that apply to {@link Ext.draw.Sprite}. For more information on all the style
  70981. * properties you can set please take a look at {@link Ext.draw.Sprite}. Some useful style properties are `opacity`, `fill`, `stroke`, `stroke-width`, etc.
  70982. *
  70983. * The possible values for a grid option are then *true*, *false*, or an object with `{ odd, even }` properties
  70984. * where each property contains a sprite style descriptor object that is defined in {@link Ext.draw.Sprite}.
  70985. *
  70986. * For example:
  70987. *
  70988. * axes: [{
  70989. * type: 'Numeric',
  70990. * position: 'left',
  70991. * fields: ['data1', 'data2', 'data3'],
  70992. * title: 'Number of Hits',
  70993. * grid: {
  70994. * odd: {
  70995. * opacity: 1,
  70996. * fill: '#ddd',
  70997. * stroke: '#bbb',
  70998. * 'stroke-width': 1
  70999. * }
  71000. * }
  71001. * }, {
  71002. * type: 'Category',
  71003. * position: 'bottom',
  71004. * fields: ['name'],
  71005. * title: 'Month of the Year',
  71006. * grid: true
  71007. * }]
  71008. *
  71009. */
  71010. /**
  71011. * @cfg {Number} majorTickSteps
  71012. * If `minimum` and `maximum` are specified it forces the number of major ticks to the specified value.
  71013. * If a number of major ticks is forced, it wont search for pretty numbers at the ticks.
  71014. */
  71015. /**
  71016. * @cfg {Number} minorTickSteps
  71017. * The number of small ticks between two major ticks. Default is zero.
  71018. */
  71019. /**
  71020. * @cfg {String} title
  71021. * The title for the Axis
  71022. */
  71023. // @private force min/max values from store
  71024. forceMinMax: false,
  71025. /**
  71026. * @cfg {Number} dashSize
  71027. * The size of the dash marker. Default's 3.
  71028. */
  71029. dashSize: 3,
  71030. /**
  71031. * @cfg {String} position
  71032. * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`.
  71033. */
  71034. position: 'bottom',
  71035. // @private
  71036. skipFirst: false,
  71037. /**
  71038. * @cfg {Number} length
  71039. * Offset axis position. Default's 0.
  71040. */
  71041. length: 0,
  71042. /**
  71043. * @cfg {Number} width
  71044. * Offset axis width. Default's 0.
  71045. */
  71046. width: 0,
  71047. /**
  71048. * @cfg {Boolean} adjustEnd
  71049. * Whether to adjust the label at the end of the axis.
  71050. */
  71051. adjustEnd: true,
  71052. majorTickSteps: false,
  71053. // @private
  71054. applyData: Ext.emptyFn,
  71055. getRange: function () {
  71056. var me = this,
  71057. chart = me.chart,
  71058. store = chart.getChartStore(),
  71059. data = store.data.items,
  71060. series = chart.series.items,
  71061. position = me.position,
  71062. boundedAxes,
  71063. seriesClasses = Ext.chart.series,
  71064. aggregations = [],
  71065. min = Infinity, max = -Infinity,
  71066. vertical = me.position === 'left' || me.position === 'right',
  71067. i, ln, ln2, j, k, dataLength = data.length, aggregates,
  71068. countedFields = {},
  71069. allFields = {},
  71070. excludable = true,
  71071. fields, fieldMap, record, field, value;
  71072. fields = me.fields;
  71073. for (j = 0, ln = fields.length; j < ln; j++) {
  71074. allFields[fields[j]] = true;
  71075. }
  71076. for (i = 0, ln = series.length; i < ln; i++) {
  71077. if (series[i].seriesIsHidden) {
  71078. continue;
  71079. }
  71080. if (!series[i].getAxesForXAndYFields) {
  71081. continue;
  71082. }
  71083. boundedAxes = series[i].getAxesForXAndYFields();
  71084. if (boundedAxes.xAxis && boundedAxes.xAxis !== position && boundedAxes.yAxis && boundedAxes.yAxis !== position) {
  71085. // If the series explicitly exclude current Axis, then exit.
  71086. continue;
  71087. }
  71088. if (seriesClasses.Bar && series[i] instanceof seriesClasses.Bar && !series[i].column) {
  71089. // If this is a horizontal bar series, then flip xField and yField.
  71090. fields = vertical ? Ext.Array.from(series[i].xField) : Ext.Array.from(series[i].yField);
  71091. } else {
  71092. fields = vertical ? Ext.Array.from(series[i].yField) : Ext.Array.from(series[i].xField);
  71093. }
  71094. if (me.fields.length) {
  71095. for (j = 0, ln2 = fields.length; j < ln2; j++) {
  71096. if (allFields[fields[j]]) {
  71097. break;
  71098. }
  71099. }
  71100. if (j == ln2) {
  71101. // Not matching fields, skipping this series.
  71102. continue;
  71103. }
  71104. }
  71105. if (aggregates = series[i].stacked) {
  71106. // If this is a bar/column series, then it will be aggregated if it is of the same direction of the axis.
  71107. if (seriesClasses.Bar && series[i] instanceof seriesClasses.Bar) {
  71108. if (series[i].column != vertical) {
  71109. aggregates = false;
  71110. excludable = false;
  71111. }
  71112. }
  71113. // Otherwise it is stacked vertically
  71114. else if (!vertical) {
  71115. aggregates = false;
  71116. excludable = false;
  71117. }
  71118. }
  71119. if (aggregates) {
  71120. fieldMap = {};
  71121. for (j = 0; j < fields.length; j++) {
  71122. if (excludable && series[i].__excludes && series[i].__excludes[j]) {
  71123. continue;
  71124. }
  71125. if (!allFields[fields[j]]) {
  71126. Ext.Logger.warn('Field `' + fields[j] + '` is not included in the ' + position + ' axis config.');
  71127. }
  71128. allFields[fields[j]] = fieldMap[fields[j]] = true;
  71129. }
  71130. aggregations.push({
  71131. fields: fieldMap,
  71132. value: 0
  71133. });
  71134. } else {
  71135. if (!fields || fields.length == 0) {
  71136. fields = me.fields;
  71137. }
  71138. for (j = 0; j < fields.length; j++) {
  71139. if (excludable && series[i].__excludes && series[i].__excludes[j]) {
  71140. continue;
  71141. }
  71142. allFields[fields[j]] = countedFields[fields[j]] = true;
  71143. }
  71144. }
  71145. }
  71146. for (i = 0; i < dataLength; i++) {
  71147. record = data[i];
  71148. for (k = 0; k < aggregations.length; k++) {
  71149. aggregations[k].value = 0;
  71150. }
  71151. for (field in allFields) {
  71152. value = record.get(field);
  71153. if (isNaN(value)) {
  71154. continue;
  71155. }
  71156. if (value === undefined) {
  71157. value = 0;
  71158. }
  71159. if (countedFields[field]) {
  71160. if (min > value) {
  71161. min = value;
  71162. }
  71163. if (max < value) {
  71164. max = value;
  71165. }
  71166. }
  71167. for (k = 0; k < aggregations.length; k++) {
  71168. if (aggregations[k].fields[field]) {
  71169. aggregations[k].value += value;
  71170. // If any aggregation is actually hit, then the min value should be at most 0.
  71171. if (min > 0) {
  71172. min = 0;
  71173. }
  71174. if (max < aggregations[k].value) {
  71175. max = aggregations[k].value;
  71176. }
  71177. }
  71178. }
  71179. }
  71180. }
  71181. if (!isFinite(max)) {
  71182. max = me.prevMax || 0;
  71183. }
  71184. if (!isFinite(min)) {
  71185. min = me.prevMin || 0;
  71186. }
  71187. //normalize min max for snapEnds.
  71188. if (min != max && (max != Math.floor(max))) {
  71189. max = Math.floor(max) + 1;
  71190. }
  71191. if (!isNaN(me.minimum)) {
  71192. min = me.minimum;
  71193. }
  71194. if (!isNaN(me.maximum)) {
  71195. max = me.maximum;
  71196. }
  71197. if (min >= max) {
  71198. // snapEnds will return NaN if max >= min;
  71199. max = min + 1;
  71200. }
  71201. return {min: min, max: max};
  71202. },
  71203. // @private creates a structure with start, end and step points.
  71204. calcEnds: function () {
  71205. var me = this,
  71206. range = me.getRange(),
  71207. min = range.min,
  71208. max = range.max,
  71209. steps, prettyNumbers, out, changedRange;
  71210. steps = (Ext.isNumber(me.majorTickSteps) ? me.majorTickSteps + 1 : me.steps);
  71211. prettyNumbers = !(Ext.isNumber(me.maximum) && Ext.isNumber(me.minimum) && Ext.isNumber(me.majorTickSteps) && me.majorTickSteps > 0);
  71212. out = Ext.draw.Draw.snapEnds(min, max, steps, prettyNumbers);
  71213. if (Ext.isNumber(me.maximum)) {
  71214. out.to = me.maximum;
  71215. changedRange = true;
  71216. }
  71217. if (Ext.isNumber(me.minimum)) {
  71218. out.from = me.minimum;
  71219. changedRange = true;
  71220. }
  71221. if (me.adjustMaximumByMajorUnit) {
  71222. out.to = Math.ceil(out.to / out.step) * out.step;
  71223. changedRange = true;
  71224. }
  71225. if (me.adjustMinimumByMajorUnit) {
  71226. out.from = Math.floor(out.from / out.step) * out.step;
  71227. changedRange = true;
  71228. }
  71229. if (changedRange) {
  71230. out.steps = Math.ceil((out.to - out.from) / out.step);
  71231. }
  71232. me.prevMin = (min == max ? 0 : min);
  71233. me.prevMax = max;
  71234. return out;
  71235. },
  71236. /**
  71237. * Renders the axis into the screen and updates its position.
  71238. */
  71239. drawAxis: function (init) {
  71240. var me = this,
  71241. i,
  71242. x = me.x,
  71243. y = me.y,
  71244. gutterX = me.chart.maxGutter[0],
  71245. gutterY = me.chart.maxGutter[1],
  71246. dashSize = me.dashSize,
  71247. subDashesX = me.minorTickSteps || 0,
  71248. subDashesY = me.minorTickSteps || 0,
  71249. length = me.length,
  71250. position = me.position,
  71251. inflections = [],
  71252. calcLabels = false,
  71253. stepCalcs = me.applyData(),
  71254. step = stepCalcs.step,
  71255. steps = stepCalcs.steps,
  71256. from = stepCalcs.from,
  71257. to = stepCalcs.to,
  71258. trueLength,
  71259. currentX,
  71260. currentY,
  71261. path,
  71262. dashesX,
  71263. dashesY,
  71264. delta;
  71265. //If no steps are specified
  71266. //then don't draw the axis. This generally happens
  71267. //when an empty store.
  71268. if (me.hidden || isNaN(step) || (from > to)) {
  71269. return;
  71270. }
  71271. me.from = stepCalcs.from;
  71272. me.to = stepCalcs.to;
  71273. if (position == 'left' || position == 'right') {
  71274. currentX = Math.floor(x) + 0.5;
  71275. path = ["M", currentX, y, "l", 0, -length];
  71276. trueLength = length - (gutterY * 2);
  71277. }
  71278. else {
  71279. currentY = Math.floor(y) + 0.5;
  71280. path = ["M", x, currentY, "l", length, 0];
  71281. trueLength = length - (gutterX * 2);
  71282. }
  71283. // Supports the case that we have only 1 record.
  71284. delta = steps && trueLength / steps;
  71285. dashesX = Math.max(subDashesX + 1, 0);
  71286. dashesY = Math.max(subDashesY + 1, 0);
  71287. if (me.type == 'Numeric' || me.type == 'Time') {
  71288. calcLabels = true;
  71289. me.labels = [stepCalcs.from];
  71290. }
  71291. if (position == 'right' || position == 'left') {
  71292. currentY = y - gutterY;
  71293. currentX = x - ((position == 'left') * dashSize * 2);
  71294. while (currentY >= y - gutterY - trueLength) {
  71295. path.push("M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0);
  71296. if (currentY != y - gutterY) {
  71297. for (i = 1; i < dashesY; i++) {
  71298. path.push("M", currentX + dashSize, Math.floor(currentY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
  71299. }
  71300. }
  71301. inflections.push([ Math.floor(x), Math.floor(currentY) ]);
  71302. currentY -= delta;
  71303. if (calcLabels) {
  71304. me.labels.push(me.labels[me.labels.length - 1] + step);
  71305. }
  71306. if (delta === 0) {
  71307. break;
  71308. }
  71309. }
  71310. if (Math.round(currentY + delta - (y - gutterY - trueLength))) {
  71311. path.push("M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0);
  71312. for (i = 1; i < dashesY; i++) {
  71313. path.push("M", currentX + dashSize, Math.floor(y - length + gutterY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
  71314. }
  71315. inflections.push([ Math.floor(x), Math.floor(currentY) ]);
  71316. if (calcLabels) {
  71317. me.labels.push(me.labels[me.labels.length - 1] + step);
  71318. }
  71319. }
  71320. } else {
  71321. currentX = x + gutterX;
  71322. currentY = y - ((position == 'top') * dashSize * 2);
  71323. while (currentX <= x + gutterX + trueLength) {
  71324. path.push("M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
  71325. if (currentX != x + gutterX) {
  71326. for (i = 1; i < dashesX; i++) {
  71327. path.push("M", Math.floor(currentX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
  71328. }
  71329. }
  71330. inflections.push([ Math.floor(currentX), Math.floor(y) ]);
  71331. currentX += delta;
  71332. if (calcLabels) {
  71333. me.labels.push(me.labels[me.labels.length - 1] + step);
  71334. }
  71335. if (delta === 0) {
  71336. break;
  71337. }
  71338. }
  71339. if (Math.round(currentX - delta - (x + gutterX + trueLength))) {
  71340. path.push("M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
  71341. for (i = 1; i < dashesX; i++) {
  71342. path.push("M", Math.floor(x + length - gutterX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
  71343. }
  71344. inflections.push([ Math.floor(currentX), Math.floor(y) ]);
  71345. if (calcLabels) {
  71346. me.labels.push(me.labels[me.labels.length - 1] + step);
  71347. }
  71348. }
  71349. }
  71350. // the label on index "inflections.length-1" is the last label that gets rendered
  71351. if (calcLabels) {
  71352. me.labels[inflections.length - 1] = +(me.labels[inflections.length - 1]).toFixed(10);
  71353. }
  71354. if (!me.axis) {
  71355. me.axis = me.chart.surface.add(Ext.apply({
  71356. type: 'path',
  71357. path: path
  71358. }, me.axisStyle));
  71359. }
  71360. me.axis.setAttributes({
  71361. path: path
  71362. }, true);
  71363. me.inflections = inflections;
  71364. if (!init && me.grid) {
  71365. me.drawGrid();
  71366. }
  71367. me.axisBBox = me.axis.getBBox();
  71368. me.drawLabel();
  71369. },
  71370. /**
  71371. * Renders an horizontal and/or vertical grid into the Surface.
  71372. */
  71373. drawGrid: function () {
  71374. var me = this,
  71375. surface = me.chart.surface,
  71376. grid = me.grid,
  71377. odd = grid.odd,
  71378. even = grid.even,
  71379. inflections = me.inflections,
  71380. ln = inflections.length - ((odd || even) ? 0 : 1),
  71381. position = me.position,
  71382. gutter = me.chart.maxGutter,
  71383. width = me.width - 2,
  71384. point, prevPoint,
  71385. i = 1,
  71386. path = [], styles, lineWidth, dlineWidth,
  71387. oddPath = [], evenPath = [];
  71388. if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) ||
  71389. (gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) {
  71390. i = 0;
  71391. ln++;
  71392. }
  71393. for (; i < ln; i++) {
  71394. point = inflections[i];
  71395. prevPoint = inflections[i - 1];
  71396. if (odd || even) {
  71397. path = (i % 2) ? oddPath : evenPath;
  71398. styles = ((i % 2) ? odd : even) || {};
  71399. lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2;
  71400. dlineWidth = 2 * lineWidth;
  71401. if (position == 'left') {
  71402. path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth,
  71403. "L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth,
  71404. "L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth,
  71405. "L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z");
  71406. }
  71407. else if (position == 'right') {
  71408. path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth,
  71409. "L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth,
  71410. "L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth,
  71411. "L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z");
  71412. }
  71413. else if (position == 'top') {
  71414. path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth,
  71415. "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth,
  71416. "L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth,
  71417. "L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z");
  71418. }
  71419. else {
  71420. path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth,
  71421. "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth,
  71422. "L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth,
  71423. "L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z");
  71424. }
  71425. } else {
  71426. if (position == 'left') {
  71427. path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", width, 0]);
  71428. }
  71429. else if (position == 'right') {
  71430. path = path.concat(["M", point[0] - 0.5, point[1] + 0.5, "l", -width, 0]);
  71431. }
  71432. else if (position == 'top') {
  71433. path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", 0, width]);
  71434. }
  71435. else {
  71436. path = path.concat(["M", point[0] + 0.5, point[1] - 0.5, "l", 0, -width]);
  71437. }
  71438. }
  71439. }
  71440. if (odd || even) {
  71441. if (oddPath.length) {
  71442. if (!me.gridOdd && oddPath.length) {
  71443. me.gridOdd = surface.add({
  71444. type: 'path',
  71445. path: oddPath
  71446. });
  71447. }
  71448. me.gridOdd.setAttributes(Ext.apply({
  71449. path: oddPath,
  71450. hidden: false
  71451. }, odd || {}), true);
  71452. }
  71453. if (evenPath.length) {
  71454. if (!me.gridEven) {
  71455. me.gridEven = surface.add({
  71456. type: 'path',
  71457. path: evenPath
  71458. });
  71459. }
  71460. me.gridEven.setAttributes(Ext.apply({
  71461. path: evenPath,
  71462. hidden: false
  71463. }, even || {}), true);
  71464. }
  71465. }
  71466. else {
  71467. if (path.length) {
  71468. if (!me.gridLines) {
  71469. me.gridLines = me.chart.surface.add({
  71470. type: 'path',
  71471. path: path,
  71472. "stroke-width": me.lineWidth || 1,
  71473. stroke: me.gridColor || '#ccc'
  71474. });
  71475. }
  71476. me.gridLines.setAttributes({
  71477. hidden: false,
  71478. path: path
  71479. }, true);
  71480. }
  71481. else if (me.gridLines) {
  71482. me.gridLines.hide(true);
  71483. }
  71484. }
  71485. },
  71486. // @private
  71487. getOrCreateLabel: function (i, text) {
  71488. var me = this,
  71489. labelGroup = me.labelGroup,
  71490. textLabel = labelGroup.getAt(i),
  71491. surface = me.chart.surface;
  71492. if (textLabel) {
  71493. if (text != textLabel.attr.text) {
  71494. textLabel.setAttributes(Ext.apply({
  71495. text: text
  71496. }, me.label), true);
  71497. textLabel._bbox = textLabel.getBBox();
  71498. }
  71499. }
  71500. else {
  71501. textLabel = surface.add(Ext.apply({
  71502. group: labelGroup,
  71503. type: 'text',
  71504. x: 0,
  71505. y: 0,
  71506. text: text
  71507. }, me.label));
  71508. surface.renderItem(textLabel);
  71509. textLabel._bbox = textLabel.getBBox();
  71510. }
  71511. //get untransformed bounding box
  71512. if (me.label.rotation) {
  71513. textLabel.setAttributes({
  71514. rotation: {
  71515. degrees: 0
  71516. }
  71517. }, true);
  71518. textLabel._ubbox = textLabel.getBBox();
  71519. textLabel.setAttributes(me.label, true);
  71520. } else {
  71521. textLabel._ubbox = textLabel._bbox;
  71522. }
  71523. return textLabel;
  71524. },
  71525. rect2pointArray: function (sprite) {
  71526. var surface = this.chart.surface,
  71527. rect = surface.getBBox(sprite, true),
  71528. p1 = [rect.x, rect.y],
  71529. p1p = p1.slice(),
  71530. p2 = [rect.x + rect.width, rect.y],
  71531. p2p = p2.slice(),
  71532. p3 = [rect.x + rect.width, rect.y + rect.height],
  71533. p3p = p3.slice(),
  71534. p4 = [rect.x, rect.y + rect.height],
  71535. p4p = p4.slice(),
  71536. matrix = sprite.matrix;
  71537. //transform the points
  71538. p1[0] = matrix.x.apply(matrix, p1p);
  71539. p1[1] = matrix.y.apply(matrix, p1p);
  71540. p2[0] = matrix.x.apply(matrix, p2p);
  71541. p2[1] = matrix.y.apply(matrix, p2p);
  71542. p3[0] = matrix.x.apply(matrix, p3p);
  71543. p3[1] = matrix.y.apply(matrix, p3p);
  71544. p4[0] = matrix.x.apply(matrix, p4p);
  71545. p4[1] = matrix.y.apply(matrix, p4p);
  71546. return [p1, p2, p3, p4];
  71547. },
  71548. intersect: function (l1, l2) {
  71549. var r1 = this.rect2pointArray(l1),
  71550. r2 = this.rect2pointArray(l2);
  71551. return !!Ext.draw.Draw.intersect(r1, r2).length;
  71552. },
  71553. drawHorizontalLabels: function () {
  71554. var me = this,
  71555. labelConf = me.label,
  71556. floor = Math.floor,
  71557. max = Math.max,
  71558. axes = me.chart.axes,
  71559. insetPadding = me.chart.insetPadding,
  71560. position = me.position,
  71561. inflections = me.inflections,
  71562. ln = inflections.length,
  71563. labels = me.labels,
  71564. maxHeight = 0,
  71565. ratio,
  71566. bbox, point, prevLabel, prevLabelId,
  71567. adjustEnd = me.adjustEnd,
  71568. hasLeft = axes.findIndex('position', 'left') != -1,
  71569. hasRight = axes.findIndex('position', 'right') != -1,
  71570. textLabel, text,
  71571. last, x, y, i, firstLabel;
  71572. last = ln - 1;
  71573. //get a reference to the first text label dimensions
  71574. point = inflections[0];
  71575. firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
  71576. ratio = Math.floor(Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)));
  71577. for (i = 0; i < ln; i++) {
  71578. point = inflections[i];
  71579. text = me.label.renderer(labels[i]);
  71580. textLabel = me.getOrCreateLabel(i, text);
  71581. bbox = textLabel._bbox;
  71582. maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
  71583. x = floor(point[0] - (ratio ? bbox.height : bbox.width) / 2);
  71584. if (adjustEnd && me.chart.maxGutter[0] == 0) {
  71585. if (i == 0 && !hasLeft) {
  71586. x = point[0];
  71587. }
  71588. else if (i == last && !hasRight) {
  71589. x = Math.min(x, point[0] - bbox.width + insetPadding);
  71590. }
  71591. }
  71592. if (position == 'top') {
  71593. y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
  71594. }
  71595. else {
  71596. y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
  71597. }
  71598. textLabel.setAttributes({
  71599. hidden: false,
  71600. x: x,
  71601. y: y
  71602. }, true);
  71603. // Skip label if there isn't available minimum space
  71604. if (i != 0 && (me.intersect(textLabel, prevLabel)
  71605. || me.intersect(textLabel, firstLabel))) {
  71606. if (i === last && prevLabelId !== 0) {
  71607. prevLabel.hide(true);
  71608. } else {
  71609. textLabel.hide(true);
  71610. continue;
  71611. }
  71612. }
  71613. prevLabel = textLabel;
  71614. prevLabelId = i;
  71615. }
  71616. return maxHeight;
  71617. },
  71618. drawVerticalLabels: function () {
  71619. var me = this,
  71620. inflections = me.inflections,
  71621. position = me.position,
  71622. ln = inflections.length,
  71623. chart = me.chart,
  71624. insetPadding = chart.insetPadding,
  71625. labels = me.labels,
  71626. maxWidth = 0,
  71627. max = Math.max,
  71628. floor = Math.floor,
  71629. ceil = Math.ceil,
  71630. axes = me.chart.axes,
  71631. gutterY = me.chart.maxGutter[1],
  71632. bbox, point, prevLabel, prevLabelId,
  71633. hasTop = axes.findIndex('position', 'top') != -1,
  71634. hasBottom = axes.findIndex('position', 'bottom') != -1,
  71635. adjustEnd = me.adjustEnd,
  71636. textLabel, text,
  71637. last = ln - 1, x, y, i;
  71638. for (i = 0; i < ln; i++) {
  71639. point = inflections[i];
  71640. text = me.label.renderer(labels[i]);
  71641. textLabel = me.getOrCreateLabel(i, text);
  71642. bbox = textLabel._bbox;
  71643. maxWidth = max(maxWidth, bbox.width + me.dashSize + me.label.padding);
  71644. y = point[1];
  71645. if (adjustEnd && gutterY < bbox.height / 2) {
  71646. if (i == last && !hasTop) {
  71647. y = Math.max(y, me.y - me.length + ceil(bbox.height / 2) - insetPadding);
  71648. }
  71649. else if (i == 0 && !hasBottom) {
  71650. y = me.y + gutterY - floor(bbox.height / 2);
  71651. }
  71652. }
  71653. if (position == 'left') {
  71654. x = point[0] - bbox.width - me.dashSize - me.label.padding - 2;
  71655. }
  71656. else {
  71657. x = point[0] + me.dashSize + me.label.padding + 2;
  71658. }
  71659. textLabel.setAttributes(Ext.apply({
  71660. hidden: false,
  71661. x: x,
  71662. y: y
  71663. }, me.label), true);
  71664. // Skip label if there isn't available minimum space
  71665. if (i != 0 && me.intersect(textLabel, prevLabel)) {
  71666. if (i === last && prevLabelId !== 0) {
  71667. prevLabel.hide(true);
  71668. } else {
  71669. textLabel.hide(true);
  71670. continue;
  71671. }
  71672. }
  71673. prevLabel = textLabel;
  71674. prevLabelId = i;
  71675. }
  71676. return maxWidth;
  71677. },
  71678. /**
  71679. * Renders the labels in the axes.
  71680. */
  71681. drawLabel: function () {
  71682. var me = this,
  71683. position = me.position,
  71684. labelGroup = me.labelGroup,
  71685. inflections = me.inflections,
  71686. maxWidth = 0,
  71687. maxHeight = 0,
  71688. ln, i;
  71689. if (position == 'left' || position == 'right') {
  71690. maxWidth = me.drawVerticalLabels();
  71691. } else {
  71692. maxHeight = me.drawHorizontalLabels();
  71693. }
  71694. // Hide unused bars
  71695. ln = labelGroup.getCount();
  71696. i = inflections.length;
  71697. for (; i < ln; i++) {
  71698. labelGroup.getAt(i).hide(true);
  71699. }
  71700. me.bbox = {};
  71701. Ext.apply(me.bbox, me.axisBBox);
  71702. me.bbox.height = maxHeight;
  71703. me.bbox.width = maxWidth;
  71704. if (Ext.isString(me.title)) {
  71705. me.drawTitle(maxWidth, maxHeight);
  71706. }
  71707. },
  71708. /**
  71709. * Updates the {@link #title} of this axis.
  71710. * @param {String} title
  71711. */
  71712. setTitle: function (title) {
  71713. this.title = title;
  71714. this.drawLabel();
  71715. },
  71716. // @private draws the title for the axis.
  71717. drawTitle: function (maxWidth, maxHeight) {
  71718. var me = this,
  71719. position = me.position,
  71720. surface = me.chart.surface,
  71721. displaySprite = me.displaySprite,
  71722. title = me.title,
  71723. rotate = (position == 'left' || position == 'right'),
  71724. x = me.x,
  71725. y = me.y,
  71726. base, bbox, pad;
  71727. if (displaySprite) {
  71728. displaySprite.setAttributes({text: title}, true);
  71729. } else {
  71730. base = {
  71731. type: 'text',
  71732. x: 0,
  71733. y: 0,
  71734. text: title
  71735. };
  71736. displaySprite = me.displaySprite = surface.add(Ext.apply(base, me.axisTitleStyle, me.labelTitle));
  71737. surface.renderItem(displaySprite);
  71738. }
  71739. bbox = displaySprite.getBBox();
  71740. pad = me.dashSize + me.label.padding;
  71741. if (rotate) {
  71742. y -= ((me.length / 2) - (bbox.height / 2));
  71743. if (position == 'left') {
  71744. x -= (maxWidth + pad + (bbox.width / 2));
  71745. }
  71746. else {
  71747. x += (maxWidth + pad + bbox.width - (bbox.width / 2));
  71748. }
  71749. me.bbox.width += bbox.width + 10;
  71750. }
  71751. else {
  71752. x += (me.length / 2) - (bbox.width * 0.5);
  71753. if (position == 'top') {
  71754. y -= (maxHeight + pad + (bbox.height * 0.3));
  71755. }
  71756. else {
  71757. y += (maxHeight + pad + (bbox.height * 0.8));
  71758. }
  71759. me.bbox.height += bbox.height + 10;
  71760. }
  71761. displaySprite.setAttributes({
  71762. translate: {
  71763. x: x,
  71764. y: y
  71765. }
  71766. }, true);
  71767. }
  71768. });
  71769. /**
  71770. * @class Ext.chart.axis.Category
  71771. *
  71772. * A type of axis that displays items in categories. This axis is generally used to
  71773. * display categorical information like names of items, month names, quarters, etc.
  71774. * but no quantitative values. For that other type of information `Number`
  71775. * axis are more suitable.
  71776. *
  71777. * As with other axis you can set the position of the axis and its title. For example:
  71778. *
  71779. * @example
  71780. * var store = Ext.create('Ext.data.JsonStore', {
  71781. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  71782. * data: [
  71783. * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
  71784. * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
  71785. * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
  71786. * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
  71787. * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
  71788. * ]
  71789. * });
  71790. *
  71791. * Ext.create('Ext.chart.Chart', {
  71792. * renderTo: Ext.getBody(),
  71793. * width: 500,
  71794. * height: 300,
  71795. * store: store,
  71796. * axes: [{
  71797. * type: 'Numeric',
  71798. * position: 'left',
  71799. * fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
  71800. * title: 'Sample Values',
  71801. * grid: {
  71802. * odd: {
  71803. * opacity: 1,
  71804. * fill: '#ddd',
  71805. * stroke: '#bbb',
  71806. * 'stroke-width': 1
  71807. * }
  71808. * },
  71809. * minimum: 0,
  71810. * adjustMinimumByMajorUnit: 0
  71811. * }, {
  71812. * type: 'Category',
  71813. * position: 'bottom',
  71814. * fields: ['name'],
  71815. * title: 'Sample Metrics',
  71816. * grid: true,
  71817. * label: {
  71818. * rotate: {
  71819. * degrees: 315
  71820. * }
  71821. * }
  71822. * }],
  71823. * series: [{
  71824. * type: 'area',
  71825. * highlight: false,
  71826. * axis: 'left',
  71827. * xField: 'name',
  71828. * yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
  71829. * style: {
  71830. * opacity: 0.93
  71831. * }
  71832. * }]
  71833. * });
  71834. *
  71835. * In this example with set the category axis to the bottom of the surface, bound the axis to
  71836. * the `name` property and set as title _Month of the Year_.
  71837. */
  71838. Ext.define('Ext.chart.axis.Category', {
  71839. /* Begin Definitions */
  71840. extend: 'Ext.chart.axis.Axis',
  71841. alternateClassName: 'Ext.chart.CategoryAxis',
  71842. alias: 'axis.category',
  71843. /* End Definitions */
  71844. /**
  71845. * A list of category names to display along this axis.
  71846. * @property {String} categoryNames
  71847. */
  71848. categoryNames: null,
  71849. /**
  71850. * Indicates whether or not to calculate the number of categories (ticks and
  71851. * labels) when there is not enough room to display all labels on the axis.
  71852. * If set to true, the axis will determine the number of categories to plot.
  71853. * If not, all categories will be plotted.
  71854. *
  71855. * @property calculateCategoryCount
  71856. * @type Boolean
  71857. */
  71858. calculateCategoryCount: false,
  71859. // @private creates an array of labels to be used when rendering.
  71860. setLabels: function() {
  71861. var store = this.chart.getChartStore(),
  71862. data = store.data.items,
  71863. d, dLen, record,
  71864. fields = this.fields,
  71865. ln = fields.length,
  71866. i;
  71867. this.labels = [];
  71868. for (d = 0, dLen = data.length; d < dLen; d++) {
  71869. record = data[d];
  71870. for (i = 0; i < ln; i++) {
  71871. this.labels.push(record.get(fields[i]));
  71872. }
  71873. }
  71874. },
  71875. // @private calculates labels positions and marker positions for rendering.
  71876. applyData: function() {
  71877. this.callParent();
  71878. this.setLabels();
  71879. var count = this.chart.getChartStore().getCount();
  71880. return {
  71881. from: 0,
  71882. to: count - 1,
  71883. power: 1,
  71884. step: 1,
  71885. steps: count - 1
  71886. };
  71887. }
  71888. });
  71889. /**
  71890. * @class Ext.chart.axis.Gauge
  71891. *
  71892. * Gauge Axis is the axis to be used with a Gauge series. The Gauge axis
  71893. * displays numeric data from an interval defined by the `minimum`, `maximum` and
  71894. * `step` configuration properties. The placement of the numeric data can be changed
  71895. * by altering the `margin` option that is set to `10` by default.
  71896. *
  71897. * A possible configuration for this axis would look like:
  71898. *
  71899. * axes: [{
  71900. * type: 'gauge',
  71901. * position: 'gauge',
  71902. * minimum: 0,
  71903. * maximum: 100,
  71904. * steps: 10,
  71905. * margin: 7
  71906. * }],
  71907. */
  71908. Ext.define('Ext.chart.axis.Gauge', {
  71909. /* Begin Definitions */
  71910. extend: 'Ext.chart.axis.Abstract',
  71911. /* End Definitions */
  71912. /**
  71913. * @cfg {Number} minimum (required)
  71914. * The minimum value of the interval to be displayed in the axis.
  71915. */
  71916. /**
  71917. * @cfg {Number} maximum (required)
  71918. * The maximum value of the interval to be displayed in the axis.
  71919. */
  71920. /**
  71921. * @cfg {Number} steps (required)
  71922. * The number of steps and tick marks to add to the interval.
  71923. */
  71924. /**
  71925. * @cfg {Number} [margin=10]
  71926. * The offset positioning of the tick marks and labels in pixels.
  71927. */
  71928. /**
  71929. * @cfg {String} title
  71930. * The title for the Axis.
  71931. */
  71932. position: 'gauge',
  71933. alias: 'axis.gauge',
  71934. drawAxis: function(init) {
  71935. var chart = this.chart,
  71936. surface = chart.surface,
  71937. bbox = chart.chartBBox,
  71938. centerX = bbox.x + (bbox.width / 2),
  71939. centerY = bbox.y + bbox.height,
  71940. margin = this.margin || 10,
  71941. rho = Math.min(bbox.width, 2 * bbox.height) /2 + margin,
  71942. sprites = [], sprite,
  71943. steps = this.steps,
  71944. i, pi = Math.PI,
  71945. cos = Math.cos,
  71946. sin = Math.sin;
  71947. if (this.sprites && !chart.resizing) {
  71948. this.drawLabel();
  71949. return;
  71950. }
  71951. if (this.margin >= 0) {
  71952. if (!this.sprites) {
  71953. //draw circles
  71954. for (i = 0; i <= steps; i++) {
  71955. sprite = surface.add({
  71956. type: 'path',
  71957. path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
  71958. centerY + (rho - margin) * sin(i / steps * pi - pi),
  71959. 'L', centerX + rho * cos(i / steps * pi - pi),
  71960. centerY + rho * sin(i / steps * pi - pi), 'Z'],
  71961. stroke: '#ccc'
  71962. });
  71963. sprite.setAttributes({
  71964. hidden: false
  71965. }, true);
  71966. sprites.push(sprite);
  71967. }
  71968. } else {
  71969. sprites = this.sprites;
  71970. //draw circles
  71971. for (i = 0; i <= steps; i++) {
  71972. sprites[i].setAttributes({
  71973. path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
  71974. centerY + (rho - margin) * sin(i / steps * pi - pi),
  71975. 'L', centerX + rho * cos(i / steps * pi - pi),
  71976. centerY + rho * sin(i / steps * pi - pi), 'Z'],
  71977. stroke: '#ccc'
  71978. }, true);
  71979. }
  71980. }
  71981. }
  71982. this.sprites = sprites;
  71983. this.drawLabel();
  71984. if (this.title) {
  71985. this.drawTitle();
  71986. }
  71987. },
  71988. drawTitle: function() {
  71989. var me = this,
  71990. chart = me.chart,
  71991. surface = chart.surface,
  71992. bbox = chart.chartBBox,
  71993. labelSprite = me.titleSprite,
  71994. labelBBox;
  71995. if (!labelSprite) {
  71996. me.titleSprite = labelSprite = surface.add({
  71997. type: 'text',
  71998. zIndex: 2
  71999. });
  72000. }
  72001. labelSprite.setAttributes(Ext.apply({
  72002. text: me.title
  72003. }, me.label || {}), true);
  72004. labelBBox = labelSprite.getBBox();
  72005. labelSprite.setAttributes({
  72006. x: bbox.x + (bbox.width / 2) - (labelBBox.width / 2),
  72007. y: bbox.y + bbox.height - (labelBBox.height / 2) - 4
  72008. }, true);
  72009. },
  72010. /**
  72011. * Updates the {@link #title} of this axis.
  72012. * @param {String} title
  72013. */
  72014. setTitle: function(title) {
  72015. this.title = title;
  72016. this.drawTitle();
  72017. },
  72018. drawLabel: function() {
  72019. var chart = this.chart,
  72020. surface = chart.surface,
  72021. bbox = chart.chartBBox,
  72022. centerX = bbox.x + (bbox.width / 2),
  72023. centerY = bbox.y + bbox.height,
  72024. margin = this.margin || 10,
  72025. rho = Math.min(bbox.width, 2 * bbox.height) /2 + 2 * margin,
  72026. round = Math.round,
  72027. labelArray = [], label,
  72028. maxValue = this.maximum || 0,
  72029. minValue = this.minimum || 0,
  72030. steps = this.steps, i = 0,
  72031. adjY,
  72032. pi = Math.PI,
  72033. cos = Math.cos,
  72034. sin = Math.sin,
  72035. labelConf = this.label,
  72036. renderer = labelConf.renderer || function(v) { return v; };
  72037. if (!this.labelArray) {
  72038. //draw scale
  72039. for (i = 0; i <= steps; i++) {
  72040. // TODO Adjust for height of text / 2 instead
  72041. adjY = (i === 0 || i === steps) ? 7 : 0;
  72042. label = surface.add({
  72043. type: 'text',
  72044. text: renderer(round(minValue + i / steps * (maxValue - minValue))),
  72045. x: centerX + rho * cos(i / steps * pi - pi),
  72046. y: centerY + rho * sin(i / steps * pi - pi) - adjY,
  72047. 'text-anchor': 'middle',
  72048. 'stroke-width': 0.2,
  72049. zIndex: 10,
  72050. stroke: '#333'
  72051. });
  72052. label.setAttributes({
  72053. hidden: false
  72054. }, true);
  72055. labelArray.push(label);
  72056. }
  72057. }
  72058. else {
  72059. labelArray = this.labelArray;
  72060. //draw values
  72061. for (i = 0; i <= steps; i++) {
  72062. // TODO Adjust for height of text / 2 instead
  72063. adjY = (i === 0 || i === steps) ? 7 : 0;
  72064. labelArray[i].setAttributes({
  72065. text: renderer(round(minValue + i / steps * (maxValue - minValue))),
  72066. x: centerX + rho * cos(i / steps * pi - pi),
  72067. y: centerY + rho * sin(i / steps * pi - pi) - adjY
  72068. }, true);
  72069. }
  72070. }
  72071. this.labelArray = labelArray;
  72072. }
  72073. });
  72074. /**
  72075. * @class Ext.chart.axis.Numeric
  72076. *
  72077. * An axis to handle numeric values. This axis is used for quantitative data as
  72078. * opposed to the category axis. You can set mininum and maximum values to the
  72079. * axis so that the values are bound to that. If no values are set, then the
  72080. * scale will auto-adjust to the values.
  72081. *
  72082. * @example
  72083. * var store = Ext.create('Ext.data.JsonStore', {
  72084. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  72085. * data: [
  72086. * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
  72087. * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
  72088. * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
  72089. * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
  72090. * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
  72091. * ]
  72092. * });
  72093. *
  72094. * Ext.create('Ext.chart.Chart', {
  72095. * renderTo: Ext.getBody(),
  72096. * width: 500,
  72097. * height: 300,
  72098. * store: store,
  72099. * axes: [{
  72100. * type: 'Numeric',
  72101. * grid: true,
  72102. * position: 'left',
  72103. * fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
  72104. * title: 'Sample Values',
  72105. * grid: {
  72106. * odd: {
  72107. * opacity: 1,
  72108. * fill: '#ddd',
  72109. * stroke: '#bbb',
  72110. * 'stroke-width': 1
  72111. * }
  72112. * },
  72113. * minimum: 0,
  72114. * adjustMinimumByMajorUnit: 0
  72115. * }, {
  72116. * type: 'Category',
  72117. * position: 'bottom',
  72118. * fields: ['name'],
  72119. * title: 'Sample Metrics',
  72120. * grid: true,
  72121. * label: {
  72122. * rotate: {
  72123. * degrees: 315
  72124. * }
  72125. * }
  72126. * }],
  72127. * series: [{
  72128. * type: 'area',
  72129. * highlight: false,
  72130. * axis: 'left',
  72131. * xField: 'name',
  72132. * yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
  72133. * style: {
  72134. * opacity: 0.93
  72135. * }
  72136. * }]
  72137. * });
  72138. *
  72139. * In this example we create an axis of Numeric type. We set a minimum value so that
  72140. * even if all series have values greater than zero, the grid starts at zero. We bind
  72141. * the axis onto the left part of the surface by setting `position` to `left`.
  72142. * We bind three different store fields to this axis by setting `fields` to an array.
  72143. * We set the title of the axis to _Number of Hits_ by using the `title` property.
  72144. * We use a `grid` configuration to set odd background rows to a certain style and even rows
  72145. * to be transparent/ignored.
  72146. */
  72147. Ext.define('Ext.chart.axis.Numeric', {
  72148. /* Begin Definitions */
  72149. extend: 'Ext.chart.axis.Axis',
  72150. alternateClassName: 'Ext.chart.NumericAxis',
  72151. /* End Definitions */
  72152. type: 'numeric',
  72153. alias: 'axis.numeric',
  72154. uses: ['Ext.data.Store'],
  72155. constructor: function(config) {
  72156. var me = this,
  72157. hasLabel = !!(config.label && config.label.renderer),
  72158. label;
  72159. me.callParent([config]);
  72160. label = me.label;
  72161. if (config.constrain == null) {
  72162. me.constrain = (config.minimum != null && config.maximum != null);
  72163. }
  72164. if (!hasLabel) {
  72165. label.renderer = function(v) {
  72166. return me.roundToDecimal(v, me.decimals);
  72167. };
  72168. }
  72169. },
  72170. roundToDecimal: function(v, dec) {
  72171. var val = Math.pow(10, dec || 0);
  72172. return Math.round(v * val) / val;
  72173. },
  72174. /**
  72175. * The minimum value drawn by the axis. If not set explicitly, the axis
  72176. * minimum will be calculated automatically.
  72177. *
  72178. * @property {Number} minimum
  72179. */
  72180. minimum: NaN,
  72181. /**
  72182. * The maximum value drawn by the axis. If not set explicitly, the axis
  72183. * maximum will be calculated automatically.
  72184. *
  72185. * @property {Number} maximum
  72186. */
  72187. maximum: NaN,
  72188. /**
  72189. * @cfg {Boolean} constrain
  72190. * If true, the values of the chart will be rendered only if they belong between minimum and maximum
  72191. * If false, all values of the chart will be rendered, regardless of whether they belong between minimum and maximum or not
  72192. * Default's true if maximum and minimum is specified.
  72193. */
  72194. constrain: true,
  72195. /**
  72196. * The number of decimals to round the value to.
  72197. *
  72198. * @property {Number} decimals
  72199. */
  72200. decimals: 2,
  72201. /**
  72202. * The scaling algorithm to use on this axis. May be "linear" or
  72203. * "logarithmic". Currently only linear scale is implemented.
  72204. *
  72205. * @property {String} scale
  72206. * @private
  72207. */
  72208. scale: "linear",
  72209. // @private constrains to datapoints between minimum and maximum only
  72210. doConstrain: function() {
  72211. var me = this,
  72212. store = me.chart.store,
  72213. items = store.data.items,
  72214. d, dLen, record,
  72215. series = me.chart.series.items,
  72216. fields = me.fields,
  72217. ln = fields.length,
  72218. range = me.calcEnds(),
  72219. min = range.from, max = range.to, i, l,
  72220. useAcum = false,
  72221. value, data = [],
  72222. addRecord;
  72223. for (i = 0, l = series.length; i < l; i++) {
  72224. if (series[i].type === 'bar' && series[i].stacked) {
  72225. // Do not constrain stacked bar chart.
  72226. return;
  72227. }
  72228. }
  72229. for (d = 0, dLen = items.length; d < dLen; d++) {
  72230. addRecord = true;
  72231. record = items[d];
  72232. for (i = 0; i < ln; i++) {
  72233. value = record.get(fields[i]);
  72234. if (+value < +min) {
  72235. addRecord = false;
  72236. break;
  72237. }
  72238. if (+value > +max) {
  72239. addRecord = false;
  72240. break;
  72241. }
  72242. }
  72243. if (addRecord) {
  72244. data.push(record);
  72245. }
  72246. }
  72247. me.chart.substore = Ext.create('Ext.data.Store', { model: store.model });
  72248. me.chart.substore.loadData(data); // data records must be loaded (not passed as config above because it's not json)
  72249. },
  72250. /**
  72251. * Indicates the position of the axis relative to the chart
  72252. *
  72253. * @property {String} position
  72254. */
  72255. position: 'left',
  72256. /**
  72257. * Indicates whether to extend maximum beyond data's maximum to the nearest
  72258. * majorUnit.
  72259. *
  72260. * @property {Boolean} adjustMaximumByMajorUnit
  72261. */
  72262. adjustMaximumByMajorUnit: false,
  72263. /**
  72264. * Indicates whether to extend the minimum beyond data's minimum to the
  72265. * nearest majorUnit.
  72266. *
  72267. * @property {Boolean} adjustMinimumByMajorUnit
  72268. */
  72269. adjustMinimumByMajorUnit: false,
  72270. // applying constraint
  72271. processView: function() {
  72272. var me = this,
  72273. constrain = me.constrain;
  72274. if (constrain) {
  72275. me.doConstrain();
  72276. }
  72277. },
  72278. // @private apply data.
  72279. applyData: function() {
  72280. this.callParent();
  72281. return this.calcEnds();
  72282. }
  72283. });
  72284. /**
  72285. * @private
  72286. */
  72287. Ext.define('Ext.chart.axis.Radial', {
  72288. /* Begin Definitions */
  72289. extend: 'Ext.chart.axis.Abstract',
  72290. /* End Definitions */
  72291. position: 'radial',
  72292. alias: 'axis.radial',
  72293. drawAxis: function(init) {
  72294. var chart = this.chart,
  72295. surface = chart.surface,
  72296. bbox = chart.chartBBox,
  72297. store = chart.store,
  72298. l = store.getCount(),
  72299. centerX = bbox.x + (bbox.width / 2),
  72300. centerY = bbox.y + (bbox.height / 2),
  72301. rho = Math.min(bbox.width, bbox.height) /2,
  72302. sprites = [], sprite,
  72303. steps = this.steps,
  72304. i, j, pi2 = Math.PI * 2,
  72305. cos = Math.cos, sin = Math.sin;
  72306. if (this.sprites && !chart.resizing) {
  72307. this.drawLabel();
  72308. return;
  72309. }
  72310. if (!this.sprites) {
  72311. //draw circles
  72312. for (i = 1; i <= steps; i++) {
  72313. sprite = surface.add({
  72314. type: 'circle',
  72315. x: centerX,
  72316. y: centerY,
  72317. radius: Math.max(rho * i / steps, 0),
  72318. stroke: '#ccc'
  72319. });
  72320. sprite.setAttributes({
  72321. hidden: false
  72322. }, true);
  72323. sprites.push(sprite);
  72324. }
  72325. //draw lines
  72326. for (i = 0; i < l; i++) {
  72327. sprite = surface.add({
  72328. type: 'path',
  72329. path: ['M', centerX, centerY, 'L', centerX + rho * cos(i / l * pi2), centerY + rho * sin(i / l * pi2), 'Z'],
  72330. stroke: '#ccc'
  72331. });
  72332. sprite.setAttributes({
  72333. hidden: false
  72334. }, true);
  72335. sprites.push(sprite);
  72336. }
  72337. } else {
  72338. sprites = this.sprites;
  72339. //draw circles
  72340. for (i = 0; i < steps; i++) {
  72341. sprites[i].setAttributes({
  72342. x: centerX,
  72343. y: centerY,
  72344. radius: Math.max(rho * (i + 1) / steps, 0),
  72345. stroke: '#ccc'
  72346. }, true);
  72347. }
  72348. //draw lines
  72349. for (j = 0; j < l; j++) {
  72350. sprites[i + j].setAttributes({
  72351. path: ['M', centerX, centerY, 'L', centerX + rho * cos(j / l * pi2), centerY + rho * sin(j / l * pi2), 'Z'],
  72352. stroke: '#ccc'
  72353. }, true);
  72354. }
  72355. }
  72356. this.sprites = sprites;
  72357. this.drawLabel();
  72358. },
  72359. drawLabel: function() {
  72360. var chart = this.chart,
  72361. seriesItems = chart.series.items,
  72362. series,
  72363. surface = chart.surface,
  72364. bbox = chart.chartBBox,
  72365. store = chart.store,
  72366. data = store.data.items,
  72367. ln, record,
  72368. centerX = bbox.x + (bbox.width / 2),
  72369. centerY = bbox.y + (bbox.height / 2),
  72370. rho = Math.min(bbox.width, bbox.height) /2,
  72371. max = Math.max, round = Math.round,
  72372. labelArray = [], label,
  72373. fields = [], nfields,
  72374. categories = [], xField,
  72375. aggregate = !this.maximum,
  72376. maxValue = this.maximum || 0,
  72377. steps = this.steps, i = 0, j, dx, dy,
  72378. pi2 = Math.PI * 2,
  72379. cos = Math.cos, sin = Math.sin,
  72380. display = this.label.display,
  72381. draw = display !== 'none',
  72382. margin = 10;
  72383. if (!draw) {
  72384. return;
  72385. }
  72386. //get all rendered fields
  72387. for (i = 0, ln = seriesItems.length; i < ln; i++) {
  72388. series = seriesItems[i];
  72389. fields.push(series.yField);
  72390. xField = series.xField;
  72391. }
  72392. //get maxValue to interpolate
  72393. for (j = 0, ln = data.length; j < ln; j++) {
  72394. record = data[j];
  72395. if (aggregate) {
  72396. for (i = 0, nfields = fields.length; i < nfields; i++) {
  72397. maxValue = max(+record.get(fields[i]), maxValue);
  72398. }
  72399. }
  72400. categories.push(record.get(xField));
  72401. }
  72402. if (!this.labelArray) {
  72403. if (display != 'categories') {
  72404. //draw scale
  72405. for (i = 1; i <= steps; i++) {
  72406. label = surface.add({
  72407. type: 'text',
  72408. text: round(i / steps * maxValue),
  72409. x: centerX,
  72410. y: centerY - rho * i / steps,
  72411. 'text-anchor': 'middle',
  72412. 'stroke-width': 0.1,
  72413. stroke: '#333'
  72414. });
  72415. label.setAttributes({
  72416. hidden: false
  72417. }, true);
  72418. labelArray.push(label);
  72419. }
  72420. }
  72421. if (display != 'scale') {
  72422. //draw text
  72423. for (j = 0, steps = categories.length; j < steps; j++) {
  72424. dx = cos(j / steps * pi2) * (rho + margin);
  72425. dy = sin(j / steps * pi2) * (rho + margin);
  72426. label = surface.add({
  72427. type: 'text',
  72428. text: categories[j],
  72429. x: centerX + dx,
  72430. y: centerY + dy,
  72431. 'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
  72432. });
  72433. label.setAttributes({
  72434. hidden: false
  72435. }, true);
  72436. labelArray.push(label);
  72437. }
  72438. }
  72439. }
  72440. else {
  72441. labelArray = this.labelArray;
  72442. if (display != 'categories') {
  72443. //draw values
  72444. for (i = 0; i < steps; i++) {
  72445. labelArray[i].setAttributes({
  72446. text: round((i + 1) / steps * maxValue),
  72447. x: centerX,
  72448. y: centerY - rho * (i + 1) / steps,
  72449. 'text-anchor': 'middle',
  72450. 'stroke-width': 0.1,
  72451. stroke: '#333'
  72452. }, true);
  72453. }
  72454. }
  72455. if (display != 'scale') {
  72456. //draw text
  72457. for (j = 0, steps = categories.length; j < steps; j++) {
  72458. dx = cos(j / steps * pi2) * (rho + margin);
  72459. dy = sin(j / steps * pi2) * (rho + margin);
  72460. if (labelArray[i + j]) {
  72461. labelArray[i + j].setAttributes({
  72462. type: 'text',
  72463. text: categories[j],
  72464. x: centerX + dx,
  72465. y: centerY + dy,
  72466. 'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
  72467. }, true);
  72468. }
  72469. }
  72470. }
  72471. }
  72472. this.labelArray = labelArray;
  72473. }
  72474. });
  72475. /**
  72476. * A type of axis whose units are measured in time values. Use this axis
  72477. * for listing dates that you will want to group or dynamically change.
  72478. * If you just want to display dates as categories then use the
  72479. * Category class for axis instead.
  72480. *
  72481. * For example:
  72482. *
  72483. * axes: [{
  72484. * type: 'Time',
  72485. * position: 'bottom',
  72486. * fields: 'date',
  72487. * title: 'Day',
  72488. * dateFormat: 'M d',
  72489. *
  72490. * constrain: true,
  72491. * fromDate: new Date('1/1/11'),
  72492. * toDate: new Date('1/7/11')
  72493. * }]
  72494. *
  72495. * In this example we're creating a time axis that has as title *Day*.
  72496. * The field the axis is bound to is `date`.
  72497. * The date format to use to display the text for the axis labels is `M d`
  72498. * which is a three letter month abbreviation followed by the day number.
  72499. * The time axis will show values for dates between `fromDate` and `toDate`.
  72500. * Since `constrain` is set to true all other values for other dates not between
  72501. * the fromDate and toDate will not be displayed.
  72502. *
  72503. */
  72504. Ext.define('Ext.chart.axis.Time', {
  72505. /* Begin Definitions */
  72506. extend: 'Ext.chart.axis.Numeric',
  72507. alternateClassName: 'Ext.chart.TimeAxis',
  72508. alias: 'axis.time',
  72509. uses: ['Ext.data.Store'],
  72510. /* End Definitions */
  72511. /**
  72512. * @cfg {String/Boolean} dateFormat
  72513. * Indicates the format the date will be rendered on.
  72514. * For example: 'M d' will render the dates as 'Jan 30', etc.
  72515. * For a list of possible format strings see {@link Ext.Date Date}
  72516. */
  72517. dateFormat: false,
  72518. /**
  72519. * @cfg {Date} fromDate The starting date for the time axis.
  72520. */
  72521. fromDate: false,
  72522. /**
  72523. * @cfg {Date} toDate The ending date for the time axis.
  72524. */
  72525. toDate: false,
  72526. /**
  72527. * @cfg {Array/Boolean} step
  72528. * An array with two components: The first is the unit of the step (day, month, year, etc). The second one is the number of units for the step (1, 2, etc.).
  72529. *
  72530. * Defaults to: [Ext.Date.DAY, 1].
  72531. */
  72532. step: [Ext.Date.DAY, 1],
  72533. /**
  72534. * @cfg {Boolean} constrain
  72535. * If true, the values of the chart will be rendered only if they belong between the fromDate and toDate.
  72536. * If false, the time axis will adapt to the new values by adding/removing steps.
  72537. */
  72538. constrain: false,
  72539. constructor: function (config) {
  72540. var me = this, label, f, df;
  72541. me.callParent([config]);
  72542. label = me.label || {};
  72543. df = this.dateFormat;
  72544. if (df) {
  72545. if (label.renderer) {
  72546. f = label.renderer;
  72547. label.renderer = function(v) {
  72548. v = f(v);
  72549. return Ext.Date.format(new Date(f(v)), df);
  72550. };
  72551. } else {
  72552. label.renderer = function(v) {
  72553. return Ext.Date.format(new Date(v >> 0), df);
  72554. };
  72555. }
  72556. }
  72557. },
  72558. // Before rendering, set current default step count to be number of records.
  72559. processView: function () {
  72560. var me = this;
  72561. if (me.fromDate) {
  72562. me.minimum = +me.fromDate;
  72563. }
  72564. if (me.toDate) {
  72565. me.maximum = +me.toDate;
  72566. }
  72567. if(me.constrain){
  72568. me.doConstrain();
  72569. }
  72570. },
  72571. // @private modifies the store and creates the labels for the axes.
  72572. calcEnds: function() {
  72573. var me = this, range, step = me.step;
  72574. if (step) {
  72575. range = me.getRange();
  72576. range = Ext.draw.Draw.snapEndsByDateAndStep(new Date(range.min), new Date(range.max), Ext.isNumber(step) ? [Date.MILLI, step]: step);
  72577. if (me.minimum) {
  72578. range.from = me.minimum;
  72579. }
  72580. if (me.maximum) {
  72581. range.to = me.maximum;
  72582. }
  72583. range.step = (range.to - range.from) / range.steps;
  72584. return range;
  72585. } else {
  72586. return me.callParent(arguments);
  72587. }
  72588. }
  72589. });
  72590. /**
  72591. * @class Ext.chart.series.Series
  72592. *
  72593. * Series is the abstract class containing the common logic to all chart series. Series includes
  72594. * methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling
  72595. * mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
  72596. *
  72597. * ## Listeners
  72598. *
  72599. * The series class supports listeners via the Observable syntax. Some of these listeners are:
  72600. *
  72601. * - `itemmouseup` When the user interacts with a marker.
  72602. * - `itemmousedown` When the user interacts with a marker.
  72603. * - `itemmousemove` When the user iteracts with a marker.
  72604. * - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
  72605. *
  72606. * For example:
  72607. *
  72608. * series: [{
  72609. * type: 'column',
  72610. * axis: 'left',
  72611. * listeners: {
  72612. * 'afterrender': function() {
  72613. * console('afterrender');
  72614. * }
  72615. * },
  72616. * xField: 'category',
  72617. * yField: 'data1'
  72618. * }]
  72619. */
  72620. Ext.define('Ext.chart.series.Series', {
  72621. /* Begin Definitions */
  72622. mixins: {
  72623. observable: 'Ext.util.Observable',
  72624. labels: 'Ext.chart.Label',
  72625. highlights: 'Ext.chart.Highlight',
  72626. tips: 'Ext.chart.Tip',
  72627. callouts: 'Ext.chart.Callout'
  72628. },
  72629. /* End Definitions */
  72630. /**
  72631. * @cfg {Boolean/Object} highlight
  72632. * If set to `true` it will highlight the markers or the series when hovering
  72633. * with the mouse. This parameter can also be an object with the same style
  72634. * properties you would apply to a {@link Ext.draw.Sprite} to apply custom
  72635. * styles to markers and series.
  72636. */
  72637. /**
  72638. * @cfg {Object} tips
  72639. * Add tooltips to the visualization's markers. The options for the tips are the
  72640. * same configuration used with {@link Ext.tip.ToolTip}. For example:
  72641. *
  72642. * tips: {
  72643. * trackMouse: true,
  72644. * width: 140,
  72645. * height: 28,
  72646. * renderer: function(storeItem, item) {
  72647. * this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
  72648. * }
  72649. * },
  72650. */
  72651. /**
  72652. * @cfg {String} type
  72653. * The type of series. Set in subclasses.
  72654. */
  72655. type: null,
  72656. /**
  72657. * @cfg {String} title
  72658. * The human-readable name of the series.
  72659. */
  72660. title: null,
  72661. /**
  72662. * @cfg {Boolean} showInLegend
  72663. * Whether to show this series in the legend.
  72664. */
  72665. showInLegend: true,
  72666. /**
  72667. * @cfg {Function} renderer
  72668. * A function that can be overridden to set custom styling properties to each rendered element.
  72669. * Passes in (sprite, record, attributes, index, store) to the function.
  72670. */
  72671. renderer: function(sprite, record, attributes, index, store) {
  72672. return attributes;
  72673. },
  72674. /**
  72675. * @cfg {Array} shadowAttributes
  72676. * An array with shadow attributes
  72677. */
  72678. shadowAttributes: null,
  72679. // @private animating flag
  72680. animating: false,
  72681. /**
  72682. * @cfg {Object} listeners
  72683. * An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
  72684. *
  72685. * - itemmouseover
  72686. * - itemmouseout
  72687. * - itemmousedown
  72688. * - itemmouseup
  72689. */
  72690. constructor: function(config) {
  72691. var me = this;
  72692. if (config) {
  72693. Ext.apply(me, config);
  72694. }
  72695. me.shadowGroups = [];
  72696. me.mixins.labels.constructor.call(me, config);
  72697. me.mixins.highlights.constructor.call(me, config);
  72698. me.mixins.tips.constructor.call(me, config);
  72699. me.mixins.callouts.constructor.call(me, config);
  72700. me.addEvents({
  72701. scope: me,
  72702. itemmouseover: true,
  72703. itemmouseout: true,
  72704. itemmousedown: true,
  72705. itemmouseup: true,
  72706. mouseleave: true,
  72707. afterdraw: true,
  72708. /**
  72709. * @event titlechange
  72710. * Fires when the series title is changed via {@link #setTitle}.
  72711. * @param {String} title The new title value
  72712. * @param {Number} index The index in the collection of titles
  72713. */
  72714. titlechange: true
  72715. });
  72716. me.mixins.observable.constructor.call(me, config);
  72717. me.on({
  72718. scope: me,
  72719. itemmouseover: me.onItemMouseOver,
  72720. itemmouseout: me.onItemMouseOut,
  72721. mouseleave: me.onMouseLeave
  72722. });
  72723. if (me.style) {
  72724. Ext.apply(me.seriesStyle, me.style);
  72725. }
  72726. },
  72727. /**
  72728. * Iterate over each of the records for this series. The default implementation simply iterates
  72729. * through the entire data store, but individual series implementations can override this to
  72730. * provide custom handling, e.g. adding/removing records.
  72731. * @param {Function} fn The function to execute for each record.
  72732. * @param {Object} scope Scope for the fn.
  72733. */
  72734. eachRecord: function(fn, scope) {
  72735. var chart = this.chart;
  72736. (chart.substore || chart.store).each(fn, scope);
  72737. },
  72738. /**
  72739. * Return the number of records being displayed in this series. Defaults to the number of
  72740. * records in the store; individual series implementations can override to provide custom handling.
  72741. */
  72742. getRecordCount: function() {
  72743. var chart = this.chart,
  72744. store = chart.substore || chart.store;
  72745. return store ? store.getCount() : 0;
  72746. },
  72747. /**
  72748. * Determines whether the series item at the given index has been excluded, i.e. toggled off in the legend.
  72749. * @param index
  72750. */
  72751. isExcluded: function(index) {
  72752. var excludes = this.__excludes;
  72753. return !!(excludes && excludes[index]);
  72754. },
  72755. // @private set the bbox and clipBox for the series
  72756. setBBox: function(noGutter) {
  72757. var me = this,
  72758. chart = me.chart,
  72759. chartBBox = chart.chartBBox,
  72760. gutterX = noGutter ? 0 : chart.maxGutter[0],
  72761. gutterY = noGutter ? 0 : chart.maxGutter[1],
  72762. clipBox, bbox;
  72763. clipBox = {
  72764. x: chartBBox.x,
  72765. y: chartBBox.y,
  72766. width: chartBBox.width,
  72767. height: chartBBox.height
  72768. };
  72769. me.clipBox = clipBox;
  72770. bbox = {
  72771. x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
  72772. y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
  72773. width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
  72774. height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
  72775. };
  72776. me.bbox = bbox;
  72777. },
  72778. // @private set the animation for the sprite
  72779. onAnimate: function(sprite, attr) {
  72780. var me = this;
  72781. sprite.stopAnimation();
  72782. if (me.animating) {
  72783. return sprite.animate(Ext.applyIf(attr, me.chart.animate));
  72784. } else {
  72785. me.animating = true;
  72786. return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
  72787. listeners: {
  72788. 'afteranimate': function() {
  72789. me.animating = false;
  72790. me.fireEvent('afterrender');
  72791. }
  72792. }
  72793. }));
  72794. }
  72795. },
  72796. // @private return the gutter.
  72797. getGutters: function() {
  72798. return [0, 0];
  72799. },
  72800. // @private wrapper for the itemmouseover event.
  72801. onItemMouseOver: function(item) {
  72802. var me = this;
  72803. if (item.series === me) {
  72804. if (me.highlight) {
  72805. me.highlightItem(item);
  72806. }
  72807. if (me.tooltip) {
  72808. me.showTip(item);
  72809. }
  72810. }
  72811. },
  72812. // @private wrapper for the itemmouseout event.
  72813. onItemMouseOut: function(item) {
  72814. var me = this;
  72815. if (item.series === me) {
  72816. me.unHighlightItem();
  72817. if (me.tooltip) {
  72818. me.hideTip(item);
  72819. }
  72820. }
  72821. },
  72822. // @private wrapper for the mouseleave event.
  72823. onMouseLeave: function() {
  72824. var me = this;
  72825. me.unHighlightItem();
  72826. if (me.tooltip) {
  72827. me.hideTip();
  72828. }
  72829. },
  72830. /**
  72831. * For a given x/y point relative to the Surface, find a corresponding item from this
  72832. * series, if any.
  72833. * @param {Number} x
  72834. * @param {Number} y
  72835. * @return {Object} An object describing the item, or null if there is no matching item.
  72836. * The exact contents of this object will vary by series type, but should always contain the following:
  72837. * @return {Ext.chart.series.Series} return.series the Series object to which the item belongs
  72838. * @return {Object} return.value the value(s) of the item's data point
  72839. * @return {Array} return.point the x/y coordinates relative to the chart box of a single point
  72840. * for this data item, which can be used as e.g. a tooltip anchor point.
  72841. * @return {Ext.draw.Sprite} return.sprite the item's rendering Sprite.
  72842. */
  72843. getItemForPoint: function(x, y) {
  72844. //if there are no items to query just return null.
  72845. if (!this.items || !this.items.length || this.seriesIsHidden) {
  72846. return null;
  72847. }
  72848. var me = this,
  72849. items = me.items,
  72850. bbox = me.bbox,
  72851. item, i, ln;
  72852. // Check bounds
  72853. if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
  72854. return null;
  72855. }
  72856. for (i = 0, ln = items.length; i < ln; i++) {
  72857. if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
  72858. return items[i];
  72859. }
  72860. }
  72861. return null;
  72862. },
  72863. isItemInPoint: function(x, y, item, i) {
  72864. return false;
  72865. },
  72866. /**
  72867. * Hides all the elements in the series.
  72868. */
  72869. hideAll: function() {
  72870. var me = this,
  72871. items = me.items,
  72872. item, len, i, j, l, sprite, shadows;
  72873. me.seriesIsHidden = true;
  72874. me._prevShowMarkers = me.showMarkers;
  72875. me.showMarkers = false;
  72876. //hide all labels
  72877. me.hideLabels(0);
  72878. //hide all sprites
  72879. for (i = 0, len = items.length; i < len; i++) {
  72880. item = items[i];
  72881. sprite = item.sprite;
  72882. if (sprite) {
  72883. sprite.setAttributes({
  72884. hidden: true
  72885. }, true);
  72886. }
  72887. if (sprite && sprite.shadows) {
  72888. shadows = sprite.shadows;
  72889. for (j = 0, l = shadows.length; j < l; ++j) {
  72890. shadows[j].setAttributes({
  72891. hidden: true
  72892. }, true);
  72893. }
  72894. }
  72895. }
  72896. },
  72897. /**
  72898. * Shows all the elements in the series.
  72899. */
  72900. showAll: function() {
  72901. var me = this,
  72902. prevAnimate = me.chart.animate;
  72903. me.chart.animate = false;
  72904. me.seriesIsHidden = false;
  72905. me.showMarkers = me._prevShowMarkers;
  72906. me.drawSeries();
  72907. me.chart.animate = prevAnimate;
  72908. },
  72909. hide: function() {
  72910. if (this.items) {
  72911. var me = this,
  72912. items = me.items,
  72913. i, j, lsh, ln, shadows;
  72914. if (items && items.length) {
  72915. for (i = 0, ln = items.length; i < ln; ++i) {
  72916. if (items[i].sprite) {
  72917. items[i].sprite.hide(true);
  72918. shadows = items[i].shadows || items[i].sprite.shadows;
  72919. if (shadows) {
  72920. for (j = 0, lsh = shadows.length; j < lsh; ++j) {
  72921. shadows[j].hide(true);
  72922. }
  72923. }
  72924. }
  72925. }
  72926. me.hideLabels();
  72927. }
  72928. }
  72929. },
  72930. /**
  72931. * Returns a string with the color to be used for the series legend item.
  72932. */
  72933. getLegendColor: function(index) {
  72934. var me = this, fill, stroke;
  72935. if (me.seriesStyle) {
  72936. fill = me.seriesStyle.fill;
  72937. stroke = me.seriesStyle.stroke;
  72938. if (fill && fill != 'none') {
  72939. return fill;
  72940. }
  72941. if(stroke){
  72942. return stroke;
  72943. }
  72944. }
  72945. return (me.colorArrayStyle)?me.colorArrayStyle[me.seriesIdx % me.colorArrayStyle.length]:'#000';
  72946. },
  72947. /**
  72948. * Checks whether the data field should be visible in the legend
  72949. * @private
  72950. * @param {Number} index The index of the current item
  72951. */
  72952. visibleInLegend: function(index){
  72953. var excludes = this.__excludes;
  72954. if (excludes) {
  72955. return !excludes[index];
  72956. }
  72957. return !this.seriesIsHidden;
  72958. },
  72959. /**
  72960. * Changes the value of the {@link #title} for the series.
  72961. * Arguments can take two forms:
  72962. * <ul>
  72963. * <li>A single String value: this will be used as the new single title for the series (applies
  72964. * to series with only one yField)</li>
  72965. * <li>A numeric index and a String value: this will set the title for a single indexed yField.</li>
  72966. * </ul>
  72967. * @param {Number} index
  72968. * @param {String} title
  72969. */
  72970. setTitle: function(index, title) {
  72971. var me = this,
  72972. oldTitle = me.title;
  72973. if (Ext.isString(index)) {
  72974. title = index;
  72975. index = 0;
  72976. }
  72977. if (Ext.isArray(oldTitle)) {
  72978. oldTitle[index] = title;
  72979. } else {
  72980. me.title = title;
  72981. }
  72982. me.fireEvent('titlechange', title, index);
  72983. }
  72984. });
  72985. /**
  72986. * @class Ext.chart.series.Cartesian
  72987. *
  72988. * Common base class for series implementations which plot values using x/y coordinates.
  72989. */
  72990. Ext.define('Ext.chart.series.Cartesian', {
  72991. /* Begin Definitions */
  72992. extend: 'Ext.chart.series.Series',
  72993. alternateClassName: ['Ext.chart.CartesianSeries', 'Ext.chart.CartesianChart'],
  72994. /* End Definitions */
  72995. /**
  72996. * The field used to access the x axis value from the items from the data
  72997. * source.
  72998. *
  72999. * @cfg xField
  73000. * @type String
  73001. */
  73002. xField: null,
  73003. /**
  73004. * The field used to access the y-axis value from the items from the data
  73005. * source.
  73006. *
  73007. * @cfg yField
  73008. * @type String
  73009. */
  73010. yField: null,
  73011. /**
  73012. * @cfg {String/String[]} axis
  73013. * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
  73014. * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
  73015. * relative scale will be used. For example, if you're using a Scatter or Line series and you'd like to have the
  73016. * values in the chart relative to the bottom and left axes then `axis` should be `['left', 'bottom']`.
  73017. */
  73018. axis: 'left',
  73019. getLegendLabels: function() {
  73020. var me = this,
  73021. labels = [],
  73022. fields, i, ln,
  73023. combinations = me.combinations,
  73024. title,
  73025. combo, label0, label1;
  73026. fields = [].concat(me.yField);
  73027. for (i = 0, ln = fields.length; i < ln; i++) {
  73028. title = me.title;
  73029. // Use the 'title' config if present, otherwise use the raw yField name
  73030. labels.push((Ext.isArray(title) ? title[i] : title) || fields[i]);
  73031. }
  73032. // Handle yFields combined via legend drag-drop
  73033. // TODO need to check to see if this is supported in extjs 4 branch
  73034. if (combinations) {
  73035. combinations = Ext.Array.from(combinations);
  73036. for (i = 0, ln = combinations.length; i < ln; i++) {
  73037. combo = combinations[i];
  73038. label0 = labels[combo[0]];
  73039. label1 = labels[combo[1]];
  73040. labels[combo[1]] = label0 + ' & ' + label1;
  73041. labels.splice(combo[0], 1);
  73042. }
  73043. }
  73044. return labels;
  73045. },
  73046. /**
  73047. * @protected Iterates over a given record's values for each of this series's yFields,
  73048. * executing a given function for each value. Any yFields that have been combined
  73049. * via legend drag-drop will be treated as a single value.
  73050. * @param {Ext.data.Model} record
  73051. * @param {Function} fn
  73052. * @param {Object} scope
  73053. */
  73054. eachYValue: function(record, fn, scope) {
  73055. var me = this,
  73056. yValueAccessors = me.getYValueAccessors(),
  73057. i, ln, accessor;
  73058. for (i = 0, ln = yValueAccessors.length; i < ln; i++) {
  73059. accessor = yValueAccessors[i];
  73060. fn.call(scope, accessor(record), i);
  73061. }
  73062. },
  73063. /**
  73064. * @protected Returns the number of yField values, taking into account fields combined
  73065. * via legend drag-drop.
  73066. * @return {Number}
  73067. */
  73068. getYValueCount: function() {
  73069. return this.getYValueAccessors().length;
  73070. },
  73071. combine: function(index1, index2) {
  73072. var me = this,
  73073. accessors = me.getYValueAccessors(),
  73074. accessor1 = accessors[index1],
  73075. accessor2 = accessors[index2];
  73076. // Combine the yValue accessors for the two indexes into a single accessor that returns their sum
  73077. accessors[index2] = function(record) {
  73078. return accessor1(record) + accessor2(record);
  73079. };
  73080. accessors.splice(index1, 1);
  73081. me.callParent([index1, index2]);
  73082. },
  73083. clearCombinations: function() {
  73084. // Clear combined accessors, they'll get regenerated on next call to getYValueAccessors
  73085. delete this.yValueAccessors;
  73086. this.callParent();
  73087. },
  73088. /**
  73089. * @protected Returns an array of functions, each of which returns the value of the yField
  73090. * corresponding to function's index in the array, for a given record (each function takes the
  73091. * record as its only argument.) If yFields have been combined by the user via legend drag-drop,
  73092. * this list of accessors will be kept in sync with those combinations.
  73093. * @return {Array} array of accessor functions
  73094. */
  73095. getYValueAccessors: function() {
  73096. var me = this,
  73097. accessors = me.yValueAccessors,
  73098. yFields, yField, i, ln;
  73099. if (!accessors) {
  73100. accessors = me.yValueAccessors = [];
  73101. yFields = [].concat(me.yField);
  73102. for (i = 0, ln = yFields.length; i < ln; i++) {
  73103. yField = yFields[i];
  73104. accessors.push(function(record) {
  73105. return record.get(yField);
  73106. });
  73107. }
  73108. }
  73109. return accessors;
  73110. },
  73111. /**
  73112. * Calculate the min and max values for this series's xField.
  73113. * @return {Array} [min, max]
  73114. */
  73115. getMinMaxXValues: function() {
  73116. var me = this,
  73117. chart = me.chart,
  73118. store = chart.getChartStore(),
  73119. data = store.data.items,
  73120. i, ln, record,
  73121. min, max,
  73122. xField = me.xField,
  73123. xValue;
  73124. if (me.getRecordCount() > 0) {
  73125. min = Infinity;
  73126. max = -min;
  73127. for (i = 0, ln = data.length; i < ln; i++) {
  73128. record = data[i];
  73129. xValue = record.get(xField);
  73130. if (xValue > max) {
  73131. max = xValue;
  73132. }
  73133. if (xValue < min) {
  73134. min = xValue;
  73135. }
  73136. }
  73137. } else {
  73138. min = max = 0;
  73139. }
  73140. return [min, max];
  73141. },
  73142. /**
  73143. * Calculate the min and max values for this series's yField(s). Takes into account yField
  73144. * combinations, exclusions, and stacking.
  73145. * @return {Array} [min, max]
  73146. */
  73147. getMinMaxYValues: function() {
  73148. var me = this,
  73149. chart = me.chart,
  73150. store = chart.getChartStore(),
  73151. data = store.data.items,
  73152. i, ln, record,
  73153. stacked = me.stacked,
  73154. min, max,
  73155. positiveTotal, negativeTotal;
  73156. function eachYValueStacked(yValue, i) {
  73157. if (!me.isExcluded(i)) {
  73158. if (yValue < 0) {
  73159. negativeTotal += yValue;
  73160. } else {
  73161. positiveTotal += yValue;
  73162. }
  73163. }
  73164. }
  73165. function eachYValue(yValue, i) {
  73166. if (!me.isExcluded(i)) {
  73167. if (yValue > max) {
  73168. max = yValue;
  73169. }
  73170. if (yValue < min) {
  73171. min = yValue;
  73172. }
  73173. }
  73174. }
  73175. if (me.getRecordCount() > 0) {
  73176. min = Infinity;
  73177. max = -min;
  73178. for (i = 0, ln = data.length; i < ln; i++) {
  73179. record = data[i];
  73180. if (stacked) {
  73181. positiveTotal = 0;
  73182. negativeTotal = 0;
  73183. me.eachYValue(record, eachYValueStacked);
  73184. if (positiveTotal > max) {
  73185. max = positiveTotal;
  73186. }
  73187. if (negativeTotal < min) {
  73188. min = negativeTotal;
  73189. }
  73190. } else {
  73191. me.eachYValue(record, eachYValue);
  73192. }
  73193. }
  73194. } else {
  73195. min = max = 0;
  73196. }
  73197. return [min, max];
  73198. },
  73199. getAxesForXAndYFields: function() {
  73200. var me = this,
  73201. axes = me.chart.axes,
  73202. axis = [].concat(me.axis),
  73203. yFields = {}, yFieldList = [].concat(me.yField),
  73204. xFields = {}, xFieldList = [].concat(me.xField),
  73205. fields, xAxis, yAxis, i, ln, flipXY;
  73206. flipXY = me.type === 'bar' && me.column === false;
  73207. if(flipXY) {
  73208. fields = yFieldList;
  73209. yFieldList = xFieldList;
  73210. xFieldList = fields;
  73211. }
  73212. if (Ext.Array.indexOf(axis, 'top') > -1) {
  73213. xAxis = 'top';
  73214. } else if (Ext.Array.indexOf(axis, 'bottom') > -1) {
  73215. xAxis = 'bottom';
  73216. } else {
  73217. if (axes.get('top') && axes.get('bottom')) {
  73218. for (i = 0, ln = xFieldList.length; i < ln; i++) {
  73219. xFields[xFieldList[i]] = true;
  73220. }
  73221. fields = [].concat(axes.get('bottom').fields);
  73222. for (i = 0, ln = fields.length; i < ln; i++) {
  73223. if (xFields[fields[i]]) {
  73224. xAxis = 'bottom';
  73225. break
  73226. }
  73227. }
  73228. fields = [].concat(axes.get('top').fields);
  73229. for (i = 0, ln = fields.length; i < ln; i++) {
  73230. if (xFields[fields[i]]) {
  73231. xAxis = 'top';
  73232. break
  73233. }
  73234. }
  73235. } else if (axes.get('top')) {
  73236. xAxis = 'top';
  73237. } else if (axes.get('bottom')) {
  73238. xAxis = 'bottom';
  73239. }
  73240. }
  73241. if (Ext.Array.indexOf(axis, 'left') > -1) {
  73242. yAxis = 'left';
  73243. } else if (Ext.Array.indexOf(axis, 'right') > -1) {
  73244. yAxis = 'right';
  73245. } else {
  73246. if (axes.get('left') && axes.get('right')) {
  73247. for (i = 0, ln = yFieldList.length; i < ln; i++) {
  73248. yFields[yFieldList[i]] = true;
  73249. }
  73250. fields = [].concat(axes.get('right').fields);
  73251. for (i = 0, ln = fields.length; i < ln; i++) {
  73252. if (yFields[fields[i]]) {
  73253. break
  73254. }
  73255. }
  73256. fields = [].concat(axes.get('left').fields);
  73257. for (i = 0, ln = fields.length; i < ln; i++) {
  73258. if (yFields[fields[i]]) {
  73259. yAxis = 'left';
  73260. break
  73261. }
  73262. }
  73263. } else if (axes.get('left')) {
  73264. yAxis = 'left';
  73265. } else if (axes.get('right')) {
  73266. yAxis = 'right';
  73267. }
  73268. }
  73269. return flipXY ? {
  73270. xAxis: yAxis,
  73271. yAxis: xAxis
  73272. }: {
  73273. xAxis: xAxis,
  73274. yAxis: yAxis
  73275. };
  73276. }
  73277. });
  73278. /**
  73279. * @class Ext.chart.series.Area
  73280. * @extends Ext.chart.series.Cartesian
  73281. *
  73282. * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
  73283. * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
  73284. * documentation for more information. A typical configuration object for the area series could be:
  73285. *
  73286. * @example
  73287. * var store = Ext.create('Ext.data.JsonStore', {
  73288. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  73289. * data: [
  73290. * { 'name': 'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13 },
  73291. * { 'name': 'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3 },
  73292. * { 'name': 'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7 },
  73293. * { 'name': 'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23 },
  73294. * { 'name': 'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
  73295. * ]
  73296. * });
  73297. *
  73298. * Ext.create('Ext.chart.Chart', {
  73299. * renderTo: Ext.getBody(),
  73300. * width: 500,
  73301. * height: 300,
  73302. * store: store,
  73303. * axes: [
  73304. * {
  73305. * type: 'Numeric',
  73306. * grid: true,
  73307. * position: 'left',
  73308. * fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
  73309. * title: 'Sample Values',
  73310. * grid: {
  73311. * odd: {
  73312. * opacity: 1,
  73313. * fill: '#ddd',
  73314. * stroke: '#bbb',
  73315. * 'stroke-width': 1
  73316. * }
  73317. * },
  73318. * minimum: 0,
  73319. * adjustMinimumByMajorUnit: 0
  73320. * },
  73321. * {
  73322. * type: 'Category',
  73323. * position: 'bottom',
  73324. * fields: ['name'],
  73325. * title: 'Sample Metrics',
  73326. * grid: true,
  73327. * label: {
  73328. * rotate: {
  73329. * degrees: 315
  73330. * }
  73331. * }
  73332. * }
  73333. * ],
  73334. * series: [{
  73335. * type: 'area',
  73336. * highlight: false,
  73337. * axis: 'left',
  73338. * xField: 'name',
  73339. * yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
  73340. * style: {
  73341. * opacity: 0.93
  73342. * }
  73343. * }]
  73344. * });
  73345. *
  73346. * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
  73347. * take the left axis to measure the data in the area series, set as xField (x values) the name field of each element in the store,
  73348. * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
  73349. * to the style object.
  73350. *
  73351. * @xtype area
  73352. */
  73353. Ext.define('Ext.chart.series.Area', {
  73354. /* Begin Definitions */
  73355. extend: 'Ext.chart.series.Cartesian',
  73356. alias: 'series.area',
  73357. requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
  73358. /* End Definitions */
  73359. type: 'area',
  73360. // @private Area charts are alyways stacked
  73361. stacked: true,
  73362. /**
  73363. * @cfg {Object} style
  73364. * Append styling properties to this object for it to override theme properties.
  73365. */
  73366. style: {},
  73367. constructor: function(config) {
  73368. this.callParent(arguments);
  73369. var me = this,
  73370. surface = me.chart.surface,
  73371. i, l;
  73372. config.highlightCfg = Ext.Object.merge({}, {
  73373. lineWidth: 3,
  73374. stroke: '#55c',
  73375. opacity: 0.8,
  73376. color: '#f00'
  73377. }, config.highlightCfg);
  73378. Ext.apply(me, config, {
  73379. __excludes: []
  73380. });
  73381. if (me.highlight) {
  73382. me.highlightSprite = surface.add({
  73383. type: 'path',
  73384. path: ['M', 0, 0],
  73385. zIndex: 1000,
  73386. opacity: 0.3,
  73387. lineWidth: 5,
  73388. hidden: true,
  73389. stroke: '#444'
  73390. });
  73391. }
  73392. me.group = surface.getGroup(me.seriesId);
  73393. },
  73394. // @private Shrinks dataSets down to a smaller size
  73395. shrink: function(xValues, yValues, size) {
  73396. var len = xValues.length,
  73397. ratio = Math.floor(len / size),
  73398. i, j,
  73399. xSum = 0,
  73400. yCompLen = this.areas.length,
  73401. ySum = [],
  73402. xRes = [],
  73403. yRes = [];
  73404. //initialize array
  73405. for (j = 0; j < yCompLen; ++j) {
  73406. ySum[j] = 0;
  73407. }
  73408. for (i = 0; i < len; ++i) {
  73409. xSum += +xValues[i];
  73410. for (j = 0; j < yCompLen; ++j) {
  73411. ySum[j] += +yValues[i][j];
  73412. }
  73413. if (i % ratio == 0) {
  73414. //push averages
  73415. xRes.push(xSum/ratio);
  73416. for (j = 0; j < yCompLen; ++j) {
  73417. ySum[j] /= ratio;
  73418. }
  73419. yRes.push(ySum);
  73420. //reset sum accumulators
  73421. xSum = 0;
  73422. for (j = 0, ySum = []; j < yCompLen; ++j) {
  73423. ySum[j] = 0;
  73424. }
  73425. }
  73426. }
  73427. return {
  73428. x: xRes,
  73429. y: yRes
  73430. };
  73431. },
  73432. // @private Get chart and data boundaries
  73433. getBounds: function() {
  73434. var me = this,
  73435. chart = me.chart,
  73436. store = chart.getChartStore(),
  73437. data = store.data.items,
  73438. i, l, record,
  73439. areas = [].concat(me.yField),
  73440. areasLen = areas.length,
  73441. xValues = [],
  73442. yValues = [],
  73443. infinity = Infinity,
  73444. minX = infinity,
  73445. minY = infinity,
  73446. maxX = -infinity,
  73447. maxY = -infinity,
  73448. math = Math,
  73449. mmin = math.min,
  73450. mmax = math.max,
  73451. boundAxis = me.getAxesForXAndYFields(),
  73452. boundXAxis = boundAxis.xAxis,
  73453. boundYAxis = boundAxis.yAxis,
  73454. ends, allowDate,
  73455. bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem, axis, out;
  73456. me.setBBox();
  73457. bbox = me.bbox;
  73458. if (axis = chart.axes.get(boundXAxis)) {
  73459. if (axis.type === 'Time') {
  73460. allowDate = true;
  73461. }
  73462. ends = axis.applyData();
  73463. minX = ends.from;
  73464. maxX = ends.to;
  73465. }
  73466. if (axis = chart.axes.get(boundYAxis)) {
  73467. ends = axis.applyData();
  73468. minY = ends.from;
  73469. maxY = ends.to;
  73470. }
  73471. // If a field was specified without a corresponding axis, create one to get bounds
  73472. if (me.xField && !Ext.isNumber(minX)) {
  73473. axis = me.getMinMaxXValues();
  73474. allowDate = true;
  73475. minX = axis[0];
  73476. maxX = axis[1];
  73477. }
  73478. if (me.yField && !Ext.isNumber(minY)) {
  73479. axis = me.getMinMaxYValues();
  73480. minY = axis[0];
  73481. maxY = axis[1];
  73482. }
  73483. if (!Ext.isNumber(minY)) {
  73484. minY = 0;
  73485. }
  73486. if (!Ext.isNumber(maxY)) {
  73487. maxY = 0;
  73488. }
  73489. for (i = 0, l = data.length; i < l; i++) {
  73490. record = data[i];
  73491. xValue = record.get(me.xField);
  73492. yValue = [];
  73493. if (typeof xValue != 'number') {
  73494. if (allowDate) {
  73495. xValue = +xValue;
  73496. } else {
  73497. xValue = i;
  73498. }
  73499. }
  73500. xValues.push(xValue);
  73501. acumY = 0;
  73502. for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
  73503. // Excluded series
  73504. if (me.__excludes[areaIndex]) {
  73505. continue;
  73506. }
  73507. areaElem = record.get(areas[areaIndex]);
  73508. if (typeof areaElem == 'number') {
  73509. yValue.push(areaElem);
  73510. }
  73511. }
  73512. yValues.push(yValue);
  73513. }
  73514. xScale = bbox.width / ((maxX - minX) || 1);
  73515. yScale = bbox.height / ((maxY - minY) || 1);
  73516. ln = xValues.length;
  73517. if ((ln > bbox.width) && me.areas) {
  73518. sumValues = me.shrink(xValues, yValues, bbox.width);
  73519. xValues = sumValues.x;
  73520. yValues = sumValues.y;
  73521. }
  73522. return {
  73523. bbox: bbox,
  73524. minX: minX,
  73525. minY: minY,
  73526. xValues: xValues,
  73527. yValues: yValues,
  73528. xScale: xScale,
  73529. yScale: yScale,
  73530. areasLen: areasLen
  73531. };
  73532. },
  73533. // @private Build an array of paths for the chart
  73534. getPaths: function() {
  73535. var me = this,
  73536. chart = me.chart,
  73537. store = chart.getChartStore(),
  73538. first = true,
  73539. bounds = me.getBounds(),
  73540. bbox = bounds.bbox,
  73541. items = me.items = [],
  73542. componentPaths = [],
  73543. componentPath,
  73544. count = 0,
  73545. paths = [],
  73546. i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
  73547. ln = bounds.xValues.length;
  73548. // Start the path
  73549. for (i = 0; i < ln; i++) {
  73550. xValue = bounds.xValues[i];
  73551. yValue = bounds.yValues[i];
  73552. x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
  73553. acumY = 0;
  73554. count = 0;
  73555. for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
  73556. // Excluded series
  73557. if (me.__excludes[areaIndex]) {
  73558. continue;
  73559. }
  73560. if (!componentPaths[areaIndex]) {
  73561. componentPaths[areaIndex] = [];
  73562. }
  73563. areaElem = yValue[count];
  73564. acumY += areaElem;
  73565. y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
  73566. if (!paths[areaIndex]) {
  73567. paths[areaIndex] = ['M', x, y];
  73568. componentPaths[areaIndex].push(['L', x, y]);
  73569. } else {
  73570. paths[areaIndex].push('L', x, y);
  73571. componentPaths[areaIndex].push(['L', x, y]);
  73572. }
  73573. if (!items[areaIndex]) {
  73574. items[areaIndex] = {
  73575. pointsUp: [],
  73576. pointsDown: [],
  73577. series: me
  73578. };
  73579. }
  73580. items[areaIndex].pointsUp.push([x, y]);
  73581. count++;
  73582. }
  73583. }
  73584. // Close the paths
  73585. for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
  73586. // Excluded series
  73587. if (me.__excludes[areaIndex]) {
  73588. continue;
  73589. }
  73590. path = paths[areaIndex];
  73591. // Close bottom path to the axis
  73592. if (areaIndex == 0 || first) {
  73593. first = false;
  73594. path.push('L', x, bbox.y + bbox.height,
  73595. 'L', bbox.x, bbox.y + bbox.height,
  73596. 'Z');
  73597. }
  73598. // Close other paths to the one before them
  73599. else {
  73600. componentPath = componentPaths[prevAreaIndex];
  73601. componentPath.reverse();
  73602. path.push('L', x, componentPath[0][2]);
  73603. for (i = 0; i < ln; i++) {
  73604. path.push(componentPath[i][0],
  73605. componentPath[i][1],
  73606. componentPath[i][2]);
  73607. items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
  73608. }
  73609. path.push('L', bbox.x, path[2], 'Z');
  73610. }
  73611. prevAreaIndex = areaIndex;
  73612. }
  73613. return {
  73614. paths: paths,
  73615. areasLen: bounds.areasLen
  73616. };
  73617. },
  73618. /**
  73619. * Draws the series for the current chart.
  73620. */
  73621. drawSeries: function() {
  73622. var me = this,
  73623. chart = me.chart,
  73624. store = chart.getChartStore(),
  73625. surface = chart.surface,
  73626. animate = chart.animate,
  73627. group = me.group,
  73628. endLineStyle = Ext.apply(me.seriesStyle, me.style),
  73629. colorArrayStyle = me.colorArrayStyle,
  73630. colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
  73631. areaIndex, areaElem, paths, path, rendererAttributes;
  73632. me.unHighlightItem();
  73633. me.cleanHighlights();
  73634. if (!store || !store.getCount() || me.seriesIsHidden) {
  73635. me.hide();
  73636. me.items = [];
  73637. return;
  73638. }
  73639. paths = me.getPaths();
  73640. if (!me.areas) {
  73641. me.areas = [];
  73642. }
  73643. for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
  73644. // Excluded series
  73645. if (me.__excludes[areaIndex]) {
  73646. continue;
  73647. }
  73648. if (!me.areas[areaIndex]) {
  73649. me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
  73650. type: 'path',
  73651. group: group,
  73652. // 'clip-rect': me.clipBox,
  73653. path: paths.paths[areaIndex],
  73654. stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
  73655. fill: colorArrayStyle[areaIndex % colorArrayLength]
  73656. }, endLineStyle || {}));
  73657. }
  73658. areaElem = me.areas[areaIndex];
  73659. path = paths.paths[areaIndex];
  73660. if (animate) {
  73661. //Add renderer to line. There is not a unique record associated with this.
  73662. rendererAttributes = me.renderer(areaElem, false, {
  73663. path: path,
  73664. // 'clip-rect': me.clipBox,
  73665. fill: colorArrayStyle[areaIndex % colorArrayLength],
  73666. stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
  73667. }, areaIndex, store);
  73668. //fill should not be used here but when drawing the special fill path object
  73669. me.animation = me.onAnimate(areaElem, {
  73670. to: rendererAttributes
  73671. });
  73672. } else {
  73673. rendererAttributes = me.renderer(areaElem, false, {
  73674. path: path,
  73675. // 'clip-rect': me.clipBox,
  73676. hidden: false,
  73677. fill: colorArrayStyle[areaIndex % colorArrayLength],
  73678. stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
  73679. }, areaIndex, store);
  73680. me.areas[areaIndex].setAttributes(rendererAttributes, true);
  73681. }
  73682. }
  73683. me.renderLabels();
  73684. me.renderCallouts();
  73685. },
  73686. // @private
  73687. onAnimate: function(sprite, attr) {
  73688. sprite.show();
  73689. return this.callParent(arguments);
  73690. },
  73691. // @private
  73692. onCreateLabel: function(storeItem, item, i, display) {
  73693. var me = this,
  73694. group = me.labelsGroup,
  73695. config = me.label,
  73696. bbox = me.bbox,
  73697. endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
  73698. return me.chart.surface.add(Ext.apply({
  73699. 'type': 'text',
  73700. 'text-anchor': 'middle',
  73701. 'group': group,
  73702. 'x': item.point[0],
  73703. 'y': bbox.y + bbox.height / 2
  73704. }, endLabelStyle || {}));
  73705. },
  73706. // @private
  73707. onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
  73708. var me = this,
  73709. chart = me.chart,
  73710. resizing = chart.resizing,
  73711. config = me.label,
  73712. format = config.renderer,
  73713. field = config.field,
  73714. bbox = me.bbox,
  73715. x = item.point[0],
  73716. y = item.point[1],
  73717. bb, width, height;
  73718. label.setAttributes({
  73719. text: format(storeItem.get(field[index])),
  73720. hidden: true
  73721. }, true);
  73722. bb = label.getBBox();
  73723. width = bb.width / 2;
  73724. height = bb.height / 2;
  73725. x = x - width < bbox.x? bbox.x + width : x;
  73726. x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
  73727. y = y - height < bbox.y? bbox.y + height : y;
  73728. y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
  73729. if (me.chart.animate && !me.chart.resizing) {
  73730. label.show(true);
  73731. me.onAnimate(label, {
  73732. to: {
  73733. x: x,
  73734. y: y
  73735. }
  73736. });
  73737. } else {
  73738. label.setAttributes({
  73739. x: x,
  73740. y: y
  73741. }, true);
  73742. if (resizing) {
  73743. me.animation.on('afteranimate', function() {
  73744. label.show(true);
  73745. });
  73746. } else {
  73747. label.show(true);
  73748. }
  73749. }
  73750. },
  73751. // @private
  73752. onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
  73753. var me = this,
  73754. chart = me.chart,
  73755. surface = chart.surface,
  73756. resizing = chart.resizing,
  73757. config = me.callouts,
  73758. items = me.items,
  73759. prev = (i == 0) ? false : items[i -1].point,
  73760. next = (i == items.length -1) ? false : items[i +1].point,
  73761. cur = item.point,
  73762. dir, norm, normal, a, aprev, anext,
  73763. bbox = callout.label.getBBox(),
  73764. offsetFromViz = 30,
  73765. offsetToSide = 10,
  73766. offsetBox = 3,
  73767. boxx, boxy, boxw, boxh,
  73768. p, clipRect = me.clipRect,
  73769. x, y;
  73770. //get the right two points
  73771. if (!prev) {
  73772. prev = cur;
  73773. }
  73774. if (!next) {
  73775. next = cur;
  73776. }
  73777. a = (next[1] - prev[1]) / (next[0] - prev[0]);
  73778. aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
  73779. anext = (next[1] - cur[1]) / (next[0] - cur[0]);
  73780. norm = Math.sqrt(1 + a * a);
  73781. dir = [1 / norm, a / norm];
  73782. normal = [-dir[1], dir[0]];
  73783. //keep the label always on the outer part of the "elbow"
  73784. if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
  73785. normal[0] *= -1;
  73786. normal[1] *= -1;
  73787. } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
  73788. normal[0] *= -1;
  73789. normal[1] *= -1;
  73790. }
  73791. //position
  73792. x = cur[0] + normal[0] * offsetFromViz;
  73793. y = cur[1] + normal[1] * offsetFromViz;
  73794. //box position and dimensions
  73795. boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
  73796. boxy = y - bbox.height /2 - offsetBox;
  73797. boxw = bbox.width + 2 * offsetBox;
  73798. boxh = bbox.height + 2 * offsetBox;
  73799. //now check if we're out of bounds and invert the normal vector correspondingly
  73800. //this may add new overlaps between labels (but labels won't be out of bounds).
  73801. if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
  73802. normal[0] *= -1;
  73803. }
  73804. if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
  73805. normal[1] *= -1;
  73806. }
  73807. //update positions
  73808. x = cur[0] + normal[0] * offsetFromViz;
  73809. y = cur[1] + normal[1] * offsetFromViz;
  73810. //update box position and dimensions
  73811. boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
  73812. boxy = y - bbox.height /2 - offsetBox;
  73813. boxw = bbox.width + 2 * offsetBox;
  73814. boxh = bbox.height + 2 * offsetBox;
  73815. //set the line from the middle of the pie to the box.
  73816. callout.lines.setAttributes({
  73817. path: ["M", cur[0], cur[1], "L", x, y, "Z"]
  73818. }, true);
  73819. //set box position
  73820. callout.box.setAttributes({
  73821. x: boxx,
  73822. y: boxy,
  73823. width: boxw,
  73824. height: boxh
  73825. }, true);
  73826. //set text position
  73827. callout.label.setAttributes({
  73828. x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
  73829. y: y
  73830. }, true);
  73831. for (p in callout) {
  73832. callout[p].show(true);
  73833. }
  73834. },
  73835. isItemInPoint: function(x, y, item, i) {
  73836. var me = this,
  73837. pointsUp = item.pointsUp,
  73838. pointsDown = item.pointsDown,
  73839. abs = Math.abs,
  73840. distChanged = false,
  73841. last = false,
  73842. dist = Infinity, p, pln, point;
  73843. for (p = 0, pln = pointsUp.length; p < pln; p++) {
  73844. point = [pointsUp[p][0], pointsUp[p][1]];
  73845. distChanged = false;
  73846. last = p == pln -1;
  73847. if (dist > abs(x - point[0])) {
  73848. dist = abs(x - point[0]);
  73849. distChanged = true;
  73850. if (last) {
  73851. ++p;
  73852. }
  73853. }
  73854. if (!distChanged || (distChanged && last)) {
  73855. point = pointsUp[p -1];
  73856. if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
  73857. item.storeIndex = p -1;
  73858. item.storeField = me.yField[i];
  73859. item.storeItem = me.chart.store.getAt(p -1);
  73860. item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
  73861. return true;
  73862. } else {
  73863. break;
  73864. }
  73865. }
  73866. }
  73867. return false;
  73868. },
  73869. /**
  73870. * Highlight this entire series.
  73871. * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
  73872. */
  73873. highlightSeries: function() {
  73874. var area, to, fillColor;
  73875. if (this._index !== undefined) {
  73876. area = this.areas[this._index];
  73877. if (area.__highlightAnim) {
  73878. area.__highlightAnim.paused = true;
  73879. }
  73880. area.__highlighted = true;
  73881. area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
  73882. area.__prevFill = area.__prevFill || area.attr.fill;
  73883. area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
  73884. fillColor = Ext.draw.Color.fromString(area.__prevFill);
  73885. to = {
  73886. lineWidth: (area.__prevLineWidth || 0) + 2
  73887. };
  73888. if (fillColor) {
  73889. to.fill = fillColor.getLighter(0.2).toString();
  73890. }
  73891. else {
  73892. to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
  73893. }
  73894. if (this.chart.animate) {
  73895. area.__highlightAnim = new Ext.fx.Anim(Ext.apply({
  73896. target: area,
  73897. to: to
  73898. }, this.chart.animate));
  73899. }
  73900. else {
  73901. area.setAttributes(to, true);
  73902. }
  73903. }
  73904. },
  73905. /**
  73906. * UnHighlight this entire series.
  73907. * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
  73908. */
  73909. unHighlightSeries: function() {
  73910. var area;
  73911. if (this._index !== undefined) {
  73912. area = this.areas[this._index];
  73913. if (area.__highlightAnim) {
  73914. area.__highlightAnim.paused = true;
  73915. }
  73916. if (area.__highlighted) {
  73917. area.__highlighted = false;
  73918. area.__highlightAnim = new Ext.fx.Anim({
  73919. target: area,
  73920. to: {
  73921. fill: area.__prevFill,
  73922. opacity: area.__prevOpacity,
  73923. lineWidth: area.__prevLineWidth
  73924. }
  73925. });
  73926. }
  73927. }
  73928. },
  73929. /**
  73930. * Highlight the specified item. If no item is provided the whole series will be highlighted.
  73931. * @param item {Object} Info about the item; same format as returned by #getItemForPoint
  73932. */
  73933. highlightItem: function(item) {
  73934. var me = this,
  73935. points, path;
  73936. if (!item) {
  73937. this.highlightSeries();
  73938. return;
  73939. }
  73940. points = item._points;
  73941. path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
  73942. : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
  73943. me.highlightSprite.setAttributes({
  73944. path: path,
  73945. hidden: false
  73946. }, true);
  73947. },
  73948. /**
  73949. * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
  73950. * @param {Object} item Info about the item; same format as returned by #getItemForPoint
  73951. */
  73952. unHighlightItem: function(item) {
  73953. if (!item) {
  73954. this.unHighlightSeries();
  73955. }
  73956. if (this.highlightSprite) {
  73957. this.highlightSprite.hide(true);
  73958. }
  73959. },
  73960. // @private
  73961. hideAll: function(index) {
  73962. var me = this;
  73963. index = (isNaN(me._index) ? index : me._index) || 0;
  73964. me.__excludes[index] = true;
  73965. me.areas[index].hide(true);
  73966. me.redraw();
  73967. },
  73968. // @private
  73969. showAll: function(index) {
  73970. var me = this;
  73971. index = (isNaN(me._index) ? index : me._index) || 0;
  73972. me.__excludes[index] = false;
  73973. me.areas[index].show(true);
  73974. me.redraw();
  73975. },
  73976. redraw: function() {
  73977. //store previous configuration for the legend
  73978. //and set it to false so we don't
  73979. //re-build label elements if not necessary.
  73980. var me = this,
  73981. prevLegendConfig;
  73982. prevLegendConfig = me.chart.legend.rebuild;
  73983. me.chart.legend.rebuild = false;
  73984. me.chart.redraw();
  73985. me.chart.legend.rebuild = prevLegendConfig;
  73986. },
  73987. hide: function() {
  73988. if (this.areas) {
  73989. var me = this,
  73990. areas = me.areas,
  73991. i, j, l, ln, shadows;
  73992. if (areas && areas.length) {
  73993. for (i = 0, ln = areas.length; i < ln; ++i) {
  73994. if (areas[i]) {
  73995. areas[i].hide(true);
  73996. }
  73997. }
  73998. me.hideLabels();
  73999. }
  74000. }
  74001. },
  74002. /**
  74003. * Returns the color of the series (to be displayed as color for the series legend item).
  74004. * @param {Object} item Info about the item; same format as returned by #getItemForPoint
  74005. */
  74006. getLegendColor: function(index) {
  74007. var me = this;
  74008. return me.colorArrayStyle[index % me.colorArrayStyle.length];
  74009. }
  74010. });
  74011. /**
  74012. * Creates a Bar Chart. A Bar Chart is a useful visualization technique to display quantitative information for
  74013. * different categories that can show some progression (or regression) in the dataset. As with all other series, the Bar
  74014. * Series must be appended in the *series* Chart array configuration. See the Chart documentation for more information.
  74015. * A typical configuration object for the bar series could be:
  74016. *
  74017. * @example
  74018. * var store = Ext.create('Ext.data.JsonStore', {
  74019. * fields: ['name', 'data'],
  74020. * data: [
  74021. * { 'name': 'metric one', 'data':10 },
  74022. * { 'name': 'metric two', 'data': 7 },
  74023. * { 'name': 'metric three', 'data': 5 },
  74024. * { 'name': 'metric four', 'data': 2 },
  74025. * { 'name': 'metric five', 'data':27 }
  74026. * ]
  74027. * });
  74028. *
  74029. * Ext.create('Ext.chart.Chart', {
  74030. * renderTo: Ext.getBody(),
  74031. * width: 500,
  74032. * height: 300,
  74033. * animate: true,
  74034. * store: store,
  74035. * axes: [{
  74036. * type: 'Numeric',
  74037. * position: 'bottom',
  74038. * fields: ['data'],
  74039. * label: {
  74040. * renderer: Ext.util.Format.numberRenderer('0,0')
  74041. * },
  74042. * title: 'Sample Values',
  74043. * grid: true,
  74044. * minimum: 0
  74045. * }, {
  74046. * type: 'Category',
  74047. * position: 'left',
  74048. * fields: ['name'],
  74049. * title: 'Sample Metrics'
  74050. * }],
  74051. * series: [{
  74052. * type: 'bar',
  74053. * axis: 'bottom',
  74054. * highlight: true,
  74055. * tips: {
  74056. * trackMouse: true,
  74057. * width: 140,
  74058. * height: 28,
  74059. * renderer: function(storeItem, item) {
  74060. * this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data') + ' views');
  74061. * }
  74062. * },
  74063. * label: {
  74064. * display: 'insideEnd',
  74065. * field: 'data',
  74066. * renderer: Ext.util.Format.numberRenderer('0'),
  74067. * orientation: 'horizontal',
  74068. * color: '#333',
  74069. * 'text-anchor': 'middle'
  74070. * },
  74071. * xField: 'name',
  74072. * yField: 'data'
  74073. * }]
  74074. * });
  74075. *
  74076. * In this configuration we set `bar` as the series type, bind the values of the bar to the bottom axis and set the
  74077. * xField or category field to the `name` parameter of the store. We also set `highlight` to true which enables smooth
  74078. * animations when bars are hovered. We also set some configuration for the bar labels to be displayed inside the bar,
  74079. * to display the information found in the `data1` property of each element store, to render a formated text with the
  74080. * `Ext.util.Format` we pass in, to have an `horizontal` orientation (as opposed to a vertical one) and we also set
  74081. * other styles like `color`, `text-anchor`, etc.
  74082. */
  74083. Ext.define('Ext.chart.series.Bar', {
  74084. /* Begin Definitions */
  74085. extend: 'Ext.chart.series.Cartesian',
  74086. alternateClassName: ['Ext.chart.BarSeries', 'Ext.chart.BarChart', 'Ext.chart.StackedBarChart'],
  74087. requires: ['Ext.chart.axis.Axis', 'Ext.fx.Anim'],
  74088. /* End Definitions */
  74089. type: 'bar',
  74090. alias: 'series.bar',
  74091. /**
  74092. * @cfg {Boolean} column Whether to set the visualization as column chart or horizontal bar chart.
  74093. */
  74094. column: false,
  74095. /**
  74096. * @cfg style Style properties that will override the theming series styles.
  74097. */
  74098. style: {},
  74099. /**
  74100. * @cfg {Number} gutter The gutter space between single bars, as a percentage of the bar width
  74101. */
  74102. gutter: 38.2,
  74103. /**
  74104. * @cfg {Number} groupGutter The gutter space between groups of bars, as a percentage of the bar width
  74105. */
  74106. groupGutter: 38.2,
  74107. /**
  74108. * @cfg {Number} xPadding Padding between the left/right axes and the bars
  74109. */
  74110. xPadding: 0,
  74111. /**
  74112. * @cfg {Number} yPadding Padding between the top/bottom axes and the bars
  74113. */
  74114. yPadding: 10,
  74115. constructor: function(config) {
  74116. this.callParent(arguments);
  74117. var me = this,
  74118. surface = me.chart.surface,
  74119. shadow = me.chart.shadow,
  74120. i, l;
  74121. config.highlightCfg = Ext.Object.merge({
  74122. lineWidth: 3,
  74123. stroke: '#55c',
  74124. opacity: 0.8,
  74125. color: '#f00'
  74126. }, config.highlightCfg);
  74127. Ext.apply(me, config, {
  74128. shadowAttributes: [{
  74129. "stroke-width": 6,
  74130. "stroke-opacity": 0.05,
  74131. stroke: 'rgb(200, 200, 200)',
  74132. translate: {
  74133. x: 1.2,
  74134. y: 1.2
  74135. }
  74136. }, {
  74137. "stroke-width": 4,
  74138. "stroke-opacity": 0.1,
  74139. stroke: 'rgb(150, 150, 150)',
  74140. translate: {
  74141. x: 0.9,
  74142. y: 0.9
  74143. }
  74144. }, {
  74145. "stroke-width": 2,
  74146. "stroke-opacity": 0.15,
  74147. stroke: 'rgb(100, 100, 100)',
  74148. translate: {
  74149. x: 0.6,
  74150. y: 0.6
  74151. }
  74152. }]
  74153. });
  74154. me.group = surface.getGroup(me.seriesId + '-bars');
  74155. if (shadow) {
  74156. for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
  74157. me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
  74158. }
  74159. }
  74160. },
  74161. // @private sets the bar girth.
  74162. getBarGirth: function() {
  74163. var me = this,
  74164. store = me.chart.getChartStore(),
  74165. column = me.column,
  74166. ln = store.getCount(),
  74167. gutter = me.gutter / 100;
  74168. return (me.chart.chartBBox[column ? 'width' : 'height'] - me[column ? 'xPadding' : 'yPadding'] * 2) / (ln * (gutter + 1) - gutter);
  74169. },
  74170. // @private returns the gutters.
  74171. getGutters: function() {
  74172. var me = this,
  74173. column = me.column,
  74174. gutter = Math.ceil(me[column ? 'xPadding' : 'yPadding'] + me.getBarGirth() / 2);
  74175. return me.column ? [gutter, 0] : [0, gutter];
  74176. },
  74177. // @private Get chart and data boundaries
  74178. getBounds: function() {
  74179. var me = this,
  74180. chart = me.chart,
  74181. store = chart.getChartStore(),
  74182. data = store.data.items,
  74183. i, ln, record,
  74184. bars = [].concat(me.yField),
  74185. barsLen = bars.length,
  74186. groupBarsLen = barsLen,
  74187. groupGutter = me.groupGutter / 100,
  74188. column = me.column,
  74189. xPadding = me.xPadding,
  74190. yPadding = me.yPadding,
  74191. stacked = me.stacked,
  74192. barWidth = me.getBarGirth(),
  74193. barWidthProperty = column ? 'width' : 'height',
  74194. math = Math,
  74195. mmin = math.min,
  74196. mmax = math.max,
  74197. mabs = math.abs,
  74198. boundAxes = me.getAxesForXAndYFields(),
  74199. boundYAxis = boundAxes.yAxis,
  74200. ends, shrunkBarWidth, groupBarWidth, bbox, minY, maxY, axis, out,
  74201. scale, zero, total, rec, j, plus, minus;
  74202. me.setBBox(true);
  74203. bbox = me.bbox;
  74204. //Skip excluded series
  74205. if (me.__excludes) {
  74206. for (j = 0, total = me.__excludes.length; j < total; j++) {
  74207. if (me.__excludes[j]) {
  74208. groupBarsLen--;
  74209. }
  74210. }
  74211. }
  74212. axis = chart.axes.get(boundYAxis);
  74213. if (axis) {
  74214. ends = axis.applyData();
  74215. minY = ends.from;
  74216. maxY = ends.to;
  74217. }
  74218. if (me.yField && !Ext.isNumber(minY)) {
  74219. out = me.getMinMaxYValues();
  74220. minY = out[0];
  74221. maxY = out[1];
  74222. }
  74223. if (!Ext.isNumber(minY)) {
  74224. minY = 0;
  74225. }
  74226. if (!Ext.isNumber(maxY)) {
  74227. maxY = 0;
  74228. }
  74229. scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (maxY - minY);
  74230. shrunkBarWidth = barWidth;
  74231. groupBarWidth = (barWidth / ((stacked ? 1 : groupBarsLen) * (groupGutter + 1) - groupGutter));
  74232. if (barWidthProperty in me.style) {
  74233. groupBarWidth = mmin(groupBarWidth, me.style[barWidthProperty]);
  74234. shrunkBarWidth = groupBarWidth * ((stacked ? 1 : groupBarsLen) * (groupGutter + 1) - groupGutter);
  74235. }
  74236. zero = (column) ? bbox.y + bbox.height - yPadding : bbox.x + xPadding;
  74237. if (stacked) {
  74238. total = [[], []];
  74239. for (i = 0, ln = data.length; i < ln; i++) {
  74240. record = data[i];
  74241. total[0][i] = total[0][i] || 0;
  74242. total[1][i] = total[1][i] || 0;
  74243. for (j = 0; j < barsLen; j++) {
  74244. if (me.__excludes && me.__excludes[j]) {
  74245. continue;
  74246. }
  74247. rec = record.get(bars[j]);
  74248. total[+(rec > 0)][i] += mabs(rec);
  74249. }
  74250. }
  74251. total[+(maxY > 0)].push(mabs(maxY));
  74252. total[+(minY > 0)].push(mabs(minY));
  74253. minus = mmax.apply(math, total[0]);
  74254. plus = mmax.apply(math, total[1]);
  74255. scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (plus + minus);
  74256. zero = zero + minus * scale * (column ? -1 : 1);
  74257. }
  74258. else if (minY / maxY < 0) {
  74259. zero = zero - minY * scale * (column ? -1 : 1);
  74260. }
  74261. return {
  74262. bars: bars,
  74263. bbox: bbox,
  74264. shrunkBarWidth: shrunkBarWidth,
  74265. barsLen: barsLen,
  74266. groupBarsLen: groupBarsLen,
  74267. barWidth: barWidth,
  74268. groupBarWidth: groupBarWidth,
  74269. scale: scale,
  74270. zero: zero,
  74271. xPadding: xPadding,
  74272. yPadding: yPadding,
  74273. signed: minY / maxY < 0,
  74274. minY: minY,
  74275. maxY: maxY
  74276. };
  74277. },
  74278. // @private Build an array of paths for the chart
  74279. getPaths: function() {
  74280. var me = this,
  74281. chart = me.chart,
  74282. store = chart.getChartStore(),
  74283. data = store.data.items,
  74284. i, total, record,
  74285. bounds = me.bounds = me.getBounds(),
  74286. items = me.items = [],
  74287. yFields = me.yField,
  74288. gutter = me.gutter / 100,
  74289. groupGutter = me.groupGutter / 100,
  74290. animate = chart.animate,
  74291. column = me.column,
  74292. group = me.group,
  74293. enableShadows = chart.shadow,
  74294. shadowGroups = me.shadowGroups,
  74295. shadowAttributes = me.shadowAttributes,
  74296. shadowGroupsLn = shadowGroups.length,
  74297. bbox = bounds.bbox,
  74298. barWidth = bounds.barWidth,
  74299. shrunkBarWidth = bounds.shrunkBarWidth,
  74300. xPadding = me.xPadding,
  74301. yPadding = me.yPadding,
  74302. stacked = me.stacked,
  74303. barsLen = bounds.barsLen,
  74304. colors = me.colorArrayStyle,
  74305. colorLength = colors && colors.length || 0,
  74306. math = Math,
  74307. mmax = math.max,
  74308. mmin = math.min,
  74309. mabs = math.abs,
  74310. j, yValue, height, totalDim, totalNegDim, bottom, top, hasShadow, barAttr, attrs, counter,
  74311. shadowIndex, shadow, sprite, offset, floorY;
  74312. for (i = 0, total = data.length; i < total; i++) {
  74313. record = data[i];
  74314. bottom = bounds.zero;
  74315. top = bounds.zero;
  74316. totalDim = 0;
  74317. totalNegDim = 0;
  74318. hasShadow = false;
  74319. for (j = 0, counter = 0; j < barsLen; j++) {
  74320. // Excluded series
  74321. if (me.__excludes && me.__excludes[j]) {
  74322. continue;
  74323. }
  74324. yValue = record.get(bounds.bars[j]);
  74325. height = Math.round((yValue - mmax(bounds.minY, 0)) * bounds.scale);
  74326. barAttr = {
  74327. fill: colors[(barsLen > 1 ? j : 0) % colorLength]
  74328. };
  74329. if (column) {
  74330. Ext.apply(barAttr, {
  74331. height: height,
  74332. width: mmax(bounds.groupBarWidth, 0),
  74333. x: (bbox.x + xPadding + (barWidth - shrunkBarWidth) * 0.5 + i * barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked),
  74334. y: bottom - height
  74335. });
  74336. }
  74337. else {
  74338. // draw in reverse order
  74339. offset = (total - 1) - i;
  74340. Ext.apply(barAttr, {
  74341. height: mmax(bounds.groupBarWidth, 0),
  74342. width: height + (bottom == bounds.zero),
  74343. x: bottom + (bottom != bounds.zero),
  74344. y: (bbox.y + yPadding + (barWidth - shrunkBarWidth) * 0.5 + offset * barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked + 1)
  74345. });
  74346. }
  74347. if (height < 0) {
  74348. if (column) {
  74349. barAttr.y = top;
  74350. barAttr.height = mabs(height);
  74351. } else {
  74352. barAttr.x = top + height;
  74353. barAttr.width = mabs(height);
  74354. }
  74355. }
  74356. if (stacked) {
  74357. if (height < 0) {
  74358. top += height * (column ? -1 : 1);
  74359. } else {
  74360. bottom += height * (column ? -1 : 1);
  74361. }
  74362. totalDim += mabs(height);
  74363. if (height < 0) {
  74364. totalNegDim += mabs(height);
  74365. }
  74366. }
  74367. barAttr.x = Math.floor(barAttr.x) + 1;
  74368. floorY = Math.floor(barAttr.y);
  74369. if (!Ext.isIE9 && barAttr.y > floorY) {
  74370. floorY--;
  74371. }
  74372. barAttr.y = floorY;
  74373. barAttr.width = Math.floor(barAttr.width);
  74374. barAttr.height = Math.floor(barAttr.height);
  74375. items.push({
  74376. series: me,
  74377. yField: yFields[j],
  74378. storeItem: record,
  74379. value: [record.get(me.xField), yValue],
  74380. attr: barAttr,
  74381. point: column ? [barAttr.x + barAttr.width / 2, yValue >= 0 ? barAttr.y : barAttr.y + barAttr.height] :
  74382. [yValue >= 0 ? barAttr.x + barAttr.width : barAttr.x, barAttr.y + barAttr.height / 2]
  74383. });
  74384. // When resizing, reset before animating
  74385. if (animate && chart.resizing) {
  74386. attrs = column ? {
  74387. x: barAttr.x,
  74388. y: bounds.zero,
  74389. width: barAttr.width,
  74390. height: 0
  74391. } : {
  74392. x: bounds.zero,
  74393. y: barAttr.y,
  74394. width: 0,
  74395. height: barAttr.height
  74396. };
  74397. if (enableShadows && (stacked && !hasShadow || !stacked)) {
  74398. hasShadow = true;
  74399. //update shadows
  74400. for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
  74401. shadow = shadowGroups[shadowIndex].getAt(stacked ? i : (i * barsLen + j));
  74402. if (shadow) {
  74403. shadow.setAttributes(attrs, true);
  74404. }
  74405. }
  74406. }
  74407. //update sprite position and width/height
  74408. sprite = group.getAt(i * barsLen + j);
  74409. if (sprite) {
  74410. sprite.setAttributes(attrs, true);
  74411. }
  74412. }
  74413. counter++;
  74414. }
  74415. if (stacked && items.length) {
  74416. items[i * counter].totalDim = totalDim;
  74417. items[i * counter].totalNegDim = totalNegDim;
  74418. }
  74419. }
  74420. if (stacked && counter == 0) {
  74421. // Remove ghost shadow ref: EXTJSIV-5982
  74422. for (i = 0, total = data.length; i < total; i++) {
  74423. for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
  74424. shadow = shadowGroups[shadowIndex].getAt(i);
  74425. if (shadow) {
  74426. shadow.hide(true);
  74427. }
  74428. }
  74429. }
  74430. }
  74431. },
  74432. // @private render/setAttributes on the shadows
  74433. renderShadows: function(i, barAttr, baseAttrs, bounds) {
  74434. var me = this,
  74435. chart = me.chart,
  74436. surface = chart.surface,
  74437. animate = chart.animate,
  74438. stacked = me.stacked,
  74439. shadowGroups = me.shadowGroups,
  74440. shadowAttributes = me.shadowAttributes,
  74441. shadowGroupsLn = shadowGroups.length,
  74442. store = chart.getChartStore(),
  74443. column = me.column,
  74444. items = me.items,
  74445. shadows = [],
  74446. zero = bounds.zero,
  74447. shadowIndex, shadowBarAttr, shadow, totalDim, totalNegDim, j, rendererAttributes;
  74448. if ((stacked && (i % bounds.groupBarsLen === 0)) || !stacked) {
  74449. j = i / bounds.groupBarsLen;
  74450. //create shadows
  74451. for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
  74452. shadowBarAttr = Ext.apply({}, shadowAttributes[shadowIndex]);
  74453. shadow = shadowGroups[shadowIndex].getAt(stacked ? j : i);
  74454. Ext.copyTo(shadowBarAttr, barAttr, 'x,y,width,height');
  74455. if (!shadow) {
  74456. shadow = surface.add(Ext.apply({
  74457. type: 'rect',
  74458. group: shadowGroups[shadowIndex]
  74459. }, Ext.apply({}, baseAttrs, shadowBarAttr)));
  74460. }
  74461. if (stacked) {
  74462. totalDim = items[i].totalDim;
  74463. totalNegDim = items[i].totalNegDim;
  74464. if (column) {
  74465. shadowBarAttr.y = zero + totalNegDim - totalDim - 1;
  74466. shadowBarAttr.height = totalDim;
  74467. }
  74468. else {
  74469. shadowBarAttr.x = zero - totalNegDim;
  74470. shadowBarAttr.width = totalDim;
  74471. }
  74472. }
  74473. rendererAttributes = me.renderer(shadow, store.getAt(j), shadowBarAttr, i, store);
  74474. rendererAttributes.hidden = !!barAttr.hidden;
  74475. if (animate) {
  74476. me.onAnimate(shadow, { to: rendererAttributes });
  74477. }
  74478. else {
  74479. shadow.setAttributes(rendererAttributes, true);
  74480. }
  74481. shadows.push(shadow);
  74482. }
  74483. }
  74484. return shadows;
  74485. },
  74486. /**
  74487. * Draws the series for the current chart.
  74488. */
  74489. drawSeries: function() {
  74490. var me = this,
  74491. chart = me.chart,
  74492. store = chart.getChartStore(),
  74493. surface = chart.surface,
  74494. animate = chart.animate,
  74495. stacked = me.stacked,
  74496. column = me.column,
  74497. enableShadows = chart.shadow,
  74498. shadowGroups = me.shadowGroups,
  74499. shadowGroupsLn = shadowGroups.length,
  74500. group = me.group,
  74501. seriesStyle = me.seriesStyle,
  74502. items, ln, i, j, baseAttrs, sprite, rendererAttributes, shadowIndex, shadowGroup,
  74503. bounds, endSeriesStyle, barAttr, attrs, anim;
  74504. if (!store || !store.getCount() || me.seriesIsHidden) {
  74505. me.hide();
  74506. me.items = [];
  74507. return;
  74508. }
  74509. //fill colors are taken from the colors array.
  74510. endSeriesStyle = Ext.apply({}, this.style, seriesStyle);
  74511. delete endSeriesStyle.fill;
  74512. delete endSeriesStyle.x;
  74513. delete endSeriesStyle.y;
  74514. delete endSeriesStyle.width;
  74515. delete endSeriesStyle.height;
  74516. me.unHighlightItem();
  74517. me.cleanHighlights();
  74518. me.getPaths();
  74519. bounds = me.bounds;
  74520. items = me.items;
  74521. baseAttrs = column ? {
  74522. y: bounds.zero,
  74523. height: 0
  74524. } : {
  74525. x: bounds.zero,
  74526. width: 0
  74527. };
  74528. ln = items.length;
  74529. // Create new or reuse sprites and animate/display
  74530. for (i = 0; i < ln; i++) {
  74531. sprite = group.getAt(i);
  74532. barAttr = items[i].attr;
  74533. if (enableShadows) {
  74534. items[i].shadows = me.renderShadows(i, barAttr, baseAttrs, bounds);
  74535. }
  74536. // Create a new sprite if needed (no height)
  74537. if (!sprite) {
  74538. attrs = Ext.apply({}, baseAttrs, barAttr);
  74539. attrs = Ext.apply(attrs, endSeriesStyle || {});
  74540. sprite = surface.add(Ext.apply({}, {
  74541. type: 'rect',
  74542. group: group
  74543. }, attrs));
  74544. }
  74545. if (animate) {
  74546. rendererAttributes = me.renderer(sprite, store.getAt(i), barAttr, i, store);
  74547. sprite._to = rendererAttributes;
  74548. anim = me.onAnimate(sprite, { to: Ext.apply(rendererAttributes, endSeriesStyle) });
  74549. if (enableShadows && stacked && (i % bounds.barsLen === 0)) {
  74550. j = i / bounds.barsLen;
  74551. for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
  74552. anim.on('afteranimate', function() {
  74553. this.show(true);
  74554. }, shadowGroups[shadowIndex].getAt(j));
  74555. }
  74556. }
  74557. }
  74558. else {
  74559. rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(barAttr, { hidden: false }), i, store);
  74560. sprite.setAttributes(Ext.apply(rendererAttributes, endSeriesStyle), true);
  74561. }
  74562. items[i].sprite = sprite;
  74563. }
  74564. // Hide unused sprites
  74565. ln = group.getCount();
  74566. for (j = i; j < ln; j++) {
  74567. group.getAt(j).hide(true);
  74568. }
  74569. if (me.stacked) {
  74570. // If stacked, we have only store.getCount() shadows.
  74571. i = store.getCount();
  74572. }
  74573. // Hide unused shadows
  74574. if (enableShadows) {
  74575. for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
  74576. shadowGroup = shadowGroups[shadowIndex];
  74577. ln = shadowGroup.getCount();
  74578. for (j = i; j < ln; j++) {
  74579. shadowGroup.getAt(j).hide(true);
  74580. }
  74581. }
  74582. }
  74583. me.renderLabels();
  74584. },
  74585. // @private handled when creating a label.
  74586. onCreateLabel: function(storeItem, item, i, display) {
  74587. var me = this,
  74588. surface = me.chart.surface,
  74589. group = me.labelsGroup,
  74590. config = me.label,
  74591. endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle || {}),
  74592. sprite;
  74593. return surface.add(Ext.apply({
  74594. type: 'text',
  74595. group: group
  74596. }, endLabelStyle || {}));
  74597. },
  74598. // @private callback used when placing a label.
  74599. onPlaceLabel: function(label, storeItem, item, i, display, animate, j, index) {
  74600. // Determine the label's final position. Starts with the configured preferred value but
  74601. // may get flipped from inside to outside or vice-versa depending on space.
  74602. var me = this,
  74603. opt = me.bounds,
  74604. groupBarWidth = opt.groupBarWidth,
  74605. column = me.column,
  74606. chart = me.chart,
  74607. chartBBox = chart.chartBBox,
  74608. resizing = chart.resizing,
  74609. xValue = item.value[0],
  74610. yValue = item.value[1],
  74611. attr = item.attr,
  74612. config = me.label,
  74613. rotate = config.orientation == 'vertical',
  74614. field = [].concat(config.field),
  74615. format = config.renderer,
  74616. text = format(storeItem.get(field[index])),
  74617. size = me.getLabelSize(text),
  74618. width = size.width,
  74619. height = size.height,
  74620. zero = opt.zero,
  74621. outside = 'outside',
  74622. insideStart = 'insideStart',
  74623. insideEnd = 'insideEnd',
  74624. offsetX = 10,
  74625. offsetY = 6,
  74626. signed = opt.signed,
  74627. x, y, finalAttr;
  74628. label.setAttributes({
  74629. text: text
  74630. });
  74631. label.isOutside = false;
  74632. if (column) {
  74633. if (display == outside) {
  74634. if (height + offsetY + attr.height > (yValue >= 0 ? zero - chartBBox.y : chartBBox.y + chartBBox.height - zero)) {
  74635. display = insideEnd;
  74636. }
  74637. } else {
  74638. if (height + offsetY > attr.height) {
  74639. display = outside;
  74640. label.isOutside = true;
  74641. }
  74642. }
  74643. x = attr.x + groupBarWidth / 2;
  74644. y = display == insideStart ?
  74645. (zero + ((height / 2 + 3) * (yValue >= 0 ? -1 : 1))) :
  74646. (yValue >= 0 ? (attr.y + ((height / 2 + 3) * (display == outside ? -1 : 1))) :
  74647. (attr.y + attr.height + ((height / 2 + 3) * (display === outside ? 1 : -1))));
  74648. }
  74649. else {
  74650. if (display == outside) {
  74651. if (width + offsetX + attr.width > (yValue >= 0 ? chartBBox.x + chartBBox.width - zero : zero - chartBBox.x)) {
  74652. display = insideEnd;
  74653. }
  74654. }
  74655. else {
  74656. if (width + offsetX > attr.width) {
  74657. display = outside;
  74658. label.isOutside = true;
  74659. }
  74660. }
  74661. x = display == insideStart ?
  74662. (zero + ((width / 2 + 5) * (yValue >= 0 ? 1 : -1))) :
  74663. (yValue >= 0 ? (attr.x + attr.width + ((width / 2 + 5) * (display === outside ? 1 : -1))) :
  74664. (attr.x + ((width / 2 + 5) * (display === outside ? -1 : 1))));
  74665. y = attr.y + groupBarWidth / 2;
  74666. }
  74667. //set position
  74668. finalAttr = {
  74669. x: x,
  74670. y: y
  74671. };
  74672. //rotate
  74673. if (rotate) {
  74674. finalAttr.rotate = {
  74675. x: x,
  74676. y: y,
  74677. degrees: 270
  74678. };
  74679. }
  74680. //check for resizing
  74681. if (animate && resizing) {
  74682. if (column) {
  74683. x = attr.x + attr.width / 2;
  74684. y = zero;
  74685. } else {
  74686. x = zero;
  74687. y = attr.y + attr.height / 2;
  74688. }
  74689. label.setAttributes({
  74690. x: x,
  74691. y: y
  74692. }, true);
  74693. if (rotate) {
  74694. label.setAttributes({
  74695. rotate: {
  74696. x: x,
  74697. y: y,
  74698. degrees: 270
  74699. }
  74700. }, true);
  74701. }
  74702. }
  74703. //handle animation
  74704. if (animate) {
  74705. me.onAnimate(label, { to: finalAttr });
  74706. }
  74707. else {
  74708. label.setAttributes(Ext.apply(finalAttr, {
  74709. hidden: false
  74710. }), true);
  74711. }
  74712. },
  74713. /* @private
  74714. * Gets the dimensions of a given bar label. Uses a single hidden sprite to avoid
  74715. * changing visible sprites.
  74716. * @param value
  74717. */
  74718. getLabelSize: function(value) {
  74719. var tester = this.testerLabel,
  74720. config = this.label,
  74721. endLabelStyle = Ext.apply({}, config, this.seriesLabelStyle || {}),
  74722. rotated = config.orientation === 'vertical',
  74723. bbox, w, h,
  74724. undef;
  74725. if (!tester) {
  74726. tester = this.testerLabel = this.chart.surface.add(Ext.apply({
  74727. type: 'text',
  74728. opacity: 0
  74729. }, endLabelStyle));
  74730. }
  74731. tester.setAttributes({
  74732. text: value
  74733. }, true);
  74734. // Flip the width/height if rotated, as getBBox returns the pre-rotated dimensions
  74735. bbox = tester.getBBox();
  74736. w = bbox.width;
  74737. h = bbox.height;
  74738. return {
  74739. width: rotated ? h : w,
  74740. height: rotated ? w : h
  74741. };
  74742. },
  74743. // @private used to animate label, markers and other sprites.
  74744. onAnimate: function(sprite, attr) {
  74745. sprite.show();
  74746. return this.callParent(arguments);
  74747. },
  74748. isItemInPoint: function(x, y, item) {
  74749. var bbox = item.sprite.getBBox();
  74750. return bbox.x <= x && bbox.y <= y
  74751. && (bbox.x + bbox.width) >= x
  74752. && (bbox.y + bbox.height) >= y;
  74753. },
  74754. // @private hide all markers
  74755. hideAll: function(index) {
  74756. var axes = this.chart.axes,
  74757. axesItems = axes.items,
  74758. ln = axesItems.length,
  74759. i = 0;
  74760. index = (isNaN(this._index) ? index : this._index) || 0;
  74761. if (!this.__excludes) {
  74762. this.__excludes = [];
  74763. }
  74764. this.__excludes[index] = true;
  74765. this.drawSeries();
  74766. for (i; i < ln; i++) {
  74767. axesItems[i].drawAxis();
  74768. }
  74769. },
  74770. // @private show all markers
  74771. showAll: function(index) {
  74772. var axes = this.chart.axes,
  74773. axesItems = axes.items,
  74774. ln = axesItems.length,
  74775. i = 0;
  74776. index = (isNaN(this._index) ? index : this._index) || 0;
  74777. if (!this.__excludes) {
  74778. this.__excludes = [];
  74779. }
  74780. this.__excludes[index] = false;
  74781. this.drawSeries();
  74782. for (i; i < ln; i++) {
  74783. axesItems[i].drawAxis();
  74784. }
  74785. },
  74786. /**
  74787. * Returns a string with the color to be used for the series legend item.
  74788. * @param index
  74789. */
  74790. getLegendColor: function(index) {
  74791. var me = this,
  74792. colorLength = me.colorArrayStyle.length;
  74793. if (me.style && me.style.fill) {
  74794. return me.style.fill;
  74795. } else {
  74796. return me.colorArrayStyle[index % colorLength];
  74797. }
  74798. },
  74799. highlightItem: function(item) {
  74800. this.callParent(arguments);
  74801. this.renderLabels();
  74802. },
  74803. unHighlightItem: function() {
  74804. this.callParent(arguments);
  74805. this.renderLabels();
  74806. },
  74807. cleanHighlights: function() {
  74808. this.callParent(arguments);
  74809. this.renderLabels();
  74810. }
  74811. });
  74812. /**
  74813. * @class Ext.chart.series.Column
  74814. *
  74815. * Creates a Column Chart. Much of the methods are inherited from Bar. A Column Chart is a useful
  74816. * visualization technique to display quantitative information for different categories that can
  74817. * show some progression (or regression) in the data set. As with all other series, the Column Series
  74818. * must be appended in the *series* Chart array configuration. See the Chart documentation for more
  74819. * information. A typical configuration object for the column series could be:
  74820. *
  74821. * @example
  74822. * var store = Ext.create('Ext.data.JsonStore', {
  74823. * fields: ['name', 'data'],
  74824. * data: [
  74825. * { 'name': 'metric one', 'data':10 },
  74826. * { 'name': 'metric two', 'data': 7 },
  74827. * { 'name': 'metric three', 'data': 5 },
  74828. * { 'name': 'metric four', 'data': 2 },
  74829. * { 'name': 'metric five', 'data':27 }
  74830. * ]
  74831. * });
  74832. *
  74833. * Ext.create('Ext.chart.Chart', {
  74834. * renderTo: Ext.getBody(),
  74835. * width: 500,
  74836. * height: 300,
  74837. * animate: true,
  74838. * store: store,
  74839. * axes: [
  74840. * {
  74841. * type: 'Numeric',
  74842. * position: 'left',
  74843. * fields: ['data'],
  74844. * label: {
  74845. * renderer: Ext.util.Format.numberRenderer('0,0')
  74846. * },
  74847. * title: 'Sample Values',
  74848. * grid: true,
  74849. * minimum: 0
  74850. * },
  74851. * {
  74852. * type: 'Category',
  74853. * position: 'bottom',
  74854. * fields: ['name'],
  74855. * title: 'Sample Metrics'
  74856. * }
  74857. * ],
  74858. * series: [
  74859. * {
  74860. * type: 'column',
  74861. * axis: 'left',
  74862. * highlight: true,
  74863. * tips: {
  74864. * trackMouse: true,
  74865. * width: 140,
  74866. * height: 28,
  74867. * renderer: function(storeItem, item) {
  74868. * this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data') + ' $');
  74869. * }
  74870. * },
  74871. * label: {
  74872. * display: 'insideEnd',
  74873. * 'text-anchor': 'middle',
  74874. * field: 'data',
  74875. * renderer: Ext.util.Format.numberRenderer('0'),
  74876. * orientation: 'vertical',
  74877. * color: '#333'
  74878. * },
  74879. * xField: 'name',
  74880. * yField: 'data'
  74881. * }
  74882. * ]
  74883. * });
  74884. *
  74885. * In this configuration we set `column` as the series type, bind the values of the bars to the bottom axis,
  74886. * set `highlight` to true so that bars are smoothly highlighted when hovered and bind the `xField` or category
  74887. * field to the data store `name` property and the `yField` as the data1 property of a store element.
  74888. */
  74889. Ext.define('Ext.chart.series.Column', {
  74890. /* Begin Definitions */
  74891. alternateClassName: ['Ext.chart.ColumnSeries', 'Ext.chart.ColumnChart', 'Ext.chart.StackedColumnChart'],
  74892. extend: 'Ext.chart.series.Bar',
  74893. /* End Definitions */
  74894. type: 'column',
  74895. alias: 'series.column',
  74896. column: true,
  74897. /**
  74898. * @cfg {Number} xPadding
  74899. * Padding between the left/right axes and the bars
  74900. */
  74901. xPadding: 10,
  74902. /**
  74903. * @cfg {Number} yPadding
  74904. * Padding between the top/bottom axes and the bars
  74905. */
  74906. yPadding: 0
  74907. });
  74908. /**
  74909. * @class Ext.chart.series.Gauge
  74910. *
  74911. * Creates a Gauge Chart. Gauge Charts are used to show progress in a certain variable. There are two ways of using the Gauge chart.
  74912. * One is setting a store element into the Gauge and selecting the field to be used from that store. Another one is instantiating the
  74913. * visualization and using the `setValue` method to adjust the value you want.
  74914. *
  74915. * An example of Gauge visualization:
  74916. *
  74917. * @example
  74918. * var store = Ext.create('Ext.data.JsonStore', {
  74919. * fields: ['data'],
  74920. * data: [
  74921. * { 'value':80 }
  74922. * ]
  74923. * });
  74924. *
  74925. * Ext.create('Ext.chart.Chart', {
  74926. * renderTo: Ext.getBody(),
  74927. * store: store,
  74928. * width: 400,
  74929. * height: 250,
  74930. * animate: true,
  74931. * insetPadding: 30,
  74932. * axes: [{
  74933. * type: 'gauge',
  74934. * position: 'gauge',
  74935. * minimum: 0,
  74936. * maximum: 100,
  74937. * steps: 10,
  74938. * margin: 10
  74939. * }],
  74940. * series: [{
  74941. * type: 'gauge',
  74942. * field: 'value',
  74943. * donut: 30,
  74944. * colorSet: ['#F49D10', '#ddd']
  74945. * }]
  74946. * });
  74947. *
  74948. * Ext.widget("button", {
  74949. * renderTo: Ext.getBody(),
  74950. * text: "Refresh",
  74951. * handler: function() {
  74952. * store.getAt(0).set('value', Math.round(Math.random()*100));
  74953. * }
  74954. * });
  74955. *
  74956. * In this example we create a special Gauge axis to be used with the gauge visualization (describing half-circle markers), and also we're
  74957. * setting a maximum, minimum and steps configuration options into the axis. The Gauge series configuration contains the store field to be bound to
  74958. * the visual display and the color set to be used with the visualization.
  74959. *
  74960. * @xtype gauge
  74961. */
  74962. Ext.define('Ext.chart.series.Gauge', {
  74963. /* Begin Definitions */
  74964. extend: 'Ext.chart.series.Series',
  74965. /* End Definitions */
  74966. type: "gauge",
  74967. alias: 'series.gauge',
  74968. rad: Math.PI / 180,
  74969. /**
  74970. * @cfg {Number} highlightDuration
  74971. * The duration for the pie slice highlight effect.
  74972. */
  74973. highlightDuration: 150,
  74974. /**
  74975. * @cfg {String} angleField (required)
  74976. * The store record field name to be used for the pie angles.
  74977. * The values bound to this field name must be positive real numbers.
  74978. */
  74979. angleField: false,
  74980. /**
  74981. * @cfg {Boolean} needle
  74982. * Use the Gauge Series as an area series or add a needle to it. Default's false.
  74983. */
  74984. needle: false,
  74985. /**
  74986. * @cfg {Boolean/Number} donut
  74987. * Use the entire disk or just a fraction of it for the gauge. Default's false.
  74988. */
  74989. donut: false,
  74990. /**
  74991. * @cfg {Boolean} showInLegend
  74992. * Whether to add the pie chart elements as legend items. Default's false.
  74993. */
  74994. showInLegend: false,
  74995. /**
  74996. * @cfg {Object} style
  74997. * An object containing styles for overriding series styles from Theming.
  74998. */
  74999. style: {},
  75000. constructor: function(config) {
  75001. this.callParent(arguments);
  75002. var me = this,
  75003. chart = me.chart,
  75004. surface = chart.surface,
  75005. store = chart.store,
  75006. shadow = chart.shadow, i, l, cfg;
  75007. Ext.apply(me, config, {
  75008. shadowAttributes: [{
  75009. "stroke-width": 6,
  75010. "stroke-opacity": 1,
  75011. stroke: 'rgb(200, 200, 200)',
  75012. translate: {
  75013. x: 1.2,
  75014. y: 2
  75015. }
  75016. },
  75017. {
  75018. "stroke-width": 4,
  75019. "stroke-opacity": 1,
  75020. stroke: 'rgb(150, 150, 150)',
  75021. translate: {
  75022. x: 0.9,
  75023. y: 1.5
  75024. }
  75025. },
  75026. {
  75027. "stroke-width": 2,
  75028. "stroke-opacity": 1,
  75029. stroke: 'rgb(100, 100, 100)',
  75030. translate: {
  75031. x: 0.6,
  75032. y: 1
  75033. }
  75034. }]
  75035. });
  75036. me.group = surface.getGroup(me.seriesId);
  75037. if (shadow) {
  75038. for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
  75039. me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
  75040. }
  75041. }
  75042. surface.customAttributes.segment = function(opt) {
  75043. return me.getSegment(opt);
  75044. };
  75045. },
  75046. // @private updates some onbefore render parameters.
  75047. initialize: function() {
  75048. var me = this,
  75049. store = me.chart.getChartStore(),
  75050. data = store.data.items,
  75051. i, ln, rec;
  75052. //Add yFields to be used in Legend.js
  75053. me.yField = [];
  75054. if (me.label.field) {
  75055. for (i = 0, ln = data.length; i < ln; i++) {
  75056. rec = data[i];
  75057. me.yField.push(rec.get(me.label.field));
  75058. }
  75059. }
  75060. },
  75061. // @private returns an object with properties for a Slice
  75062. getSegment: function(opt) {
  75063. var me = this,
  75064. rad = me.rad,
  75065. cos = Math.cos,
  75066. sin = Math.sin,
  75067. abs = Math.abs,
  75068. x = me.centerX,
  75069. y = me.centerY,
  75070. x1 = 0, x2 = 0, x3 = 0, x4 = 0,
  75071. y1 = 0, y2 = 0, y3 = 0, y4 = 0,
  75072. delta = 1e-2,
  75073. r = opt.endRho - opt.startRho,
  75074. startAngle = opt.startAngle,
  75075. endAngle = opt.endAngle,
  75076. midAngle = (startAngle + endAngle) / 2 * rad,
  75077. margin = opt.margin || 0,
  75078. flag = abs(endAngle - startAngle) > 180,
  75079. a1 = Math.min(startAngle, endAngle) * rad,
  75080. a2 = Math.max(startAngle, endAngle) * rad,
  75081. singleSlice = false;
  75082. x += margin * cos(midAngle);
  75083. y += margin * sin(midAngle);
  75084. x1 = x + opt.startRho * cos(a1);
  75085. y1 = y + opt.startRho * sin(a1);
  75086. x2 = x + opt.endRho * cos(a1);
  75087. y2 = y + opt.endRho * sin(a1);
  75088. x3 = x + opt.startRho * cos(a2);
  75089. y3 = y + opt.startRho * sin(a2);
  75090. x4 = x + opt.endRho * cos(a2);
  75091. y4 = y + opt.endRho * sin(a2);
  75092. if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
  75093. singleSlice = true;
  75094. }
  75095. //Solves mysterious clipping bug with IE
  75096. if (singleSlice) {
  75097. return {
  75098. path: [
  75099. ["M", x1, y1],
  75100. ["L", x2, y2],
  75101. ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
  75102. ["Z"]]
  75103. };
  75104. } else {
  75105. return {
  75106. path: [
  75107. ["M", x1, y1],
  75108. ["L", x2, y2],
  75109. ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
  75110. ["L", x3, y3],
  75111. ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
  75112. ["Z"]]
  75113. };
  75114. }
  75115. },
  75116. // @private utility function to calculate the middle point of a pie slice.
  75117. calcMiddle: function(item) {
  75118. var me = this,
  75119. rad = me.rad,
  75120. slice = item.slice,
  75121. x = me.centerX,
  75122. y = me.centerY,
  75123. startAngle = slice.startAngle,
  75124. endAngle = slice.endAngle,
  75125. radius = Math.max(('rho' in slice) ? slice.rho: me.radius, me.label.minMargin),
  75126. donut = +me.donut,
  75127. a1 = Math.min(startAngle, endAngle) * rad,
  75128. a2 = Math.max(startAngle, endAngle) * rad,
  75129. midAngle = -(a1 + (a2 - a1) / 2),
  75130. xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
  75131. ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
  75132. item.middle = {
  75133. x: xm,
  75134. y: ym
  75135. };
  75136. },
  75137. /**
  75138. * Draws the series for the current chart.
  75139. */
  75140. drawSeries: function() {
  75141. var me = this,
  75142. chart = me.chart,
  75143. store = chart.getChartStore(),
  75144. group = me.group,
  75145. animate = me.chart.animate,
  75146. axis = me.chart.axes.get(0),
  75147. minimum = axis && axis.minimum || me.minimum || 0,
  75148. maximum = axis && axis.maximum || me.maximum || 0,
  75149. field = me.angleField || me.field || me.xField,
  75150. surface = chart.surface,
  75151. chartBBox = chart.chartBBox,
  75152. rad = me.rad,
  75153. donut = +me.donut,
  75154. values = {},
  75155. items = [],
  75156. seriesStyle = me.seriesStyle,
  75157. seriesLabelStyle = me.seriesLabelStyle,
  75158. colorArrayStyle = me.colorArrayStyle,
  75159. colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
  75160. gutterX = chart.maxGutter[0],
  75161. gutterY = chart.maxGutter[1],
  75162. cos = Math.cos,
  75163. sin = Math.sin,
  75164. rendererAttributes, centerX, centerY, slice, slices, sprite, value,
  75165. item, ln, record, i, j, startAngle, endAngle, middleAngle, sliceLength, path,
  75166. p, spriteOptions, bbox, splitAngle, sliceA, sliceB;
  75167. Ext.apply(seriesStyle, me.style || {});
  75168. me.setBBox();
  75169. bbox = me.bbox;
  75170. //override theme colors
  75171. if (me.colorSet) {
  75172. colorArrayStyle = me.colorSet;
  75173. colorArrayLength = colorArrayStyle.length;
  75174. }
  75175. //if not store or store is empty then there's nothing to draw
  75176. if (!store || !store.getCount() || me.seriesIsHidden) {
  75177. me.hide();
  75178. me.items = [];
  75179. return;
  75180. }
  75181. centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
  75182. centerY = me.centerY = chartBBox.y + chartBBox.height;
  75183. me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
  75184. me.slices = slices = [];
  75185. me.items = items = [];
  75186. if (!me.value) {
  75187. record = store.getAt(0);
  75188. me.value = record.get(field);
  75189. }
  75190. value = me.value;
  75191. if (me.needle) {
  75192. sliceA = {
  75193. series: me,
  75194. value: value,
  75195. startAngle: -180,
  75196. endAngle: 0,
  75197. rho: me.radius
  75198. };
  75199. splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
  75200. slices.push(sliceA);
  75201. } else {
  75202. splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
  75203. sliceA = {
  75204. series: me,
  75205. value: value,
  75206. startAngle: -180,
  75207. endAngle: splitAngle,
  75208. rho: me.radius
  75209. };
  75210. sliceB = {
  75211. series: me,
  75212. value: me.maximum - value,
  75213. startAngle: splitAngle,
  75214. endAngle: 0,
  75215. rho: me.radius
  75216. };
  75217. slices.push(sliceA, sliceB);
  75218. }
  75219. //do pie slices after.
  75220. for (i = 0, ln = slices.length; i < ln; i++) {
  75221. slice = slices[i];
  75222. sprite = group.getAt(i);
  75223. //set pie slice properties
  75224. rendererAttributes = Ext.apply({
  75225. segment: {
  75226. startAngle: slice.startAngle,
  75227. endAngle: slice.endAngle,
  75228. margin: 0,
  75229. rho: slice.rho,
  75230. startRho: slice.rho * +donut / 100,
  75231. endRho: slice.rho
  75232. }
  75233. }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
  75234. item = Ext.apply({},
  75235. rendererAttributes.segment, {
  75236. slice: slice,
  75237. series: me,
  75238. storeItem: record,
  75239. index: i
  75240. });
  75241. items[i] = item;
  75242. // Create a new sprite if needed (no height)
  75243. if (!sprite) {
  75244. spriteOptions = Ext.apply({
  75245. type: "path",
  75246. group: group
  75247. }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
  75248. sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
  75249. }
  75250. slice.sprite = slice.sprite || [];
  75251. item.sprite = sprite;
  75252. slice.sprite.push(sprite);
  75253. if (animate) {
  75254. rendererAttributes = me.renderer(sprite, record, rendererAttributes, i, store);
  75255. sprite._to = rendererAttributes;
  75256. me.onAnimate(sprite, {
  75257. to: rendererAttributes
  75258. });
  75259. } else {
  75260. rendererAttributes = me.renderer(sprite, record, Ext.apply(rendererAttributes, {
  75261. hidden: false
  75262. }), i, store);
  75263. sprite.setAttributes(rendererAttributes, true);
  75264. }
  75265. }
  75266. if (me.needle) {
  75267. splitAngle = splitAngle * Math.PI / 180;
  75268. if (!me.needleSprite) {
  75269. me.needleSprite = me.chart.surface.add({
  75270. type: 'path',
  75271. path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
  75272. centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
  75273. 'L', centerX + me.radius * cos(splitAngle),
  75274. centerY + -Math.abs(me.radius * sin(splitAngle))],
  75275. 'stroke-width': 4,
  75276. 'stroke': '#222'
  75277. });
  75278. } else {
  75279. if (animate) {
  75280. me.onAnimate(me.needleSprite, {
  75281. to: {
  75282. path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
  75283. centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
  75284. 'L', centerX + me.radius * cos(splitAngle),
  75285. centerY + -Math.abs(me.radius * sin(splitAngle))]
  75286. }
  75287. });
  75288. } else {
  75289. me.needleSprite.setAttributes({
  75290. type: 'path',
  75291. path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
  75292. centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
  75293. 'L', centerX + me.radius * cos(splitAngle),
  75294. centerY + -Math.abs(me.radius * sin(splitAngle))]
  75295. });
  75296. }
  75297. }
  75298. me.needleSprite.setAttributes({
  75299. hidden: false
  75300. }, true);
  75301. }
  75302. delete me.value;
  75303. },
  75304. /**
  75305. * Sets the Gauge chart to the current specified value.
  75306. */
  75307. setValue: function (value) {
  75308. this.value = value;
  75309. this.drawSeries();
  75310. },
  75311. // @private callback for when creating a label sprite.
  75312. onCreateLabel: function(storeItem, item, i, display) {},
  75313. // @private callback for when placing a label sprite.
  75314. onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {},
  75315. // @private callback for when placing a callout.
  75316. onPlaceCallout: function() {},
  75317. // @private handles sprite animation for the series.
  75318. onAnimate: function(sprite, attr) {
  75319. sprite.show();
  75320. return this.callParent(arguments);
  75321. },
  75322. isItemInPoint: function(x, y, item, i) {
  75323. var me = this,
  75324. cx = me.centerX,
  75325. cy = me.centerY,
  75326. abs = Math.abs,
  75327. dx = abs(x - cx),
  75328. dy = abs(y - cy),
  75329. startAngle = item.startAngle,
  75330. endAngle = item.endAngle,
  75331. rho = Math.sqrt(dx * dx + dy * dy),
  75332. angle = Math.atan2(y - cy, x - cx) / me.rad;
  75333. //Only trigger events for the filled portion of the Gauge.
  75334. return (i === 0) && (angle >= startAngle && angle < endAngle &&
  75335. rho >= item.startRho && rho <= item.endRho);
  75336. },
  75337. // @private shows all elements in the series.
  75338. showAll: function() {
  75339. if (!isNaN(this._index)) {
  75340. this.__excludes[this._index] = false;
  75341. this.drawSeries();
  75342. }
  75343. },
  75344. /**
  75345. * Returns the color of the series (to be displayed as color for the series legend item).
  75346. * @param item {Object} Info about the item; same format as returned by #getItemForPoint
  75347. */
  75348. getLegendColor: function(index) {
  75349. var me = this;
  75350. return me.colorArrayStyle[index % me.colorArrayStyle.length];
  75351. }
  75352. });
  75353. /**
  75354. * @class Ext.chart.series.Line
  75355. * @extends Ext.chart.series.Cartesian
  75356. *
  75357. * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
  75358. * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
  75359. * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
  75360. * documentation for more information. A typical configuration object for the line series could be:
  75361. *
  75362. * @example
  75363. * var store = Ext.create('Ext.data.JsonStore', {
  75364. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  75365. * data: [
  75366. * { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
  75367. * { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
  75368. * { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
  75369. * { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
  75370. * { 'name': 'metric five', 'data1': 4, 'data2': 4, 'data3': 36, 'data4': 13, 'data5': 33 }
  75371. * ]
  75372. * });
  75373. *
  75374. * Ext.create('Ext.chart.Chart', {
  75375. * renderTo: Ext.getBody(),
  75376. * width: 500,
  75377. * height: 300,
  75378. * animate: true,
  75379. * store: store,
  75380. * axes: [
  75381. * {
  75382. * type: 'Numeric',
  75383. * position: 'left',
  75384. * fields: ['data1', 'data2'],
  75385. * label: {
  75386. * renderer: Ext.util.Format.numberRenderer('0,0')
  75387. * },
  75388. * title: 'Sample Values',
  75389. * grid: true,
  75390. * minimum: 0
  75391. * },
  75392. * {
  75393. * type: 'Category',
  75394. * position: 'bottom',
  75395. * fields: ['name'],
  75396. * title: 'Sample Metrics'
  75397. * }
  75398. * ],
  75399. * series: [
  75400. * {
  75401. * type: 'line',
  75402. * highlight: {
  75403. * size: 7,
  75404. * radius: 7
  75405. * },
  75406. * axis: 'left',
  75407. * xField: 'name',
  75408. * yField: 'data1',
  75409. * markerConfig: {
  75410. * type: 'cross',
  75411. * size: 4,
  75412. * radius: 4,
  75413. * 'stroke-width': 0
  75414. * }
  75415. * },
  75416. * {
  75417. * type: 'line',
  75418. * highlight: {
  75419. * size: 7,
  75420. * radius: 7
  75421. * },
  75422. * axis: 'left',
  75423. * fill: true,
  75424. * xField: 'name',
  75425. * yField: 'data2',
  75426. * markerConfig: {
  75427. * type: 'circle',
  75428. * size: 4,
  75429. * radius: 4,
  75430. * 'stroke-width': 0
  75431. * }
  75432. * }
  75433. * ]
  75434. * });
  75435. *
  75436. * In this configuration we're adding two series (or lines), one bound to the `data1`
  75437. * property of the store and the other to `data3`. The type for both configurations is
  75438. * `line`. The `xField` for both series is the same, the name propert of the store.
  75439. * Both line series share the same axis, the left axis. You can set particular marker
  75440. * configuration by adding properties onto the markerConfig object. Both series have
  75441. * an object as highlight so that markers animate smoothly to the properties in highlight
  75442. * when hovered. The second series has `fill=true` which means that the line will also
  75443. * have an area below it of the same color.
  75444. *
  75445. * **Note:** In the series definition remember to explicitly set the axis to bind the
  75446. * values of the line series to. This can be done by using the `axis` configuration property.
  75447. */
  75448. Ext.define('Ext.chart.series.Line', {
  75449. /* Begin Definitions */
  75450. extend: 'Ext.chart.series.Cartesian',
  75451. alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
  75452. requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
  75453. /* End Definitions */
  75454. type: 'line',
  75455. alias: 'series.line',
  75456. /**
  75457. * @cfg {String} axis
  75458. * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
  75459. * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
  75460. * relative scale will be used.
  75461. */
  75462. /**
  75463. * @cfg {Number} selectionTolerance
  75464. * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
  75465. */
  75466. selectionTolerance: 20,
  75467. /**
  75468. * @cfg {Boolean} showMarkers
  75469. * Whether markers should be displayed at the data points along the line. If true,
  75470. * then the {@link #markerConfig} config item will determine the markers' styling.
  75471. */
  75472. showMarkers: true,
  75473. /**
  75474. * @cfg {Object} markerConfig
  75475. * The display style for the markers. Only used if {@link #showMarkers} is true.
  75476. * The markerConfig is a configuration object containing the same set of properties defined in
  75477. * the Sprite class. For example, if we were to set red circles as markers to the line series we could
  75478. * pass the object:
  75479. *
  75480. <pre><code>
  75481. markerConfig: {
  75482. type: 'circle',
  75483. radius: 4,
  75484. 'fill': '#f00'
  75485. }
  75486. </code></pre>
  75487. */
  75488. markerConfig: {},
  75489. /**
  75490. * @cfg {Object} style
  75491. * An object containing style properties for the visualization lines and fill.
  75492. * These styles will override the theme styles. The following are valid style properties:
  75493. *
  75494. * - `stroke` - an rgb or hex color string for the background color of the line
  75495. * - `stroke-width` - the width of the stroke (integer)
  75496. * - `fill` - the background fill color string (hex or rgb), only works if {@link #fill} is `true`
  75497. * - `opacity` - the opacity of the line and the fill color (decimal)
  75498. *
  75499. * Example usage:
  75500. *
  75501. * style: {
  75502. * stroke: '#00ff00',
  75503. * 'stroke-width': 10,
  75504. * fill: '#80A080',
  75505. * opacity: 0.2
  75506. * }
  75507. */
  75508. style: {},
  75509. /**
  75510. * @cfg {Boolean/Number} smooth
  75511. * If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise
  75512. * straight line segments will be drawn.
  75513. *
  75514. * A numeric value is interpreted as a divisor of the horizontal distance between consecutive points in
  75515. * the line; larger numbers result in sharper curves while smaller numbers result in smoother curves.
  75516. *
  75517. * If set to `true` then a default numeric value of 3 will be used. Defaults to `false`.
  75518. */
  75519. smooth: false,
  75520. /**
  75521. * @private Default numeric smoothing value to be used when {@link #smooth} = true.
  75522. */
  75523. defaultSmoothness: 3,
  75524. /**
  75525. * @cfg {Boolean} fill
  75526. * If true, the area below the line will be filled in using the {@link #style eefill} and
  75527. * {@link #style opacity} config properties. Defaults to false.
  75528. */
  75529. fill: false,
  75530. constructor: function(config) {
  75531. this.callParent(arguments);
  75532. var me = this,
  75533. surface = me.chart.surface,
  75534. shadow = me.chart.shadow,
  75535. i, l;
  75536. config.highlightCfg = Ext.Object.merge({ 'stroke-width': 3 }, config.highlightCfg);
  75537. Ext.apply(me, config, {
  75538. shadowAttributes: [{
  75539. "stroke-width": 6,
  75540. "stroke-opacity": 0.05,
  75541. stroke: 'rgb(0, 0, 0)',
  75542. translate: {
  75543. x: 1,
  75544. y: 1
  75545. }
  75546. }, {
  75547. "stroke-width": 4,
  75548. "stroke-opacity": 0.1,
  75549. stroke: 'rgb(0, 0, 0)',
  75550. translate: {
  75551. x: 1,
  75552. y: 1
  75553. }
  75554. }, {
  75555. "stroke-width": 2,
  75556. "stroke-opacity": 0.15,
  75557. stroke: 'rgb(0, 0, 0)',
  75558. translate: {
  75559. x: 1,
  75560. y: 1
  75561. }
  75562. }]
  75563. });
  75564. me.group = surface.getGroup(me.seriesId);
  75565. if (me.showMarkers) {
  75566. me.markerGroup = surface.getGroup(me.seriesId + '-markers');
  75567. }
  75568. if (shadow) {
  75569. for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
  75570. me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
  75571. }
  75572. }
  75573. },
  75574. // @private makes an average of points when there are more data points than pixels to be rendered.
  75575. shrink: function(xValues, yValues, size) {
  75576. // Start at the 2nd point...
  75577. var len = xValues.length,
  75578. ratio = Math.floor(len / size),
  75579. i = 1,
  75580. xSum = 0,
  75581. ySum = 0,
  75582. xRes = [+xValues[0]],
  75583. yRes = [+yValues[0]];
  75584. for (; i < len; ++i) {
  75585. xSum += +xValues[i] || 0;
  75586. ySum += +yValues[i] || 0;
  75587. if (i % ratio == 0) {
  75588. xRes.push(xSum/ratio);
  75589. yRes.push(ySum/ratio);
  75590. xSum = 0;
  75591. ySum = 0;
  75592. }
  75593. }
  75594. return {
  75595. x: xRes,
  75596. y: yRes
  75597. };
  75598. },
  75599. /**
  75600. * Draws the series for the current chart.
  75601. */
  75602. drawSeries: function() {
  75603. var me = this,
  75604. chart = me.chart,
  75605. chartAxes = chart.axes,
  75606. store = chart.getChartStore(),
  75607. data = store.data.items,
  75608. record,
  75609. storeCount = store.getCount(),
  75610. surface = me.chart.surface,
  75611. bbox = {},
  75612. group = me.group,
  75613. showMarkers = me.showMarkers,
  75614. markerGroup = me.markerGroup,
  75615. enableShadows = chart.shadow,
  75616. shadowGroups = me.shadowGroups,
  75617. shadowAttributes = me.shadowAttributes,
  75618. smooth = me.smooth,
  75619. lnsh = shadowGroups.length,
  75620. dummyPath = ["M"],
  75621. path = ["M"],
  75622. renderPath = ["M"],
  75623. smoothPath = ["M"],
  75624. markerIndex = chart.markerIndex,
  75625. axes = [].concat(me.axis),
  75626. shadowBarAttr,
  75627. xValues = [],
  75628. xValueMap = {},
  75629. yValues = [],
  75630. yValueMap = {},
  75631. onbreak = false,
  75632. storeIndices = [],
  75633. markerStyle = me.markerStyle,
  75634. seriesStyle = me.seriesStyle,
  75635. colorArrayStyle = me.colorArrayStyle,
  75636. colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
  75637. isNumber = Ext.isNumber,
  75638. seriesIdx = me.seriesIdx,
  75639. boundAxes = me.getAxesForXAndYFields(),
  75640. boundXAxis = boundAxes.xAxis,
  75641. boundYAxis = boundAxes.yAxis,
  75642. shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
  75643. x, y, prevX, prevY, firstX, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
  75644. yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
  75645. endLineStyle, type, count, opacity, lineOpacity, fillOpacity, fillDefaultValue;
  75646. if (me.fireEvent('beforedraw', me) === false) {
  75647. return;
  75648. }
  75649. //if store is empty or the series is excluded in the legend then there's nothing to draw.
  75650. if (!storeCount || me.seriesIsHidden) {
  75651. me.hide();
  75652. me.items = [];
  75653. if (me.line) {
  75654. me.line.hide(true);
  75655. if (me.line.shadows) {
  75656. shadows = me.line.shadows;
  75657. for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
  75658. shadow = shadows[j];
  75659. shadow.hide(true);
  75660. }
  75661. }
  75662. if (me.fillPath) {
  75663. me.fillPath.hide(true);
  75664. }
  75665. }
  75666. me.line = null;
  75667. me.fillPath = null;
  75668. return;
  75669. }
  75670. //prepare style objects for line and markers
  75671. endMarkerStyle = Ext.apply(markerStyle || {}, me.markerConfig, {
  75672. fill: me.seriesStyle.fill || colorArrayStyle[seriesIdx % colorArrayStyle.length]
  75673. });
  75674. type = endMarkerStyle.type;
  75675. delete endMarkerStyle.type;
  75676. endLineStyle = seriesStyle;
  75677. //if no stroke with is specified force it to 0.5 because this is
  75678. //about making *lines*
  75679. if (!endLineStyle['stroke-width']) {
  75680. endLineStyle['stroke-width'] = 0.5;
  75681. }
  75682. //set opacity values
  75683. opacity = 'opacity' in endLineStyle ? endLineStyle.opacity : 1;
  75684. fillDefaultValue = 'opacity' in endLineStyle ? endLineStyle.opacity : 0.3;
  75685. lineOpacity = 'lineOpacity' in endLineStyle ? endLineStyle.lineOpacity : opacity;
  75686. fillOpacity = 'fillOpacity' in endLineStyle ? endLineStyle.fillOpacity : fillDefaultValue;
  75687. //If we're using a time axis and we need to translate the points,
  75688. //then reuse the first markers as the last markers.
  75689. if (markerIndex && markerGroup && markerGroup.getCount()) {
  75690. for (i = 0; i < markerIndex; i++) {
  75691. marker = markerGroup.getAt(i);
  75692. markerGroup.remove(marker);
  75693. markerGroup.add(marker);
  75694. markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
  75695. marker.setAttributes({
  75696. x: 0,
  75697. y: 0,
  75698. translate: {
  75699. x: markerAux.attr.translation.x,
  75700. y: markerAux.attr.translation.y
  75701. }
  75702. }, true);
  75703. }
  75704. }
  75705. me.unHighlightItem();
  75706. me.cleanHighlights();
  75707. me.setBBox();
  75708. bbox = me.bbox;
  75709. me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
  75710. if (axis = chartAxes.get(boundXAxis)) {
  75711. ends = axis.applyData();
  75712. minX = ends.from;
  75713. maxX = ends.to;
  75714. }
  75715. if (axis = chartAxes.get(boundYAxis)) {
  75716. ends = axis.applyData();
  75717. minY = ends.from;
  75718. maxY = ends.to;
  75719. }
  75720. // If a field was specified without a corresponding axis, create one to get bounds
  75721. if (me.xField && !Ext.isNumber(minX)) {
  75722. axis = me.getMinMaxXValues();
  75723. minX = axis[0];
  75724. maxX = axis[1];
  75725. }
  75726. if (me.yField && !Ext.isNumber(minY)) {
  75727. axis = me.getMinMaxYValues();
  75728. minY = axis[0];
  75729. maxY = axis[1];
  75730. }
  75731. if (isNaN(minX)) {
  75732. minX = 0;
  75733. xScale = bbox.width / ((storeCount - 1) || 1);
  75734. }
  75735. else {
  75736. xScale = bbox.width / ((maxX - minX) || (storeCount -1) || 1);
  75737. }
  75738. if (isNaN(minY)) {
  75739. minY = 0;
  75740. yScale = bbox.height / ((storeCount - 1) || 1);
  75741. }
  75742. else {
  75743. yScale = bbox.height / ((maxY - minY) || (storeCount - 1) || 1);
  75744. }
  75745. // Extract all x and y values from the store
  75746. for (i = 0, ln = data.length; i < ln; i++) {
  75747. record = data[i];
  75748. xValue = record.get(me.xField);
  75749. // Ensure a value
  75750. if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)
  75751. //set as uniform distribution if the axis is a category axis.
  75752. || boundXAxis && chartAxes.get(boundXAxis) && chartAxes.get(boundXAxis).type == 'Category') {
  75753. if (xValue in xValueMap) {
  75754. xValue = xValueMap[xValue];
  75755. } else {
  75756. xValue = xValueMap[xValue] = i;
  75757. }
  75758. }
  75759. // Filter out values that don't fit within the pan/zoom buffer area
  75760. yValue = record.get(me.yField);
  75761. //skip undefined values
  75762. if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
  75763. if (Ext.isDefined(Ext.global.console)) {
  75764. Ext.global.console.warn("[Ext.chart.series.Line] Skipping a store element with an undefined value at ", record, xValue, yValue);
  75765. }
  75766. continue;
  75767. }
  75768. // Ensure a value
  75769. if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)
  75770. //set as uniform distribution if the axis is a category axis.
  75771. || boundYAxis && chartAxes.get(boundYAxis) && chartAxes.get(boundYAxis).type == 'Category') {
  75772. yValue = i;
  75773. }
  75774. storeIndices.push(i);
  75775. xValues.push(xValue);
  75776. yValues.push(yValue);
  75777. }
  75778. ln = xValues.length;
  75779. if (ln > bbox.width) {
  75780. coords = me.shrink(xValues, yValues, bbox.width);
  75781. xValues = coords.x;
  75782. yValues = coords.y;
  75783. }
  75784. me.items = [];
  75785. count = 0;
  75786. ln = xValues.length;
  75787. for (i = 0; i < ln; i++) {
  75788. xValue = xValues[i];
  75789. yValue = yValues[i];
  75790. if (yValue === false) {
  75791. if (path.length == 1) {
  75792. path = [];
  75793. }
  75794. onbreak = true;
  75795. me.items.push(false);
  75796. continue;
  75797. } else {
  75798. x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
  75799. y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
  75800. if (onbreak) {
  75801. onbreak = false;
  75802. path.push('M');
  75803. }
  75804. path = path.concat([x, y]);
  75805. }
  75806. if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
  75807. firstY = y;
  75808. firstX = x;
  75809. }
  75810. // If this is the first line, create a dummypath to animate in from.
  75811. if (!me.line || chart.resizing) {
  75812. dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
  75813. }
  75814. // When resizing, reset before animating
  75815. if (chart.animate && chart.resizing && me.line) {
  75816. me.line.setAttributes({
  75817. path: dummyPath,
  75818. opacity: lineOpacity
  75819. }, true);
  75820. if (me.fillPath) {
  75821. me.fillPath.setAttributes({
  75822. path: dummyPath,
  75823. opacity: fillOpacity
  75824. }, true);
  75825. }
  75826. if (me.line.shadows) {
  75827. shadows = me.line.shadows;
  75828. for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
  75829. shadow = shadows[j];
  75830. shadow.setAttributes({
  75831. path: dummyPath
  75832. }, true);
  75833. }
  75834. }
  75835. }
  75836. if (showMarkers) {
  75837. marker = markerGroup.getAt(count++);
  75838. if (!marker) {
  75839. marker = Ext.chart.Shape[type](surface, Ext.apply({
  75840. group: [group, markerGroup],
  75841. x: 0, y: 0,
  75842. translate: {
  75843. x: +(prevX || x),
  75844. y: prevY || (bbox.y + bbox.height / 2)
  75845. },
  75846. value: '"' + xValue + ', ' + yValue + '"',
  75847. zIndex: 4000
  75848. }, endMarkerStyle));
  75849. marker._to = {
  75850. translate: {
  75851. x: +x,
  75852. y: +y
  75853. }
  75854. };
  75855. } else {
  75856. marker.setAttributes({
  75857. value: '"' + xValue + ', ' + yValue + '"',
  75858. x: 0, y: 0,
  75859. hidden: false
  75860. }, true);
  75861. marker._to = {
  75862. translate: {
  75863. x: +x,
  75864. y: +y
  75865. }
  75866. };
  75867. }
  75868. }
  75869. me.items.push({
  75870. series: me,
  75871. value: [xValue, yValue],
  75872. point: [x, y],
  75873. sprite: marker,
  75874. storeItem: store.getAt(storeIndices[i])
  75875. });
  75876. prevX = x;
  75877. prevY = y;
  75878. }
  75879. if (path.length <= 1) {
  75880. //nothing to be rendered
  75881. return;
  75882. }
  75883. if (me.smooth) {
  75884. smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
  75885. }
  75886. renderPath = smooth ? smoothPath : path;
  75887. //Correct path if we're animating timeAxis intervals
  75888. if (chart.markerIndex && me.previousPath) {
  75889. fromPath = me.previousPath;
  75890. if (!smooth) {
  75891. Ext.Array.erase(fromPath, 1, 2);
  75892. }
  75893. } else {
  75894. fromPath = path;
  75895. }
  75896. // Only create a line if one doesn't exist.
  75897. if (!me.line) {
  75898. me.line = surface.add(Ext.apply({
  75899. type: 'path',
  75900. group: group,
  75901. path: dummyPath,
  75902. stroke: endLineStyle.stroke || endLineStyle.fill
  75903. }, endLineStyle || {}));
  75904. //set configuration opacity
  75905. me.line.setAttributes({
  75906. opacity: lineOpacity
  75907. }, true);
  75908. if (enableShadows) {
  75909. me.line.setAttributes(Ext.apply({}, me.shadowOptions), true);
  75910. }
  75911. //unset fill here (there's always a default fill withing the themes).
  75912. me.line.setAttributes({
  75913. fill: 'none',
  75914. zIndex: 3000
  75915. });
  75916. if (!endLineStyle.stroke && colorArrayLength) {
  75917. me.line.setAttributes({
  75918. stroke: colorArrayStyle[seriesIdx % colorArrayLength]
  75919. }, true);
  75920. }
  75921. if (enableShadows) {
  75922. //create shadows
  75923. shadows = me.line.shadows = [];
  75924. for (shindex = 0; shindex < lnsh; shindex++) {
  75925. shadowBarAttr = shadowAttributes[shindex];
  75926. shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
  75927. shadow = surface.add(Ext.apply({}, {
  75928. type: 'path',
  75929. group: shadowGroups[shindex]
  75930. }, shadowBarAttr));
  75931. shadows.push(shadow);
  75932. }
  75933. }
  75934. }
  75935. if (me.fill) {
  75936. fillPath = renderPath.concat([
  75937. ["L", x, bbox.y + bbox.height],
  75938. ["L", firstX, bbox.y + bbox.height],
  75939. ["L", firstX, firstY]
  75940. ]);
  75941. if (!me.fillPath) {
  75942. me.fillPath = surface.add({
  75943. group: group,
  75944. type: 'path',
  75945. fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
  75946. path: dummyPath
  75947. });
  75948. }
  75949. }
  75950. markerCount = showMarkers && markerGroup.getCount();
  75951. if (chart.animate) {
  75952. fill = me.fill;
  75953. line = me.line;
  75954. //Add renderer to line. There is not unique record associated with this.
  75955. rendererAttributes = me.renderer(line, false, { path: renderPath }, i, store);
  75956. Ext.apply(rendererAttributes, endLineStyle || {}, {
  75957. stroke: endLineStyle.stroke || endLineStyle.fill
  75958. });
  75959. //fill should not be used here but when drawing the special fill path object
  75960. delete rendererAttributes.fill;
  75961. line.show(true);
  75962. if (chart.markerIndex && me.previousPath) {
  75963. me.animation = animation = me.onAnimate(line, {
  75964. to: rendererAttributes,
  75965. from: {
  75966. path: fromPath
  75967. }
  75968. });
  75969. } else {
  75970. me.animation = animation = me.onAnimate(line, {
  75971. to: rendererAttributes
  75972. });
  75973. }
  75974. //animate shadows
  75975. if (enableShadows) {
  75976. shadows = line.shadows;
  75977. for(j = 0; j < lnsh; j++) {
  75978. shadows[j].show(true);
  75979. if (chart.markerIndex && me.previousPath) {
  75980. me.onAnimate(shadows[j], {
  75981. to: { path: renderPath },
  75982. from: { path: fromPath }
  75983. });
  75984. } else {
  75985. me.onAnimate(shadows[j], {
  75986. to: { path: renderPath }
  75987. });
  75988. }
  75989. }
  75990. }
  75991. //animate fill path
  75992. if (fill) {
  75993. me.fillPath.show(true);
  75994. me.onAnimate(me.fillPath, {
  75995. to: Ext.apply({}, {
  75996. path: fillPath,
  75997. fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
  75998. 'stroke-width': 0,
  75999. opacity: fillOpacity
  76000. }, endLineStyle || {})
  76001. });
  76002. }
  76003. //animate markers
  76004. if (showMarkers) {
  76005. count = 0;
  76006. for(i = 0; i < ln; i++) {
  76007. if (me.items[i]) {
  76008. item = markerGroup.getAt(count++);
  76009. if (item) {
  76010. rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
  76011. me.onAnimate(item, {
  76012. to: Ext.apply(rendererAttributes, endMarkerStyle || {})
  76013. });
  76014. item.show(true);
  76015. }
  76016. }
  76017. }
  76018. for(; count < markerCount; count++) {
  76019. item = markerGroup.getAt(count);
  76020. item.hide(true);
  76021. }
  76022. // for(i = 0; i < (chart.markerIndex || 0)-1; i++) {
  76023. // item = markerGroup.getAt(i);
  76024. // item.hide(true);
  76025. // }
  76026. }
  76027. } else {
  76028. rendererAttributes = me.renderer(me.line, false, { path: renderPath, hidden: false }, i, store);
  76029. Ext.apply(rendererAttributes, endLineStyle || {}, {
  76030. stroke: endLineStyle.stroke || endLineStyle.fill
  76031. });
  76032. //fill should not be used here but when drawing the special fill path object
  76033. delete rendererAttributes.fill;
  76034. me.line.setAttributes(rendererAttributes, true);
  76035. me.line.setAttributes({
  76036. opacity: lineOpacity
  76037. }, true);
  76038. //set path for shadows
  76039. if (enableShadows) {
  76040. shadows = me.line.shadows;
  76041. for(j = 0; j < lnsh; j++) {
  76042. shadows[j].setAttributes({
  76043. path: renderPath,
  76044. hidden: false
  76045. }, true);
  76046. }
  76047. }
  76048. if (me.fill) {
  76049. me.fillPath.setAttributes({
  76050. path: fillPath,
  76051. hidden: false,
  76052. opacity: fillOpacity
  76053. }, true);
  76054. }
  76055. if (showMarkers) {
  76056. count = 0;
  76057. for(i = 0; i < ln; i++) {
  76058. if (me.items[i]) {
  76059. item = markerGroup.getAt(count++);
  76060. if (item) {
  76061. rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
  76062. item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
  76063. if (!item.attr.hidden) {
  76064. item.show(true);
  76065. }
  76066. }
  76067. }
  76068. }
  76069. for(; count < markerCount; count++) {
  76070. item = markerGroup.getAt(count);
  76071. item.hide(true);
  76072. }
  76073. }
  76074. }
  76075. if (chart.markerIndex) {
  76076. if (me.smooth) {
  76077. Ext.Array.erase(path, 1, 2);
  76078. } else {
  76079. Ext.Array.splice(path, 1, 0, path[1], path[2]);
  76080. }
  76081. me.previousPath = path;
  76082. }
  76083. me.renderLabels();
  76084. me.renderCallouts();
  76085. me.fireEvent('draw', me);
  76086. },
  76087. // @private called when a label is to be created.
  76088. onCreateLabel: function(storeItem, item, i, display) {
  76089. var me = this,
  76090. group = me.labelsGroup,
  76091. config = me.label,
  76092. bbox = me.bbox,
  76093. endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
  76094. return me.chart.surface.add(Ext.apply({
  76095. 'type': 'text',
  76096. 'text-anchor': 'middle',
  76097. 'group': group,
  76098. 'x': item.point[0],
  76099. 'y': bbox.y + bbox.height / 2
  76100. }, endLabelStyle || {}));
  76101. },
  76102. // @private called when a label is to be created.
  76103. onPlaceLabel: function(label, storeItem, item, i, display, animate) {
  76104. var me = this,
  76105. chart = me.chart,
  76106. resizing = chart.resizing,
  76107. config = me.label,
  76108. format = config.renderer,
  76109. field = config.field,
  76110. bbox = me.bbox,
  76111. x = item.point[0],
  76112. y = item.point[1],
  76113. radius = item.sprite.attr.radius,
  76114. bb, width, height;
  76115. label.setAttributes({
  76116. text: format(storeItem.get(field)),
  76117. hidden: true
  76118. }, true);
  76119. if (display == 'rotate') {
  76120. label.setAttributes({
  76121. 'text-anchor': 'start',
  76122. 'rotation': {
  76123. x: x,
  76124. y: y,
  76125. degrees: -45
  76126. }
  76127. }, true);
  76128. //correct label position to fit into the box
  76129. bb = label.getBBox();
  76130. width = bb.width;
  76131. height = bb.height;
  76132. x = x < bbox.x? bbox.x : x;
  76133. x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
  76134. y = (y - height < bbox.y)? bbox.y + height : y;
  76135. } else if (display == 'under' || display == 'over') {
  76136. //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
  76137. bb = item.sprite.getBBox();
  76138. bb.width = bb.width || (radius * 2);
  76139. bb.height = bb.height || (radius * 2);
  76140. y = y + (display == 'over'? -bb.height : bb.height);
  76141. //correct label position to fit into the box
  76142. bb = label.getBBox();
  76143. width = bb.width/2;
  76144. height = bb.height/2;
  76145. x = x - width < bbox.x? bbox.x + width : x;
  76146. x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
  76147. y = y - height < bbox.y? bbox.y + height : y;
  76148. y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
  76149. }
  76150. if (me.chart.animate && !me.chart.resizing) {
  76151. label.show(true);
  76152. me.onAnimate(label, {
  76153. to: {
  76154. x: x,
  76155. y: y
  76156. }
  76157. });
  76158. } else {
  76159. label.setAttributes({
  76160. x: x,
  76161. y: y
  76162. }, true);
  76163. if (resizing && me.animation) {
  76164. me.animation.on('afteranimate', function() {
  76165. label.show(true);
  76166. });
  76167. } else {
  76168. label.show(true);
  76169. }
  76170. }
  76171. },
  76172. // @private Overriding highlights.js highlightItem method.
  76173. highlightItem: function() {
  76174. var me = this;
  76175. me.callParent(arguments);
  76176. if (me.line && !me.highlighted) {
  76177. if (!('__strokeWidth' in me.line)) {
  76178. me.line.__strokeWidth = parseFloat(me.line.attr['stroke-width']) || 0;
  76179. }
  76180. if (me.line.__anim) {
  76181. me.line.__anim.paused = true;
  76182. }
  76183. me.line.__anim = Ext.create('Ext.fx.Anim', {
  76184. target: me.line,
  76185. to: {
  76186. 'stroke-width': me.line.__strokeWidth + 3
  76187. }
  76188. });
  76189. me.highlighted = true;
  76190. }
  76191. },
  76192. // @private Overriding highlights.js unHighlightItem method.
  76193. unHighlightItem: function() {
  76194. var me = this;
  76195. me.callParent(arguments);
  76196. if (me.line && me.highlighted) {
  76197. me.line.__anim = Ext.create('Ext.fx.Anim', {
  76198. target: me.line,
  76199. to: {
  76200. 'stroke-width': me.line.__strokeWidth
  76201. }
  76202. });
  76203. me.highlighted = false;
  76204. }
  76205. },
  76206. // @private called when a callout needs to be placed.
  76207. onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
  76208. if (!display) {
  76209. return;
  76210. }
  76211. var me = this,
  76212. chart = me.chart,
  76213. surface = chart.surface,
  76214. resizing = chart.resizing,
  76215. config = me.callouts,
  76216. items = me.items,
  76217. prev = i == 0? false : items[i -1].point,
  76218. next = (i == items.length -1)? false : items[i +1].point,
  76219. cur = [+item.point[0], +item.point[1]],
  76220. dir, norm, normal, a, aprev, anext,
  76221. offsetFromViz = config.offsetFromViz || 30,
  76222. offsetToSide = config.offsetToSide || 10,
  76223. offsetBox = config.offsetBox || 3,
  76224. boxx, boxy, boxw, boxh,
  76225. p, clipRect = me.clipRect,
  76226. bbox = {
  76227. width: config.styles.width || 10,
  76228. height: config.styles.height || 10
  76229. },
  76230. x, y;
  76231. //get the right two points
  76232. if (!prev) {
  76233. prev = cur;
  76234. }
  76235. if (!next) {
  76236. next = cur;
  76237. }
  76238. a = (next[1] - prev[1]) / (next[0] - prev[0]);
  76239. aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
  76240. anext = (next[1] - cur[1]) / (next[0] - cur[0]);
  76241. norm = Math.sqrt(1 + a * a);
  76242. dir = [1 / norm, a / norm];
  76243. normal = [-dir[1], dir[0]];
  76244. //keep the label always on the outer part of the "elbow"
  76245. if (aprev > 0 && anext < 0 && normal[1] < 0
  76246. || aprev < 0 && anext > 0 && normal[1] > 0) {
  76247. normal[0] *= -1;
  76248. normal[1] *= -1;
  76249. } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0
  76250. || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
  76251. normal[0] *= -1;
  76252. normal[1] *= -1;
  76253. }
  76254. //position
  76255. x = cur[0] + normal[0] * offsetFromViz;
  76256. y = cur[1] + normal[1] * offsetFromViz;
  76257. //box position and dimensions
  76258. boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
  76259. boxy = y - bbox.height /2 - offsetBox;
  76260. boxw = bbox.width + 2 * offsetBox;
  76261. boxh = bbox.height + 2 * offsetBox;
  76262. //now check if we're out of bounds and invert the normal vector correspondingly
  76263. //this may add new overlaps between labels (but labels won't be out of bounds).
  76264. if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
  76265. normal[0] *= -1;
  76266. }
  76267. if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
  76268. normal[1] *= -1;
  76269. }
  76270. //update positions
  76271. x = cur[0] + normal[0] * offsetFromViz;
  76272. y = cur[1] + normal[1] * offsetFromViz;
  76273. //update box position and dimensions
  76274. boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
  76275. boxy = y - bbox.height /2 - offsetBox;
  76276. boxw = bbox.width + 2 * offsetBox;
  76277. boxh = bbox.height + 2 * offsetBox;
  76278. if (chart.animate) {
  76279. //set the line from the middle of the pie to the box.
  76280. me.onAnimate(callout.lines, {
  76281. to: {
  76282. path: ["M", cur[0], cur[1], "L", x, y, "Z"]
  76283. }
  76284. });
  76285. //set component position
  76286. if (callout.panel) {
  76287. callout.panel.setPosition(boxx, boxy, true);
  76288. }
  76289. }
  76290. else {
  76291. //set the line from the middle of the pie to the box.
  76292. callout.lines.setAttributes({
  76293. path: ["M", cur[0], cur[1], "L", x, y, "Z"]
  76294. }, true);
  76295. //set component position
  76296. if (callout.panel) {
  76297. callout.panel.setPosition(boxx, boxy);
  76298. }
  76299. }
  76300. for (p in callout) {
  76301. callout[p].show(true);
  76302. }
  76303. },
  76304. isItemInPoint: function(x, y, item, i) {
  76305. var me = this,
  76306. items = me.items,
  76307. tolerance = me.selectionTolerance,
  76308. result = null,
  76309. prevItem,
  76310. nextItem,
  76311. prevPoint,
  76312. nextPoint,
  76313. ln,
  76314. x1,
  76315. y1,
  76316. x2,
  76317. y2,
  76318. xIntersect,
  76319. yIntersect,
  76320. dist1, dist2, dist, midx, midy,
  76321. sqrt = Math.sqrt, abs = Math.abs;
  76322. nextItem = items[i];
  76323. prevItem = i && items[i - 1];
  76324. if (i >= ln) {
  76325. prevItem = items[ln - 1];
  76326. }
  76327. prevPoint = prevItem && prevItem.point;
  76328. nextPoint = nextItem && nextItem.point;
  76329. x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
  76330. y1 = prevItem ? prevPoint[1] : nextPoint[1];
  76331. x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
  76332. y2 = nextItem ? nextPoint[1] : prevPoint[1];
  76333. dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
  76334. dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
  76335. dist = Math.min(dist1, dist2);
  76336. if (dist <= tolerance) {
  76337. return dist == dist1? prevItem : nextItem;
  76338. }
  76339. return false;
  76340. },
  76341. // @private toggle visibility of all series elements (markers, sprites).
  76342. toggleAll: function(show) {
  76343. var me = this,
  76344. i, ln, shadow, shadows;
  76345. if (!show) {
  76346. Ext.chart.series.Cartesian.prototype.hideAll.call(me);
  76347. }
  76348. else {
  76349. Ext.chart.series.Cartesian.prototype.showAll.call(me);
  76350. }
  76351. if (me.line) {
  76352. me.line.setAttributes({
  76353. hidden: !show
  76354. }, true);
  76355. //hide shadows too
  76356. if (me.line.shadows) {
  76357. for (i = 0, shadows = me.line.shadows, ln = shadows.length; i < ln; i++) {
  76358. shadow = shadows[i];
  76359. shadow.setAttributes({
  76360. hidden: !show
  76361. }, true);
  76362. }
  76363. }
  76364. }
  76365. if (me.fillPath) {
  76366. me.fillPath.setAttributes({
  76367. hidden: !show
  76368. }, true);
  76369. }
  76370. },
  76371. // @private hide all series elements (markers, sprites).
  76372. hideAll: function() {
  76373. this.toggleAll(false);
  76374. },
  76375. // @private hide all series elements (markers, sprites).
  76376. showAll: function() {
  76377. this.toggleAll(true);
  76378. }
  76379. });
  76380. /**
  76381. * @class Ext.chart.series.Pie
  76382. *
  76383. * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different
  76384. * categories that also have a meaning as a whole.
  76385. * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart
  76386. * documentation for more information. A typical configuration object for the pie series could be:
  76387. *
  76388. * @example
  76389. * var store = Ext.create('Ext.data.JsonStore', {
  76390. * fields: ['name', 'data'],
  76391. * data: [
  76392. * { 'name': 'metric one', 'data': 10 },
  76393. * { 'name': 'metric two', 'data': 7 },
  76394. * { 'name': 'metric three', 'data': 5 },
  76395. * { 'name': 'metric four', 'data': 2 },
  76396. * { 'name': 'metric five', 'data': 27 }
  76397. * ]
  76398. * });
  76399. *
  76400. * Ext.create('Ext.chart.Chart', {
  76401. * renderTo: Ext.getBody(),
  76402. * width: 500,
  76403. * height: 350,
  76404. * animate: true,
  76405. * store: store,
  76406. * theme: 'Base:gradients',
  76407. * series: [{
  76408. * type: 'pie',
  76409. * angleField: 'data',
  76410. * showInLegend: true,
  76411. * tips: {
  76412. * trackMouse: true,
  76413. * width: 140,
  76414. * height: 28,
  76415. * renderer: function(storeItem, item) {
  76416. * // calculate and display percentage on hover
  76417. * var total = 0;
  76418. * store.each(function(rec) {
  76419. * total += rec.get('data');
  76420. * });
  76421. * this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data') / total * 100) + '%');
  76422. * }
  76423. * },
  76424. * highlight: {
  76425. * segment: {
  76426. * margin: 20
  76427. * }
  76428. * },
  76429. * label: {
  76430. * field: 'name',
  76431. * display: 'rotate',
  76432. * contrast: true,
  76433. * font: '18px Arial'
  76434. * }
  76435. * }]
  76436. * });
  76437. *
  76438. * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options
  76439. * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item.
  76440. *
  76441. * We set `data` as the value of the field to determine the angle span for each pie slice. We also set a label configuration object
  76442. * where we set the field name of the store field to be renderer as text for the label. The labels will also be displayed rotated.
  76443. *
  76444. * We set `contrast` to `true` to flip the color of the label if it is to similar to the background color. Finally, we set the font family
  76445. * and size through the `font` parameter.
  76446. *
  76447. * @xtype pie
  76448. */
  76449. Ext.define('Ext.chart.series.Pie', {
  76450. /* Begin Definitions */
  76451. alternateClassName: ['Ext.chart.PieSeries', 'Ext.chart.PieChart'],
  76452. extend: 'Ext.chart.series.Series',
  76453. /* End Definitions */
  76454. type: "pie",
  76455. alias: 'series.pie',
  76456. accuracy: 100000,
  76457. rad: Math.PI * 2 / 100000,
  76458. /**
  76459. * @cfg {Number} highlightDuration
  76460. * The duration for the pie slice highlight effect.
  76461. */
  76462. highlightDuration: 150,
  76463. /**
  76464. * @cfg {String} angleField (required)
  76465. * The store record field name to be used for the pie angles.
  76466. * The values bound to this field name must be positive real numbers.
  76467. */
  76468. angleField: false,
  76469. /**
  76470. * @cfg {String} field
  76471. * Alias for {@link #angleField}.
  76472. */
  76473. /**
  76474. * @cfg {String} xField
  76475. * Alias for {@link #angleField}.
  76476. */
  76477. /**
  76478. * @cfg {String} lengthField
  76479. * The store record field name to be used for the pie slice lengths.
  76480. * The values bound to this field name must be positive real numbers.
  76481. */
  76482. lengthField: false,
  76483. /**
  76484. * @cfg {Boolean/Number} donut
  76485. * Whether to set the pie chart as donut chart.
  76486. * Default's false. Can be set to a particular percentage to set the radius
  76487. * of the donut chart.
  76488. */
  76489. donut: false,
  76490. /**
  76491. * @cfg {Boolean} showInLegend
  76492. * Whether to add the pie chart elements as legend items. Default's false.
  76493. */
  76494. showInLegend: false,
  76495. /**
  76496. * @cfg {Array} colorSet
  76497. * An array of color values which will be used, in order, as the pie slice fill colors.
  76498. */
  76499. /**
  76500. * @cfg {Object} style
  76501. * An object containing styles for overriding series styles from Theming.
  76502. */
  76503. style: {},
  76504. constructor: function(config) {
  76505. this.callParent(arguments);
  76506. var me = this,
  76507. chart = me.chart,
  76508. surface = chart.surface,
  76509. store = chart.store,
  76510. shadow = chart.shadow, i, l, cfg;
  76511. config.highlightCfg = Ext.merge({
  76512. segment: {
  76513. margin: 20
  76514. }
  76515. }, config.highlightCfg);
  76516. Ext.apply(me, config, {
  76517. shadowAttributes: [{
  76518. "stroke-width": 6,
  76519. "stroke-opacity": 1,
  76520. stroke: 'rgb(200, 200, 200)',
  76521. translate: {
  76522. x: 1.2,
  76523. y: 2
  76524. }
  76525. },
  76526. {
  76527. "stroke-width": 4,
  76528. "stroke-opacity": 1,
  76529. stroke: 'rgb(150, 150, 150)',
  76530. translate: {
  76531. x: 0.9,
  76532. y: 1.5
  76533. }
  76534. },
  76535. {
  76536. "stroke-width": 2,
  76537. "stroke-opacity": 1,
  76538. stroke: 'rgb(100, 100, 100)',
  76539. translate: {
  76540. x: 0.6,
  76541. y: 1
  76542. }
  76543. }]
  76544. });
  76545. me.group = surface.getGroup(me.seriesId);
  76546. if (shadow) {
  76547. for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
  76548. me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
  76549. }
  76550. }
  76551. surface.customAttributes.segment = function(opt) {
  76552. //Browsers will complain if we create a path
  76553. //element that has no path commands. So ensure a dummy
  76554. //path command for an empty path.
  76555. var ans = me.getSegment(opt);
  76556. if (!ans.path || ans.path.length === 0) {
  76557. ans.path = ['M', 0, 0];
  76558. }
  76559. return ans;
  76560. };
  76561. me.__excludes = me.__excludes || [];
  76562. },
  76563. // @private updates some onbefore render parameters.
  76564. initialize: function() {
  76565. var me = this,
  76566. store = me.chart.getChartStore(),
  76567. data = store.data.items,
  76568. i, ln, rec;
  76569. //Add yFields to be used in Legend.js
  76570. me.yField = [];
  76571. if (me.label.field) {
  76572. for (i = 0, ln = data.length; i < ln; i++) {
  76573. rec = data[i];
  76574. me.yField.push(rec.get(me.label.field));
  76575. }
  76576. }
  76577. },
  76578. // @private returns an object with properties for a PieSlice.
  76579. getSegment: function(opt) {
  76580. var me = this,
  76581. rad = me.rad,
  76582. cos = Math.cos,
  76583. sin = Math.sin,
  76584. x = me.centerX,
  76585. y = me.centerY,
  76586. x1 = 0, x2 = 0, x3 = 0, x4 = 0,
  76587. y1 = 0, y2 = 0, y3 = 0, y4 = 0,
  76588. x5 = 0, y5 = 0, x6 = 0, y6 = 0,
  76589. delta = 1e-2,
  76590. startAngle = opt.startAngle,
  76591. endAngle = opt.endAngle,
  76592. midAngle = (startAngle + endAngle) / 2 * rad,
  76593. margin = opt.margin || 0,
  76594. a1 = Math.min(startAngle, endAngle) * rad,
  76595. a2 = Math.max(startAngle, endAngle) * rad,
  76596. c1 = cos(a1), s1 = sin(a1),
  76597. c2 = cos(a2), s2 = sin(a2),
  76598. cm = cos(midAngle), sm = sin(midAngle),
  76599. flag = 0, hsqr2 = 0.7071067811865476; // sqrt(0.5)
  76600. if (a2 - a1 < delta) {
  76601. return {path: ""};
  76602. }
  76603. if (margin !== 0) {
  76604. x += margin * cm;
  76605. y += margin * sm;
  76606. }
  76607. x2 = x + opt.endRho * c1;
  76608. y2 = y + opt.endRho * s1;
  76609. x4 = x + opt.endRho * c2;
  76610. y4 = y + opt.endRho * s2;
  76611. x6 = x + opt.endRho * cm;
  76612. y6 = y + opt.endRho * sm;
  76613. if (opt.startRho !== 0) {
  76614. x1 = x + opt.startRho * c1;
  76615. y1 = y + opt.startRho * s1;
  76616. x3 = x + opt.startRho * c2;
  76617. y3 = y + opt.startRho * s2;
  76618. x5 = x + opt.startRho * cm;
  76619. y5 = y + opt.startRho * sm;
  76620. return {
  76621. path: [
  76622. ["M", x2, y2],
  76623. ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
  76624. ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
  76625. ["L", x3, y3],
  76626. ["A", opt.startRho, opt.startRho, 0, flag, 0, x5, y5], ["L", x5, y5],
  76627. ["A", opt.startRho, opt.startRho, 0, 0, 0, x1, y1], ["L", x1, y1],
  76628. ["Z"]
  76629. ]
  76630. };
  76631. } else {
  76632. return {
  76633. path: [
  76634. ["M", x, y],
  76635. ["L", x2, y2],
  76636. ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
  76637. ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
  76638. ["L", x, y],
  76639. ["Z"]
  76640. ]
  76641. };
  76642. }
  76643. },
  76644. // @private utility function to calculate the middle point of a pie slice.
  76645. calcMiddle: function(item) {
  76646. var me = this,
  76647. rad = me.rad,
  76648. slice = item.slice,
  76649. x = me.centerX,
  76650. y = me.centerY,
  76651. startAngle = slice.startAngle,
  76652. endAngle = slice.endAngle,
  76653. donut = +me.donut,
  76654. midAngle = -(startAngle + endAngle) * rad / 2,
  76655. r = (item.endRho + item.startRho) / 2,
  76656. xm = x + r * Math.cos(midAngle),
  76657. ym = y - r * Math.sin(midAngle);
  76658. item.middle = {
  76659. x: xm,
  76660. y: ym
  76661. };
  76662. },
  76663. /**
  76664. * Draws the series for the current chart.
  76665. */
  76666. drawSeries: function() {
  76667. var me = this,
  76668. store = me.chart.getChartStore(),
  76669. data = store.data.items,
  76670. record,
  76671. group = me.group,
  76672. animate = me.chart.animate,
  76673. field = me.angleField || me.field || me.xField,
  76674. lenField = [].concat(me.lengthField),
  76675. totalLenField = 0,
  76676. chart = me.chart,
  76677. surface = chart.surface,
  76678. chartBBox = chart.chartBBox,
  76679. enableShadows = chart.shadow,
  76680. shadowGroups = me.shadowGroups,
  76681. shadowAttributes = me.shadowAttributes,
  76682. lnsh = shadowGroups.length,
  76683. layers = lenField.length,
  76684. rhoAcum = 0,
  76685. donut = +me.donut,
  76686. layerTotals = [],
  76687. items = [],
  76688. totalField = 0,
  76689. maxLenField = 0,
  76690. angle = 0,
  76691. seriesStyle = me.seriesStyle,
  76692. colorArrayStyle = me.colorArrayStyle,
  76693. colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
  76694. rendererAttributes,
  76695. shadowAttr,
  76696. shadows,
  76697. shadow,
  76698. shindex,
  76699. centerX,
  76700. centerY,
  76701. deltaRho,
  76702. first = 0,
  76703. slice,
  76704. slices,
  76705. sprite,
  76706. value,
  76707. item,
  76708. lenValue,
  76709. ln,
  76710. i,
  76711. j,
  76712. endAngle,
  76713. path,
  76714. p,
  76715. spriteOptions, bbox;
  76716. Ext.apply(seriesStyle, me.style || {});
  76717. me.setBBox();
  76718. bbox = me.bbox;
  76719. //override theme colors
  76720. if (me.colorSet) {
  76721. colorArrayStyle = me.colorSet;
  76722. colorArrayLength = colorArrayStyle.length;
  76723. }
  76724. //if not store or store is empty then there's nothing to draw
  76725. if (!store || !store.getCount() || me.seriesIsHidden) {
  76726. me.hide();
  76727. me.items = [];
  76728. return;
  76729. }
  76730. me.unHighlightItem();
  76731. me.cleanHighlights();
  76732. centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
  76733. centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
  76734. me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
  76735. me.slices = slices = [];
  76736. me.items = items = [];
  76737. for (i = 0, ln = data.length; i < ln; i++) {
  76738. record = data[i];
  76739. if (this.__excludes && this.__excludes[i]) {
  76740. //hidden series
  76741. continue;
  76742. }
  76743. totalField += +record.get(field);
  76744. if (lenField[0]) {
  76745. for (j = 0, totalLenField = 0; j < layers; j++) {
  76746. totalLenField += +record.get(lenField[j]);
  76747. }
  76748. layerTotals[i] = totalLenField;
  76749. maxLenField = Math.max(maxLenField, totalLenField);
  76750. }
  76751. }
  76752. totalField = totalField || 1;
  76753. for (i = 0, ln = data.length; i < ln; i++) {
  76754. record = data[i];
  76755. if (this.__excludes && this.__excludes[i]) {
  76756. value = 0;
  76757. } else {
  76758. value = record.get(field);
  76759. if (first == 0) {
  76760. first = 1;
  76761. }
  76762. }
  76763. // First slice
  76764. if (first == 1) {
  76765. first = 2;
  76766. me.firstAngle = angle = me.accuracy * value / totalField / 2;
  76767. for (j = 0; j < i; j++) {
  76768. slices[j].startAngle = slices[j].endAngle = me.firstAngle;
  76769. }
  76770. }
  76771. endAngle = angle - me.accuracy * value / totalField;
  76772. slice = {
  76773. series: me,
  76774. value: value,
  76775. startAngle: angle,
  76776. endAngle: endAngle,
  76777. storeItem: record
  76778. };
  76779. if (lenField[0]) {
  76780. lenValue = +layerTotals[i];
  76781. //removing the floor will break Opera 11.6*
  76782. slice.rho = Math.floor(me.radius / maxLenField * lenValue);
  76783. } else {
  76784. slice.rho = me.radius;
  76785. }
  76786. slices[i] = slice;
  76787. // Do not remove this closure for the sake of https://sencha.jira.com/browse/EXTJSIV-5836
  76788. (function () {
  76789. angle = endAngle;
  76790. })();
  76791. }
  76792. //do all shadows first.
  76793. if (enableShadows) {
  76794. for (i = 0, ln = slices.length; i < ln; i++) {
  76795. slice = slices[i];
  76796. slice.shadowAttrs = [];
  76797. for (j = 0, rhoAcum = 0, shadows = []; j < layers; j++) {
  76798. sprite = group.getAt(i * layers + j);
  76799. deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
  76800. //set pie slice properties
  76801. rendererAttributes = {
  76802. segment: {
  76803. startAngle: slice.startAngle,
  76804. endAngle: slice.endAngle,
  76805. margin: 0,
  76806. rho: slice.rho,
  76807. startRho: rhoAcum + (deltaRho * donut / 100),
  76808. endRho: rhoAcum + deltaRho
  76809. },
  76810. hidden: !slice.value && (slice.startAngle % me.accuracy) == (slice.endAngle % me.accuracy)
  76811. };
  76812. //create shadows
  76813. for (shindex = 0, shadows = []; shindex < lnsh; shindex++) {
  76814. shadowAttr = shadowAttributes[shindex];
  76815. shadow = shadowGroups[shindex].getAt(i);
  76816. if (!shadow) {
  76817. shadow = chart.surface.add(Ext.apply({}, {
  76818. type: 'path',
  76819. group: shadowGroups[shindex],
  76820. strokeLinejoin: "round"
  76821. }, rendererAttributes, shadowAttr));
  76822. }
  76823. shadowAttr = me.renderer(shadow, store.getAt(i), Ext.apply({}, rendererAttributes, shadowAttr), i, store);
  76824. if (animate) {
  76825. me.onAnimate(shadow, {
  76826. to: shadowAttr
  76827. });
  76828. } else {
  76829. shadow.setAttributes(shadowAttr, true);
  76830. }
  76831. shadows.push(shadow);
  76832. }
  76833. slice.shadowAttrs[j] = shadows;
  76834. }
  76835. }
  76836. }
  76837. //do pie slices after.
  76838. for (i = 0, ln = slices.length; i < ln; i++) {
  76839. slice = slices[i];
  76840. for (j = 0, rhoAcum = 0; j < layers; j++) {
  76841. sprite = group.getAt(i * layers + j);
  76842. deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
  76843. //set pie slice properties
  76844. rendererAttributes = Ext.apply({
  76845. segment: {
  76846. startAngle: slice.startAngle,
  76847. endAngle: slice.endAngle,
  76848. margin: 0,
  76849. rho: slice.rho,
  76850. startRho: rhoAcum + (deltaRho * donut / 100),
  76851. endRho: rhoAcum + deltaRho
  76852. },
  76853. hidden: (!slice.value && (slice.startAngle % me.accuracy) == (slice.endAngle % me.accuracy))
  76854. }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
  76855. item = Ext.apply({},
  76856. rendererAttributes.segment, {
  76857. slice: slice,
  76858. series: me,
  76859. storeItem: slice.storeItem,
  76860. index: i
  76861. });
  76862. me.calcMiddle(item);
  76863. if (enableShadows) {
  76864. item.shadows = slice.shadowAttrs[j];
  76865. }
  76866. items[i] = item;
  76867. // Create a new sprite if needed (no height)
  76868. if (!sprite) {
  76869. spriteOptions = Ext.apply({
  76870. type: "path",
  76871. group: group,
  76872. middle: item.middle
  76873. }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
  76874. sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
  76875. }
  76876. slice.sprite = slice.sprite || [];
  76877. item.sprite = sprite;
  76878. slice.sprite.push(sprite);
  76879. slice.point = [item.middle.x, item.middle.y];
  76880. if (animate) {
  76881. rendererAttributes = me.renderer(sprite, store.getAt(i), rendererAttributes, i, store);
  76882. sprite._to = rendererAttributes;
  76883. sprite._animating = true;
  76884. me.onAnimate(sprite, {
  76885. to: rendererAttributes,
  76886. listeners: {
  76887. afteranimate: {
  76888. fn: function() {
  76889. this._animating = false;
  76890. },
  76891. scope: sprite
  76892. }
  76893. }
  76894. });
  76895. } else {
  76896. rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(rendererAttributes, {
  76897. hidden: false
  76898. }), i, store);
  76899. sprite.setAttributes(rendererAttributes, true);
  76900. }
  76901. rhoAcum += deltaRho;
  76902. }
  76903. }
  76904. // Hide unused bars
  76905. ln = group.getCount();
  76906. for (i = 0; i < ln; i++) {
  76907. if (!slices[(i / layers) >> 0] && group.getAt(i)) {
  76908. group.getAt(i).hide(true);
  76909. }
  76910. }
  76911. if (enableShadows) {
  76912. lnsh = shadowGroups.length;
  76913. for (shindex = 0; shindex < ln; shindex++) {
  76914. if (!slices[(shindex / layers) >> 0]) {
  76915. for (j = 0; j < lnsh; j++) {
  76916. if (shadowGroups[j].getAt(shindex)) {
  76917. shadowGroups[j].getAt(shindex).hide(true);
  76918. }
  76919. }
  76920. }
  76921. }
  76922. }
  76923. me.renderLabels();
  76924. me.renderCallouts();
  76925. },
  76926. // @private callback for when creating a label sprite.
  76927. onCreateLabel: function(storeItem, item, i, display) {
  76928. var me = this,
  76929. group = me.labelsGroup,
  76930. config = me.label,
  76931. centerX = me.centerX,
  76932. centerY = me.centerY,
  76933. middle = item.middle,
  76934. endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config || {});
  76935. return me.chart.surface.add(Ext.apply({
  76936. 'type': 'text',
  76937. 'text-anchor': 'middle',
  76938. 'group': group,
  76939. 'x': middle.x,
  76940. 'y': middle.y
  76941. }, endLabelStyle));
  76942. },
  76943. // @private callback for when placing a label sprite.
  76944. onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
  76945. var me = this,
  76946. chart = me.chart,
  76947. resizing = chart.resizing,
  76948. config = me.label,
  76949. format = config.renderer,
  76950. field = [].concat(config.field),
  76951. centerX = me.centerX,
  76952. centerY = me.centerY,
  76953. middle = item.middle,
  76954. opt = {
  76955. x: middle.x,
  76956. y: middle.y
  76957. },
  76958. x = middle.x - centerX,
  76959. y = middle.y - centerY,
  76960. from = {},
  76961. rho = 1,
  76962. theta = Math.atan2(y, x || 1),
  76963. dg = theta * 180 / Math.PI,
  76964. prevDg;
  76965. opt.hidden = false;
  76966. if (this.__excludes && this.__excludes[i]) {
  76967. opt.hidden = true;
  76968. }
  76969. function fixAngle(a) {
  76970. if (a < 0) {
  76971. a += 360;
  76972. }
  76973. return a % 360;
  76974. }
  76975. label.setAttributes({
  76976. text: format(storeItem.get(field[index]))
  76977. }, true);
  76978. switch (display) {
  76979. case 'outside':
  76980. rho = Math.sqrt(x * x + y * y) * 2;
  76981. //update positions
  76982. opt.x = rho * Math.cos(theta) + centerX;
  76983. opt.y = rho * Math.sin(theta) + centerY;
  76984. break;
  76985. case 'rotate':
  76986. dg = fixAngle(dg);
  76987. dg = (dg > 90 && dg < 270) ? dg + 180: dg;
  76988. prevDg = label.attr.rotation.degrees;
  76989. if (prevDg != null && Math.abs(prevDg - dg) > 180 * 0.5) {
  76990. if (dg > prevDg) {
  76991. dg -= 360;
  76992. } else {
  76993. dg += 360;
  76994. }
  76995. dg = dg % 360;
  76996. } else {
  76997. dg = fixAngle(dg);
  76998. }
  76999. //update rotation angle
  77000. opt.rotate = {
  77001. degrees: dg,
  77002. x: opt.x,
  77003. y: opt.y
  77004. };
  77005. break;
  77006. default:
  77007. break;
  77008. }
  77009. //ensure the object has zero translation
  77010. opt.translate = {
  77011. x: 0, y: 0
  77012. };
  77013. if (animate && !resizing && (display != 'rotate' || prevDg != null)) {
  77014. me.onAnimate(label, {
  77015. to: opt
  77016. });
  77017. } else {
  77018. label.setAttributes(opt, true);
  77019. }
  77020. label._from = from;
  77021. },
  77022. // @private callback for when placing a callout sprite.
  77023. onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
  77024. var me = this,
  77025. chart = me.chart,
  77026. centerX = me.centerX,
  77027. centerY = me.centerY,
  77028. middle = item.middle,
  77029. opt = {
  77030. x: middle.x,
  77031. y: middle.y
  77032. },
  77033. x = middle.x - centerX,
  77034. y = middle.y - centerY,
  77035. rho = 1,
  77036. rhoCenter,
  77037. theta = Math.atan2(y, x || 1),
  77038. bbox = callout.label.getBBox(),
  77039. offsetFromViz = 20,
  77040. offsetToSide = 10,
  77041. offsetBox = 10,
  77042. p;
  77043. //should be able to config this.
  77044. rho = item.endRho + offsetFromViz;
  77045. rhoCenter = (item.endRho + item.startRho) / 2 + (item.endRho - item.startRho) / 3;
  77046. //update positions
  77047. opt.x = rho * Math.cos(theta) + centerX;
  77048. opt.y = rho * Math.sin(theta) + centerY;
  77049. x = rhoCenter * Math.cos(theta);
  77050. y = rhoCenter * Math.sin(theta);
  77051. if (chart.animate) {
  77052. //set the line from the middle of the pie to the box.
  77053. me.onAnimate(callout.lines, {
  77054. to: {
  77055. path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
  77056. }
  77057. });
  77058. //set box position
  77059. me.onAnimate(callout.box, {
  77060. to: {
  77061. x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
  77062. y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
  77063. width: bbox.width + 2 * offsetBox,
  77064. height: bbox.height + 2 * offsetBox
  77065. }
  77066. });
  77067. //set text position
  77068. me.onAnimate(callout.label, {
  77069. to: {
  77070. x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
  77071. y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
  77072. }
  77073. });
  77074. } else {
  77075. //set the line from the middle of the pie to the box.
  77076. callout.lines.setAttributes({
  77077. path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
  77078. },
  77079. true);
  77080. //set box position
  77081. callout.box.setAttributes({
  77082. x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
  77083. y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
  77084. width: bbox.width + 2 * offsetBox,
  77085. height: bbox.height + 2 * offsetBox
  77086. },
  77087. true);
  77088. //set text position
  77089. callout.label.setAttributes({
  77090. x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
  77091. y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
  77092. },
  77093. true);
  77094. }
  77095. for (p in callout) {
  77096. callout[p].show(true);
  77097. }
  77098. },
  77099. // @private handles sprite animation for the series.
  77100. onAnimate: function(sprite, attr) {
  77101. sprite.show();
  77102. return this.callParent(arguments);
  77103. },
  77104. isItemInPoint: function(x, y, item, i) {
  77105. var me = this,
  77106. cx = me.centerX,
  77107. cy = me.centerY,
  77108. abs = Math.abs,
  77109. dx = abs(x - cx),
  77110. dy = abs(y - cy),
  77111. startAngle = item.startAngle,
  77112. endAngle = item.endAngle,
  77113. rho = Math.sqrt(dx * dx + dy * dy),
  77114. angle = Math.atan2(y - cy, x - cx) / me.rad;
  77115. // normalize to the same range of angles created by drawSeries
  77116. if (angle > me.firstAngle) {
  77117. angle -= me.accuracy;
  77118. }
  77119. return (angle <= startAngle && angle > endAngle
  77120. && rho >= item.startRho && rho <= item.endRho);
  77121. },
  77122. // @private hides all elements in the series.
  77123. hideAll: function(index) {
  77124. var i, l, shadow, shadows, sh, lsh, sprite;
  77125. index = (isNaN(this._index) ? index : this._index) || 0;
  77126. this.__excludes = this.__excludes || [];
  77127. this.__excludes[index] = true;
  77128. sprite = this.slices[index].sprite;
  77129. for (sh = 0, lsh = sprite.length; sh < lsh; sh++) {
  77130. sprite[sh].setAttributes({
  77131. hidden: true
  77132. }, true);
  77133. }
  77134. if (this.slices[index].shadowAttrs) {
  77135. for (i = 0, shadows = this.slices[index].shadowAttrs, l = shadows.length; i < l; i++) {
  77136. shadow = shadows[i];
  77137. for (sh = 0, lsh = shadow.length; sh < lsh; sh++) {
  77138. shadow[sh].setAttributes({
  77139. hidden: true
  77140. }, true);
  77141. }
  77142. }
  77143. }
  77144. this.drawSeries();
  77145. },
  77146. // @private shows all elements in the series.
  77147. showAll: function(index) {
  77148. index = (isNaN(this._index) ? index : this._index) || 0;
  77149. this.__excludes[index] = false;
  77150. this.drawSeries();
  77151. },
  77152. /**
  77153. * Highlight the specified item. If no item is provided the whole series will be highlighted.
  77154. * @param item {Object} Info about the item; same format as returned by #getItemForPoint
  77155. */
  77156. highlightItem: function(item) {
  77157. var me = this,
  77158. rad = me.rad,
  77159. highlightSegment,
  77160. animate,
  77161. attrs,
  77162. i,
  77163. shadows,
  77164. shadow,
  77165. ln,
  77166. to,
  77167. itemHighlightSegment,
  77168. prop,
  77169. group,
  77170. display,
  77171. label,
  77172. middle,
  77173. r,
  77174. x,
  77175. y;
  77176. item = item || this.items[this._index];
  77177. //TODO(nico): sometimes in IE itemmouseover is triggered
  77178. //twice without triggering itemmouseout in between. This
  77179. //fixes the highlighting bug. Eventually, events should be
  77180. //changed to trigger one itemmouseout between two itemmouseovers.
  77181. this.unHighlightItem();
  77182. if (!item || me.animating || (item.sprite && item.sprite._animating)) {
  77183. return;
  77184. }
  77185. me.callParent([item]);
  77186. if (!me.highlight) {
  77187. return;
  77188. }
  77189. if ('segment' in me.highlightCfg) {
  77190. highlightSegment = me.highlightCfg.segment;
  77191. animate = me.chart.animate;
  77192. //animate labels
  77193. if (me.labelsGroup) {
  77194. group = me.labelsGroup;
  77195. display = me.label.display;
  77196. label = group.getAt(item.index);
  77197. middle = (item.startAngle + item.endAngle) / 2 * rad;
  77198. r = highlightSegment.margin || 0;
  77199. x = r * Math.cos(middle);
  77200. y = r * Math.sin(middle);
  77201. //TODO(nico): rounding to 1e-10
  77202. //gives the right translation. Translation
  77203. //was buggy for very small numbers. In this
  77204. //case we're not looking to translate to very small
  77205. //numbers but not to translate at all.
  77206. if (Math.abs(x) < 1e-10) {
  77207. x = 0;
  77208. }
  77209. if (Math.abs(y) < 1e-10) {
  77210. y = 0;
  77211. }
  77212. if (animate) {
  77213. label.stopAnimation();
  77214. label.animate({
  77215. to: {
  77216. translate: {
  77217. x: x,
  77218. y: y
  77219. }
  77220. },
  77221. duration: me.highlightDuration
  77222. });
  77223. }
  77224. else {
  77225. label.setAttributes({
  77226. translate: {
  77227. x: x,
  77228. y: y
  77229. }
  77230. }, true);
  77231. }
  77232. }
  77233. //animate shadows
  77234. if (me.chart.shadow && item.shadows) {
  77235. i = 0;
  77236. shadows = item.shadows;
  77237. ln = shadows.length;
  77238. for (; i < ln; i++) {
  77239. shadow = shadows[i];
  77240. to = {};
  77241. itemHighlightSegment = item.sprite._from.segment;
  77242. for (prop in itemHighlightSegment) {
  77243. if (! (prop in highlightSegment)) {
  77244. to[prop] = itemHighlightSegment[prop];
  77245. }
  77246. }
  77247. attrs = {
  77248. segment: Ext.applyIf(to, me.highlightCfg.segment)
  77249. };
  77250. if (animate) {
  77251. shadow.stopAnimation();
  77252. shadow.animate({
  77253. to: attrs,
  77254. duration: me.highlightDuration
  77255. });
  77256. }
  77257. else {
  77258. shadow.setAttributes(attrs, true);
  77259. }
  77260. }
  77261. }
  77262. }
  77263. },
  77264. /**
  77265. * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
  77266. * @param item {Object} Info about the item; same format as returned by #getItemForPoint
  77267. */
  77268. unHighlightItem: function() {
  77269. var me = this,
  77270. items,
  77271. animate,
  77272. shadowsEnabled,
  77273. group,
  77274. len,
  77275. i,
  77276. j,
  77277. display,
  77278. shadowLen,
  77279. p,
  77280. to,
  77281. ihs,
  77282. hs,
  77283. sprite,
  77284. shadows,
  77285. shadow,
  77286. item,
  77287. label,
  77288. attrs;
  77289. if (!me.highlight) {
  77290. return;
  77291. }
  77292. if (('segment' in me.highlightCfg) && me.items) {
  77293. items = me.items;
  77294. animate = me.chart.animate;
  77295. shadowsEnabled = !!me.chart.shadow;
  77296. group = me.labelsGroup;
  77297. len = items.length;
  77298. i = 0;
  77299. j = 0;
  77300. display = me.label.display;
  77301. for (; i < len; i++) {
  77302. item = items[i];
  77303. if (!item) {
  77304. continue;
  77305. }
  77306. sprite = item.sprite;
  77307. if (sprite && sprite._highlighted) {
  77308. //animate labels
  77309. if (group) {
  77310. label = group.getAt(item.index);
  77311. attrs = Ext.apply({
  77312. translate: {
  77313. x: 0,
  77314. y: 0
  77315. }
  77316. },
  77317. display == 'rotate' ? {
  77318. rotate: {
  77319. x: label.attr.x,
  77320. y: label.attr.y,
  77321. degrees: label.attr.rotation.degrees
  77322. }
  77323. }: {});
  77324. if (animate) {
  77325. label.stopAnimation();
  77326. label.animate({
  77327. to: attrs,
  77328. duration: me.highlightDuration
  77329. });
  77330. }
  77331. else {
  77332. label.setAttributes(attrs, true);
  77333. }
  77334. }
  77335. if (shadowsEnabled) {
  77336. shadows = item.shadows;
  77337. shadowLen = shadows.length;
  77338. for (; j < shadowLen; j++) {
  77339. to = {};
  77340. ihs = item.sprite._to.segment;
  77341. hs = item.sprite._from.segment;
  77342. Ext.apply(to, hs);
  77343. for (p in ihs) {
  77344. if (! (p in hs)) {
  77345. to[p] = ihs[p];
  77346. }
  77347. }
  77348. shadow = shadows[j];
  77349. if (animate) {
  77350. shadow.stopAnimation();
  77351. shadow.animate({
  77352. to: {
  77353. segment: to
  77354. },
  77355. duration: me.highlightDuration
  77356. });
  77357. }
  77358. else {
  77359. shadow.setAttributes({ segment: to }, true);
  77360. }
  77361. }
  77362. }
  77363. }
  77364. }
  77365. }
  77366. me.callParent(arguments);
  77367. },
  77368. /**
  77369. * Returns the color of the series (to be displayed as color for the series legend item).
  77370. * @param item {Object} Info about the item; same format as returned by #getItemForPoint
  77371. */
  77372. getLegendColor: function(index) {
  77373. var me = this;
  77374. return (me.colorSet && me.colorSet[index % me.colorSet.length]) || me.colorArrayStyle[index % me.colorArrayStyle.length];
  77375. }
  77376. });
  77377. /**
  77378. * @class Ext.chart.series.Radar
  77379. *
  77380. * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for
  77381. * a constrained number of categories.
  77382. *
  77383. * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart
  77384. * documentation for more information. A typical configuration object for the radar series could be:
  77385. *
  77386. * @example
  77387. * var store = Ext.create('Ext.data.JsonStore', {
  77388. * fields: ['name', 'data1', 'data2', 'data3'],
  77389. * data: [
  77390. * { 'name': 'metric one', 'data1': 14, 'data2': 12, 'data3': 13 },
  77391. * { 'name': 'metric two', 'data1': 16, 'data2': 8, 'data3': 3 },
  77392. * { 'name': 'metric three', 'data1': 14, 'data2': 2, 'data3': 7 },
  77393. * { 'name': 'metric four', 'data1': 6, 'data2': 14, 'data3': 23 },
  77394. * { 'name': 'metric five', 'data1': 36, 'data2': 38, 'data3': 33 }
  77395. * ]
  77396. * });
  77397. *
  77398. * Ext.create('Ext.chart.Chart', {
  77399. * renderTo: Ext.getBody(),
  77400. * width: 500,
  77401. * height: 300,
  77402. * animate: true,
  77403. * theme:'Category2',
  77404. * store: store,
  77405. * axes: [{
  77406. * type: 'Radial',
  77407. * position: 'radial',
  77408. * label: {
  77409. * display: true
  77410. * }
  77411. * }],
  77412. * series: [{
  77413. * type: 'radar',
  77414. * xField: 'name',
  77415. * yField: 'data1',
  77416. * showInLegend: true,
  77417. * showMarkers: true,
  77418. * markerConfig: {
  77419. * radius: 5,
  77420. * size: 5
  77421. * },
  77422. * style: {
  77423. * 'stroke-width': 2,
  77424. * fill: 'none'
  77425. * }
  77426. * },{
  77427. * type: 'radar',
  77428. * xField: 'name',
  77429. * yField: 'data2',
  77430. * showMarkers: true,
  77431. * showInLegend: true,
  77432. * markerConfig: {
  77433. * radius: 5,
  77434. * size: 5
  77435. * },
  77436. * style: {
  77437. * 'stroke-width': 2,
  77438. * fill: 'none'
  77439. * }
  77440. * },{
  77441. * type: 'radar',
  77442. * xField: 'name',
  77443. * yField: 'data3',
  77444. * showMarkers: true,
  77445. * showInLegend: true,
  77446. * markerConfig: {
  77447. * radius: 5,
  77448. * size: 5
  77449. * },
  77450. * style: {
  77451. * 'stroke-width': 2,
  77452. * fill: 'none'
  77453. * }
  77454. * }]
  77455. * });
  77456. *
  77457. * In this configuration we add three series to the chart. Each of these series is bound to the same
  77458. * categories field, `name` but bound to different properties for each category, `data1`, `data2` and
  77459. * `data3` respectively. All series display markers by having `showMarkers` enabled. The configuration
  77460. * for the markers of each series can be set by adding properties onto the markerConfig object.
  77461. * Finally we override some theme styling properties by adding properties to the `style` object.
  77462. *
  77463. * @xtype radar
  77464. */
  77465. Ext.define('Ext.chart.series.Radar', {
  77466. /* Begin Definitions */
  77467. extend: 'Ext.chart.series.Series',
  77468. requires: ['Ext.chart.Shape', 'Ext.fx.Anim'],
  77469. /* End Definitions */
  77470. type: "radar",
  77471. alias: 'series.radar',
  77472. rad: Math.PI / 180,
  77473. showInLegend: false,
  77474. /**
  77475. * @cfg {Object} style
  77476. * An object containing styles for overriding series styles from Theming.
  77477. */
  77478. style: {},
  77479. constructor: function(config) {
  77480. this.callParent(arguments);
  77481. var me = this,
  77482. surface = me.chart.surface, i, l;
  77483. me.group = surface.getGroup(me.seriesId);
  77484. if (me.showMarkers) {
  77485. me.markerGroup = surface.getGroup(me.seriesId + '-markers');
  77486. }
  77487. },
  77488. /**
  77489. * Draws the series for the current chart.
  77490. */
  77491. drawSeries: function() {
  77492. var me = this,
  77493. store = me.chart.getChartStore(),
  77494. data = store.data.items,
  77495. d, record,
  77496. group = me.group,
  77497. sprite,
  77498. chart = me.chart,
  77499. seriesItems = chart.series.items,
  77500. s, sLen, series,
  77501. animate = chart.animate,
  77502. field = me.field || me.yField,
  77503. surface = chart.surface,
  77504. chartBBox = chart.chartBBox,
  77505. seriesIdx = me.seriesIdx,
  77506. colorArrayStyle = me.colorArrayStyle,
  77507. centerX, centerY,
  77508. items,
  77509. radius,
  77510. maxValue = 0,
  77511. fields = [],
  77512. max = Math.max,
  77513. cos = Math.cos,
  77514. sin = Math.sin,
  77515. pi2 = Math.PI * 2,
  77516. l = store.getCount(),
  77517. startPath, path, x, y, rho,
  77518. i, nfields,
  77519. seriesStyle = me.seriesStyle,
  77520. seriesLabelStyle = me.seriesLabelStyle,
  77521. first = chart.resizing || !me.radar,
  77522. axis = chart.axes && chart.axes.get(0),
  77523. aggregate = !(axis && axis.maximum);
  77524. me.setBBox();
  77525. maxValue = aggregate? 0 : (axis.maximum || 0);
  77526. Ext.apply(seriesStyle, me.style || {});
  77527. //if the store is empty then there's nothing to draw
  77528. if (!store || !store.getCount() || me.seriesIsHidden) {
  77529. me.hide();
  77530. me.items = [];
  77531. if (me.radar) {
  77532. me.radar.hide(true);
  77533. }
  77534. me.radar = null;
  77535. return;
  77536. }
  77537. if(!seriesStyle['stroke']){
  77538. seriesStyle['stroke'] = colorArrayStyle[seriesIdx % colorArrayStyle.length];
  77539. }
  77540. me.unHighlightItem();
  77541. me.cleanHighlights();
  77542. centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
  77543. centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
  77544. me.radius = radius = Math.min(chartBBox.width, chartBBox.height) /2;
  77545. me.items = items = [];
  77546. if (aggregate) {
  77547. //get all renderer fields
  77548. for (s = 0, sLen = seriesItems.length; s < sLen; s++) {
  77549. series = seriesItems[s];
  77550. fields.push(series.yField);
  77551. }
  77552. //get maxValue to interpolate
  77553. for (d = 0; d < l; d++) {
  77554. record = data[d];
  77555. for (i = 0, nfields = fields.length; i < nfields; i++) {
  77556. maxValue = max(+record.get(fields[i]), maxValue);
  77557. }
  77558. }
  77559. }
  77560. //ensure non-zero value.
  77561. maxValue = maxValue || 1;
  77562. //create path and items
  77563. startPath = []; path = [];
  77564. for (i = 0; i < l; i++) {
  77565. record = data[i];
  77566. rho = radius * record.get(field) / maxValue;
  77567. x = rho * cos(i / l * pi2);
  77568. y = rho * sin(i / l * pi2);
  77569. if (i == 0) {
  77570. path.push('M', x + centerX, y + centerY);
  77571. startPath.push('M', 0.01 * x + centerX, 0.01 * y + centerY);
  77572. } else {
  77573. path.push('L', x + centerX, y + centerY);
  77574. startPath.push('L', 0.01 * x + centerX, 0.01 * y + centerY);
  77575. }
  77576. items.push({
  77577. sprite: false, //TODO(nico): add markers
  77578. point: [centerX + x, centerY + y],
  77579. storeItem: record,
  77580. series: me
  77581. });
  77582. }
  77583. path.push('Z');
  77584. //create path sprite
  77585. if (!me.radar) {
  77586. me.radar = surface.add(Ext.apply({
  77587. type: 'path',
  77588. group: group,
  77589. path: startPath
  77590. }, seriesStyle || {}));
  77591. }
  77592. //reset on resizing
  77593. if (chart.resizing) {
  77594. me.radar.setAttributes({
  77595. path: startPath
  77596. }, true);
  77597. }
  77598. //render/animate
  77599. if (chart.animate) {
  77600. me.onAnimate(me.radar, {
  77601. to: Ext.apply({
  77602. path: path
  77603. }, seriesStyle || {})
  77604. });
  77605. } else {
  77606. me.radar.setAttributes(Ext.apply({
  77607. path: path
  77608. }, seriesStyle || {}), true);
  77609. }
  77610. //render markers, labels and callouts
  77611. if (me.showMarkers) {
  77612. me.drawMarkers();
  77613. }
  77614. me.renderLabels();
  77615. me.renderCallouts();
  77616. },
  77617. // @private draws the markers for the lines (if any).
  77618. drawMarkers: function() {
  77619. var me = this,
  77620. chart = me.chart,
  77621. surface = chart.surface,
  77622. markerStyle = Ext.apply({}, me.markerStyle || {}),
  77623. endMarkerStyle = Ext.apply(markerStyle, me.markerConfig, {
  77624. fill: me.colorArrayStyle[me.seriesIdx % me.colorArrayStyle.length]
  77625. }),
  77626. items = me.items,
  77627. type = endMarkerStyle.type,
  77628. markerGroup = me.markerGroup,
  77629. centerX = me.centerX,
  77630. centerY = me.centerY,
  77631. item, i, l, marker;
  77632. delete endMarkerStyle.type;
  77633. for (i = 0, l = items.length; i < l; i++) {
  77634. item = items[i];
  77635. marker = markerGroup.getAt(i);
  77636. if (!marker) {
  77637. marker = Ext.chart.Shape[type](surface, Ext.apply({
  77638. group: markerGroup,
  77639. x: 0,
  77640. y: 0,
  77641. translate: {
  77642. x: centerX,
  77643. y: centerY
  77644. }
  77645. }, endMarkerStyle));
  77646. }
  77647. else {
  77648. marker.show();
  77649. }
  77650. item.sprite = marker;
  77651. if (chart.resizing) {
  77652. marker.setAttributes({
  77653. x: 0,
  77654. y: 0,
  77655. translate: {
  77656. x: centerX,
  77657. y: centerY
  77658. }
  77659. }, true);
  77660. }
  77661. marker._to = {
  77662. translate: {
  77663. x: item.point[0],
  77664. y: item.point[1]
  77665. }
  77666. };
  77667. //render/animate
  77668. if (chart.animate) {
  77669. me.onAnimate(marker, {
  77670. to: marker._to
  77671. });
  77672. }
  77673. else {
  77674. marker.setAttributes(Ext.apply(marker._to, endMarkerStyle || {}), true);
  77675. }
  77676. }
  77677. },
  77678. isItemInPoint: function(x, y, item) {
  77679. var point,
  77680. tolerance = 10,
  77681. abs = Math.abs;
  77682. point = item.point;
  77683. return (abs(point[0] - x) <= tolerance &&
  77684. abs(point[1] - y) <= tolerance);
  77685. },
  77686. // @private callback for when creating a label sprite.
  77687. onCreateLabel: function(storeItem, item, i, display) {
  77688. var me = this,
  77689. group = me.labelsGroup,
  77690. config = me.label,
  77691. centerX = me.centerX,
  77692. centerY = me.centerY,
  77693. point = item.point,
  77694. endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config);
  77695. return me.chart.surface.add(Ext.apply({
  77696. 'type': 'text',
  77697. 'text-anchor': 'middle',
  77698. 'group': group,
  77699. 'x': centerX,
  77700. 'y': centerY
  77701. }, config || {}));
  77702. },
  77703. // @private callback for when placing a label sprite.
  77704. onPlaceLabel: function(label, storeItem, item, i, display, animate) {
  77705. var me = this,
  77706. chart = me.chart,
  77707. resizing = chart.resizing,
  77708. config = me.label,
  77709. format = config.renderer,
  77710. field = config.field,
  77711. centerX = me.centerX,
  77712. centerY = me.centerY,
  77713. opt = {
  77714. x: item.point[0],
  77715. y: item.point[1]
  77716. },
  77717. x = opt.x - centerX,
  77718. y = opt.y - centerY;
  77719. label.setAttributes({
  77720. text: format(storeItem.get(field)),
  77721. hidden: true
  77722. },
  77723. true);
  77724. if (resizing) {
  77725. label.setAttributes({
  77726. x: centerX,
  77727. y: centerY
  77728. }, true);
  77729. }
  77730. if (animate) {
  77731. label.show(true);
  77732. me.onAnimate(label, {
  77733. to: opt
  77734. });
  77735. } else {
  77736. label.setAttributes(opt, true);
  77737. label.show(true);
  77738. }
  77739. },
  77740. // @private for toggling (show/hide) series.
  77741. toggleAll: function(show) {
  77742. var me = this,
  77743. i, ln, shadow, shadows;
  77744. if (!show) {
  77745. Ext.chart.series.Radar.superclass.hideAll.call(me);
  77746. }
  77747. else {
  77748. Ext.chart.series.Radar.superclass.showAll.call(me);
  77749. }
  77750. if (me.radar) {
  77751. me.radar.setAttributes({
  77752. hidden: !show
  77753. }, true);
  77754. //hide shadows too
  77755. if (me.radar.shadows) {
  77756. for (i = 0, shadows = me.radar.shadows, ln = shadows.length; i < ln; i++) {
  77757. shadow = shadows[i];
  77758. shadow.setAttributes({
  77759. hidden: !show
  77760. }, true);
  77761. }
  77762. }
  77763. }
  77764. },
  77765. // @private hide all elements in the series.
  77766. hideAll: function() {
  77767. this.toggleAll(false);
  77768. this.hideMarkers(0);
  77769. },
  77770. // @private show all elements in the series.
  77771. showAll: function() {
  77772. this.toggleAll(true);
  77773. },
  77774. // @private hide all markers that belong to `markerGroup`
  77775. hideMarkers: function(index) {
  77776. var me = this,
  77777. count = me.markerGroup && me.markerGroup.getCount() || 0,
  77778. i = index || 0;
  77779. for (; i < count; i++) {
  77780. me.markerGroup.getAt(i).hide(true);
  77781. }
  77782. }
  77783. });
  77784. /**
  77785. * @class Ext.chart.series.Scatter
  77786. * @extends Ext.chart.series.Cartesian
  77787. *
  77788. * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
  77789. * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
  77790. * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
  77791. * documentation for more information on creating charts. A typical configuration object for the scatter could be:
  77792. *
  77793. * @example
  77794. * var store = Ext.create('Ext.data.JsonStore', {
  77795. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  77796. * data: [
  77797. * { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
  77798. * { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
  77799. * { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
  77800. * { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
  77801. * { 'name': 'metric five', 'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
  77802. * ]
  77803. * });
  77804. *
  77805. * Ext.create('Ext.chart.Chart', {
  77806. * renderTo: Ext.getBody(),
  77807. * width: 500,
  77808. * height: 300,
  77809. * animate: true,
  77810. * theme:'Category2',
  77811. * store: store,
  77812. * axes: [{
  77813. * type: 'Numeric',
  77814. * position: 'left',
  77815. * fields: ['data2', 'data3'],
  77816. * title: 'Sample Values',
  77817. * grid: true,
  77818. * minimum: 0
  77819. * }, {
  77820. * type: 'Category',
  77821. * position: 'bottom',
  77822. * fields: ['name'],
  77823. * title: 'Sample Metrics'
  77824. * }],
  77825. * series: [{
  77826. * type: 'scatter',
  77827. * markerConfig: {
  77828. * radius: 5,
  77829. * size: 5
  77830. * },
  77831. * axis: 'left',
  77832. * xField: 'name',
  77833. * yField: 'data2'
  77834. * }, {
  77835. * type: 'scatter',
  77836. * markerConfig: {
  77837. * radius: 5,
  77838. * size: 5
  77839. * },
  77840. * axis: 'left',
  77841. * xField: 'name',
  77842. * yField: 'data3'
  77843. * }]
  77844. * });
  77845. *
  77846. * In this configuration we add three different categories of scatter series. Each of them is bound to a different field of the same data store,
  77847. * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
  77848. * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as
  77849. * axis to show the current values of the elements.
  77850. *
  77851. * @xtype scatter
  77852. */
  77853. Ext.define('Ext.chart.series.Scatter', {
  77854. /* Begin Definitions */
  77855. extend: 'Ext.chart.series.Cartesian',
  77856. requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],
  77857. /* End Definitions */
  77858. type: 'scatter',
  77859. alias: 'series.scatter',
  77860. /**
  77861. * @cfg {Object} markerConfig
  77862. * The display style for the scatter series markers.
  77863. */
  77864. /**
  77865. * @cfg {Object} style
  77866. * Append styling properties to this object for it to override theme properties.
  77867. */
  77868. /**
  77869. * @cfg {String/Array} axis
  77870. * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
  77871. * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
  77872. * relative scale will be used. If multiple axes are being used, they should both be specified in in the configuration.
  77873. */
  77874. constructor: function(config) {
  77875. this.callParent(arguments);
  77876. var me = this,
  77877. shadow = me.chart.shadow,
  77878. surface = me.chart.surface, i, l;
  77879. Ext.apply(me, config, {
  77880. style: {},
  77881. markerConfig: {},
  77882. shadowAttributes: [{
  77883. "stroke-width": 6,
  77884. "stroke-opacity": 0.05,
  77885. stroke: 'rgb(0, 0, 0)'
  77886. }, {
  77887. "stroke-width": 4,
  77888. "stroke-opacity": 0.1,
  77889. stroke: 'rgb(0, 0, 0)'
  77890. }, {
  77891. "stroke-width": 2,
  77892. "stroke-opacity": 0.15,
  77893. stroke: 'rgb(0, 0, 0)'
  77894. }]
  77895. });
  77896. me.group = surface.getGroup(me.seriesId);
  77897. if (shadow) {
  77898. for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
  77899. me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
  77900. }
  77901. }
  77902. },
  77903. // @private Get chart and data boundaries
  77904. getBounds: function() {
  77905. var me = this,
  77906. chart = me.chart,
  77907. store = chart.getChartStore(),
  77908. chartAxes = chart.axes,
  77909. boundAxes = me.getAxesForXAndYFields(),
  77910. boundXAxis = boundAxes.xAxis,
  77911. boundYAxis = boundAxes.yAxis,
  77912. bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
  77913. me.setBBox();
  77914. bbox = me.bbox;
  77915. if (axis = chartAxes.get(boundXAxis)) {
  77916. ends = axis.applyData();
  77917. minX = ends.from;
  77918. maxX = ends.to;
  77919. }
  77920. if (axis = chartAxes.get(boundYAxis)) {
  77921. ends = axis.applyData();
  77922. minY = ends.from;
  77923. maxY = ends.to;
  77924. }
  77925. // If a field was specified without a corresponding axis, create one to get bounds
  77926. if (me.xField && !Ext.isNumber(minX)) {
  77927. axis = me.getMinMaxXValues();
  77928. minX = axis[0];
  77929. maxX = axis[1];
  77930. }
  77931. if (me.yField && !Ext.isNumber(minY)) {
  77932. axis = me.getMinMaxYValues();
  77933. minY = axis[0];
  77934. maxY = axis[1];
  77935. }
  77936. if (isNaN(minX)) {
  77937. minX = 0;
  77938. maxX = store.getCount() - 1;
  77939. xScale = bbox.width / (store.getCount() - 1);
  77940. }
  77941. else {
  77942. xScale = bbox.width / (maxX - minX);
  77943. }
  77944. if (isNaN(minY)) {
  77945. minY = 0;
  77946. maxY = store.getCount() - 1;
  77947. yScale = bbox.height / (store.getCount() - 1);
  77948. }
  77949. else {
  77950. yScale = bbox.height / (maxY - minY);
  77951. }
  77952. return {
  77953. bbox: bbox,
  77954. minX: minX,
  77955. minY: minY,
  77956. xScale: xScale,
  77957. yScale: yScale
  77958. };
  77959. },
  77960. // @private Build an array of paths for the chart
  77961. getPaths: function() {
  77962. var me = this,
  77963. chart = me.chart,
  77964. enableShadows = chart.shadow,
  77965. store = chart.getChartStore(),
  77966. data = store.data.items,
  77967. i, ln, record,
  77968. group = me.group,
  77969. bounds = me.bounds = me.getBounds(),
  77970. bbox = me.bbox,
  77971. xScale = bounds.xScale,
  77972. yScale = bounds.yScale,
  77973. minX = bounds.minX,
  77974. minY = bounds.minY,
  77975. boxX = bbox.x,
  77976. boxY = bbox.y,
  77977. boxHeight = bbox.height,
  77978. items = me.items = [],
  77979. attrs = [],
  77980. x, y, xValue, yValue, sprite;
  77981. for (i = 0, ln = data.length; i < ln; i++) {
  77982. record = data[i];
  77983. xValue = record.get(me.xField);
  77984. yValue = record.get(me.yField);
  77985. //skip undefined or null values
  77986. if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)
  77987. || xValue == null || yValue == null) {
  77988. if (Ext.isDefined(Ext.global.console)) {
  77989. Ext.global.console.warn("[Ext.chart.series.Scatter] Skipping a store element with a value which is either undefined or null at ", record, xValue, yValue);
  77990. }
  77991. continue;
  77992. }
  77993. // Ensure a value
  77994. if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)) {
  77995. xValue = i;
  77996. }
  77997. if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)) {
  77998. yValue = i;
  77999. }
  78000. x = boxX + (xValue - minX) * xScale;
  78001. y = boxY + boxHeight - (yValue - minY) * yScale;
  78002. attrs.push({
  78003. x: x,
  78004. y: y
  78005. });
  78006. me.items.push({
  78007. series: me,
  78008. value: [xValue, yValue],
  78009. point: [x, y],
  78010. storeItem: record
  78011. });
  78012. // When resizing, reset before animating
  78013. if (chart.animate && chart.resizing) {
  78014. sprite = group.getAt(i);
  78015. if (sprite) {
  78016. me.resetPoint(sprite);
  78017. if (enableShadows) {
  78018. me.resetShadow(sprite);
  78019. }
  78020. }
  78021. }
  78022. }
  78023. return attrs;
  78024. },
  78025. // @private translate point to the center
  78026. resetPoint: function(sprite) {
  78027. var bbox = this.bbox;
  78028. sprite.setAttributes({
  78029. translate: {
  78030. x: (bbox.x + bbox.width) / 2,
  78031. y: (bbox.y + bbox.height) / 2
  78032. }
  78033. }, true);
  78034. },
  78035. // @private translate shadows of a sprite to the center
  78036. resetShadow: function(sprite) {
  78037. var me = this,
  78038. shadows = sprite.shadows,
  78039. shadowAttributes = me.shadowAttributes,
  78040. ln = me.shadowGroups.length,
  78041. bbox = me.bbox,
  78042. i, attr;
  78043. for (i = 0; i < ln; i++) {
  78044. attr = Ext.apply({}, shadowAttributes[i]);
  78045. // TODO: fix this with setAttributes
  78046. if (attr.translate) {
  78047. attr.translate.x += (bbox.x + bbox.width) / 2;
  78048. attr.translate.y += (bbox.y + bbox.height) / 2;
  78049. }
  78050. else {
  78051. attr.translate = {
  78052. x: (bbox.x + bbox.width) / 2,
  78053. y: (bbox.y + bbox.height) / 2
  78054. };
  78055. }
  78056. shadows[i].setAttributes(attr, true);
  78057. }
  78058. },
  78059. // @private create a new point
  78060. createPoint: function(attr, type) {
  78061. var me = this,
  78062. chart = me.chart,
  78063. group = me.group,
  78064. bbox = me.bbox;
  78065. return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
  78066. x: 0,
  78067. y: 0,
  78068. group: group,
  78069. translate: {
  78070. x: (bbox.x + bbox.width) / 2,
  78071. y: (bbox.y + bbox.height) / 2
  78072. }
  78073. }, attr));
  78074. },
  78075. // @private create a new set of shadows for a sprite
  78076. createShadow: function(sprite, endMarkerStyle, type) {
  78077. var me = this,
  78078. chart = me.chart,
  78079. shadowGroups = me.shadowGroups,
  78080. shadowAttributes = me.shadowAttributes,
  78081. lnsh = shadowGroups.length,
  78082. bbox = me.bbox,
  78083. i, shadow, shadows, attr;
  78084. sprite.shadows = shadows = [];
  78085. for (i = 0; i < lnsh; i++) {
  78086. attr = Ext.apply({}, shadowAttributes[i]);
  78087. if (attr.translate) {
  78088. attr.translate.x += (bbox.x + bbox.width) / 2;
  78089. attr.translate.y += (bbox.y + bbox.height) / 2;
  78090. }
  78091. else {
  78092. Ext.apply(attr, {
  78093. translate: {
  78094. x: (bbox.x + bbox.width) / 2,
  78095. y: (bbox.y + bbox.height) / 2
  78096. }
  78097. });
  78098. }
  78099. Ext.apply(attr, endMarkerStyle);
  78100. shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
  78101. x: 0,
  78102. y: 0,
  78103. group: shadowGroups[i]
  78104. }, attr));
  78105. shadows.push(shadow);
  78106. }
  78107. },
  78108. /**
  78109. * Draws the series for the current chart.
  78110. */
  78111. drawSeries: function() {
  78112. var me = this,
  78113. chart = me.chart,
  78114. store = chart.getChartStore(),
  78115. group = me.group,
  78116. enableShadows = chart.shadow,
  78117. shadowGroups = me.shadowGroups,
  78118. shadowAttributes = me.shadowAttributes,
  78119. lnsh = shadowGroups.length,
  78120. sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,
  78121. rendererAttributes, shadowAttribute;
  78122. endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);
  78123. type = endMarkerStyle.type;
  78124. delete endMarkerStyle.type;
  78125. //if the store is empty then there's nothing to be rendered
  78126. if (!store || !store.getCount()) {
  78127. me.hide();
  78128. me.items = [];
  78129. return;
  78130. }
  78131. me.unHighlightItem();
  78132. me.cleanHighlights();
  78133. attrs = me.getPaths();
  78134. ln = attrs.length;
  78135. for (i = 0; i < ln; i++) {
  78136. attr = attrs[i];
  78137. sprite = group.getAt(i);
  78138. Ext.apply(attr, endMarkerStyle);
  78139. // Create a new sprite if needed (no height)
  78140. if (!sprite) {
  78141. sprite = me.createPoint(attr, type);
  78142. if (enableShadows) {
  78143. me.createShadow(sprite, endMarkerStyle, type);
  78144. }
  78145. }
  78146. shadows = sprite.shadows;
  78147. if (chart.animate) {
  78148. rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
  78149. sprite._to = rendererAttributes;
  78150. me.onAnimate(sprite, {
  78151. to: rendererAttributes
  78152. });
  78153. //animate shadows
  78154. for (shindex = 0; shindex < lnsh; shindex++) {
  78155. shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
  78156. rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, {
  78157. hidden: false,
  78158. translate: {
  78159. x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
  78160. y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
  78161. }
  78162. }, shadowAttribute), i, store);
  78163. me.onAnimate(shadows[shindex], { to: rendererAttributes });
  78164. }
  78165. }
  78166. else {
  78167. rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
  78168. sprite._to = rendererAttributes;
  78169. sprite.setAttributes(rendererAttributes, true);
  78170. //animate shadows
  78171. for (shindex = 0; shindex < lnsh; shindex++) {
  78172. shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
  78173. rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, {
  78174. hidden: false,
  78175. translate: {
  78176. x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
  78177. y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
  78178. }
  78179. }, shadowAttribute), i, store);
  78180. shadows[shindex].setAttributes(rendererAttributes, true);
  78181. }
  78182. }
  78183. me.items[i].sprite = sprite;
  78184. }
  78185. // Hide unused sprites
  78186. ln = group.getCount();
  78187. for (i = attrs.length; i < ln; i++) {
  78188. group.getAt(i).hide(true);
  78189. }
  78190. me.renderLabels();
  78191. me.renderCallouts();
  78192. },
  78193. // @private callback for when creating a label sprite.
  78194. onCreateLabel: function(storeItem, item, i, display) {
  78195. var me = this,
  78196. group = me.labelsGroup,
  78197. config = me.label,
  78198. endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
  78199. bbox = me.bbox;
  78200. return me.chart.surface.add(Ext.apply({
  78201. type: 'text',
  78202. group: group,
  78203. x: item.point[0],
  78204. y: bbox.y + bbox.height / 2
  78205. }, endLabelStyle));
  78206. },
  78207. // @private callback for when placing a label sprite.
  78208. onPlaceLabel: function(label, storeItem, item, i, display, animate) {
  78209. var me = this,
  78210. chart = me.chart,
  78211. resizing = chart.resizing,
  78212. config = me.label,
  78213. format = config.renderer,
  78214. field = config.field,
  78215. bbox = me.bbox,
  78216. x = item.point[0],
  78217. y = item.point[1],
  78218. radius = item.sprite.attr.radius,
  78219. bb, width, height, anim;
  78220. label.setAttributes({
  78221. text: format(storeItem.get(field)),
  78222. hidden: true
  78223. }, true);
  78224. if (display == 'rotate') {
  78225. label.setAttributes({
  78226. 'text-anchor': 'start',
  78227. 'rotation': {
  78228. x: x,
  78229. y: y,
  78230. degrees: -45
  78231. }
  78232. }, true);
  78233. //correct label position to fit into the box
  78234. bb = label.getBBox();
  78235. width = bb.width;
  78236. height = bb.height;
  78237. x = x < bbox.x? bbox.x : x;
  78238. x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
  78239. y = (y - height < bbox.y)? bbox.y + height : y;
  78240. } else if (display == 'under' || display == 'over') {
  78241. //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
  78242. bb = item.sprite.getBBox();
  78243. bb.width = bb.width || (radius * 2);
  78244. bb.height = bb.height || (radius * 2);
  78245. y = y + (display == 'over'? -bb.height : bb.height);
  78246. //correct label position to fit into the box
  78247. bb = label.getBBox();
  78248. width = bb.width/2;
  78249. height = bb.height/2;
  78250. x = x - width < bbox.x ? bbox.x + width : x;
  78251. x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
  78252. y = y - height < bbox.y? bbox.y + height : y;
  78253. y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
  78254. }
  78255. if (!chart.animate) {
  78256. label.setAttributes({
  78257. x: x,
  78258. y: y
  78259. }, true);
  78260. label.show(true);
  78261. }
  78262. else {
  78263. if (resizing) {
  78264. anim = item.sprite.getActiveAnimation();
  78265. if (anim) {
  78266. anim.on('afteranimate', function() {
  78267. label.setAttributes({
  78268. x: x,
  78269. y: y
  78270. }, true);
  78271. label.show(true);
  78272. });
  78273. }
  78274. else {
  78275. label.show(true);
  78276. }
  78277. }
  78278. else {
  78279. me.onAnimate(label, {
  78280. to: {
  78281. x: x,
  78282. y: y
  78283. }
  78284. });
  78285. }
  78286. }
  78287. },
  78288. // @private callback for when placing a callout sprite.
  78289. onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
  78290. var me = this,
  78291. chart = me.chart,
  78292. surface = chart.surface,
  78293. resizing = chart.resizing,
  78294. config = me.callouts,
  78295. items = me.items,
  78296. cur = item.point,
  78297. normal,
  78298. bbox = callout.label.getBBox(),
  78299. offsetFromViz = 30,
  78300. offsetToSide = 10,
  78301. offsetBox = 3,
  78302. boxx, boxy, boxw, boxh,
  78303. p, clipRect = me.bbox,
  78304. x, y;
  78305. //position
  78306. normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
  78307. x = cur[0] + normal[0] * offsetFromViz;
  78308. y = cur[1] + normal[1] * offsetFromViz;
  78309. //box position and dimensions
  78310. boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
  78311. boxy = y - bbox.height /2 - offsetBox;
  78312. boxw = bbox.width + 2 * offsetBox;
  78313. boxh = bbox.height + 2 * offsetBox;
  78314. //now check if we're out of bounds and invert the normal vector correspondingly
  78315. //this may add new overlaps between labels (but labels won't be out of bounds).
  78316. if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
  78317. normal[0] *= -1;
  78318. }
  78319. if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
  78320. normal[1] *= -1;
  78321. }
  78322. //update positions
  78323. x = cur[0] + normal[0] * offsetFromViz;
  78324. y = cur[1] + normal[1] * offsetFromViz;
  78325. //update box position and dimensions
  78326. boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
  78327. boxy = y - bbox.height /2 - offsetBox;
  78328. boxw = bbox.width + 2 * offsetBox;
  78329. boxh = bbox.height + 2 * offsetBox;
  78330. if (chart.animate) {
  78331. //set the line from the middle of the pie to the box.
  78332. me.onAnimate(callout.lines, {
  78333. to: {
  78334. path: ["M", cur[0], cur[1], "L", x, y, "Z"]
  78335. }
  78336. }, true);
  78337. //set box position
  78338. me.onAnimate(callout.box, {
  78339. to: {
  78340. x: boxx,
  78341. y: boxy,
  78342. width: boxw,
  78343. height: boxh
  78344. }
  78345. }, true);
  78346. //set text position
  78347. me.onAnimate(callout.label, {
  78348. to: {
  78349. x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
  78350. y: y
  78351. }
  78352. }, true);
  78353. } else {
  78354. //set the line from the middle of the pie to the box.
  78355. callout.lines.setAttributes({
  78356. path: ["M", cur[0], cur[1], "L", x, y, "Z"]
  78357. }, true);
  78358. //set box position
  78359. callout.box.setAttributes({
  78360. x: boxx,
  78361. y: boxy,
  78362. width: boxw,
  78363. height: boxh
  78364. }, true);
  78365. //set text position
  78366. callout.label.setAttributes({
  78367. x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
  78368. y: y
  78369. }, true);
  78370. }
  78371. for (p in callout) {
  78372. callout[p].show(true);
  78373. }
  78374. },
  78375. // @private handles sprite animation for the series.
  78376. onAnimate: function(sprite, attr) {
  78377. sprite.show();
  78378. return this.callParent(arguments);
  78379. },
  78380. isItemInPoint: function(x, y, item) {
  78381. var point,
  78382. tolerance = 10,
  78383. abs = Math.abs;
  78384. function dist(point) {
  78385. var dx = abs(point[0] - x),
  78386. dy = abs(point[1] - y);
  78387. return Math.sqrt(dx * dx + dy * dy);
  78388. }
  78389. point = item.point;
  78390. return (point[0] - tolerance <= x && point[0] + tolerance >= x &&
  78391. point[1] - tolerance <= y && point[1] + tolerance >= y);
  78392. }
  78393. });
  78394. /**
  78395. * This layout allows you to easily render content into an HTML table. The total number of columns can be specified, and
  78396. * rowspan and colspan can be used to create complex layouts within the table. This class is intended to be extended or
  78397. * created via the `layout: {type: 'table'}` {@link Ext.container.Container#layout} config, and should generally not
  78398. * need to be created directly via the new keyword.
  78399. *
  78400. * Note that when creating a layout via config, the layout-specific config properties must be passed in via the {@link
  78401. * Ext.container.Container#layout} object which will then be applied internally to the layout. In the case of
  78402. * TableLayout, the only valid layout config properties are {@link #columns} and {@link #tableAttrs}. However, the items
  78403. * added to a TableLayout can supply the following table-specific config properties:
  78404. *
  78405. * - **rowspan** Applied to the table cell containing the item.
  78406. * - **colspan** Applied to the table cell containing the item.
  78407. * - **cellId** An id applied to the table cell containing the item.
  78408. * - **cellCls** A CSS class name added to the table cell containing the item.
  78409. *
  78410. * The basic concept of building up a TableLayout is conceptually very similar to building up a standard HTML table. You
  78411. * simply add each panel (or "cell") that you want to include along with any span attributes specified as the special
  78412. * config properties of rowspan and colspan which work exactly like their HTML counterparts. Rather than explicitly
  78413. * creating and nesting rows and columns as you would in HTML, you simply specify the total column count in the
  78414. * layout config and start adding panels in their natural order from left to right, top to bottom. The layout will
  78415. * automatically figure out, based on the column count, rowspans and colspans, how to position each panel within the
  78416. * table. Just like with HTML tables, your rowspans and colspans must add up correctly in your overall layout or you'll
  78417. * end up with missing and/or extra cells! Example usage:
  78418. *
  78419. * @example
  78420. * Ext.create('Ext.panel.Panel', {
  78421. * title: 'Table Layout',
  78422. * width: 300,
  78423. * height: 150,
  78424. * layout: {
  78425. * type: 'table',
  78426. * // The total column count must be specified here
  78427. * columns: 3
  78428. * },
  78429. * defaults: {
  78430. * // applied to each contained panel
  78431. * bodyStyle: 'padding:20px'
  78432. * },
  78433. * items: [{
  78434. * html: 'Cell A content',
  78435. * rowspan: 2
  78436. * },{
  78437. * html: 'Cell B content',
  78438. * colspan: 2
  78439. * },{
  78440. * html: 'Cell C content',
  78441. * cellCls: 'highlight'
  78442. * },{
  78443. * html: 'Cell D content'
  78444. * }],
  78445. * renderTo: Ext.getBody()
  78446. * });
  78447. */
  78448. Ext.define('Ext.layout.container.Table', {
  78449. /* Begin Definitions */
  78450. alias: ['layout.table'],
  78451. extend: 'Ext.layout.container.Container',
  78452. alternateClassName: 'Ext.layout.TableLayout',
  78453. /* End Definitions */
  78454. /**
  78455. * @cfg {Number} columns
  78456. * The total number of columns to create in the table for this layout. If not specified, all Components added to
  78457. * this layout will be rendered into a single row using one column per Component.
  78458. */
  78459. // private
  78460. monitorResize:false,
  78461. type: 'table',
  78462. clearEl: true, // Base class will not create it if already truthy. Not needed in tables.
  78463. targetCls: Ext.baseCSSPrefix + 'table-layout-ct',
  78464. tableCls: Ext.baseCSSPrefix + 'table-layout',
  78465. cellCls: Ext.baseCSSPrefix + 'table-layout-cell',
  78466. /**
  78467. * @cfg {Object} tableAttrs
  78468. * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
  78469. * create the layout's `<table>` element. Example:
  78470. *
  78471. * {
  78472. * xtype: 'panel',
  78473. * layout: {
  78474. * type: 'table',
  78475. * columns: 3,
  78476. * tableAttrs: {
  78477. * style: {
  78478. * width: '100%'
  78479. * }
  78480. * }
  78481. * }
  78482. * }
  78483. */
  78484. tableAttrs: null,
  78485. /**
  78486. * @cfg {Object} trAttrs
  78487. * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
  78488. * create the layout's `<tr>` elements.
  78489. */
  78490. /**
  78491. * @cfg {Object} tdAttrs
  78492. * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
  78493. * create the layout's `<td>` elements.
  78494. */
  78495. itemSizePolicy: {
  78496. setsWidth: 0,
  78497. setsHeight: 0
  78498. },
  78499. getItemSizePolicy: function (item) {
  78500. return this.itemSizePolicy;
  78501. },
  78502. getLayoutItems: function() {
  78503. var me = this,
  78504. result = [],
  78505. items = me.callParent(),
  78506. item,
  78507. len = items.length, i;
  78508. for (i = 0; i < len; i++) {
  78509. item = items[i];
  78510. if (!item.hidden) {
  78511. result.push(item);
  78512. }
  78513. }
  78514. return result;
  78515. },
  78516. /**
  78517. * @private
  78518. * Iterates over all passed items, ensuring they are rendered in a cell in the proper
  78519. * location in the table structure.
  78520. */
  78521. renderChildren: function() {
  78522. var me = this,
  78523. items = me.getLayoutItems(),
  78524. tbody = me.owner.getTargetEl().child('table', true).tBodies[0],
  78525. rows = tbody.rows,
  78526. i = 0,
  78527. len = items.length,
  78528. cells, curCell, rowIdx, cellIdx, item, trEl, tdEl, itemCt;
  78529. // Calculate the correct cell structure for the current items
  78530. cells = me.calculateCells(items);
  78531. // Loop over each cell and compare to the current cells in the table, inserting/
  78532. // removing/moving cells as needed, and making sure each item is rendered into
  78533. // the correct cell.
  78534. for (; i < len; i++) {
  78535. curCell = cells[i];
  78536. rowIdx = curCell.rowIdx;
  78537. cellIdx = curCell.cellIdx;
  78538. item = items[i];
  78539. // If no row present, create and insert one
  78540. trEl = rows[rowIdx];
  78541. if (!trEl) {
  78542. trEl = tbody.insertRow(rowIdx);
  78543. if (me.trAttrs) {
  78544. trEl.set(me.trAttrs);
  78545. }
  78546. }
  78547. // If no cell present, create and insert one
  78548. itemCt = tdEl = Ext.get(trEl.cells[cellIdx] || trEl.insertCell(cellIdx));
  78549. if (me.needsDivWrap()) { //create wrapper div if needed - see docs below
  78550. itemCt = tdEl.first() || tdEl.createChild({tag: 'div'});
  78551. itemCt.setWidth(null);
  78552. }
  78553. // Render or move the component into the cell
  78554. if (!item.rendered) {
  78555. me.renderItem(item, itemCt, 0);
  78556. }
  78557. else if (!me.isValidParent(item, itemCt, rowIdx, cellIdx, tbody)) {
  78558. me.moveItem(item, itemCt, 0);
  78559. }
  78560. // Set the cell properties
  78561. if (me.tdAttrs) {
  78562. tdEl.set(me.tdAttrs);
  78563. }
  78564. if (item.tdAttrs) {
  78565. tdEl.set(item.tdAttrs);
  78566. }
  78567. tdEl.set({
  78568. colSpan: item.colspan || 1,
  78569. rowSpan: item.rowspan || 1,
  78570. id: item.cellId || '',
  78571. cls: me.cellCls + ' ' + (item.cellCls || '')
  78572. });
  78573. // If at the end of a row, remove any extra cells
  78574. if (!cells[i + 1] || cells[i + 1].rowIdx !== rowIdx) {
  78575. cellIdx++;
  78576. while (trEl.cells[cellIdx]) {
  78577. trEl.deleteCell(cellIdx);
  78578. }
  78579. }
  78580. }
  78581. // Delete any extra rows
  78582. rowIdx++;
  78583. while (tbody.rows[rowIdx]) {
  78584. tbody.deleteRow(rowIdx);
  78585. }
  78586. },
  78587. calculate: function (ownerContext) {
  78588. if (!ownerContext.hasDomProp('containerChildrenDone')) {
  78589. this.done = false;
  78590. } else {
  78591. var targetContext = ownerContext.targetContext,
  78592. widthShrinkWrap = ownerContext.widthModel.shrinkWrap,
  78593. heightShrinkWrap = ownerContext.heightModel.shrinkWrap,
  78594. shrinkWrap = heightShrinkWrap || widthShrinkWrap,
  78595. table = shrinkWrap && targetContext.el.child('table', true),
  78596. targetPadding = shrinkWrap && targetContext.getPaddingInfo();
  78597. if (widthShrinkWrap) {
  78598. ownerContext.setContentWidth(table.offsetWidth + targetPadding.width, true);
  78599. }
  78600. if (heightShrinkWrap) {
  78601. ownerContext.setContentHeight(table.offsetHeight + targetPadding.height, true);
  78602. }
  78603. }
  78604. },
  78605. finalizeLayout: function() {
  78606. if (this.needsDivWrap()) {
  78607. // set wrapper div width to match layed out item - see docs below
  78608. var items = this.getLayoutItems(),
  78609. i,
  78610. iLen = items.length,
  78611. item;
  78612. for (i = 0; i < iLen; i++) {
  78613. item = items[i];
  78614. Ext.fly(item.el.dom.parentNode).setWidth(item.getWidth());
  78615. }
  78616. }
  78617. if (Ext.isIE6 || (Ext.isIEQuirks)) {
  78618. // Fixes an issue where the table won't paint
  78619. this.owner.getTargetEl().child('table').repaint();
  78620. }
  78621. },
  78622. /**
  78623. * @private
  78624. * Determine the row and cell indexes for each component, taking into consideration
  78625. * the number of columns and each item's configured colspan/rowspan values.
  78626. * @param {Array} items The layout components
  78627. * @return {Object[]} List of row and cell indexes for each of the components
  78628. */
  78629. calculateCells: function(items) {
  78630. var cells = [],
  78631. rowIdx = 0,
  78632. colIdx = 0,
  78633. cellIdx = 0,
  78634. totalCols = this.columns || Infinity,
  78635. rowspans = [], //rolling list of active rowspans for each column
  78636. i = 0, j,
  78637. len = items.length,
  78638. item;
  78639. for (; i < len; i++) {
  78640. item = items[i];
  78641. // Find the first available row/col slot not taken up by a spanning cell
  78642. while (colIdx >= totalCols || rowspans[colIdx] > 0) {
  78643. if (colIdx >= totalCols) {
  78644. // move down to next row
  78645. colIdx = 0;
  78646. cellIdx = 0;
  78647. rowIdx++;
  78648. // decrement all rowspans
  78649. for (j = 0; j < totalCols; j++) {
  78650. if (rowspans[j] > 0) {
  78651. rowspans[j]--;
  78652. }
  78653. }
  78654. } else {
  78655. colIdx++;
  78656. }
  78657. }
  78658. // Add the cell info to the list
  78659. cells.push({
  78660. rowIdx: rowIdx,
  78661. cellIdx: cellIdx
  78662. });
  78663. // Increment
  78664. for (j = item.colspan || 1; j; --j) {
  78665. rowspans[colIdx] = item.rowspan || 1;
  78666. ++colIdx;
  78667. }
  78668. ++cellIdx;
  78669. }
  78670. return cells;
  78671. },
  78672. getRenderTree: function() {
  78673. var me = this,
  78674. items = me.getLayoutItems(),
  78675. cells,
  78676. rows = [],
  78677. result = Ext.apply({
  78678. tag: 'table',
  78679. role: 'presentation',
  78680. cls: me.tableCls,
  78681. cellspacing: 0,
  78682. cn: {
  78683. tag: 'tbody',
  78684. cn: rows
  78685. }
  78686. }, me.tableAttrs),
  78687. tdAttrs = me.tdAttrs,
  78688. needsDivWrap = me.needsDivWrap(),
  78689. i, len = items.length, item, curCell, tr, rowIdx, cellIdx, cell;
  78690. // Calculate the correct cell structure for the current items
  78691. cells = me.calculateCells(items);
  78692. for (i = 0; i < len; i++) {
  78693. item = items[i];
  78694. curCell = cells[i];
  78695. rowIdx = curCell.rowIdx;
  78696. cellIdx = curCell.cellIdx;
  78697. // If no row present, create and insert one
  78698. tr = rows[rowIdx];
  78699. if (!tr) {
  78700. tr = rows[rowIdx] = {
  78701. tag: 'tr',
  78702. cn: []
  78703. };
  78704. if (me.trAttrs) {
  78705. Ext.apply(tr, me.trAttrs);
  78706. }
  78707. }
  78708. // If no cell present, create and insert one
  78709. cell = tr.cn[cellIdx] = {
  78710. tag: 'td'
  78711. };
  78712. if (tdAttrs) {
  78713. Ext.apply(cell, tdAttrs);
  78714. }
  78715. Ext.apply(cell, {
  78716. colSpan: item.colspan || 1,
  78717. rowSpan: item.rowspan || 1,
  78718. id: item.cellId || '',
  78719. cls: me.cellCls + ' ' + (item.cellCls || '')
  78720. });
  78721. if (needsDivWrap) { //create wrapper div if needed - see docs below
  78722. cell = cell.cn = {
  78723. tag: 'div'
  78724. };
  78725. }
  78726. me.configureItem(item);
  78727. // The DomHelper config of the item is the cell's sole child
  78728. cell.cn = item.getRenderTree();
  78729. }
  78730. return result;
  78731. },
  78732. isValidParent: function(item, target, rowIdx, cellIdx) {
  78733. var tbody,
  78734. correctCell,
  78735. table;
  78736. // If we were called with the 3 arg signature just check that the parent table of the item is within the render target
  78737. if (arguments.length === 3) {
  78738. table = item.el.up('table');
  78739. return table && table.dom.parentNode === target.dom;
  78740. }
  78741. tbody = this.owner.getTargetEl().child('table', true).tBodies[0];
  78742. correctCell = tbody.rows[rowIdx].cells[cellIdx];
  78743. return item.el.dom.parentNode === correctCell;
  78744. },
  78745. /**
  78746. * @private
  78747. * Opera 10.5 has a bug where if a table cell's child has box-sizing:border-box and padding, it
  78748. * will include that padding in the size of the cell, making it always larger than the
  78749. * shrink-wrapped size of its contents. To get around this we have to wrap the contents in a div
  78750. * and then set that div's width to match the item rendered within it afterLayout. This method
  78751. * determines whether we need the wrapper div; it currently does a straight UA sniff as this bug
  78752. * seems isolated to just Opera 10.5, but feature detection could be added here if needed.
  78753. */
  78754. needsDivWrap: function() {
  78755. return Ext.isOpera10_5;
  78756. }
  78757. });
  78758. /**
  78759. * Provides a container for arranging a group of related Buttons in a tabular manner.
  78760. *
  78761. * @example
  78762. * Ext.create('Ext.panel.Panel', {
  78763. * title: 'Panel with ButtonGroup',
  78764. * width: 300,
  78765. * height:200,
  78766. * renderTo: document.body,
  78767. * bodyPadding: 10,
  78768. * html: 'HTML Panel Content',
  78769. * tbar: [{
  78770. * xtype: 'buttongroup',
  78771. * columns: 3,
  78772. * title: 'Clipboard',
  78773. * items: [{
  78774. * text: 'Paste',
  78775. * scale: 'large',
  78776. * rowspan: 3,
  78777. * iconCls: 'add',
  78778. * iconAlign: 'top',
  78779. * cls: 'btn-as-arrow'
  78780. * },{
  78781. * xtype:'splitbutton',
  78782. * text: 'Menu Button',
  78783. * scale: 'large',
  78784. * rowspan: 3,
  78785. * iconCls: 'add',
  78786. * iconAlign: 'top',
  78787. * arrowAlign:'bottom',
  78788. * menu: [{ text: 'Menu Item 1' }]
  78789. * },{
  78790. * xtype:'splitbutton', text: 'Cut', iconCls: 'add16', menu: [{text: 'Cut Menu Item'}]
  78791. * },{
  78792. * text: 'Copy', iconCls: 'add16'
  78793. * },{
  78794. * text: 'Format', iconCls: 'add16'
  78795. * }]
  78796. * }]
  78797. * });
  78798. *
  78799. */
  78800. Ext.define('Ext.container.ButtonGroup', {
  78801. extend: 'Ext.panel.Panel',
  78802. alias: 'widget.buttongroup',
  78803. alternateClassName: 'Ext.ButtonGroup',
  78804. requires: ['Ext.layout.container.Table'],
  78805. /**
  78806. * @cfg {Number} columns
  78807. * The `columns` configuration property passed to the {@link #layout configured layout manager}.
  78808. * See {@link Ext.layout.container.Table#columns}.
  78809. */
  78810. /**
  78811. * @cfg {String} baseCls
  78812. * @inheritdoc
  78813. */
  78814. baseCls: Ext.baseCSSPrefix + 'btn-group',
  78815. /**
  78816. * @cfg {Object} layout
  78817. * @inheritdoc
  78818. */
  78819. layout: {
  78820. type: 'table'
  78821. },
  78822. defaultType: 'button',
  78823. /**
  78824. * @cfg {Boolean} frame
  78825. * @inheritdoc
  78826. */
  78827. frame: true,
  78828. frameHeader: false,
  78829. titleAlign: 'center',
  78830. initComponent : function() {
  78831. // Copy the component's columns config to the layout if specified
  78832. var me = this,
  78833. cols = me.columns;
  78834. me.noTitleCls = me.baseCls + '-notitle';
  78835. if (cols) {
  78836. me.layout = Ext.apply({}, {columns: cols}, me.layout);
  78837. }
  78838. if (!me.title) {
  78839. me.addCls(me.noTitleCls);
  78840. }
  78841. me.callParent(arguments);
  78842. },
  78843. // private
  78844. onBeforeAdd: function(component) {
  78845. if (component.isButton) {
  78846. component.ui = component.ui + '-toolbar';
  78847. }
  78848. this.callParent(arguments);
  78849. },
  78850. //private
  78851. applyDefaults: function(c) {
  78852. if (!Ext.isString(c)) {
  78853. c = this.callParent(arguments);
  78854. }
  78855. return c;
  78856. }
  78857. /**
  78858. * @cfg {Array} tools
  78859. * @private
  78860. */
  78861. /**
  78862. * @cfg {Boolean} collapsible
  78863. * @private
  78864. */
  78865. /**
  78866. * @cfg {Boolean} collapseMode
  78867. * @private
  78868. */
  78869. /**
  78870. * @cfg {Boolean} animCollapse
  78871. * @private
  78872. */
  78873. /**
  78874. * @cfg {Boolean} closable
  78875. * @private
  78876. */
  78877. });
  78878. /**
  78879. * A specialized container representing the viewable application area (the browser viewport).
  78880. *
  78881. * The Viewport renders itself to the document body, and automatically sizes itself to the size of
  78882. * the browser viewport and manages window resizing. There may only be one Viewport created
  78883. * in a page.
  78884. *
  78885. * Like any {@link Ext.container.Container Container}, a Viewport will only perform sizing and positioning
  78886. * on its child Components if you configure it with a {@link #layout}.
  78887. *
  78888. * A Common layout used with Viewports is {@link Ext.layout.container.Border border layout}, but if the
  78889. * required layout is simpler, a different layout should be chosen.
  78890. *
  78891. * For example, to simply make a single child item occupy all available space, use
  78892. * {@link Ext.layout.container.Fit fit layout}.
  78893. *
  78894. * To display one "active" item at full size from a choice of several child items, use
  78895. * {@link Ext.layout.container.Card card layout}.
  78896. *
  78897. * Inner layouts are available because all {@link Ext.panel.Panel Panel}s
  78898. * added to the Viewport, either through its {@link #cfg-items}, or the {@link #method-add}
  78899. * method of any of its child Panels may themselves have a layout.
  78900. *
  78901. * The Viewport does not provide scrolling, so child Panels within the Viewport should provide
  78902. * for scrolling if needed using the {@link #autoScroll} config.
  78903. *
  78904. * An example showing a classic application border layout:
  78905. *
  78906. * @example
  78907. * Ext.create('Ext.container.Viewport', {
  78908. * layout: 'border',
  78909. * items: [{
  78910. * region: 'north',
  78911. * html: '<h1 class="x-panel-header">Page Title</h1>',
  78912. * border: false,
  78913. * margins: '0 0 5 0'
  78914. * }, {
  78915. * region: 'west',
  78916. * collapsible: true,
  78917. * title: 'Navigation',
  78918. * width: 150
  78919. * // could use a TreePanel or AccordionLayout for navigational items
  78920. * }, {
  78921. * region: 'south',
  78922. * title: 'South Panel',
  78923. * collapsible: true,
  78924. * html: 'Information goes here',
  78925. * split: true,
  78926. * height: 100,
  78927. * minHeight: 100
  78928. * }, {
  78929. * region: 'east',
  78930. * title: 'East Panel',
  78931. * collapsible: true,
  78932. * split: true,
  78933. * width: 150
  78934. * }, {
  78935. * region: 'center',
  78936. * xtype: 'tabpanel', // TabPanel itself has no title
  78937. * activeTab: 0, // First tab active by default
  78938. * items: {
  78939. * title: 'Default Tab',
  78940. * html: 'The first tab\'s content. Others may be added dynamically'
  78941. * }
  78942. * }]
  78943. * });
  78944. */
  78945. Ext.define('Ext.container.Viewport', {
  78946. extend: 'Ext.container.Container',
  78947. alias: 'widget.viewport',
  78948. requires: ['Ext.EventManager'],
  78949. alternateClassName: 'Ext.Viewport',
  78950. // Privatize config options which, if used, would interfere with the
  78951. // correct operation of the Viewport as the sole manager of the
  78952. // layout of the document body.
  78953. /**
  78954. * @cfg {String/HTMLElement/Ext.Element} applyTo
  78955. * @private
  78956. */
  78957. /**
  78958. * @cfg {Boolean} allowDomMove
  78959. * @private
  78960. */
  78961. /**
  78962. * @cfg {String/HTMLElement/Ext.Element} renderTo
  78963. * Always renders to document body.
  78964. * @private
  78965. */
  78966. /**
  78967. * @cfg {Number} height
  78968. * Sets itself to viewport width.
  78969. * @private
  78970. */
  78971. /**
  78972. * @cfg {Number} width
  78973. * Sets itself to viewport height.
  78974. * @private
  78975. */
  78976. /**
  78977. * @property {Boolean} isViewport
  78978. * `true` in this class to identify an object as an instantiated Viewport, or subclass thereof.
  78979. */
  78980. isViewport: true,
  78981. ariaRole: 'application',
  78982. preserveElOnDestroy: true,
  78983. initComponent : function() {
  78984. var me = this,
  78985. html = document.body.parentNode,
  78986. el;
  78987. // Get the DOM disruption over with beforfe the Viewport renders and begins a layout
  78988. Ext.getScrollbarSize();
  78989. // Clear any dimensions, we will size later on
  78990. me.width = me.height = undefined;
  78991. me.callParent(arguments);
  78992. Ext.fly(html).addCls(Ext.baseCSSPrefix + 'viewport');
  78993. if (me.autoScroll) {
  78994. delete me.autoScroll;
  78995. Ext.fly(html).setStyle('overflow', 'auto');
  78996. }
  78997. me.el = el = Ext.getBody();
  78998. el.setHeight = Ext.emptyFn;
  78999. el.setWidth = Ext.emptyFn;
  79000. el.setSize = Ext.emptyFn;
  79001. el.dom.scroll = 'no';
  79002. me.allowDomMove = false;
  79003. me.renderTo = me.el;
  79004. },
  79005. onRender: function() {
  79006. var me = this;
  79007. me.callParent(arguments);
  79008. // Important to start life as the proper size (to avoid extra layouts)
  79009. // But after render so that the size is not stamped into the body
  79010. me.width = Ext.Element.getViewportWidth();
  79011. me.height = Ext.Element.getViewportHeight();
  79012. },
  79013. afterFirstLayout: function() {
  79014. var me = this;
  79015. me.callParent(arguments);
  79016. setTimeout(function() {
  79017. Ext.EventManager.onWindowResize(me.fireResize, me);
  79018. }, 1);
  79019. },
  79020. fireResize : function(width, height){
  79021. // In IE we can get resize events that have our current size, so we ignore them
  79022. // to avoid the useless layout...
  79023. if (width != this.width || height != this.height) {
  79024. this.setSize(width, height);
  79025. }
  79026. }
  79027. });
  79028. /**
  79029. * @author Ed Spencer
  79030. *
  79031. * Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model}
  79032. * data. Usually developers will not need to create or interact with proxies directly.
  79033. *
  79034. * # Types of Proxy
  79035. *
  79036. * There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}.
  79037. * The Client proxies save their data locally and include the following subclasses:
  79038. *
  79039. * - {@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it
  79040. * - {@link Ext.data.proxy.SessionStorage SessionStorageProxy} - saves its data to sessionStorage if the browsers supports it
  79041. * - {@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed
  79042. *
  79043. * The Server proxies save their data by sending requests to some remote server. These proxies include:
  79044. *
  79045. * - {@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain
  79046. * - {@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain
  79047. * - {@link Ext.data.proxy.Rest Rest} - uses RESTful HTTP methods (GET/PUT/POST/DELETE) to communicate with server
  79048. * - {@link Ext.data.proxy.Direct Direct} - uses {@link Ext.direct.Manager} to send requests
  79049. *
  79050. * Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four
  79051. * operations are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy}
  79052. * respectively. Each Proxy subclass implements these functions.
  79053. *
  79054. * The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation
  79055. * encapsulates information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances
  79056. * that are to be modified, etc. See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD
  79057. * method also accepts a callback function to be called asynchronously on completion.
  79058. *
  79059. * Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch}
  79060. * method.
  79061. */
  79062. Ext.define('Ext.data.proxy.Proxy', {
  79063. alias: 'proxy.proxy',
  79064. alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
  79065. uses: [
  79066. 'Ext.data.Batch',
  79067. 'Ext.data.Operation',
  79068. 'Ext.data.Model'
  79069. ],
  79070. mixins: {
  79071. observable: 'Ext.util.Observable'
  79072. },
  79073. /**
  79074. * @cfg {String} batchOrder
  79075. * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this to set a different
  79076. * order for the batched CRUD actions to be executed in. Defaults to 'create,update,destroy'.
  79077. */
  79078. batchOrder: 'create,update,destroy',
  79079. /**
  79080. * @cfg {Boolean} batchActions
  79081. * True to batch actions of a particular type when synchronizing the store. Defaults to true.
  79082. */
  79083. batchActions: true,
  79084. /**
  79085. * @cfg {String} defaultReaderType
  79086. * The default registered reader type. Defaults to 'json'.
  79087. * @private
  79088. */
  79089. defaultReaderType: 'json',
  79090. /**
  79091. * @cfg {String} defaultWriterType
  79092. * The default registered writer type. Defaults to 'json'.
  79093. * @private
  79094. */
  79095. defaultWriterType: 'json',
  79096. /**
  79097. * @cfg {String/Ext.data.Model} model
  79098. * The name of the Model to tie to this Proxy. Can be either the string name of the Model, or a reference to the
  79099. * Model constructor. Required.
  79100. */
  79101. /**
  79102. * @cfg {Object/String/Ext.data.reader.Reader} reader
  79103. * The Ext.data.reader.Reader to use to decode the server's response or data read from client. This can either be a
  79104. * Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
  79105. */
  79106. /**
  79107. * @cfg {Object/String/Ext.data.writer.Writer} writer
  79108. * The Ext.data.writer.Writer to use to encode any request sent to the server or saved to client. This can either be
  79109. * a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
  79110. */
  79111. /**
  79112. * @property {Boolean} isProxy
  79113. * `true` in this class to identify an object as an instantiated Proxy, or subclass thereof.
  79114. */
  79115. isProxy: true,
  79116. /**
  79117. * Creates the Proxy
  79118. * @param {Object} config (optional) Config object.
  79119. */
  79120. constructor: function(config) {
  79121. config = config || {};
  79122. if (config.model === undefined) {
  79123. delete config.model;
  79124. }
  79125. this.mixins.observable.constructor.call(this, config);
  79126. if (this.model !== undefined && !(this.model instanceof Ext.data.Model)) {
  79127. this.setModel(this.model);
  79128. }
  79129. /**
  79130. * @event metachange
  79131. * Fires when this proxy's reader provides new metadata. Metadata usually consists
  79132. * of new field definitions, but can include any configuration data required by an
  79133. * application, and can be processed as needed in the event handler.
  79134. * This event is currently only fired for JsonReaders. Note that this event is also
  79135. * propagated by {@link Ext.data.Store}, which is typically where it would be handled.
  79136. * @param {Ext.data.proxy.Proxy} this
  79137. * @param {Object} meta The JSON metadata
  79138. */
  79139. },
  79140. /**
  79141. * Sets the model associated with this proxy. This will only usually be called by a Store
  79142. *
  79143. * @param {String/Ext.data.Model} model The new model. Can be either the model name string,
  79144. * or a reference to the model's constructor
  79145. * @param {Boolean} setOnStore Sets the new model on the associated Store, if one is present
  79146. */
  79147. setModel: function(model, setOnStore) {
  79148. this.model = Ext.ModelManager.getModel(model);
  79149. var reader = this.reader,
  79150. writer = this.writer;
  79151. this.setReader(reader);
  79152. this.setWriter(writer);
  79153. if (setOnStore && this.store) {
  79154. this.store.setModel(this.model);
  79155. }
  79156. },
  79157. /**
  79158. * Returns the model attached to this Proxy
  79159. * @return {Ext.data.Model} The model
  79160. */
  79161. getModel: function() {
  79162. return this.model;
  79163. },
  79164. /**
  79165. * Sets the Proxy's Reader by string, config object or Reader instance
  79166. *
  79167. * @param {String/Object/Ext.data.reader.Reader} reader The new Reader, which can be either a type string,
  79168. * a configuration object or an Ext.data.reader.Reader instance
  79169. * @return {Ext.data.reader.Reader} The attached Reader object
  79170. */
  79171. setReader: function(reader) {
  79172. var me = this,
  79173. needsCopy = true;
  79174. if (reader === undefined || typeof reader == 'string') {
  79175. reader = {
  79176. type: reader
  79177. };
  79178. needsCopy = false;
  79179. }
  79180. if (reader.isReader) {
  79181. reader.setModel(me.model);
  79182. } else {
  79183. if (needsCopy) {
  79184. reader = Ext.apply({}, reader);
  79185. }
  79186. Ext.applyIf(reader, {
  79187. proxy: me,
  79188. model: me.model,
  79189. type : me.defaultReaderType
  79190. });
  79191. reader = Ext.createByAlias('reader.' + reader.type, reader);
  79192. }
  79193. if (reader.onMetaChange) {
  79194. reader.onMetaChange = Ext.Function.createSequence(reader.onMetaChange, this.onMetaChange, this);
  79195. }
  79196. me.reader = reader;
  79197. return me.reader;
  79198. },
  79199. /**
  79200. * Returns the reader currently attached to this proxy instance
  79201. * @return {Ext.data.reader.Reader} The Reader instance
  79202. */
  79203. getReader: function() {
  79204. return this.reader;
  79205. },
  79206. /**
  79207. * @private
  79208. * Called each time the reader's onMetaChange is called so that the proxy can fire the metachange event
  79209. */
  79210. onMetaChange: function(meta) {
  79211. this.fireEvent('metachange', this, meta);
  79212. },
  79213. /**
  79214. * Sets the Proxy's Writer by string, config object or Writer instance
  79215. *
  79216. * @param {String/Object/Ext.data.writer.Writer} writer The new Writer, which can be either a type string,
  79217. * a configuration object or an Ext.data.writer.Writer instance
  79218. * @return {Ext.data.writer.Writer} The attached Writer object
  79219. */
  79220. setWriter: function(writer) {
  79221. var me = this,
  79222. needsCopy = true;
  79223. if (writer === undefined || typeof writer == 'string') {
  79224. writer = {
  79225. type: writer
  79226. };
  79227. needsCopy = false;
  79228. }
  79229. if (!writer.isWriter) {
  79230. if (needsCopy) {
  79231. writer = Ext.apply({}, writer);
  79232. }
  79233. Ext.applyIf(writer, {
  79234. model: me.model,
  79235. type : me.defaultWriterType
  79236. });
  79237. writer = Ext.createByAlias('writer.' + writer.type, writer);
  79238. }
  79239. me.writer = writer;
  79240. return me.writer;
  79241. },
  79242. /**
  79243. * Returns the writer currently attached to this proxy instance
  79244. * @return {Ext.data.writer.Writer} The Writer instance
  79245. */
  79246. getWriter: function() {
  79247. return this.writer;
  79248. },
  79249. /**
  79250. * Performs the given create operation.
  79251. * @param {Ext.data.Operation} operation The Operation to perform
  79252. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  79253. * successful or not)
  79254. * @param {Object} scope Scope to execute the callback function in
  79255. * @method
  79256. */
  79257. create: Ext.emptyFn,
  79258. /**
  79259. * Performs the given read operation.
  79260. * @param {Ext.data.Operation} operation The Operation to perform
  79261. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  79262. * successful or not)
  79263. * @param {Object} scope Scope to execute the callback function in
  79264. * @method
  79265. */
  79266. read: Ext.emptyFn,
  79267. /**
  79268. * Performs the given update operation.
  79269. * @param {Ext.data.Operation} operation The Operation to perform
  79270. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  79271. * successful or not)
  79272. * @param {Object} scope Scope to execute the callback function in
  79273. * @method
  79274. */
  79275. update: Ext.emptyFn,
  79276. /**
  79277. * Performs the given destroy operation.
  79278. * @param {Ext.data.Operation} operation The Operation to perform
  79279. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  79280. * successful or not)
  79281. * @param {Object} scope Scope to execute the callback function in
  79282. * @method
  79283. */
  79284. destroy: Ext.emptyFn,
  79285. /**
  79286. * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used
  79287. * internally by {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
  79288. *
  79289. * myProxy.batch({
  79290. * create : [myModel1, myModel2],
  79291. * update : [myModel3],
  79292. * destroy: [myModel4, myModel5]
  79293. * });
  79294. *
  79295. * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and
  79296. * have not been saved before, 3 has been saved previously but needs to be updated, and 4 and 5 have already been
  79297. * saved but should now be destroyed.
  79298. *
  79299. * Note that the previous version of this method took 2 arguments (operations and listeners). While this is still
  79300. * supported for now, the current signature is now a single `options` argument that can contain both operations and
  79301. * listeners, in addition to other options. The multi-argument signature will likely be deprecated in a future release.
  79302. *
  79303. * @param {Object} options Object containing one or more properties supported by the batch method:
  79304. *
  79305. * @param {Object} options.operations Object containing the Model instances to act upon, keyed by action name
  79306. *
  79307. * @param {Object} [options.listeners] Event listeners object passed straight through to the Batch -
  79308. * see {@link Ext.data.Batch} for details
  79309. *
  79310. * @param {Ext.data.Batch/Object} [options.batch] A {@link Ext.data.Batch} object (or batch config to apply
  79311. * to the created batch). If unspecified a default batch will be auto-created.
  79312. *
  79313. * @param {Function} [options.callback] The function to be called upon completion of processing the batch.
  79314. * The callback is called regardless of success or failure and is passed the following parameters:
  79315. * @param {Ext.data.Batch} options.callback.batch The {@link Ext.data.Batch batch} that was processed,
  79316. * containing all operations in their current state after processing
  79317. * @param {Object} options.callback.options The options argument that was originally passed into batch
  79318. *
  79319. * @param {Function} [options.success] The function to be called upon successful completion of the batch. The
  79320. * success function is called only if no exceptions were reported in any operations. If one or more exceptions
  79321. * occurred then the `failure` function will be called instead. The success function is called
  79322. * with the following parameters:
  79323. * @param {Ext.data.Batch} options.success.batch The {@link Ext.data.Batch batch} that was processed,
  79324. * containing all operations in their current state after processing
  79325. * @param {Object} options.success.options The options argument that was originally passed into batch
  79326. *
  79327. * @param {Function} [options.failure] The function to be called upon unsuccessful completion of the batch. The
  79328. * failure function is called when one or more operations returns an exception during processing (even if some
  79329. * operations were also successful). In this case you can check the batch's {@link Ext.data.Batch#exceptions
  79330. * exceptions} array to see exactly which operations had exceptions. The failure function is called with the
  79331. * following parameters:
  79332. * @param {Ext.data.Batch} options.failure.batch The {@link Ext.data.Batch batch} that was processed,
  79333. * containing all operations in their current state after processing
  79334. * @param {Object} options.failure.options The options argument that was originally passed into batch
  79335. *
  79336. * @param {Object} [options.scope] The scope in which to execute any callbacks (i.e. the `this` object inside
  79337. * the callback, success and/or failure functions). Defaults to the proxy.
  79338. *
  79339. * @return {Ext.data.Batch} The newly created Batch
  79340. */
  79341. batch: function(options, /* deprecated */listeners) {
  79342. var me = this,
  79343. useBatch = me.batchActions,
  79344. batch,
  79345. records,
  79346. actions, aLen, action, a, r, rLen, record;
  79347. if (options.operations === undefined) {
  79348. // the old-style (operations, listeners) signature was called
  79349. // so convert to the single options argument syntax
  79350. options = {
  79351. operations: options,
  79352. listeners: listeners
  79353. };
  79354. }
  79355. if (options.batch) {
  79356. if (Ext.isDefined(options.batch.runOperation)) {
  79357. batch = Ext.applyIf(options.batch, {
  79358. proxy: me,
  79359. listeners: {}
  79360. });
  79361. }
  79362. } else {
  79363. options.batch = {
  79364. proxy: me,
  79365. listeners: options.listeners || {}
  79366. };
  79367. }
  79368. if (!batch) {
  79369. batch = new Ext.data.Batch(options.batch);
  79370. }
  79371. batch.on('complete', Ext.bind(me.onBatchComplete, me, [options], 0));
  79372. actions = me.batchOrder.split(',');
  79373. aLen = actions.length;
  79374. for (a = 0; a < aLen; a++) {
  79375. action = actions[a];
  79376. records = options.operations[action];
  79377. if (records) {
  79378. if (useBatch) {
  79379. batch.add(new Ext.data.Operation({
  79380. action : action,
  79381. records : records
  79382. }));
  79383. } else {
  79384. rLen = records.length;
  79385. for (r = 0; r < rLen; r++) {
  79386. record = records[r];
  79387. batch.add(new Ext.data.Operation({
  79388. action : action,
  79389. records : [record]
  79390. }));
  79391. }
  79392. }
  79393. }
  79394. }
  79395. batch.start();
  79396. return batch;
  79397. },
  79398. /**
  79399. * @private
  79400. * The internal callback that the proxy uses to call any specified user callbacks after completion of a batch
  79401. */
  79402. onBatchComplete: function(batchOptions, batch) {
  79403. var scope = batchOptions.scope || this;
  79404. if (batch.hasException) {
  79405. if (Ext.isFunction(batchOptions.failure)) {
  79406. Ext.callback(batchOptions.failure, scope, [batch, batchOptions]);
  79407. }
  79408. } else if (Ext.isFunction(batchOptions.success)) {
  79409. Ext.callback(batchOptions.success, scope, [batch, batchOptions]);
  79410. }
  79411. if (Ext.isFunction(batchOptions.callback)) {
  79412. Ext.callback(batchOptions.callback, scope, [batch, batchOptions]);
  79413. }
  79414. }
  79415. }, function() {
  79416. //backwards compatibility
  79417. Ext.data.DataProxy = this;
  79418. });
  79419. /**
  79420. * @author Ed Spencer
  79421. *
  79422. * AbstractStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.TreeStore}. It's never used directly,
  79423. * but offers a set of methods used by both of those subclasses.
  79424. *
  79425. * We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what
  79426. * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what
  79427. * AbstractStore is and is not.
  79428. *
  79429. * AbstractStore provides the basic configuration for anything that can be considered a Store. It expects to be
  79430. * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a
  79431. * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.
  79432. *
  79433. * AbstractStore provides a few helpful methods such as {@link #method-load} and {@link #sync}, which load and save data
  79434. * respectively, passing the requests through the configured {@link #proxy}. Both built-in Store subclasses add extra
  79435. * behavior to each of these functions. Note also that each AbstractStore subclass has its own way of storing data -
  79436. * in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.MixedCollection MixedCollection}, whereas in
  79437. * {@link Ext.data.TreeStore TreeStore} we use a {@link Ext.data.Tree} to maintain the data's hierarchy.
  79438. *
  79439. * The store provides filtering and sorting support. This sorting/filtering can happen on the client side
  79440. * or can be completed on the server. This is controlled by the {@link Ext.data.Store#remoteSort remoteSort} and
  79441. * {@link Ext.data.Store#remoteFilter remoteFilter} config options. For more information see the {@link #sort} and
  79442. * {@link Ext.data.Store#filter filter} methods.
  79443. */
  79444. Ext.define('Ext.data.AbstractStore', {
  79445. requires: [
  79446. 'Ext.util.MixedCollection',
  79447. 'Ext.data.proxy.Proxy',
  79448. 'Ext.data.Operation',
  79449. 'Ext.util.Filter'
  79450. ],
  79451. mixins: {
  79452. observable: 'Ext.util.Observable',
  79453. sortable: 'Ext.util.Sortable'
  79454. },
  79455. statics: {
  79456. /**
  79457. * Creates a store from config object.
  79458. *
  79459. * @param {Object/Ext.data.AbstractStore} store A config for
  79460. * the store to be created. It may contain a `type` field
  79461. * which defines the particular type of store to create.
  79462. *
  79463. * Alteratively passing an actual store to this method will
  79464. * just return it, no changes made.
  79465. *
  79466. * @return {Ext.data.AbstractStore} The created store.
  79467. * @static
  79468. */
  79469. create: function(store) {
  79470. if (!store.isStore) {
  79471. if (!store.type) {
  79472. store.type = 'store';
  79473. }
  79474. store = Ext.createByAlias('store.' + store.type, store);
  79475. }
  79476. return store;
  79477. }
  79478. },
  79479. /**
  79480. * @cfg {Boolean} remoteSort
  79481. * True to defer any sorting operation to the server. If false, sorting is done locally on the client.
  79482. */
  79483. remoteSort : false,
  79484. /**
  79485. * @cfg {Boolean} remoteFilter
  79486. * True to defer any filtering operation to the server. If false, filtering is done locally on the client.
  79487. */
  79488. remoteFilter: false,
  79489. /**
  79490. * @cfg {String/Ext.data.proxy.Proxy/Object} proxy
  79491. * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -
  79492. * see {@link #setProxy} for details.
  79493. */
  79494. /**
  79495. * @cfg {Boolean/Object} autoLoad
  79496. * If data is not specified, and if autoLoad is true or an Object, this store's load method is automatically called
  79497. * after creation. If the value of autoLoad is an Object, this Object will be passed to the store's load method.
  79498. * Defaults to false.
  79499. */
  79500. autoLoad: undefined,
  79501. /**
  79502. * @cfg {Boolean} autoSync
  79503. * True to automatically sync the Store with its Proxy after every edit to one of its Records. Defaults to false.
  79504. */
  79505. autoSync: false,
  79506. /**
  79507. * @cfg {String} batchUpdateMode
  79508. * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
  79509. * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
  79510. * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
  79511. * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
  79512. */
  79513. batchUpdateMode: 'operation',
  79514. /**
  79515. * @cfg {Boolean} filterOnLoad
  79516. * If true, any filters attached to this Store will be run after loading data, before the datachanged event is fired.
  79517. * Defaults to true, ignored if {@link Ext.data.Store#remoteFilter remoteFilter} is true
  79518. */
  79519. filterOnLoad: true,
  79520. /**
  79521. * @cfg {Boolean} sortOnLoad
  79522. * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
  79523. * Defaults to true, igored if {@link Ext.data.Store#remoteSort remoteSort} is true
  79524. */
  79525. sortOnLoad: true,
  79526. /**
  79527. * @property {Boolean} implicitModel
  79528. * True if a model was created implicitly for this Store. This happens if a fields array is passed to the Store's
  79529. * constructor instead of a model constructor or name.
  79530. * @private
  79531. */
  79532. implicitModel: false,
  79533. /**
  79534. * @property {String} defaultProxyType
  79535. * The string type of the Proxy to create if none is specified. This defaults to creating a
  79536. * {@link Ext.data.proxy.Memory memory proxy}.
  79537. */
  79538. defaultProxyType: 'memory',
  79539. /**
  79540. * @property {Boolean} isDestroyed
  79541. * True if the Store has already been destroyed. If this is true, the reference to Store should be deleted
  79542. * as it will not function correctly any more.
  79543. */
  79544. isDestroyed: false,
  79545. /**
  79546. * @property {Boolean} isStore
  79547. * `true` in this class to identify an object as an instantiated Store, or subclass thereof.
  79548. */
  79549. isStore: true,
  79550. /**
  79551. * @cfg {String} storeId
  79552. * Unique identifier for this store. If present, this Store will be registered with the {@link Ext.data.StoreManager},
  79553. * making it easy to reuse elsewhere.
  79554. *
  79555. * Note that when store is instatiated by Controller, the storeId will be overridden by the name of the store.
  79556. */
  79557. /**
  79558. * @cfg {Object[]} fields
  79559. * This may be used in place of specifying a {@link #model} configuration. The fields should be a
  79560. * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
  79561. * with these fields. In general this configuration option should only be used for simple stores like
  79562. * a two-field store of ComboBox. For anything more complicated, such as specifying a particular id property or
  79563. * associations, a {@link Ext.data.Model} should be defined and specified for the {@link #model}
  79564. * config.
  79565. */
  79566. /**
  79567. * @cfg {String} model
  79568. * Name of the {@link Ext.data.Model Model} associated with this store.
  79569. * The string is used as an argument for {@link Ext.ModelManager#getModel}.
  79570. */
  79571. /**
  79572. * @cfg {Object[]/Function[]} filters
  79573. * Array of {@link Ext.util.Filter Filters} for this store. Can also be passed array of
  79574. * functions which will be used as the {@link Ext.util.Filter#filterFn filterFn} config
  79575. * for filters:
  79576. *
  79577. * filters: [
  79578. * function(item) {
  79579. * return item.internalId > 0;
  79580. * }
  79581. * ]
  79582. *
  79583. * To filter after the grid is loaded use the {@link Ext.data.Store#filterBy filterBy} function.
  79584. */
  79585. sortRoot: 'data',
  79586. //documented above
  79587. constructor: function(config) {
  79588. var me = this,
  79589. filters;
  79590. /**
  79591. * @event add
  79592. * Fired when a Model instance has been added to this Store
  79593. * @param {Ext.data.Store} store The store
  79594. * @param {Ext.data.Model[]} records The Model instances that were added
  79595. * @param {Number} index The index at which the instances were inserted
  79596. */
  79597. /**
  79598. * @event remove
  79599. * Fired when a Model instance has been removed from this Store
  79600. * @param {Ext.data.Store} store The Store object
  79601. * @param {Ext.data.Model} record The record that was removed
  79602. * @param {Number} index The index of the record that was removed
  79603. */
  79604. /**
  79605. * @event update
  79606. * Fires when a Model instance has been updated
  79607. * @param {Ext.data.Store} this
  79608. * @param {Ext.data.Model} record The Model instance that was updated
  79609. * @param {String} operation The update operation being performed. Value may be one of:
  79610. *
  79611. * Ext.data.Model.EDIT
  79612. * Ext.data.Model.REJECT
  79613. * Ext.data.Model.COMMIT
  79614. * @param {String[]} modifiedFieldNames Array of field names changed during edit.
  79615. */
  79616. /**
  79617. * @event datachanged
  79618. * Fires whenever the records in the Store have changed in some way - this could include adding or removing
  79619. * records, or updating the data in existing records
  79620. * @param {Ext.data.Store} this The data store
  79621. */
  79622. /**
  79623. * @event refresh
  79624. * Fires when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.) and a
  79625. * widget that is using this Store as a Record cache should refresh its view.
  79626. * @param {Ext.data.Store} this The data store
  79627. */
  79628. /**
  79629. * @event beforeload
  79630. * Fires before a request is made for a new data object. If the beforeload handler returns false the load
  79631. * action will be canceled.
  79632. * @param {Ext.data.Store} store This Store
  79633. * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
  79634. * load the Store
  79635. */
  79636. /**
  79637. * @event load
  79638. * Fires whenever the store reads data from a remote data source.
  79639. * @param {Ext.data.Store} this
  79640. * @param {Ext.data.Model[]} records An array of records
  79641. * @param {Boolean} successful True if the operation was successful.
  79642. */
  79643. /**
  79644. * @event write
  79645. * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}
  79646. * @param {Ext.data.Store} store This Store
  79647. * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object that was used in
  79648. * the write
  79649. */
  79650. /**
  79651. * @event beforesync
  79652. * Fired before a call to {@link #sync} is executed. Return false from any listener to cancel the sync
  79653. * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
  79654. */
  79655. /**
  79656. * @event clear
  79657. * Fired after the {@link #removeAll} method is called.
  79658. * @param {Ext.data.Store} this
  79659. */
  79660. /**
  79661. * @event metachange
  79662. * Fires when this store's underlying reader (available via the proxy) provides new metadata.
  79663. * Metadata usually consists of new field definitions, but can include any configuration data
  79664. * required by an application, and can be processed as needed in the event handler.
  79665. * This event is currently only fired for JsonReaders.
  79666. * @param {Ext.data.Store} this
  79667. * @param {Object} meta The JSON metadata
  79668. */
  79669. Ext.apply(me, config);
  79670. // don't use *config* anymore from here on... use *me* instead...
  79671. /**
  79672. * Temporary cache in which removed model instances are kept until successfully synchronised with a Proxy,
  79673. * at which point this is cleared.
  79674. * @protected
  79675. * @property {Ext.data.Model[]} removed
  79676. */
  79677. me.removed = [];
  79678. me.mixins.observable.constructor.apply(me, arguments);
  79679. var configModel = me.model;
  79680. me.model = Ext.ModelManager.getModel(me.model);
  79681. /**
  79682. * @property {Object} modelDefaults
  79683. * @private
  79684. * A set of default values to be applied to every model instance added via {@link Ext.data.Store#insert insert} or created
  79685. * via {@link Ext.data.Store#createModel createModel}. This is used internally by associations to set foreign keys and
  79686. * other fields. See the Association classes source code for examples. This should not need to be used by application developers.
  79687. */
  79688. Ext.applyIf(me, {
  79689. modelDefaults: {}
  79690. });
  79691. //Supports the 3.x style of simply passing an array of fields to the store, implicitly creating a model
  79692. if (!me.model && me.fields) {
  79693. me.model = Ext.define('Ext.data.Store.ImplicitModel-' + (me.storeId || Ext.id()), {
  79694. extend: 'Ext.data.Model',
  79695. fields: me.fields,
  79696. proxy: me.proxy || me.defaultProxyType
  79697. });
  79698. delete me.fields;
  79699. me.implicitModel = true;
  79700. }
  79701. if (!me.model && me.useModelWarning !== false) {
  79702. // There are a number of ways things could have gone wrong, try to give as much information as possible
  79703. var logMsg = [
  79704. Ext.getClassName(me) || 'Store',
  79705. ' created with no model.'
  79706. ];
  79707. if (typeof configModel === 'string') {
  79708. logMsg.push(" The name '", configModel, "'", ' does not correspond to a valid model.');
  79709. }
  79710. Ext.log.warn(logMsg.join(''));
  79711. }
  79712. //ensures that the Proxy is instantiated correctly
  79713. me.setProxy(me.proxy || me.model.getProxy());
  79714. me.proxy.on('metachange', me.onMetaChange, me);
  79715. if (me.id && !me.storeId) {
  79716. me.storeId = me.id;
  79717. delete me.id;
  79718. }
  79719. if (me.storeId) {
  79720. Ext.data.StoreManager.register(me);
  79721. }
  79722. me.mixins.sortable.initSortable.call(me);
  79723. /**
  79724. * @property {Ext.util.MixedCollection} filters
  79725. * The collection of {@link Ext.util.Filter Filters} currently applied to this Store
  79726. */
  79727. filters = me.decodeFilters(me.filters);
  79728. me.filters = new Ext.util.MixedCollection();
  79729. me.filters.addAll(filters);
  79730. },
  79731. /**
  79732. * Sets the Store's Proxy by string, config object or Proxy instance
  79733. * @param {String/Object/Ext.data.proxy.Proxy} proxy The new Proxy, which can be either a type string, a configuration object
  79734. * or an Ext.data.proxy.Proxy instance
  79735. * @return {Ext.data.proxy.Proxy} The attached Proxy object
  79736. */
  79737. setProxy: function(proxy) {
  79738. var me = this;
  79739. if (proxy instanceof Ext.data.proxy.Proxy) {
  79740. proxy.setModel(me.model);
  79741. } else {
  79742. if (Ext.isString(proxy)) {
  79743. proxy = {
  79744. type: proxy
  79745. };
  79746. }
  79747. Ext.applyIf(proxy, {
  79748. model: me.model
  79749. });
  79750. proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
  79751. }
  79752. me.proxy = proxy;
  79753. return me.proxy;
  79754. },
  79755. /**
  79756. * Returns the proxy currently attached to this proxy instance
  79757. * @return {Ext.data.proxy.Proxy} The Proxy instance
  79758. */
  79759. getProxy: function() {
  79760. return this.proxy;
  79761. },
  79762. // private
  79763. onMetaChange: function(proxy, meta) {
  79764. this.fireEvent('metachange', this, meta);
  79765. },
  79766. //saves any phantom records
  79767. create: function(data, options) {
  79768. var me = this,
  79769. instance = Ext.ModelManager.create(Ext.applyIf(data, me.modelDefaults), me.model.modelName),
  79770. operation;
  79771. options = options || {};
  79772. Ext.applyIf(options, {
  79773. action : 'create',
  79774. records: [instance]
  79775. });
  79776. operation = new Ext.data.Operation(options);
  79777. me.proxy.create(operation, me.onProxyWrite, me);
  79778. return instance;
  79779. },
  79780. read: function() {
  79781. return this.load.apply(this, arguments);
  79782. },
  79783. update: function(options) {
  79784. var me = this,
  79785. operation;
  79786. options = options || {};
  79787. Ext.applyIf(options, {
  79788. action : 'update',
  79789. records: me.getUpdatedRecords()
  79790. });
  79791. operation = new Ext.data.Operation(options);
  79792. return me.proxy.update(operation, me.onProxyWrite, me);
  79793. },
  79794. /**
  79795. * @private
  79796. * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
  79797. * the updates provided by the Proxy
  79798. */
  79799. onProxyWrite: function(operation) {
  79800. var me = this,
  79801. success = operation.wasSuccessful(),
  79802. records = operation.getRecords();
  79803. switch (operation.action) {
  79804. case 'create':
  79805. me.onCreateRecords(records, operation, success);
  79806. break;
  79807. case 'update':
  79808. me.onUpdateRecords(records, operation, success);
  79809. break;
  79810. case 'destroy':
  79811. me.onDestroyRecords(records, operation, success);
  79812. break;
  79813. }
  79814. if (success) {
  79815. me.fireEvent('write', me, operation);
  79816. me.fireEvent('datachanged', me);
  79817. me.fireEvent('refresh', me);
  79818. }
  79819. //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
  79820. Ext.callback(operation.callback, operation.scope || me, [records, operation, success]);
  79821. },
  79822. // may be implemented by store subclasses
  79823. onCreateRecords: Ext.emptyFn,
  79824. // may be implemented by store subclasses
  79825. onUpdateRecords: Ext.emptyFn,
  79826. /**
  79827. * Removes any records when a write is returned from the server.
  79828. * @private
  79829. * @param {Ext.data.Model[]} records The array of removed records
  79830. * @param {Ext.data.Operation} operation The operation that just completed
  79831. * @param {Boolean} success True if the operation was successful
  79832. */
  79833. onDestroyRecords: function(records, operation, success) {
  79834. if (success) {
  79835. this.removed = [];
  79836. }
  79837. },
  79838. //tells the attached proxy to destroy the given records
  79839. destroy: function(options) {
  79840. var me = this,
  79841. operation;
  79842. options = options || {};
  79843. Ext.applyIf(options, {
  79844. action : 'destroy',
  79845. records: me.getRemovedRecords()
  79846. });
  79847. operation = new Ext.data.Operation(options);
  79848. return me.proxy.destroy(operation, me.onProxyWrite, me);
  79849. },
  79850. /**
  79851. * @private
  79852. * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
  79853. * to onProxyWrite.
  79854. */
  79855. onBatchOperationComplete: function(batch, operation) {
  79856. return this.onProxyWrite(operation);
  79857. },
  79858. /**
  79859. * @private
  79860. * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
  79861. * and updates the Store's internal data MixedCollection.
  79862. */
  79863. onBatchComplete: function(batch, operation) {
  79864. var me = this,
  79865. operations = batch.operations,
  79866. length = operations.length,
  79867. i;
  79868. me.suspendEvents();
  79869. for (i = 0; i < length; i++) {
  79870. me.onProxyWrite(operations[i]);
  79871. }
  79872. me.resumeEvents();
  79873. me.fireEvent('datachanged', me);
  79874. me.fireEvent('refresh', me);
  79875. },
  79876. /**
  79877. * @private
  79878. */
  79879. onBatchException: function(batch, operation) {
  79880. // //decide what to do... could continue with the next operation
  79881. // batch.start();
  79882. //
  79883. // //or retry the last operation
  79884. // batch.retry();
  79885. },
  79886. /**
  79887. * @private
  79888. * Filter function for new records.
  79889. */
  79890. filterNew: function(item) {
  79891. // only want phantom records that are valid
  79892. return item.phantom === true && item.isValid();
  79893. },
  79894. /**
  79895. * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
  79896. * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one)
  79897. * @return {Ext.data.Model[]} The Model instances
  79898. */
  79899. getNewRecords: function() {
  79900. return [];
  79901. },
  79902. /**
  79903. * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy
  79904. * @return {Ext.data.Model[]} The updated Model instances
  79905. */
  79906. getUpdatedRecords: function() {
  79907. return [];
  79908. },
  79909. /**
  79910. * Gets all {@link Ext.data.Model records} added or updated since the last commit. Note that the order of records
  79911. * returned is not deterministic and does not indicate the order in which records were modified. Note also that
  79912. * removed records are not included (use {@link #getRemovedRecords} for that).
  79913. * @return {Ext.data.Model[]} The added and updated Model instances
  79914. */
  79915. getModifiedRecords : function(){
  79916. return [].concat(this.getNewRecords(), this.getUpdatedRecords());
  79917. },
  79918. /**
  79919. * @private
  79920. * Filter function for updated records.
  79921. */
  79922. filterUpdated: function(item) {
  79923. // only want dirty records, not phantoms that are valid
  79924. return item.dirty === true && item.phantom !== true && item.isValid();
  79925. },
  79926. /**
  79927. * Returns any records that have been removed from the store but not yet destroyed on the proxy.
  79928. * @return {Ext.data.Model[]} The removed Model instances
  79929. */
  79930. getRemovedRecords: function() {
  79931. return this.removed;
  79932. },
  79933. filter: function(filters, value) {
  79934. },
  79935. /**
  79936. * @private
  79937. * Normalizes an array of filter objects, ensuring that they are all Ext.util.Filter instances
  79938. * @param {Object[]} filters The filters array
  79939. * @return {Ext.util.Filter[]} Array of Ext.util.Filter objects
  79940. */
  79941. decodeFilters: function(filters) {
  79942. if (!Ext.isArray(filters)) {
  79943. if (filters === undefined) {
  79944. filters = [];
  79945. } else {
  79946. filters = [filters];
  79947. }
  79948. }
  79949. var length = filters.length,
  79950. Filter = Ext.util.Filter,
  79951. config, i;
  79952. for (i = 0; i < length; i++) {
  79953. config = filters[i];
  79954. if (!(config instanceof Filter)) {
  79955. Ext.apply(config, {
  79956. root: 'data'
  79957. });
  79958. //support for 3.x style filters where a function can be defined as 'fn'
  79959. if (config.fn) {
  79960. config.filterFn = config.fn;
  79961. }
  79962. //support a function to be passed as a filter definition
  79963. if (typeof config == 'function') {
  79964. config = {
  79965. filterFn: config
  79966. };
  79967. }
  79968. filters[i] = new Filter(config);
  79969. }
  79970. }
  79971. return filters;
  79972. },
  79973. clearFilter: function(supressEvent) {
  79974. },
  79975. isFiltered: function() {
  79976. },
  79977. filterBy: function(fn, scope) {
  79978. },
  79979. /**
  79980. * Synchronizes the store with its {@link #proxy}. This asks the proxy to batch together any new, updated
  79981. * and deleted records in the store, updating the store's internal representation of the records
  79982. * as each operation completes.
  79983. *
  79984. * @param {Object} [options] Object containing one or more properties supported by the sync method (these get
  79985. * passed along to the underlying proxy's {@link Ext.data.Proxy#batch batch} method):
  79986. *
  79987. * @param {Ext.data.Batch/Object} [options.batch] A {@link Ext.data.Batch} object (or batch config to apply
  79988. * to the created batch). If unspecified a default batch will be auto-created as needed.
  79989. *
  79990. * @param {Function} [options.callback] The function to be called upon completion of the sync.
  79991. * The callback is called regardless of success or failure and is passed the following parameters:
  79992. * @param {Ext.data.Batch} options.callback.batch The {@link Ext.data.Batch batch} that was processed,
  79993. * containing all operations in their current state after processing
  79994. * @param {Object} options.callback.options The options argument that was originally passed into sync
  79995. *
  79996. * @param {Function} [options.success] The function to be called upon successful completion of the sync. The
  79997. * success function is called only if no exceptions were reported in any operations. If one or more exceptions
  79998. * occurred then the failure function will be called instead. The success function is called
  79999. * with the following parameters:
  80000. * @param {Ext.data.Batch} options.success.batch The {@link Ext.data.Batch batch} that was processed,
  80001. * containing all operations in their current state after processing
  80002. * @param {Object} options.success.options The options argument that was originally passed into sync
  80003. *
  80004. * @param {Function} [options.failure] The function to be called upon unsuccessful completion of the sync. The
  80005. * failure function is called when one or more operations returns an exception during processing (even if some
  80006. * operations were also successful). In this case you can check the batch's {@link Ext.data.Batch#exceptions
  80007. * exceptions} array to see exactly which operations had exceptions. The failure function is called with the
  80008. * following parameters:
  80009. * @param {Ext.data.Batch} options.failure.batch The {@link Ext.data.Batch} that was processed, containing all
  80010. * operations in their current state after processing
  80011. * @param {Object} options.failure.options The options argument that was originally passed into sync
  80012. *
  80013. * @param {Object} [options.scope] The scope in which to execute any callbacks (i.e. the `this` object inside
  80014. * the callback, success and/or failure functions). Defaults to the store's proxy.
  80015. *
  80016. * @return {Ext.data.Store} this
  80017. */
  80018. sync: function(options) {
  80019. var me = this,
  80020. operations = {},
  80021. toCreate = me.getNewRecords(),
  80022. toUpdate = me.getUpdatedRecords(),
  80023. toDestroy = me.getRemovedRecords(),
  80024. needsSync = false;
  80025. if (toCreate.length > 0) {
  80026. operations.create = toCreate;
  80027. needsSync = true;
  80028. }
  80029. if (toUpdate.length > 0) {
  80030. operations.update = toUpdate;
  80031. needsSync = true;
  80032. }
  80033. if (toDestroy.length > 0) {
  80034. operations.destroy = toDestroy;
  80035. needsSync = true;
  80036. }
  80037. if (needsSync && me.fireEvent('beforesync', operations) !== false) {
  80038. options = options || {};
  80039. me.proxy.batch(Ext.apply(options, {
  80040. operations: operations,
  80041. listeners: me.getBatchListeners()
  80042. }));
  80043. }
  80044. return me;
  80045. },
  80046. /**
  80047. * @private
  80048. * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
  80049. * This is broken out into a separate function to allow for customisation of the listeners
  80050. * @return {Object} The listeners object
  80051. */
  80052. getBatchListeners: function() {
  80053. var me = this,
  80054. listeners = {
  80055. scope: me,
  80056. exception: me.onBatchException
  80057. };
  80058. if (me.batchUpdateMode == 'operation') {
  80059. listeners.operationcomplete = me.onBatchOperationComplete;
  80060. } else {
  80061. listeners.complete = me.onBatchComplete;
  80062. }
  80063. return listeners;
  80064. },
  80065. /**
  80066. * Saves all pending changes via the configured {@link #proxy}. Use {@link #sync} instead.
  80067. * @deprecated 4.0.0 Will be removed in the next major version
  80068. */
  80069. save: function() {
  80070. return this.sync.apply(this, arguments);
  80071. },
  80072. /**
  80073. * Loads the Store using its configured {@link #proxy}.
  80074. * @param {Object} options (optional) config object. This is passed into the {@link Ext.data.Operation Operation}
  80075. * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function
  80076. *
  80077. * @return {Ext.data.Store} this
  80078. */
  80079. load: function(options) {
  80080. var me = this,
  80081. operation;
  80082. options = Ext.apply({
  80083. action: 'read',
  80084. filters: me.filters.items,
  80085. sorters: me.getSorters()
  80086. }, options);
  80087. me.lastOptions = options;
  80088. operation = new Ext.data.Operation(options);
  80089. if (me.fireEvent('beforeload', me, operation) !== false) {
  80090. me.loading = true;
  80091. me.proxy.read(operation, me.onProxyLoad, me);
  80092. }
  80093. return me;
  80094. },
  80095. /**
  80096. * Reloads the store using the last options passed to the {@link #method-load} method.
  80097. * @param {Object} options A config object which contains options which may override the options passed to the previous load call.
  80098. */
  80099. reload: function(options) {
  80100. return this.load(Ext.apply(this.lastOptions, options));
  80101. },
  80102. /**
  80103. * @private
  80104. * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
  80105. * @param {Ext.data.Model} record The model instance that was edited
  80106. * @param {String[]} modifiedFieldNames Array of field names changed during edit.
  80107. */
  80108. afterEdit : function(record, modifiedFieldNames) {
  80109. var me = this,
  80110. i, shouldSync;
  80111. if (me.autoSync && !me.autoSyncSuspended) {
  80112. for (i = modifiedFieldNames.length; i--;) {
  80113. // only sync if persistent fields were modified
  80114. if (record.fields.get(modifiedFieldNames[i]).persist) {
  80115. shouldSync = true;
  80116. break;
  80117. }
  80118. }
  80119. if (shouldSync) {
  80120. me.sync();
  80121. }
  80122. }
  80123. me.fireEvent('update', me, record, Ext.data.Model.EDIT, modifiedFieldNames);
  80124. },
  80125. /**
  80126. * @private
  80127. * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
  80128. * @param {Ext.data.Model} record The model instance that was edited
  80129. */
  80130. afterReject : function(record) {
  80131. // Must pass the 5th param (modifiedFieldNames) as null, otherwise the
  80132. // event firing machinery appends the listeners "options" object to the arg list
  80133. // which may get used as the modified fields array by a handler.
  80134. // This array is used for selective grid cell updating by Grid View.
  80135. // Null will be treated as though all cells need updating.
  80136. this.fireEvent('update', this, record, Ext.data.Model.REJECT, null);
  80137. },
  80138. /**
  80139. * @private
  80140. * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
  80141. * @param {Ext.data.Model} record The model instance that was edited
  80142. */
  80143. afterCommit : function(record) {
  80144. // Must pass the 5th param (modifiedFieldNames) as null, otherwise the
  80145. // event firing machinery appends the listeners "options" object to the arg list
  80146. // which may get used as the modified fields array by a handler.
  80147. // This array is used for selective grid cell updating by Grid View.
  80148. // Null will be treated as though all cells need updating.
  80149. this.fireEvent('update', this, record, Ext.data.Model.COMMIT, null);
  80150. },
  80151. // private
  80152. destroyStore: function() {
  80153. var me = this;
  80154. if (!me.isDestroyed) {
  80155. if (me.storeId) {
  80156. Ext.data.StoreManager.unregister(me);
  80157. }
  80158. me.clearData();
  80159. me.data = me.tree = me.sorters = me.filters = me.groupers = null;
  80160. if (me.reader) {
  80161. me.reader.destroyReader();
  80162. }
  80163. me.proxy = me.reader = me.writer = null;
  80164. me.clearListeners();
  80165. me.isDestroyed = true;
  80166. if (me.implicitModel) {
  80167. Ext.destroy(me.model);
  80168. } else {
  80169. me.model = null;
  80170. }
  80171. }
  80172. },
  80173. // private
  80174. doSort: function(sorterFn) {
  80175. var me = this;
  80176. if (me.remoteSort) {
  80177. //the load function will pick up the new sorters and request the sorted data from the proxy
  80178. me.load();
  80179. } else {
  80180. me.data.sortBy(sorterFn);
  80181. me.fireEvent('datachanged', me);
  80182. me.fireEvent('refresh', me);
  80183. }
  80184. },
  80185. // to be implemented by subclasses
  80186. clearData: Ext.emptyFn,
  80187. // to be implemented by subclasses
  80188. getCount: Ext.emptyFn,
  80189. // to be implemented by subclasses
  80190. getById: Ext.emptyFn,
  80191. /**
  80192. * Removes all records from the store. This method does a "fast remove",
  80193. * individual remove events are not called. The {@link #clear} event is
  80194. * fired upon completion.
  80195. * @method
  80196. */
  80197. removeAll: Ext.emptyFn,
  80198. // individual store subclasses should implement a "fast" remove
  80199. // and fire a clear event afterwards
  80200. /**
  80201. * Returns true if the Store is currently performing a load operation
  80202. * @return {Boolean} True if the Store is currently loading
  80203. */
  80204. isLoading: function() {
  80205. return !!this.loading;
  80206. },
  80207. /**
  80208. * Suspends automatically syncing the Store with its Proxy. Only applicable if {@link #autoSync} is `true`
  80209. */
  80210. suspendAutoSync: function() {
  80211. this.autoSyncSuspended = true;
  80212. },
  80213. /**
  80214. * Resumes automatically syncing the Store with its Proxy. Only applicable if {@link #autoSync} is `true`
  80215. */
  80216. resumeAutoSync: function() {
  80217. this.autoSyncSuspended = false;
  80218. }
  80219. });
  80220. /**
  80221. * @author Ed Spencer
  80222. *
  80223. * Simple wrapper class that represents a set of records returned by a Proxy.
  80224. */
  80225. Ext.define('Ext.data.ResultSet', {
  80226. /**
  80227. * @cfg {Boolean} loaded
  80228. * True if the records have already been loaded. This is only meaningful when dealing with
  80229. * SQL-backed proxies.
  80230. */
  80231. loaded: true,
  80232. /**
  80233. * @cfg {Number} count
  80234. * The number of records in this ResultSet. Note that total may differ from this number.
  80235. */
  80236. count: 0,
  80237. /**
  80238. * @cfg {Number} total
  80239. * The total number of records reported by the data source. This ResultSet may form a subset of
  80240. * those records (see {@link #count}).
  80241. */
  80242. total: 0,
  80243. /**
  80244. * @cfg {Boolean} success
  80245. * True if the ResultSet loaded successfully, false if any errors were encountered.
  80246. */
  80247. success: false,
  80248. /**
  80249. * @cfg {Ext.data.Model[]} records (required)
  80250. * The array of record instances.
  80251. */
  80252. /**
  80253. * Creates the resultSet
  80254. * @param {Object} [config] Config object.
  80255. */
  80256. constructor: function(config) {
  80257. Ext.apply(this, config);
  80258. /**
  80259. * @property {Number} totalRecords
  80260. * Copy of this.total.
  80261. * @deprecated Will be removed in Ext JS 5.0. Use {@link #total} instead.
  80262. */
  80263. this.totalRecords = this.total;
  80264. if (config.count === undefined) {
  80265. this.count = this.records.length;
  80266. }
  80267. }
  80268. });
  80269. /**
  80270. * @author Ed Spencer
  80271. *
  80272. * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link
  80273. * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create
  80274. * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy},
  80275. * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property:
  80276. *
  80277. * Ext.create('Ext.data.Store', {
  80278. * model: 'User',
  80279. * proxy: {
  80280. * type: 'ajax',
  80281. * url : 'users.json',
  80282. * reader: {
  80283. * type: 'json',
  80284. * root: 'users'
  80285. * }
  80286. * },
  80287. * });
  80288. *
  80289. * The above reader is configured to consume a JSON string that looks something like this:
  80290. *
  80291. * {
  80292. * "success": true,
  80293. * "users": [
  80294. * { "name": "User 1" },
  80295. * { "name": "User 2" }
  80296. * ]
  80297. * }
  80298. *
  80299. *
  80300. * # Loading Nested Data
  80301. *
  80302. * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.association.Association
  80303. * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a
  80304. * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models:
  80305. *
  80306. * Ext.define("User", {
  80307. * extend: 'Ext.data.Model',
  80308. * fields: [
  80309. * 'id', 'name'
  80310. * ],
  80311. *
  80312. * hasMany: {model: 'Order', name: 'orders'},
  80313. *
  80314. * proxy: {
  80315. * type: 'rest',
  80316. * url : 'users.json',
  80317. * reader: {
  80318. * type: 'json',
  80319. * root: 'users'
  80320. * }
  80321. * }
  80322. * });
  80323. *
  80324. * Ext.define("Order", {
  80325. * extend: 'Ext.data.Model',
  80326. * fields: [
  80327. * 'id', 'total'
  80328. * ],
  80329. *
  80330. * hasMany : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
  80331. * belongsTo: 'User'
  80332. * });
  80333. *
  80334. * Ext.define("OrderItem", {
  80335. * extend: 'Ext.data.Model',
  80336. * fields: [
  80337. * 'id', 'price', 'quantity', 'order_id', 'product_id'
  80338. * ],
  80339. *
  80340. * belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
  80341. * });
  80342. *
  80343. * Ext.define("Product", {
  80344. * extend: 'Ext.data.Model',
  80345. * fields: [
  80346. * 'id', 'name'
  80347. * ],
  80348. *
  80349. * hasMany: 'OrderItem'
  80350. * });
  80351. *
  80352. * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems.
  80353. * Finally, each OrderItem has a single Product. This allows us to consume data like this:
  80354. *
  80355. * {
  80356. * "users": [
  80357. * {
  80358. * "id": 123,
  80359. * "name": "Ed",
  80360. * "orders": [
  80361. * {
  80362. * "id": 50,
  80363. * "total": 100,
  80364. * "order_items": [
  80365. * {
  80366. * "id" : 20,
  80367. * "price" : 40,
  80368. * "quantity": 2,
  80369. * "product" : {
  80370. * "id": 1000,
  80371. * "name": "MacBook Pro"
  80372. * }
  80373. * },
  80374. * {
  80375. * "id" : 21,
  80376. * "price" : 20,
  80377. * "quantity": 3,
  80378. * "product" : {
  80379. * "id": 1001,
  80380. * "name": "iPhone"
  80381. * }
  80382. * }
  80383. * ]
  80384. * }
  80385. * ]
  80386. * }
  80387. * ]
  80388. * }
  80389. *
  80390. * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the
  80391. * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case),
  80392. * and finally the Product associated with each OrderItem. Now we can read the data and use it as follows:
  80393. *
  80394. * var store = Ext.create('Ext.data.Store', {
  80395. * model: "User"
  80396. * });
  80397. *
  80398. * store.load({
  80399. * callback: function() {
  80400. * //the user that was loaded
  80401. * var user = store.first();
  80402. *
  80403. * console.log("Orders for " + user.get('name') + ":")
  80404. *
  80405. * //iterate over the Orders for each User
  80406. * user.orders().each(function(order) {
  80407. * console.log("Order ID: " + order.getId() + ", which contains items:");
  80408. *
  80409. * //iterate over the OrderItems for each Order
  80410. * order.orderItems().each(function(orderItem) {
  80411. * //we know that the Product data is already loaded, so we can use the synchronous getProduct
  80412. * //usually, we would use the asynchronous version (see {@link Ext.data.association.BelongsTo})
  80413. * var product = orderItem.getProduct();
  80414. *
  80415. * console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
  80416. * });
  80417. * });
  80418. * }
  80419. * });
  80420. *
  80421. * Running the code above results in the following:
  80422. *
  80423. * Orders for Ed:
  80424. * Order ID: 50, which contains items:
  80425. * 2 orders of MacBook Pro
  80426. * 3 orders of iPhone
  80427. */
  80428. Ext.define('Ext.data.reader.Reader', {
  80429. requires: ['Ext.data.ResultSet', 'Ext.XTemplate'],
  80430. alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
  80431. mixins: {
  80432. observable: 'Ext.util.Observable'
  80433. },
  80434. /**
  80435. * @cfg {String} idProperty
  80436. * Name of the property within a row object that contains a record identifier value. Defaults to the id of the
  80437. * model. If an idProperty is explicitly specified it will override the idProperty defined on the model.
  80438. */
  80439. /**
  80440. * @cfg {String} [totalProperty="total"]
  80441. * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if
  80442. * the whole dataset is not passed in one go, but is being paged from the remote server.
  80443. */
  80444. totalProperty: 'total',
  80445. /**
  80446. * @cfg {String} [successProperty="success"]
  80447. * Name of the property from which to retrieve the `success` attribute, the value of which indicates
  80448. * whether a given request succeeded or failed (typically a boolean or 'true'|'false'). See
  80449. * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.
  80450. */
  80451. successProperty: 'success',
  80452. /**
  80453. * @cfg {String} root
  80454. * The name of the property which contains the data items corresponding to the Model(s) for which this
  80455. * Reader is configured. For JSON reader it's a property name (or a dot-separated list of property names
  80456. * if the root is nested). For XML reader it's a CSS selector. For Array reader the root is not applicable
  80457. * since the data is assumed to be a single-level array of arrays.
  80458. *
  80459. * By default the natural root of the data will be used: the root JSON array, the root XML element, or the array.
  80460. *
  80461. * The data packet value for this property should be an empty array to clear the data or show no data.
  80462. */
  80463. root: '',
  80464. /**
  80465. * @cfg {String} messageProperty
  80466. * The name of the property which contains a response message. This property is optional.
  80467. */
  80468. /**
  80469. * @cfg {Boolean} [implicitIncludes=true]
  80470. * True to automatically parse models nested within other models in a response object. See the
  80471. * Ext.data.reader.Reader intro docs for full explanation.
  80472. */
  80473. implicitIncludes: true,
  80474. /**
  80475. * @cfg {Boolean} [readRecordsOnFailure=true]
  80476. * True to extract the records from a data packet even if the {@link #successProperty} returns false.
  80477. */
  80478. readRecordsOnFailure: true,
  80479. /**
  80480. * @property {Object} metaData
  80481. * The raw meta data that was most recently read, if any. Meta data can include existing
  80482. * Reader config options like {@link #idProperty}, {@link #totalProperty}, etc. that get
  80483. * automatically applied to the Reader, and those can still be accessed directly from the Reader
  80484. * if needed. However, meta data is also often used to pass other custom data to be processed
  80485. * by application code. For example, it is common when reconfiguring the data model of a grid to
  80486. * also pass a corresponding column model config to be applied to the grid. Any such data will
  80487. * not get applied to the Reader directly (it just gets passed through and is ignored by Ext).
  80488. * This metaData property gives you access to all meta data that was passed, including any such
  80489. * custom data ignored by the reader.
  80490. *
  80491. * This is a read-only property, and it will get replaced each time a new meta data object is
  80492. * passed to the reader. Note that typically you would handle proxy's
  80493. * {@link Ext.data.proxy.Proxy#metachange metachange} event which passes this exact same meta
  80494. * object to listeners. However this property is available if it's more convenient to access it
  80495. * via the reader directly in certain cases.
  80496. * @readonly
  80497. */
  80498. /*
  80499. * @property {Boolean} isReader
  80500. * `true` in this class to identify an object as an instantiated Reader, or subclass thereof.
  80501. */
  80502. isReader: true,
  80503. // Private flag to the generated convertRecordData function to indicate whether to apply Field default
  80504. // values to fields for which no value is present in the raw data.
  80505. // This is set to false by a Server Proxy which is reading the response from a "create" or "update" operation.
  80506. applyDefaults: true,
  80507. lastFieldGeneration: null,
  80508. /**
  80509. * Creates new Reader.
  80510. * @param {Object} config (optional) Config object.
  80511. */
  80512. constructor: function(config) {
  80513. var me = this;
  80514. me.mixins.observable.constructor.call(me, config);
  80515. me.fieldCount = 0;
  80516. me.model = Ext.ModelManager.getModel(me.model);
  80517. me.accessExpressionFn = Ext.Function.bind(me.createFieldAccessExpression, me);
  80518. // Extractors can only be calculated if the fields MixedCollection has been set.
  80519. // A Model may only complete its setup (set the prototype properties) after asynchronous loading
  80520. // which would mean that there may be no "fields"
  80521. // If this happens, the load callback will call proxy.setModel which calls reader.setModel which
  80522. // triggers buildExtractors.
  80523. if (me.model && me.model.prototype.fields) {
  80524. me.buildExtractors();
  80525. }
  80526. this.addEvents(
  80527. /**
  80528. * @event
  80529. * Fires when the reader receives improperly encoded data from the server
  80530. * @param {Ext.data.reader.Reader} reader A reference to this reader
  80531. * @param {XMLHttpRequest} response The XMLHttpRequest response object
  80532. * @param {Ext.data.ResultSet} error The error object
  80533. */
  80534. 'exception'
  80535. );
  80536. },
  80537. /**
  80538. * Sets a new model for the reader.
  80539. * @private
  80540. * @param {Object} model The model to set.
  80541. * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
  80542. */
  80543. setModel: function(model, setOnProxy) {
  80544. var me = this;
  80545. me.model = Ext.ModelManager.getModel(model);
  80546. me.buildExtractors(true);
  80547. if (setOnProxy && me.proxy) {
  80548. me.proxy.setModel(me.model, true);
  80549. }
  80550. },
  80551. /**
  80552. * Reads the given response object. This method normalizes the different types of response object that may be passed to it.
  80553. * If it's an XMLHttpRequest object, hand off to the subclass' {@link #getResponseData} method.
  80554. * Else, hand off the reading of records to the {@link #readRecords} method.
  80555. * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
  80556. * @return {Ext.data.ResultSet} The parsed or default ResultSet object
  80557. */
  80558. read: function(response) {
  80559. var data;
  80560. if (response) {
  80561. data = response.responseText ? this.getResponseData(response) : this.readRecords(response);
  80562. }
  80563. return data || this.nullResultSet;
  80564. },
  80565. /**
  80566. * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function
  80567. * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional
  80568. * processing should not be needed.
  80569. * @param {Object} data The raw data object
  80570. * @return {Ext.data.ResultSet} A ResultSet object
  80571. */
  80572. readRecords: function(data) {
  80573. var me = this,
  80574. success,
  80575. recordCount,
  80576. records,
  80577. root,
  80578. total,
  80579. value,
  80580. message;
  80581. /*
  80582. * We check here whether fields collection has changed since the last read.
  80583. * This works around an issue when a Model is used for both a Tree and another
  80584. * source, because the tree decorates the model with extra fields and it causes
  80585. * issues because the readers aren't notified.
  80586. */
  80587. if (me.lastFieldGeneration !== me.model.prototype.fields.generation) {
  80588. me.buildExtractors(true);
  80589. }
  80590. /**
  80591. * @property {Object} rawData
  80592. * The raw data object that was last passed to {@link #readRecords}. Stored for further processing if needed.
  80593. */
  80594. me.rawData = data;
  80595. data = me.getData(data);
  80596. success = true;
  80597. recordCount = 0;
  80598. records = [];
  80599. if (me.successProperty) {
  80600. value = me.getSuccess(data);
  80601. if (value === false || value === 'false') {
  80602. success = false;
  80603. }
  80604. }
  80605. if (me.messageProperty) {
  80606. message = me.getMessage(data);
  80607. }
  80608. // Only try and extract other data if call was successful
  80609. if (me.readRecordsOnFailure || success) {
  80610. // If we pass an array as the data, we dont use getRoot on the data.
  80611. // Instead the root equals to the data.
  80612. root = Ext.isArray(data) ? data : me.getRoot(data);
  80613. if (root) {
  80614. total = root.length;
  80615. }
  80616. if (me.totalProperty) {
  80617. value = parseInt(me.getTotal(data), 10);
  80618. if (!isNaN(value)) {
  80619. total = value;
  80620. }
  80621. }
  80622. if (root) {
  80623. records = me.extractData(root);
  80624. recordCount = records.length;
  80625. }
  80626. }
  80627. return new Ext.data.ResultSet({
  80628. total : total || recordCount,
  80629. count : recordCount,
  80630. records: records,
  80631. success: success,
  80632. message: message
  80633. });
  80634. },
  80635. /**
  80636. * Returns extracted, type-cast rows of data.
  80637. * @param {Object[]/Object} root from server response
  80638. * @return {Array} An array of records containing the extracted data
  80639. * @private
  80640. */
  80641. extractData : function(root) {
  80642. var me = this,
  80643. records = [],
  80644. Model = me.model,
  80645. length = root.length,
  80646. convertedValues, node, record, i;
  80647. if (!root.length && Ext.isObject(root)) {
  80648. root = [root];
  80649. length = 1;
  80650. }
  80651. for (i = 0; i < length; i++) {
  80652. node = root[i];
  80653. if (!node.isModel) {
  80654. // Create a record with an empty data object.
  80655. // Populate that data object by extracting and converting field values from raw data
  80656. record = new Model(undefined, me.getId(node), node, convertedValues = {});
  80657. // If the server did not include an id in the response data, the Model constructor will mark the record as phantom.
  80658. // We need to set phantom to false here because records created from a server response using a reader by definition are not phantom records.
  80659. record.phantom = false;
  80660. // Use generated function to extract all fields at once
  80661. me.convertRecordData(convertedValues, node, record);
  80662. records.push(record);
  80663. if (me.implicitIncludes) {
  80664. me.readAssociated(record, node);
  80665. }
  80666. } else {
  80667. // If we're given a model instance in the data, just push it on
  80668. // without doing any conversion
  80669. records.push(node);
  80670. }
  80671. }
  80672. return records;
  80673. },
  80674. /**
  80675. * @private
  80676. * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
  80677. * on the record provided.
  80678. * @param {Ext.data.Model} record The record to load associations for
  80679. * @param {Object} data The data object
  80680. * @return {String} Return value description
  80681. */
  80682. readAssociated: function(record, data) {
  80683. var associations = record.associations.items,
  80684. i = 0,
  80685. length = associations.length,
  80686. association, associationData, proxy, reader;
  80687. for (; i < length; i++) {
  80688. association = associations[i];
  80689. associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
  80690. if (associationData) {
  80691. reader = association.getReader();
  80692. if (!reader) {
  80693. proxy = association.associatedModel.proxy;
  80694. // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
  80695. if (proxy) {
  80696. reader = proxy.getReader();
  80697. } else {
  80698. reader = new this.constructor({
  80699. model: association.associatedName
  80700. });
  80701. }
  80702. }
  80703. association.read(record, reader, associationData);
  80704. }
  80705. }
  80706. },
  80707. /**
  80708. * @private
  80709. * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
  80710. * record, this should return the relevant part of that data for the given association name. This is only really
  80711. * needed to support the XML Reader, which has to do a query to get the associated data object
  80712. * @param {Object} data The raw data object
  80713. * @param {String} associationName The name of the association to get data for (uses associationKey if present)
  80714. * @return {Object} The root
  80715. */
  80716. getAssociatedDataRoot: function(data, associationName) {
  80717. return data[associationName];
  80718. },
  80719. getFields: function() {
  80720. return this.model.prototype.fields.items;
  80721. },
  80722. /**
  80723. * @private
  80724. * By default this function just returns what is passed to it. It can be overridden in a subclass
  80725. * to return something else. See XmlReader for an example.
  80726. * @param {Object} data The data object
  80727. * @return {Object} The normalized data object
  80728. */
  80729. getData: function(data) {
  80730. return data;
  80731. },
  80732. /**
  80733. * @private
  80734. * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
  80735. * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
  80736. * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
  80737. * @param {Object} data The data object
  80738. * @return {Object} The same data object
  80739. */
  80740. getRoot: function(data) {
  80741. return data;
  80742. },
  80743. /**
  80744. * Takes a raw response object (as passed to the {@link #read} method) and returns the useful data
  80745. * segment from it. This must be implemented by each subclass.
  80746. * @param {Object} response The response object
  80747. * @return {Ext.data.ResultSet} A ResultSet object
  80748. */
  80749. getResponseData: function(response) {
  80750. Ext.Error.raise("getResponseData must be implemented in the Ext.data.reader.Reader subclass");
  80751. },
  80752. /**
  80753. * @private
  80754. * Reconfigures the meta data tied to this Reader
  80755. */
  80756. onMetaChange : function(meta) {
  80757. var me = this,
  80758. fields = meta.fields || me.getFields(),
  80759. newModel,
  80760. clientIdProperty;
  80761. // save off the raw meta data
  80762. me.metaData = meta;
  80763. // set any reader-specific configs from meta if available
  80764. me.root = meta.root || me.root;
  80765. me.idProperty = meta.idProperty || me.idProperty;
  80766. me.totalProperty = meta.totalProperty || me.totalProperty;
  80767. me.successProperty = meta.successProperty || me.successProperty;
  80768. me.messageProperty = meta.messageProperty || me.messageProperty;
  80769. clientIdProperty = meta.clientIdProperty;
  80770. if (me.model) {
  80771. me.model.setFields(fields, me.idProperty, clientIdProperty);
  80772. me.setModel(me.model, true);
  80773. }
  80774. else {
  80775. newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
  80776. extend: 'Ext.data.Model',
  80777. fields: fields,
  80778. clientIdProperty: clientIdProperty
  80779. });
  80780. if (me.idProperty) {
  80781. // We only do this if the reader actually has a custom idProperty set,
  80782. // otherwise let the model use its own default value. It is valid for
  80783. // the reader idProperty to be undefined, in which case it will use the
  80784. // model's idProperty (in getIdProperty()).
  80785. newModel.idProperty = me.idProperty;
  80786. }
  80787. me.setModel(newModel, true);
  80788. }
  80789. },
  80790. /**
  80791. * Get the idProperty to use for extracting data
  80792. * @private
  80793. * @return {String} The id property
  80794. */
  80795. getIdProperty: function(){
  80796. return this.idProperty || this.model.prototype.idProperty;
  80797. },
  80798. /**
  80799. * @private
  80800. * This builds optimized functions for retrieving record data and meta data from an object.
  80801. * Subclasses may need to implement their own getRoot function.
  80802. * @param {Boolean} [force=false] True to automatically remove existing extractor functions first
  80803. */
  80804. buildExtractors: function(force) {
  80805. var me = this,
  80806. idProp = me.getIdProperty(),
  80807. totalProp = me.totalProperty,
  80808. successProp = me.successProperty,
  80809. messageProp = me.messageProperty,
  80810. accessor,
  80811. idField,
  80812. map;
  80813. if (force === true) {
  80814. delete me.convertRecordData;
  80815. }
  80816. if (me.convertRecordData) {
  80817. return;
  80818. }
  80819. //build the extractors for all the meta data
  80820. if (totalProp) {
  80821. me.getTotal = me.createAccessor(totalProp);
  80822. }
  80823. if (successProp) {
  80824. me.getSuccess = me.createAccessor(successProp);
  80825. }
  80826. if (messageProp) {
  80827. me.getMessage = me.createAccessor(messageProp);
  80828. }
  80829. if (idProp) {
  80830. idField = me.model.prototype.fields.get(idProp);
  80831. if (idField) {
  80832. map = idField.mapping;
  80833. idProp = (map !== undefined && map !== null) ? map : idProp;
  80834. }
  80835. accessor = me.createAccessor(idProp);
  80836. me.getId = function(record) {
  80837. var id = accessor.call(me, record);
  80838. return (id === undefined || id === '') ? null : id;
  80839. };
  80840. } else {
  80841. me.getId = function() {
  80842. return null;
  80843. };
  80844. }
  80845. me.convertRecordData = me.buildRecordDataExtractor();
  80846. me.lastFieldGeneration = me.model.prototype.fields.generation;
  80847. },
  80848. recordDataExtractorTemplate : [
  80849. 'var me = this\n',
  80850. ' ,fields = me.model.prototype.fields\n',
  80851. ' ,value\n',
  80852. ' ,internalId\n',
  80853. '<tpl for="fields">',
  80854. ' ,__field{#} = fields.get("{name}")\n',
  80855. '</tpl>', ';\n',
  80856. 'return function(dest, source, record) {\n',
  80857. '<tpl for="fields">',
  80858. // createFieldAccessExpression must be implemented in subclasses to extract data from the source object in the correct way
  80859. ' value = {[ this.createFieldAccessExpression(values, "__field" + xindex, "source") ]};\n',
  80860. // Code for processing a source property when a custom convert is defined
  80861. '<tpl if="hasCustomConvert">',
  80862. ' dest["{name}"] = value === undefined ? __field{#}.convert(__field{#}.defaultValue, record) : __field{#}.convert(value, record);\n',
  80863. // Code for processing a source property when there is a default value
  80864. '<tpl elseif="defaultValue !== undefined">',
  80865. ' if (value === undefined) {\n',
  80866. ' if (me.applyDefaults) {\n',
  80867. '<tpl if="convert">',
  80868. ' dest["{name}"] = __field{#}.convert(__field{#}.defaultValue, record);\n',
  80869. '<tpl else>',
  80870. ' dest["{name}"] = __field{#}.defaultValue\n',
  80871. '</tpl>',
  80872. ' };\n',
  80873. ' } else {\n',
  80874. '<tpl if="convert">',
  80875. ' dest["{name}"] = __field{#}.convert(value, record);\n',
  80876. '<tpl else>',
  80877. ' dest["{name}"] = value;\n',
  80878. '</tpl>',
  80879. ' };',
  80880. // Code for processing a source property value when there is no default value
  80881. '<tpl else>',
  80882. ' if (value !== undefined) {\n',
  80883. '<tpl if="convert">',
  80884. ' dest["{name}"] = __field{#}.convert(value, record);\n',
  80885. '<tpl else>',
  80886. ' dest["{name}"] = value;\n',
  80887. '</tpl>',
  80888. ' }\n',
  80889. '</tpl>',
  80890. '</tpl>',
  80891. // set the client id as the internalId of the record.
  80892. // clientId handles the case where a client side record did not previously exist on the server,
  80893. // so the server is passing back a client id that can be used to pair the server side record up with the client record
  80894. '<tpl if="clientIdProp">',
  80895. ' if (record && (internalId = {[ this.createFieldAccessExpression(\{mapping: values.clientIdProp\}, null, "source") ]})) {\n',
  80896. ' record.{["internalId"]} = internalId;\n',
  80897. ' }\n',
  80898. '</tpl>',
  80899. '};'
  80900. ],
  80901. /**
  80902. * @private
  80903. * Return a function which will read a raw row object in the format this Reader accepts, and populates
  80904. * a record's data object with converted data values.
  80905. *
  80906. * The returned function must be passed the following parameters:
  80907. *
  80908. * - dest A record's empty data object into which the new field value properties are injected.
  80909. * - source A raw row data object of whatever type this Reader consumes
  80910. * - record The record which is being populated.
  80911. *
  80912. */
  80913. buildRecordDataExtractor: function() {
  80914. var me = this,
  80915. modelProto = me.model.prototype,
  80916. templateData = {
  80917. clientIdProp: modelProto.clientIdProperty,
  80918. fields: modelProto.fields.items
  80919. };
  80920. me.recordDataExtractorTemplate.createFieldAccessExpression = me.accessExpressionFn;
  80921. // Here we are creating a new Function and invoking it immediately in the scope of this Reader
  80922. // It declares several vars capturing the configured context of this Reader, and returns a function
  80923. // which, when passed a record data object, a raw data row in the format this Reader is configured to read,
  80924. // and the record which is being created, will populate the record's data object from the raw row data.
  80925. return Ext.functionFactory(me.recordDataExtractorTemplate.apply(templateData)).call(me);
  80926. },
  80927. destroyReader: function() {
  80928. var me = this;
  80929. delete me.proxy;
  80930. delete me.model;
  80931. delete me.convertRecordData;
  80932. delete me.getId;
  80933. delete me.getTotal;
  80934. delete me.getSuccess;
  80935. delete me.getMessage;
  80936. }
  80937. }, function() {
  80938. var proto = this.prototype;
  80939. Ext.apply(proto, {
  80940. // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
  80941. nullResultSet: new Ext.data.ResultSet({
  80942. total : 0,
  80943. count : 0,
  80944. records: [],
  80945. success: true
  80946. }),
  80947. recordDataExtractorTemplate: new Ext.XTemplate(proto.recordDataExtractorTemplate)
  80948. });
  80949. });
  80950. /**
  80951. * @author Ed Spencer
  80952. *
  80953. * The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually
  80954. * happens as a result of loading a Store - for example we might create something like this:
  80955. *
  80956. * Ext.define('User', {
  80957. * extend: 'Ext.data.Model',
  80958. * fields: ['id', 'name', 'email']
  80959. * });
  80960. *
  80961. * var store = Ext.create('Ext.data.Store', {
  80962. * model: 'User',
  80963. * proxy: {
  80964. * type: 'ajax',
  80965. * url : 'users.json',
  80966. * reader: {
  80967. * type: 'json'
  80968. * }
  80969. * }
  80970. * });
  80971. *
  80972. * The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
  80973. * not already familiar with them.
  80974. *
  80975. * We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s
  80976. * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
  80977. * Store, so it is as if we passed this instead:
  80978. *
  80979. * reader: {
  80980. * type : 'json',
  80981. * model: 'User'
  80982. * }
  80983. *
  80984. * The reader we set up is ready to read data from our server - at the moment it will accept a response like this:
  80985. *
  80986. * [
  80987. * {
  80988. * "id": 1,
  80989. * "name": "Ed Spencer",
  80990. * "email": "ed@sencha.com"
  80991. * },
  80992. * {
  80993. * "id": 2,
  80994. * "name": "Abe Elias",
  80995. * "email": "abe@sencha.com"
  80996. * }
  80997. * ]
  80998. *
  80999. * ## Reading other JSON formats
  81000. *
  81001. * If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually
  81002. * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the
  81003. * {@link #cfg-root} configuration to parse data that comes back like this:
  81004. *
  81005. * {
  81006. * "users": [
  81007. * {
  81008. * "id": 1,
  81009. * "name": "Ed Spencer",
  81010. * "email": "ed@sencha.com"
  81011. * },
  81012. * {
  81013. * "id": 2,
  81014. * "name": "Abe Elias",
  81015. * "email": "abe@sencha.com"
  81016. * }
  81017. * ]
  81018. * }
  81019. *
  81020. * To parse this we just pass in a {@link #root} configuration that matches the 'users' above:
  81021. *
  81022. * reader: {
  81023. * type: 'json',
  81024. * root: 'users'
  81025. * }
  81026. *
  81027. * Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata
  81028. * around each record inside a nested structure like this:
  81029. *
  81030. * {
  81031. * "total": 122,
  81032. * "offset": 0,
  81033. * "users": [
  81034. * {
  81035. * "id": "ed-spencer-1",
  81036. * "value": 1,
  81037. * "user": {
  81038. * "id": 1,
  81039. * "name": "Ed Spencer",
  81040. * "email": "ed@sencha.com"
  81041. * }
  81042. * }
  81043. * ]
  81044. * }
  81045. *
  81046. * In the case above the record data is nested an additional level inside the "users" array as each "user" item has
  81047. * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the
  81048. * JSON above we need to specify the {@link #record} configuration like this:
  81049. *
  81050. * reader: {
  81051. * type : 'json',
  81052. * root : 'users',
  81053. * record: 'user'
  81054. * }
  81055. *
  81056. * ## Response MetaData
  81057. *
  81058. * The server can return metadata in its response, in addition to the record data, that describe attributes
  81059. * of the data set itself or are used to reconfigure the Reader. To pass metadata in the response you simply
  81060. * add a `metaData` attribute to the root of the response data. The metaData attribute can contain anything,
  81061. * but supports a specific set of properties that are handled by the Reader if they are present:
  81062. *
  81063. * - {@link #root}: the property name of the root response node containing the record data
  81064. * - {@link #idProperty}: property name for the primary key field of the data
  81065. * - {@link #totalProperty}: property name for the total number of records in the data
  81066. * - {@link #successProperty}: property name for the success status of the response
  81067. * - {@link #messageProperty}: property name for an optional response message
  81068. * - {@link Ext.data.Model#cfg-fields fields}: Config used to reconfigure the Model's fields before converting the
  81069. * response data into records
  81070. *
  81071. * An initial Reader configuration containing all of these properties might look like this ("fields" would be
  81072. * included in the Model definition, not shown):
  81073. *
  81074. * reader: {
  81075. * type : 'json',
  81076. * root : 'root',
  81077. * idProperty : 'id',
  81078. * totalProperty : 'total',
  81079. * successProperty: 'success',
  81080. * messageProperty: 'message'
  81081. * }
  81082. *
  81083. * If you were to pass a response object containing attributes different from those initially defined above, you could
  81084. * use the `metaData` attribute to reconifgure the Reader on the fly. For example:
  81085. *
  81086. * {
  81087. * "count": 1,
  81088. * "ok": true,
  81089. * "msg": "Users found",
  81090. * "users": [{
  81091. * "userId": 123,
  81092. * "name": "Ed Spencer",
  81093. * "email": "ed@sencha.com"
  81094. * }],
  81095. * "metaData": {
  81096. * "root": "users",
  81097. * "idProperty": 'userId',
  81098. * "totalProperty": 'count',
  81099. * "successProperty": 'ok',
  81100. * "messageProperty": 'msg'
  81101. * }
  81102. * }
  81103. *
  81104. * You can also place any other arbitrary data you need into the `metaData` attribute which will be ignored by the Reader,
  81105. * but will be accessible via the Reader's {@link #metaData} property (which is also passed to listeners via the Proxy's
  81106. * {@link Ext.data.proxy.Proxy#metachange metachange} event (also relayed by the {@link Ext.data.AbstractStore#metachange
  81107. * store}). Application code can then process the passed metadata in any way it chooses.
  81108. *
  81109. * A simple example for how this can be used would be customizing the fields for a Model that is bound to a grid. By passing
  81110. * the `fields` property the Model will be automatically updated by the Reader internally, but that change will not be
  81111. * reflected automatically in the grid unless you also update the column configuration. You could do this manually, or you
  81112. * could simply pass a standard grid {@link Ext.panel.Table#columns column} config object as part of the `metaData` attribute
  81113. * and then pass that along to the grid. Here's a very simple example for how that could be accomplished:
  81114. *
  81115. * // response format:
  81116. * {
  81117. * ...
  81118. * "metaData": {
  81119. * "fields": [
  81120. * { "name": "userId", "type": "int" },
  81121. * { "name": "name", "type": "string" },
  81122. * { "name": "birthday", "type": "date", "dateFormat": "Y-j-m" },
  81123. * ],
  81124. * "columns": [
  81125. * { "text": "User ID", "dataIndex": "userId", "width": 40 },
  81126. * { "text": "User Name", "dataIndex": "name", "flex": 1 },
  81127. * { "text": "Birthday", "dataIndex": "birthday", "flex": 1, "format": 'Y-j-m', "xtype": "datecolumn" }
  81128. * ]
  81129. * }
  81130. * }
  81131. *
  81132. * The Reader will automatically read the meta fields config and rebuild the Model based on the new fields, but to handle
  81133. * the new column configuration you would need to handle the metadata within the application code. This is done simply enough
  81134. * by handling the metachange event on either the store or the proxy, e.g.:
  81135. *
  81136. * var store = Ext.create('Ext.data.Store', {
  81137. * ...
  81138. * listeners: {
  81139. * 'metachange': function(store, meta) {
  81140. * myGrid.reconfigure(store, meta.columns);
  81141. * }
  81142. * }
  81143. * });
  81144. *
  81145. */
  81146. Ext.define('Ext.data.reader.Json', {
  81147. extend: 'Ext.data.reader.Reader',
  81148. alternateClassName: 'Ext.data.JsonReader',
  81149. alias : 'reader.json',
  81150. root: '',
  81151. /**
  81152. * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
  81153. * See the JsonReader intro docs for more details. This is not often needed.
  81154. */
  81155. /**
  81156. * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
  81157. * reading values.
  81158. *
  81159. * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
  81160. * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name
  81161. * "foo.bar.baz" direct from the root object.
  81162. */
  81163. useSimpleAccessors: false,
  81164. /**
  81165. * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
  81166. * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
  81167. * @param {Object} data The raw JSON data
  81168. * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
  81169. */
  81170. readRecords: function(data) {
  81171. //this has to be before the call to super because we use the meta data in the superclass readRecords
  81172. if (data.metaData) {
  81173. this.onMetaChange(data.metaData);
  81174. }
  81175. /**
  81176. * @property {Object} jsonData
  81177. * A copy of this.rawData.
  81178. * @deprecated Will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead.
  81179. */
  81180. this.jsonData = data;
  81181. return this.callParent([data]);
  81182. },
  81183. //inherit docs
  81184. getResponseData: function(response) {
  81185. var data, error;
  81186. try {
  81187. data = Ext.decode(response.responseText);
  81188. return this.readRecords(data);
  81189. } catch (ex) {
  81190. error = new Ext.data.ResultSet({
  81191. total : 0,
  81192. count : 0,
  81193. records: [],
  81194. success: false,
  81195. message: ex.message
  81196. });
  81197. this.fireEvent('exception', this, response, error);
  81198. Ext.Logger.warn('Unable to parse the JSON returned by the server');
  81199. return error;
  81200. }
  81201. },
  81202. //inherit docs
  81203. buildExtractors : function() {
  81204. var me = this;
  81205. me.callParent(arguments);
  81206. if (me.root) {
  81207. me.getRoot = me.createAccessor(me.root);
  81208. } else {
  81209. me.getRoot = function(root) {
  81210. return root;
  81211. };
  81212. }
  81213. },
  81214. /**
  81215. * @private
  81216. * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
  81217. * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
  81218. * @param {Object} root The JSON root node
  81219. * @return {Ext.data.Model[]} The records
  81220. */
  81221. extractData: function(root) {
  81222. var recordName = this.record,
  81223. data = [],
  81224. length, i;
  81225. if (recordName) {
  81226. length = root.length;
  81227. if (!length && Ext.isObject(root)) {
  81228. length = 1;
  81229. root = [root];
  81230. }
  81231. for (i = 0; i < length; i++) {
  81232. data[i] = root[i][recordName];
  81233. }
  81234. } else {
  81235. data = root;
  81236. }
  81237. return this.callParent([data]);
  81238. },
  81239. /**
  81240. * @private
  81241. * @method
  81242. * Returns an accessor function for the given property string. Gives support for properties such as the following:
  81243. *
  81244. * - 'someProperty'
  81245. * - 'some.property'
  81246. * - 'some["property"]'
  81247. *
  81248. * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
  81249. */
  81250. createAccessor: (function() {
  81251. var re = /[\[\.]/;
  81252. return function(expr) {
  81253. if (Ext.isEmpty(expr)) {
  81254. return Ext.emptyFn;
  81255. }
  81256. if (Ext.isFunction(expr)) {
  81257. return expr;
  81258. }
  81259. if (this.useSimpleAccessors !== true) {
  81260. var i = String(expr).search(re);
  81261. if (i >= 0) {
  81262. return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
  81263. }
  81264. }
  81265. return function(obj) {
  81266. return obj[expr];
  81267. };
  81268. };
  81269. }()),
  81270. /**
  81271. * @private
  81272. * @method
  81273. * Returns an accessor expression for the passed Field. Gives support for properties such as the following:
  81274. *
  81275. * - 'someProperty'
  81276. * - 'some.property'
  81277. * - 'some["property"]'
  81278. *
  81279. * This is used by buildExtractors to create optimized on extractor function which converts raw data into model instances.
  81280. */
  81281. createFieldAccessExpression: (function() {
  81282. var re = /[\[\.]/;
  81283. return function(field, fieldVarName, dataName) {
  81284. var me = this,
  81285. hasMap = (field.mapping !== null),
  81286. map = hasMap ? field.mapping : field.name,
  81287. result,
  81288. operatorSearch;
  81289. if (typeof map === 'function') {
  81290. result = fieldVarName + '.mapping(' + dataName + ', this)';
  81291. } else if (this.useSimpleAccessors === true || ((operatorSearch = String(map).search(re)) < 0)) {
  81292. if (!hasMap || isNaN(map)) {
  81293. // If we don't provide a mapping, we may have a field name that is numeric
  81294. map = '"' + map + '"';
  81295. }
  81296. result = dataName + "[" + map + "]";
  81297. } else {
  81298. result = dataName + (operatorSearch > 0 ? '.' : '') + map;
  81299. }
  81300. return result;
  81301. };
  81302. }())
  81303. });
  81304. /**
  81305. * @author Ed Spencer
  81306. *
  81307. * Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is responsible for taking a
  81308. * set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request} object and modifying that request based on
  81309. * the Operations.
  81310. *
  81311. * For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model} instances based on
  81312. * the config options passed to the JsonWriter's constructor.
  81313. *
  81314. * Writers are not needed for any kind of local storage - whether via a {@link Ext.data.proxy.WebStorage Web Storage
  81315. * proxy} (see {@link Ext.data.proxy.LocalStorage localStorage} and {@link Ext.data.proxy.SessionStorage
  81316. * sessionStorage}) or just in memory via a {@link Ext.data.proxy.Memory MemoryProxy}.
  81317. */
  81318. Ext.define('Ext.data.writer.Writer', {
  81319. alias: 'writer.base',
  81320. alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
  81321. /**
  81322. * @cfg {Boolean} writeAllFields
  81323. * True to write all fields from the record to the server. If set to false it will only send the fields that were
  81324. * modified. Note that any fields that have {@link Ext.data.Field#persist} set to false will still be ignored.
  81325. */
  81326. writeAllFields: true,
  81327. /**
  81328. * @cfg {String} nameProperty
  81329. * This property is used to read the key for each value that will be sent to the server. For example:
  81330. *
  81331. * Ext.define('Person', {
  81332. * extend: 'Ext.data.Model',
  81333. * fields: [{
  81334. * name: 'first',
  81335. * mapping: 'firstName'
  81336. * }, {
  81337. * name: 'last',
  81338. * mapping: 'lastName'
  81339. * }, {
  81340. * name: 'age'
  81341. * }]
  81342. * });
  81343. * new Ext.data.writer.Writer({
  81344. * writeAllFields: true,
  81345. * nameProperty: 'mapping'
  81346. * });
  81347. *
  81348. * // This will be sent to the server
  81349. * {
  81350. * firstName: 'first name value',
  81351. * lastName: 'last name value',
  81352. * age: 1
  81353. * }
  81354. *
  81355. * If the value is not present, the field name will always be used.
  81356. */
  81357. nameProperty: 'name',
  81358. /*
  81359. * @property {Boolean} isWriter
  81360. * `true` in this class to identify an object as an instantiated Writer, or subclass thereof.
  81361. */
  81362. isWriter: true,
  81363. /**
  81364. * Creates new Writer.
  81365. * @param {Object} [config] Config object.
  81366. */
  81367. constructor: function(config) {
  81368. Ext.apply(this, config);
  81369. },
  81370. /**
  81371. * Prepares a Proxy's Ext.data.Request object
  81372. * @param {Ext.data.Request} request The request object
  81373. * @return {Ext.data.Request} The modified request object
  81374. */
  81375. write: function(request) {
  81376. var operation = request.operation,
  81377. records = operation.records || [],
  81378. len = records.length,
  81379. i = 0,
  81380. data = [];
  81381. for (; i < len; i++) {
  81382. data.push(this.getRecordData(records[i], operation));
  81383. }
  81384. return this.writeRecords(request, data);
  81385. },
  81386. /**
  81387. * Formats the data for each record before sending it to the server. This
  81388. * method should be overridden to format the data in a way that differs from the default.
  81389. * @param {Ext.data.Model} record The record that we are writing to the server.
  81390. * @param {Ext.data.Operation} [operation] An operation object.
  81391. * @return {Object} An object literal of name/value keys to be written to the server.
  81392. * By default this method returns the data property on the record.
  81393. */
  81394. getRecordData: function(record, operation) {
  81395. var isPhantom = record.phantom === true,
  81396. writeAll = this.writeAllFields || isPhantom,
  81397. nameProperty = this.nameProperty,
  81398. fields = record.fields,
  81399. fieldItems = fields.items,
  81400. data = {},
  81401. clientIdProperty = record.clientIdProperty,
  81402. changes,
  81403. name,
  81404. field,
  81405. key,
  81406. value,
  81407. f, fLen;
  81408. if (writeAll) {
  81409. fLen = fieldItems.length;
  81410. for (f = 0; f < fLen; f++) {
  81411. field = fieldItems[f];
  81412. if (field.persist) {
  81413. name = field[nameProperty] || field.name;
  81414. value = record.get(field.name);
  81415. if (field.serialize) {
  81416. data[name] = field.serialize(value, record);
  81417. } else if (field.type === Ext.data.Types.DATE && field.dateFormat) {
  81418. data[name] = Ext.Date.format(value, field.dateFormat);
  81419. } else {
  81420. data[name] = value;
  81421. }
  81422. }
  81423. }
  81424. } else {
  81425. // Only write the changes
  81426. changes = record.getChanges();
  81427. for (key in changes) {
  81428. if (changes.hasOwnProperty(key)) {
  81429. field = fields.get(key);
  81430. if (field.persist) {
  81431. name = field[nameProperty] || field.name;
  81432. value = record.get(field.name);
  81433. if (field.serialize) {
  81434. data[name] = field.serialize(value, record);
  81435. } else if (field.type === Ext.data.Types.DATE && field.dateFormat) {
  81436. data[name] = Ext.Date.format(value, field.dateFormat);
  81437. } else {
  81438. data[name] = value;
  81439. }
  81440. }
  81441. }
  81442. }
  81443. }
  81444. if (isPhantom) {
  81445. if (clientIdProperty && operation && operation.records.length > 1) {
  81446. // include clientId for phantom records, if multiple records are being written to the server in one operation.
  81447. // The server can then return the clientId with each record so the operation can match the server records with the client records
  81448. data[clientIdProperty] = record.internalId;
  81449. }
  81450. } else {
  81451. // always include the id for non phantoms
  81452. data[record.idProperty] = record.getId();
  81453. }
  81454. return data;
  81455. }
  81456. });
  81457. /**
  81458. * @class Ext.data.writer.Json
  81459. This class is used to write {@link Ext.data.Model} data to the server in a JSON format.
  81460. The {@link #allowSingle} configuration can be set to false to force the records to always be
  81461. encoded in an array, even if there is only a single record being sent.
  81462. * @markdown
  81463. */
  81464. Ext.define('Ext.data.writer.Json', {
  81465. extend: 'Ext.data.writer.Writer',
  81466. alternateClassName: 'Ext.data.JsonWriter',
  81467. alias: 'writer.json',
  81468. /**
  81469. * @cfg {String} root The key under which the records in this Writer will be placed. Defaults to <tt>undefined</tt>.
  81470. * Example generated request, using root: 'records':
  81471. <pre><code>
  81472. {'records': [{name: 'my record'}, {name: 'another record'}]}
  81473. </code></pre>
  81474. */
  81475. root: undefined,
  81476. /**
  81477. * @cfg {Boolean} encode True to use Ext.encode() on the data before sending. Defaults to <tt>false</tt>.
  81478. * The encode option should only be set to true when a {@link #root} is defined, because the values will be
  81479. * sent as part of the request parameters as opposed to a raw post. The root will be the name of the parameter
  81480. * sent to the server.
  81481. */
  81482. encode: false,
  81483. /**
  81484. * @cfg {Boolean} allowSingle False to ensure that records are always wrapped in an array, even if there is only
  81485. * one record being sent. When there is more than one record, they will always be encoded into an array.
  81486. * Defaults to <tt>true</tt>. Example:
  81487. * <pre><code>
  81488. // with allowSingle: true
  81489. "root": {
  81490. "first": "Mark",
  81491. "last": "Corrigan"
  81492. }
  81493. // with allowSingle: false
  81494. "root": [{
  81495. "first": "Mark",
  81496. "last": "Corrigan"
  81497. }]
  81498. * </code></pre>
  81499. */
  81500. allowSingle: true,
  81501. //inherit docs
  81502. writeRecords: function(request, data) {
  81503. var root = this.root;
  81504. if (this.allowSingle && data.length == 1) {
  81505. // convert to single object format
  81506. data = data[0];
  81507. }
  81508. if (this.encode) {
  81509. if (root) {
  81510. // sending as a param, need to encode
  81511. request.params[root] = Ext.encode(data);
  81512. } else {
  81513. Ext.Error.raise('Must specify a root when using encode');
  81514. }
  81515. } else {
  81516. // send as jsonData
  81517. request.jsonData = request.jsonData || {};
  81518. if (root) {
  81519. request.jsonData[root] = data;
  81520. } else {
  81521. request.jsonData = data;
  81522. }
  81523. }
  81524. return request;
  81525. }
  81526. });
  81527. /**
  81528. * @author Ed Spencer
  81529. *
  81530. * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and
  81531. * would not usually be used directly.
  81532. *
  81533. * ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been
  81534. * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now
  81535. * an alias of AjaxProxy).
  81536. * @private
  81537. */
  81538. Ext.define('Ext.data.proxy.Server', {
  81539. extend: 'Ext.data.proxy.Proxy',
  81540. alias : 'proxy.server',
  81541. alternateClassName: 'Ext.data.ServerProxy',
  81542. uses : ['Ext.data.Request'],
  81543. /**
  81544. * @cfg {String} url
  81545. * The URL from which to request the data object.
  81546. */
  81547. /**
  81548. * @cfg {String} pageParam
  81549. * The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to undefined if you don't
  81550. * want to send a page parameter.
  81551. */
  81552. pageParam: 'page',
  81553. /**
  81554. * @cfg {String} startParam
  81555. * The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this to undefined if you don't
  81556. * want to send a start parameter.
  81557. */
  81558. startParam: 'start',
  81559. /**
  81560. * @cfg {String} limitParam
  81561. * The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this to undefined if you don't
  81562. * want to send a limit parameter.
  81563. */
  81564. limitParam: 'limit',
  81565. /**
  81566. * @cfg {String} groupParam
  81567. * The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this to undefined if you don't
  81568. * want to send a group parameter.
  81569. */
  81570. groupParam: 'group',
  81571. /**
  81572. * @cfg {String} groupDirectionParam
  81573. * The name of the direction parameter to send in a request. **This is only used when simpleGroupMode is set to
  81574. * true.** Defaults to 'groupDir'.
  81575. */
  81576. groupDirectionParam: 'groupDir',
  81577. /**
  81578. * @cfg {String} sortParam
  81579. * The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this to undefined if you don't
  81580. * want to send a sort parameter.
  81581. */
  81582. sortParam: 'sort',
  81583. /**
  81584. * @cfg {String} filterParam
  81585. * The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set this to undefined if you don't
  81586. * want to send a filter parameter.
  81587. */
  81588. filterParam: 'filter',
  81589. /**
  81590. * @cfg {String} directionParam
  81591. * The name of the direction parameter to send in a request. **This is only used when simpleSortMode is set to
  81592. * true.** Defaults to 'dir'.
  81593. */
  81594. directionParam: 'dir',
  81595. /**
  81596. * @cfg {Boolean} simpleSortMode
  81597. * Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a
  81598. * remote sort is requested. The {@link #directionParam} and {@link #sortParam} will be sent with the property name
  81599. * and either 'ASC' or 'DESC'.
  81600. */
  81601. simpleSortMode: false,
  81602. /**
  81603. * @cfg {Boolean} simpleGroupMode
  81604. * Enabling simpleGroupMode in conjunction with remoteGroup will only send one group property and a direction when a
  81605. * remote group is requested. The {@link #groupDirectionParam} and {@link #groupParam} will be sent with the property name and either 'ASC'
  81606. * or 'DESC'.
  81607. */
  81608. simpleGroupMode: false,
  81609. /**
  81610. * @cfg {Boolean} noCache
  81611. * Disable caching by adding a unique parameter name to the request. Set to false to allow caching. Defaults to true.
  81612. */
  81613. noCache : true,
  81614. /**
  81615. * @cfg {String} cacheString
  81616. * The name of the cache param added to the url when using noCache. Defaults to "_dc".
  81617. */
  81618. cacheString: "_dc",
  81619. /**
  81620. * @cfg {Number} timeout
  81621. * The number of milliseconds to wait for a response. Defaults to 30000 milliseconds (30 seconds).
  81622. */
  81623. timeout : 30000,
  81624. /**
  81625. * @cfg {Object} api
  81626. * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:
  81627. *
  81628. * api: {
  81629. * create : undefined,
  81630. * read : undefined,
  81631. * update : undefined,
  81632. * destroy : undefined
  81633. * }
  81634. *
  81635. * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate
  81636. * {@link #api} property, or if undefined default to the configured
  81637. * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.
  81638. *
  81639. * For example:
  81640. *
  81641. * api: {
  81642. * create : '/controller/new',
  81643. * read : '/controller/load',
  81644. * update : '/controller/update',
  81645. * destroy : '/controller/destroy_action'
  81646. * }
  81647. *
  81648. * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the
  81649. * configured {@link Ext.data.proxy.Server#url url}.
  81650. */
  81651. constructor: function(config) {
  81652. var me = this;
  81653. config = config || {};
  81654. /**
  81655. * @event exception
  81656. * Fires when the server returns an exception
  81657. * @param {Ext.data.proxy.Proxy} this
  81658. * @param {Object} response The response from the AJAX request
  81659. * @param {Ext.data.Operation} operation The operation that triggered request
  81660. */
  81661. me.callParent([config]);
  81662. /**
  81663. * @cfg {Object} extraParams
  81664. * Extra parameters that will be included on every request. Individual requests with params of the same name
  81665. * will override these params when they are in conflict.
  81666. */
  81667. me.extraParams = config.extraParams || {};
  81668. me.api = Ext.apply({}, config.api || me.api);
  81669. //backwards compatibility, will be deprecated in 5.0
  81670. me.nocache = me.noCache;
  81671. },
  81672. //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
  81673. create: function() {
  81674. return this.doRequest.apply(this, arguments);
  81675. },
  81676. read: function() {
  81677. return this.doRequest.apply(this, arguments);
  81678. },
  81679. update: function() {
  81680. return this.doRequest.apply(this, arguments);
  81681. },
  81682. destroy: function() {
  81683. return this.doRequest.apply(this, arguments);
  81684. },
  81685. /**
  81686. * Sets a value in the underlying {@link #extraParams}.
  81687. * @param {String} name The key for the new value
  81688. * @param {Object} value The value
  81689. */
  81690. setExtraParam: function(name, value) {
  81691. this.extraParams[name] = value;
  81692. },
  81693. /**
  81694. * Creates an {@link Ext.data.Request Request} object from {@link Ext.data.Operation Operation}.
  81695. *
  81696. * This gets called from doRequest methods in subclasses of Server proxy.
  81697. *
  81698. * @param {Ext.data.Operation} operation The operation to execute
  81699. * @return {Ext.data.Request} The request object
  81700. */
  81701. buildRequest: function(operation) {
  81702. var me = this,
  81703. params = Ext.applyIf(operation.params || {}, me.extraParams || {}),
  81704. request;
  81705. //copy any sorters, filters etc into the params so they can be sent over the wire
  81706. params = Ext.applyIf(params, me.getParams(operation));
  81707. if (operation.id !== undefined && params.id === undefined) {
  81708. params.id = operation.id;
  81709. }
  81710. request = new Ext.data.Request({
  81711. params : params,
  81712. action : operation.action,
  81713. records : operation.records,
  81714. operation: operation,
  81715. url : operation.url,
  81716. // this is needed by JsonSimlet in order to properly construct responses for
  81717. // requests from this proxy
  81718. proxy: me
  81719. });
  81720. request.url = me.buildUrl(request);
  81721. /*
  81722. * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
  81723. * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
  81724. */
  81725. operation.request = request;
  81726. return request;
  81727. },
  81728. // Should this be documented as protected method?
  81729. processResponse: function(success, operation, request, response, callback, scope) {
  81730. var me = this,
  81731. reader,
  81732. result;
  81733. if (success === true) {
  81734. reader = me.getReader();
  81735. // Apply defaults to incoming data only for read operations.
  81736. // For create and update, there will already be a client-side record
  81737. // to match with which will contain any defaulted in values.
  81738. reader.applyDefaults = operation.action === 'read';
  81739. result = reader.read(me.extractResponseData(response));
  81740. if (result.success !== false) {
  81741. //see comment in buildRequest for why we include the response object here
  81742. Ext.apply(operation, {
  81743. response: response,
  81744. resultSet: result
  81745. });
  81746. operation.commitRecords(result.records);
  81747. operation.setCompleted();
  81748. operation.setSuccessful();
  81749. } else {
  81750. operation.setException(result.message);
  81751. me.fireEvent('exception', this, response, operation);
  81752. }
  81753. } else {
  81754. me.setException(operation, response);
  81755. me.fireEvent('exception', this, response, operation);
  81756. }
  81757. //this callback is the one that was passed to the 'read' or 'write' function above
  81758. if (typeof callback == 'function') {
  81759. callback.call(scope || me, operation);
  81760. }
  81761. me.afterRequest(request, success);
  81762. },
  81763. /**
  81764. * Sets up an exception on the operation
  81765. * @private
  81766. * @param {Ext.data.Operation} operation The operation
  81767. * @param {Object} response The response
  81768. */
  81769. setException: function(operation, response) {
  81770. operation.setException({
  81771. status: response.status,
  81772. statusText: response.statusText
  81773. });
  81774. },
  81775. /**
  81776. * Template method to allow subclasses to specify how to get the response for the reader.
  81777. * @template
  81778. * @private
  81779. * @param {Object} response The server response
  81780. * @return {Object} The response data to be used by the reader
  81781. */
  81782. extractResponseData: function(response) {
  81783. return response;
  81784. },
  81785. /**
  81786. * Encode any values being sent to the server. Can be overridden in subclasses.
  81787. * @private
  81788. * @param {Array} An array of sorters/filters.
  81789. * @return {Object} The encoded value
  81790. */
  81791. applyEncoding: function(value) {
  81792. return Ext.encode(value);
  81793. },
  81794. /**
  81795. * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
  81796. * this simply JSON-encodes the sorter data
  81797. * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects
  81798. * @return {String} The encoded sorters
  81799. */
  81800. encodeSorters: function(sorters) {
  81801. var min = [],
  81802. length = sorters.length,
  81803. i = 0;
  81804. for (; i < length; i++) {
  81805. min[i] = {
  81806. property : sorters[i].property,
  81807. direction: sorters[i].direction
  81808. };
  81809. }
  81810. return this.applyEncoding(min);
  81811. },
  81812. /**
  81813. * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
  81814. * this simply JSON-encodes the filter data
  81815. * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects
  81816. * @return {String} The encoded filters
  81817. */
  81818. encodeFilters: function(filters) {
  81819. var min = [],
  81820. length = filters.length,
  81821. i = 0;
  81822. for (; i < length; i++) {
  81823. min[i] = {
  81824. property: filters[i].property,
  81825. value : filters[i].value
  81826. };
  81827. }
  81828. return this.applyEncoding(min);
  81829. },
  81830. /**
  81831. * @private
  81832. * Copy any sorters, filters etc into the params so they can be sent over the wire
  81833. */
  81834. getParams: function(operation) {
  81835. var me = this,
  81836. params = {},
  81837. isDef = Ext.isDefined,
  81838. groupers = operation.groupers,
  81839. sorters = operation.sorters,
  81840. filters = operation.filters,
  81841. page = operation.page,
  81842. start = operation.start,
  81843. limit = operation.limit,
  81844. simpleSortMode = me.simpleSortMode,
  81845. simpleGroupMode = me.simpleGroupMode,
  81846. pageParam = me.pageParam,
  81847. startParam = me.startParam,
  81848. limitParam = me.limitParam,
  81849. groupParam = me.groupParam,
  81850. groupDirectionParam = me.groupDirectionParam,
  81851. sortParam = me.sortParam,
  81852. filterParam = me.filterParam,
  81853. directionParam = me.directionParam;
  81854. if (pageParam && isDef(page)) {
  81855. params[pageParam] = page;
  81856. }
  81857. if (startParam && isDef(start)) {
  81858. params[startParam] = start;
  81859. }
  81860. if (limitParam && isDef(limit)) {
  81861. params[limitParam] = limit;
  81862. }
  81863. if (groupParam && groupers && groupers.length > 0) {
  81864. // Grouper is a subclass of sorter, so we can just use the sorter method
  81865. if (simpleGroupMode) {
  81866. params[groupParam] = groupers[0].property;
  81867. params[groupDirectionParam] = groupers[0].direction || 'ASC';
  81868. } else {
  81869. params[groupParam] = me.encodeSorters(groupers);
  81870. }
  81871. }
  81872. if (sortParam && sorters && sorters.length > 0) {
  81873. if (simpleSortMode) {
  81874. params[sortParam] = sorters[0].property;
  81875. params[directionParam] = sorters[0].direction;
  81876. } else {
  81877. params[sortParam] = me.encodeSorters(sorters);
  81878. }
  81879. }
  81880. if (filterParam && filters && filters.length > 0) {
  81881. params[filterParam] = me.encodeFilters(filters);
  81882. }
  81883. return params;
  81884. },
  81885. /**
  81886. * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the
  81887. * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.
  81888. * @param {Ext.data.Request} request The request object
  81889. * @return {String} The url
  81890. */
  81891. buildUrl: function(request) {
  81892. var me = this,
  81893. url = me.getUrl(request);
  81894. if (!url) {
  81895. Ext.Error.raise("You are using a ServerProxy but have not supplied it with a url.");
  81896. }
  81897. if (me.noCache) {
  81898. url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
  81899. }
  81900. return url;
  81901. },
  81902. /**
  81903. * Get the url for the request taking into account the order of priority,
  81904. * - The request
  81905. * - The api
  81906. * - The url
  81907. * @private
  81908. * @param {Ext.data.Request} request The request
  81909. * @return {String} The url
  81910. */
  81911. getUrl: function(request) {
  81912. return request.url || this.api[request.action] || this.url;
  81913. },
  81914. /**
  81915. * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all
  81916. * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link
  81917. * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as
  81918. * each of the methods that delegate to it.
  81919. *
  81920. * @param {Ext.data.Operation} operation The Ext.data.Operation object
  81921. * @param {Function} callback The callback function to call when the Operation has completed
  81922. * @param {Object} scope The scope in which to execute the callback
  81923. */
  81924. doRequest: function(operation, callback, scope) {
  81925. Ext.Error.raise("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
  81926. },
  81927. /**
  81928. * Optional callback function which can be used to clean up after a request has been completed.
  81929. * @param {Ext.data.Request} request The Request object
  81930. * @param {Boolean} success True if the request was successful
  81931. * @protected
  81932. * @template
  81933. * @method
  81934. */
  81935. afterRequest: Ext.emptyFn,
  81936. onDestroy: function() {
  81937. Ext.destroy(this.reader, this.writer);
  81938. }
  81939. });
  81940. /**
  81941. * @author Ed Spencer
  81942. *
  81943. * AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX requests to load
  81944. * data from the server, usually to be placed into a {@link Ext.data.Store Store}. Let's take a look at a typical setup.
  81945. * Here we're going to set up a Store that has an AjaxProxy. To prepare, we'll also set up a {@link Ext.data.Model
  81946. * Model}:
  81947. *
  81948. * Ext.define('User', {
  81949. * extend: 'Ext.data.Model',
  81950. * fields: ['id', 'name', 'email']
  81951. * });
  81952. *
  81953. * //The Store contains the AjaxProxy as an inline configuration
  81954. * var store = Ext.create('Ext.data.Store', {
  81955. * model: 'User',
  81956. * proxy: {
  81957. * type: 'ajax',
  81958. * url : 'users.json'
  81959. * }
  81960. * });
  81961. *
  81962. * store.load();
  81963. *
  81964. * Our example is going to load user data into a Store, so we start off by defining a {@link Ext.data.Model Model} with
  81965. * the fields that we expect the server to return. Next we set up the Store itself, along with a
  81966. * {@link Ext.data.Store#proxy proxy} configuration. This configuration was automatically turned into an
  81967. * Ext.data.proxy.Ajax instance, with the url we specified being passed into AjaxProxy's constructor.
  81968. * It's as if we'd done this:
  81969. *
  81970. * new Ext.data.proxy.Ajax({
  81971. * url: 'users.json',
  81972. * model: 'User',
  81973. * reader: 'json'
  81974. * });
  81975. *
  81976. * A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are set by default when we
  81977. * create the proxy via the Store - the Store already knows about the Model, and Proxy's default {@link
  81978. * Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.
  81979. *
  81980. * Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we configured
  81981. * ('users.json' in this case). As we're performing a read, it sends a GET request to that url (see
  81982. * {@link #actionMethods} to customize this - by default any kind of read will be sent as a GET request and any kind of write
  81983. * will be sent as a POST request).
  81984. *
  81985. * # Limitations
  81986. *
  81987. * AjaxProxy cannot be used to retrieve data from other domains. If your application is running on http://domainA.com it
  81988. * cannot load data from http://domainB.com because browsers have a built-in security policy that prohibits domains
  81989. * talking to each other via AJAX.
  81990. *
  81991. * If you need to read data from another domain and can't set up a proxy server (some software that runs on your own
  81992. * domain's web server and transparently forwards requests to http://domainB.com, making it look like they actually came
  81993. * from http://domainA.com), you can use {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with
  81994. * Padding), which can help you get around the problem so long as the server on http://domainB.com is set up to support
  81995. * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.
  81996. *
  81997. * # Readers and Writers
  81998. *
  81999. * AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode the server's response.
  82000. * If no Reader is supplied, AjaxProxy will default to using a {@link Ext.data.reader.Json JsonReader}. Reader
  82001. * configuration can be passed in as a simple object, which the Proxy automatically turns into a {@link
  82002. * Ext.data.reader.Reader Reader} instance:
  82003. *
  82004. * var proxy = new Ext.data.proxy.Ajax({
  82005. * model: 'User',
  82006. * reader: {
  82007. * type: 'xml',
  82008. * root: 'users'
  82009. * }
  82010. * });
  82011. *
  82012. * proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
  82013. *
  82014. * # Url generation
  82015. *
  82016. * AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url it generates for
  82017. * each request. These are controlled with the following configuration options:
  82018. *
  82019. * - {@link #pageParam} - controls how the page number is sent to the server (see also {@link #startParam} and {@link #limitParam})
  82020. * - {@link #sortParam} - controls how sort information is sent to the server
  82021. * - {@link #groupParam} - controls how grouping information is sent to the server
  82022. * - {@link #filterParam} - controls how filter information is sent to the server
  82023. *
  82024. * Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see how we can customize
  82025. * the generated urls, let's say we're loading the Proxy with the following Operation:
  82026. *
  82027. * var operation = new Ext.data.Operation({
  82028. * action: 'read',
  82029. * page : 2
  82030. * });
  82031. *
  82032. * Now we'll issue the request for this Operation by calling {@link #read}:
  82033. *
  82034. * var proxy = new Ext.data.proxy.Ajax({
  82035. * url: '/users'
  82036. * });
  82037. *
  82038. * proxy.read(operation); //GET /users?page=2
  82039. *
  82040. * Easy enough - the Proxy just copied the page property from the Operation. We can customize how this page data is sent
  82041. * to the server:
  82042. *
  82043. * var proxy = new Ext.data.proxy.Ajax({
  82044. * url: '/users',
  82045. * pageParam: 'pageNumber'
  82046. * });
  82047. *
  82048. * proxy.read(operation); //GET /users?pageNumber=2
  82049. *
  82050. * Alternatively, our Operation could have been configured to send start and limit parameters instead of page:
  82051. *
  82052. * var operation = new Ext.data.Operation({
  82053. * action: 'read',
  82054. * start : 50,
  82055. * limit : 25
  82056. * });
  82057. *
  82058. * var proxy = new Ext.data.proxy.Ajax({
  82059. * url: '/users'
  82060. * });
  82061. *
  82062. * proxy.read(operation); //GET /users?start=50&limit;=25
  82063. *
  82064. * Again we can customize this url:
  82065. *
  82066. * var proxy = new Ext.data.proxy.Ajax({
  82067. * url: '/users',
  82068. * startParam: 'startIndex',
  82069. * limitParam: 'limitIndex'
  82070. * });
  82071. *
  82072. * proxy.read(operation); //GET /users?startIndex=50&limitIndex;=25
  82073. *
  82074. * AjaxProxy will also send sort and filter information to the server. Let's take a look at how this looks with a more
  82075. * expressive Operation object:
  82076. *
  82077. * var operation = new Ext.data.Operation({
  82078. * action: 'read',
  82079. * sorters: [
  82080. * new Ext.util.Sorter({
  82081. * property : 'name',
  82082. * direction: 'ASC'
  82083. * }),
  82084. * new Ext.util.Sorter({
  82085. * property : 'age',
  82086. * direction: 'DESC'
  82087. * })
  82088. * ],
  82089. * filters: [
  82090. * new Ext.util.Filter({
  82091. * property: 'eyeColor',
  82092. * value : 'brown'
  82093. * })
  82094. * ]
  82095. * });
  82096. *
  82097. * This is the type of object that is generated internally when loading a {@link Ext.data.Store Store} with sorters and
  82098. * filters defined. By default the AjaxProxy will JSON encode the sorters and filters, resulting in something like this
  82099. * (note that the url is escaped before sending the request, but is left unescaped here for clarity):
  82100. *
  82101. * var proxy = new Ext.data.proxy.Ajax({
  82102. * url: '/users'
  82103. * });
  82104. *
  82105. * proxy.read(operation); //GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter;=[{"property":"eyeColor","value":"brown"}]
  82106. *
  82107. * We can again customize how this is created by supplying a few configuration options. Let's say our server is set up
  82108. * to receive sorting information is a format like "sortBy=name#ASC,age#DESC". We can configure AjaxProxy to provide
  82109. * that format like this:
  82110. *
  82111. * var proxy = new Ext.data.proxy.Ajax({
  82112. * url: '/users',
  82113. * sortParam: 'sortBy',
  82114. * filterParam: 'filterBy',
  82115. *
  82116. * //our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
  82117. * encodeSorters: function(sorters) {
  82118. * var length = sorters.length,
  82119. * sortStrs = [],
  82120. * sorter, i;
  82121. *
  82122. * for (i = 0; i < length; i++) {
  82123. * sorter = sorters[i];
  82124. *
  82125. * sortStrs[i] = sorter.property + '#' + sorter.direction
  82126. * }
  82127. *
  82128. * return sortStrs.join(",");
  82129. * }
  82130. * });
  82131. *
  82132. * proxy.read(operation); //GET /users?sortBy=name#ASC,age#DESC&filterBy;=[{"property":"eyeColor","value":"brown"}]
  82133. *
  82134. * We can also provide a custom {@link #encodeFilters} function to encode our filters.
  82135. *
  82136. * @constructor
  82137. * Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the Store's call to
  82138. * {@link Ext.data.Store#method-load load} will override any specified callback and params options. In this case, use the
  82139. * {@link Ext.data.Store Store}'s events to modify parameters, or react to loading events.
  82140. *
  82141. * @param {Object} config (optional) Config object.
  82142. * If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make the request.
  82143. */
  82144. Ext.define('Ext.data.proxy.Ajax', {
  82145. requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
  82146. extend: 'Ext.data.proxy.Server',
  82147. alias: 'proxy.ajax',
  82148. alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
  82149. /**
  82150. * @property {Object} actionMethods
  82151. * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to 'GET' for 'read' actions
  82152. * and 'POST' for 'create', 'update' and 'destroy' actions. The {@link Ext.data.proxy.Rest} maps these to the
  82153. * correct RESTful methods.
  82154. */
  82155. actionMethods: {
  82156. create : 'POST',
  82157. read : 'GET',
  82158. update : 'POST',
  82159. destroy: 'POST'
  82160. },
  82161. /**
  82162. * @cfg {Object} headers
  82163. * Any headers to add to the Ajax request. Defaults to undefined.
  82164. */
  82165. doRequest: function(operation, callback, scope) {
  82166. var writer = this.getWriter(),
  82167. request = this.buildRequest(operation, callback, scope);
  82168. if (operation.allowWrite()) {
  82169. request = writer.write(request);
  82170. }
  82171. Ext.apply(request, {
  82172. headers : this.headers,
  82173. timeout : this.timeout,
  82174. scope : this,
  82175. callback : this.createRequestCallback(request, operation, callback, scope),
  82176. method : this.getMethod(request),
  82177. disableCaching: false // explicitly set it to false, ServerProxy handles caching
  82178. });
  82179. Ext.Ajax.request(request);
  82180. return request;
  82181. },
  82182. /**
  82183. * Returns the HTTP method name for a given request. By default this returns based on a lookup on
  82184. * {@link #actionMethods}.
  82185. * @param {Ext.data.Request} request The request object
  82186. * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE')
  82187. */
  82188. getMethod: function(request) {
  82189. return this.actionMethods[request.action];
  82190. },
  82191. /**
  82192. * @private
  82193. * TODO: This is currently identical to the JsonPProxy version except for the return function's signature. There is a lot
  82194. * of code duplication inside the returned function so we need to find a way to DRY this up.
  82195. * @param {Ext.data.Request} request The Request object
  82196. * @param {Ext.data.Operation} operation The Operation being executed
  82197. * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
  82198. * passed to doRequest
  82199. * @param {Object} scope The scope in which to execute the callback function
  82200. * @return {Function} The callback function
  82201. */
  82202. createRequestCallback: function(request, operation, callback, scope) {
  82203. var me = this;
  82204. return function(options, success, response) {
  82205. me.processResponse(success, operation, request, response, callback, scope);
  82206. };
  82207. }
  82208. }, function() {
  82209. //backwards compatibility, remove in Ext JS 5.0
  82210. Ext.data.HttpProxy = this;
  82211. });
  82212. /**
  82213. * @author Ed Spencer
  82214. *
  82215. * Base class for any client-side storage. Used as a superclass for {@link Ext.data.proxy.Memory Memory} and
  82216. * {@link Ext.data.proxy.WebStorage Web Storage} proxies. Do not use directly, use one of the subclasses instead.
  82217. * @private
  82218. */
  82219. Ext.define('Ext.data.proxy.Client', {
  82220. extend: 'Ext.data.proxy.Proxy',
  82221. alternateClassName: 'Ext.data.ClientProxy',
  82222. /**
  82223. * @property {Boolean} isSynchronous
  82224. * `true` in this class to identify that requests made on this proxy are
  82225. * performed synchronously
  82226. */
  82227. isSynchronous: true,
  82228. /**
  82229. * Abstract function that must be implemented by each ClientProxy subclass. This should purge all record data
  82230. * from the client side storage, as well as removing any supporting data (such as lists of record IDs)
  82231. */
  82232. clear: function() {
  82233. Ext.Error.raise("The Ext.data.proxy.Client subclass that you are using has not defined a 'clear' function. See src/data/ClientProxy.js for details.");
  82234. }
  82235. });
  82236. /**
  82237. * @author Ed Spencer
  82238. *
  82239. * In-memory proxy. This proxy simply uses a local variable for data storage/retrieval, so its contents are lost on
  82240. * every page refresh.
  82241. *
  82242. * Usually this Proxy isn't used directly, serving instead as a helper to a {@link Ext.data.Store Store} where a reader
  82243. * is required to load data. For example, say we have a Store for a User model and have some inline data we want to
  82244. * load, but this data isn't in quite the right format: we can use a MemoryProxy with a JsonReader to read it into our
  82245. * Store:
  82246. *
  82247. * //this is the model we will be using in the store
  82248. * Ext.define('User', {
  82249. * extend: 'Ext.data.Model',
  82250. * fields: [
  82251. * {name: 'id', type: 'int'},
  82252. * {name: 'name', type: 'string'},
  82253. * {name: 'phone', type: 'string', mapping: 'phoneNumber'}
  82254. * ]
  82255. * });
  82256. *
  82257. * //this data does not line up to our model fields - the phone field is called phoneNumber
  82258. * var data = {
  82259. * users: [
  82260. * {
  82261. * id: 1,
  82262. * name: 'Ed Spencer',
  82263. * phoneNumber: '555 1234'
  82264. * },
  82265. * {
  82266. * id: 2,
  82267. * name: 'Abe Elias',
  82268. * phoneNumber: '666 1234'
  82269. * }
  82270. * ]
  82271. * };
  82272. *
  82273. * //note how we set the 'root' in the reader to match the data structure above
  82274. * var store = Ext.create('Ext.data.Store', {
  82275. * autoLoad: true,
  82276. * model: 'User',
  82277. * data : data,
  82278. * proxy: {
  82279. * type: 'memory',
  82280. * reader: {
  82281. * type: 'json',
  82282. * root: 'users'
  82283. * }
  82284. * }
  82285. * });
  82286. */
  82287. Ext.define('Ext.data.proxy.Memory', {
  82288. extend: 'Ext.data.proxy.Client',
  82289. alias: 'proxy.memory',
  82290. alternateClassName: 'Ext.data.MemoryProxy',
  82291. /**
  82292. * @cfg {Object} data
  82293. * Optional data to pass to configured Reader.
  82294. */
  82295. constructor: function(config) {
  82296. this.callParent([config]);
  82297. //ensures that the reader has been instantiated properly
  82298. this.setReader(this.reader);
  82299. },
  82300. /**
  82301. * @private
  82302. * Fake processing function to commit the records, set the current operation
  82303. * to successful and call the callback if provided. This function is shared
  82304. * by the create, update and destroy methods to perform the bare minimum
  82305. * processing required for the proxy to register a result from the action.
  82306. */
  82307. updateOperation: function(operation, callback, scope) {
  82308. var i = 0,
  82309. recs = operation.getRecords(),
  82310. len = recs.length;
  82311. for (i; i < len; i++) {
  82312. recs[i].commit();
  82313. }
  82314. operation.setCompleted();
  82315. operation.setSuccessful();
  82316. Ext.callback(callback, scope || this, [operation]);
  82317. },
  82318. /**
  82319. * Currently this is a hard-coded method that simply commits any records and sets the operation to successful,
  82320. * then calls the callback function, if provided. It is essentially mocking a server call in memory, but since
  82321. * there is no real back end in this case there's not much else to do. This method can be easily overridden to
  82322. * implement more complex logic if needed.
  82323. * @param {Ext.data.Operation} operation The Operation to perform
  82324. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  82325. * successful or not)
  82326. * @param {Object} scope Scope to execute the callback function in
  82327. * @method
  82328. */
  82329. create: function() {
  82330. this.updateOperation.apply(this, arguments);
  82331. },
  82332. /**
  82333. * Currently this is a hard-coded method that simply commits any records and sets the operation to successful,
  82334. * then calls the callback function, if provided. It is essentially mocking a server call in memory, but since
  82335. * there is no real back end in this case there's not much else to do. This method can be easily overridden to
  82336. * implement more complex logic if needed.
  82337. * @param {Ext.data.Operation} operation The Operation to perform
  82338. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  82339. * successful or not)
  82340. * @param {Object} scope Scope to execute the callback function in
  82341. * @method
  82342. */
  82343. update: function() {
  82344. this.updateOperation.apply(this, arguments);
  82345. },
  82346. /**
  82347. * Currently this is a hard-coded method that simply commits any records and sets the operation to successful,
  82348. * then calls the callback function, if provided. It is essentially mocking a server call in memory, but since
  82349. * there is no real back end in this case there's not much else to do. This method can be easily overridden to
  82350. * implement more complex logic if needed.
  82351. * @param {Ext.data.Operation} operation The Operation to perform
  82352. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  82353. * successful or not)
  82354. * @param {Object} scope Scope to execute the callback function in
  82355. * @method
  82356. */
  82357. destroy: function() {
  82358. this.updateOperation.apply(this, arguments);
  82359. },
  82360. /**
  82361. * Reads data from the configured {@link #data} object. Uses the Proxy's {@link #reader}, if present.
  82362. * @param {Ext.data.Operation} operation The read Operation
  82363. * @param {Function} callback The callback to call when reading has completed
  82364. * @param {Object} scope The scope to call the callback function in
  82365. */
  82366. read: function(operation, callback, scope) {
  82367. var me = this;
  82368. operation.resultSet = me.getReader().read(me.data);
  82369. operation.setCompleted();
  82370. operation.setSuccessful();
  82371. Ext.callback(callback, scope || me, [operation]);
  82372. },
  82373. clear: Ext.emptyFn
  82374. });
  82375. /**
  82376. * @private
  82377. * @class Ext.util.LruCache
  82378. * @extend Ext.util.HashMap
  82379. * A linked {@link Ext.util.HashMap HashMap} implementation which maintains most recently accessed
  82380. * items at the end of the list, and purges the cache down to the most recently accessed {@link #maxSize} items
  82381. * upon add.
  82382. */
  82383. Ext.define('Ext.util.LruCache', {
  82384. extend: 'Ext.util.HashMap',
  82385. /**
  82386. * @cfg {Number} maxSize The maximum size the cache is allowed to grow to before further additions cause
  82387. * removal of the least recently used entry.
  82388. */
  82389. constructor: function(config) {
  82390. Ext.apply(this, config);
  82391. this.callParent([config]);
  82392. },
  82393. /*
  82394. * @inheritdoc
  82395. */
  82396. add: function(key, newValue) {
  82397. var me = this,
  82398. existingKey = me.findKey(newValue),
  82399. entry;
  82400. // "new" value is in the list.
  82401. if (existingKey) {
  82402. me.unlinkEntry(entry = me.map[existingKey]);
  82403. entry.prev = me.last;
  82404. entry.next = null;
  82405. }
  82406. // Genuinely new: create an entry for it.
  82407. else {
  82408. entry = {
  82409. prev: me.last,
  82410. next: null,
  82411. key: key,
  82412. value: newValue
  82413. };
  82414. }
  82415. // If the list is not empty, update the last entry
  82416. if (me.last) {
  82417. me.last.next = entry;
  82418. }
  82419. // List is empty
  82420. else {
  82421. me.first = entry;
  82422. }
  82423. me.last = entry;
  82424. me.callParent([key, entry]);
  82425. me.prune();
  82426. return newValue;
  82427. },
  82428. // @private
  82429. insertBefore: function(key, newValue, sibling) {
  82430. var me = this,
  82431. existingKey,
  82432. entry;
  82433. // NOT an assignment.
  82434. // If there is a following sibling
  82435. if (sibling = this.map[this.findKey(sibling)]) {
  82436. existingKey = me.findKey(newValue);
  82437. // "new" value is in the list.
  82438. if (existingKey) {
  82439. me.unlinkEntry(entry = me.map[existingKey]);
  82440. }
  82441. // Genuinely new: create an entry for it.
  82442. else {
  82443. entry = {
  82444. prev: sibling.prev,
  82445. next: sibling,
  82446. key: key,
  82447. value: newValue
  82448. };
  82449. }
  82450. if (sibling.prev) {
  82451. entry.prev.next = entry;
  82452. } else {
  82453. me.first = entry;
  82454. }
  82455. entry.next = sibling;
  82456. sibling.prev = entry;
  82457. me.prune();
  82458. return newValue;
  82459. }
  82460. // No following sibling, it's just an add.
  82461. else {
  82462. return me.add(key, newValue);
  82463. }
  82464. },
  82465. /*
  82466. * @inheritdoc
  82467. */
  82468. get: function(key) {
  82469. var entry = this.map[key];
  82470. if (entry) {
  82471. // If it's not the end, move to end of list on get
  82472. if (entry.next) {
  82473. this.moveToEnd(entry);
  82474. }
  82475. return entry.value;
  82476. }
  82477. },
  82478. /*
  82479. * @private
  82480. */
  82481. removeAtKey: function(key) {
  82482. this.unlinkEntry(this.map[key]);
  82483. return this.callParent(arguments);
  82484. },
  82485. /*
  82486. * @inheritdoc
  82487. */
  82488. clear: function(/* private */ initial) {
  82489. this.first = this.last = null;
  82490. return this.callParent(arguments);
  82491. },
  82492. // private. Only used by internal methods.
  82493. unlinkEntry: function(entry) {
  82494. // Stitch the list back up.
  82495. if (entry) {
  82496. if (entry.next) {
  82497. entry.next.prev = entry.prev;
  82498. } else {
  82499. this.last = entry.prev;
  82500. }
  82501. if (entry.prev) {
  82502. entry.prev.next = entry.next;
  82503. } else {
  82504. this.first = entry.next;
  82505. }
  82506. entry.prev = entry.next = null;
  82507. }
  82508. },
  82509. // private. Only used by internal methods.
  82510. moveToEnd: function(entry) {
  82511. this.unlinkEntry(entry);
  82512. // NOT an assignment.
  82513. // If the list is not empty, update the last entry
  82514. if (entry.prev = this.last) {
  82515. this.last.next = entry;
  82516. }
  82517. // List is empty
  82518. else {
  82519. this.first = entry;
  82520. }
  82521. this.last = entry;
  82522. },
  82523. /*
  82524. * @private
  82525. */
  82526. getArray: function(isKey) {
  82527. var arr = [],
  82528. entry = this.first;
  82529. while (entry) {
  82530. arr.push(isKey ? entry.key: entry.value);
  82531. entry = entry.next;
  82532. }
  82533. return arr;
  82534. },
  82535. /**
  82536. * Executes the specified function once for each item in the cache.
  82537. * Returning false from the function will cease iteration.
  82538. *
  82539. * By default, iteration is from least recently used to most recent.
  82540. *
  82541. * The paramaters passed to the function are:
  82542. * <div class="mdetail-params"><ul>
  82543. * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
  82544. * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
  82545. * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
  82546. * </ul></div>
  82547. * @param {Function} fn The function to execute.
  82548. * @param {Object} scope The scope (<code>this</code> reference) to execute in. Defaults to this LruCache.
  82549. * @param {Boolean} [reverse=false] Pass <code>true</code> to iterate the list in reverse (most recent first) order.
  82550. * @return {Ext.util.LruCache} this
  82551. */
  82552. each: function(fn, scope, reverse) {
  82553. var me = this,
  82554. entry = reverse ? me.last : me.first,
  82555. length = me.length;
  82556. scope = scope || me;
  82557. while (entry) {
  82558. if (fn.call(scope, entry.key, entry.value, length) === false) {
  82559. break;
  82560. }
  82561. entry = reverse ? entry.prev : entry.next;
  82562. }
  82563. return me;
  82564. },
  82565. /**
  82566. * @private
  82567. */
  82568. findKey: function(value) {
  82569. var key,
  82570. map = this.map;
  82571. for (key in map) {
  82572. if (map.hasOwnProperty(key) && map[key].value === value) {
  82573. return key;
  82574. }
  82575. }
  82576. return undefined;
  82577. },
  82578. /**
  82579. * Purge the least recently used entries if the maxSize has been exceeded.
  82580. */
  82581. prune: function() {
  82582. var me = this,
  82583. purgeCount = me.maxSize ? (me.length - me.maxSize) : 0;
  82584. if (purgeCount > 0) {
  82585. for (; me.first && purgeCount; purgeCount--) {
  82586. me.removeAtKey(me.first.key);
  82587. }
  82588. }
  82589. }
  82590. /**
  82591. * @method containsKey
  82592. * @private
  82593. */
  82594. /**
  82595. * @method contains
  82596. * @private
  82597. */
  82598. /**
  82599. * @method getKeys
  82600. * @private
  82601. */
  82602. /**
  82603. * @method getValues
  82604. * @private
  82605. */
  82606. });
  82607. /**
  82608. * The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load data via a
  82609. * {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting}, {@link #filter filtering}
  82610. * and querying the {@link Ext.data.Model model} instances contained within it.
  82611. *
  82612. * Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:
  82613. *
  82614. * // Set up a {@link Ext.data.Model model} to use in our Store
  82615. * Ext.define('User', {
  82616. * extend: 'Ext.data.Model',
  82617. * fields: [
  82618. * {name: 'firstName', type: 'string'},
  82619. * {name: 'lastName', type: 'string'},
  82620. * {name: 'age', type: 'int'},
  82621. * {name: 'eyeColor', type: 'string'}
  82622. * ]
  82623. * });
  82624. *
  82625. * var myStore = Ext.create('Ext.data.Store', {
  82626. * model: 'User',
  82627. * proxy: {
  82628. * type: 'ajax',
  82629. * url: '/users.json',
  82630. * reader: {
  82631. * type: 'json',
  82632. * root: 'users'
  82633. * }
  82634. * },
  82635. * autoLoad: true
  82636. * });
  82637. *
  82638. * In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy to use a
  82639. * {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object - {@link
  82640. * Ext.data.reader.Json see the docs on JsonReader} for details.
  82641. *
  82642. * ## Inline data
  82643. *
  82644. * Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #cfg-data} into
  82645. * Model instances:
  82646. *
  82647. * Ext.create('Ext.data.Store', {
  82648. * model: 'User',
  82649. * data : [
  82650. * {firstName: 'Ed', lastName: 'Spencer'},
  82651. * {firstName: 'Tommy', lastName: 'Maintz'},
  82652. * {firstName: 'Aaron', lastName: 'Conran'},
  82653. * {firstName: 'Jamie', lastName: 'Avins'}
  82654. * ]
  82655. * });
  82656. *
  82657. * Loading inline data using the method above is great if the data is in the correct format already (e.g. it doesn't
  82658. * need to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode
  82659. * the data structure, use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory
  82660. * MemoryProxy} docs for an example).
  82661. *
  82662. * Additional data can also be loaded locally using {@link #method-add}.
  82663. *
  82664. * ## Dynamic Loading
  82665. *
  82666. * Stores can be dynamically updated by calling the {@link #method-load} method:
  82667. *
  82668. * store.load({
  82669. * params: {
  82670. * group: 3,
  82671. * type: 'user'
  82672. * },
  82673. * callback: function(records, operation, success) {
  82674. * // do something after the load finishes
  82675. * },
  82676. * scope: this
  82677. * });
  82678. *
  82679. * Here a bunch of arbitrary parameters is passed along with the load request and a callback function is set
  82680. * up to do something after the loading is over.
  82681. *
  82682. * ## Loading Nested Data
  82683. *
  82684. * Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
  82685. * Instead of issuing an AJAX request for the User and a series of additional AJAX requests for each Order, we can load
  82686. * a nested dataset and allow the Reader to automatically populate the associated models. Below is a brief example, see
  82687. * the {@link Ext.data.reader.Reader} intro docs for a full explanation:
  82688. *
  82689. * var store = Ext.create('Ext.data.Store', {
  82690. * autoLoad: true,
  82691. * model: "User",
  82692. * proxy: {
  82693. * type: 'ajax',
  82694. * url: 'users.json',
  82695. * reader: {
  82696. * type: 'json',
  82697. * root: 'users'
  82698. * }
  82699. * }
  82700. * });
  82701. *
  82702. * Which would consume a response like this:
  82703. *
  82704. * {
  82705. * "users": [{
  82706. * "id": 1,
  82707. * "name": "Ed",
  82708. * "orders": [{
  82709. * "id": 10,
  82710. * "total": 10.76,
  82711. * "status": "invoiced"
  82712. * },{
  82713. * "id": 11,
  82714. * "total": 13.45,
  82715. * "status": "shipped"
  82716. * }]
  82717. * }]
  82718. * }
  82719. *
  82720. * See the {@link Ext.data.reader.Reader} intro docs for a full explanation.
  82721. *
  82722. * ## Filtering and Sorting
  82723. *
  82724. * Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and
  82725. * {@link #cfg-filters} are held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage.
  82726. * Usually it is sufficient to either just specify sorters and filters in the Store configuration or call {@link #sort}
  82727. * or {@link #filter}:
  82728. *
  82729. * var store = Ext.create('Ext.data.Store', {
  82730. * model: 'User',
  82731. * sorters: [{
  82732. * property: 'age',
  82733. * direction: 'DESC'
  82734. * }, {
  82735. * property: 'firstName',
  82736. * direction: 'ASC'
  82737. * }],
  82738. *
  82739. * filters: [{
  82740. * property: 'firstName',
  82741. * value: /Ed/
  82742. * }]
  82743. * });
  82744. *
  82745. * The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By
  82746. * default, sorting and filtering are both performed locally by the Store - see {@link #remoteSort} and
  82747. * {@link #remoteFilter} to allow the server to perform these operations instead.
  82748. *
  82749. * Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter
  82750. * to the Store and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all
  82751. * existing filters). Note that by default {@link #sortOnFilter} is set to true, which means that your sorters are
  82752. * automatically reapplied if using local sorting.
  82753. *
  82754. * store.filter('eyeColor', 'Brown');
  82755. *
  82756. * Change the sorting at any time by calling {@link #sort}:
  82757. *
  82758. * store.sort('height', 'ASC');
  82759. *
  82760. * Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no
  82761. * arguments, the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new
  82762. * ones, just add them to the MixedCollection:
  82763. *
  82764. * store.sorters.add(new Ext.util.Sorter({
  82765. * property : 'shoeSize',
  82766. * direction: 'ASC'
  82767. * }));
  82768. *
  82769. * store.sort();
  82770. *
  82771. * ## Registering with StoreManager
  82772. *
  82773. * Any Store that is instantiated with a {@link #storeId} will automatically be registed with the {@link
  82774. * Ext.data.StoreManager StoreManager}. This makes it easy to reuse the same store in multiple views:
  82775. *
  82776. * //this store can be used several times
  82777. * Ext.create('Ext.data.Store', {
  82778. * model: 'User',
  82779. * storeId: 'usersStore'
  82780. * });
  82781. *
  82782. * new Ext.List({
  82783. * store: 'usersStore',
  82784. * //other config goes here
  82785. * });
  82786. *
  82787. * new Ext.view.View({
  82788. * store: 'usersStore',
  82789. * //other config goes here
  82790. * });
  82791. *
  82792. * ## Further Reading
  82793. *
  82794. * Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
  82795. * pieces and how they fit together, see:
  82796. *
  82797. * - {@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used
  82798. * - {@link Ext.data.Model Model} - the core class in the data package
  82799. * - {@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response
  82800. *
  82801. * @author Ed Spencer
  82802. */
  82803. Ext.define('Ext.data.Store', {
  82804. extend: 'Ext.data.AbstractStore',
  82805. alias: 'store.store',
  82806. // Required classes must be loaded before the definition callback runs
  82807. // The class definition callback creates a dummy Store which requires that
  82808. // all the classes below have been loaded.
  82809. requires: [
  82810. 'Ext.data.StoreManager',
  82811. 'Ext.data.Model',
  82812. 'Ext.data.proxy.Ajax',
  82813. 'Ext.data.proxy.Memory',
  82814. 'Ext.data.reader.Json',
  82815. 'Ext.data.writer.Json',
  82816. 'Ext.util.LruCache'
  82817. ],
  82818. uses: [
  82819. 'Ext.ModelManager',
  82820. 'Ext.util.Grouper'
  82821. ],
  82822. remoteSort: false,
  82823. remoteFilter: false,
  82824. /**
  82825. * @cfg {Boolean} remoteGroup
  82826. * True if the grouping should apply on the server side, false if it is local only. If the
  82827. * grouping is local, it can be applied immediately to the data. If it is remote, then it will simply act as a
  82828. * helper, automatically sending the grouping information to the server.
  82829. */
  82830. remoteGroup : false,
  82831. /**
  82832. * @cfg {String/Ext.data.proxy.Proxy/Object} proxy
  82833. * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -
  82834. * see {@link #setProxy} for details.
  82835. */
  82836. /**
  82837. * @cfg {Object[]/Ext.data.Model[]} data
  82838. * Array of Model instances or data objects to load locally. See "Inline data" above for details.
  82839. */
  82840. /**
  82841. * @cfg {String} groupField
  82842. * The field by which to group data in the store. Internally, grouping is very similar to sorting - the
  82843. * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
  82844. * level of grouping, and groups can be fetched via the {@link #getGroups} method.
  82845. */
  82846. groupField: undefined,
  82847. /**
  82848. * @cfg {String} groupDir
  82849. * The direction in which sorting should be applied when grouping. Supported values are "ASC" and "DESC".
  82850. */
  82851. groupDir: "ASC",
  82852. /**
  82853. * @cfg {Number} trailingBufferZone
  82854. * When {@link #buffered}, the number of extra records to keep cached on the trailing side of scrolling buffer
  82855. * as scrolling proceeds. A larger number means fewer replenishments from the server.
  82856. */
  82857. trailingBufferZone: 25,
  82858. /**
  82859. * @cfg {Number} leadingBufferZone
  82860. * When {@link #buffered}, the number of extra rows to keep cached on the leading side of scrolling buffer
  82861. * as scrolling proceeds. A larger number means fewer replenishments from the server.
  82862. */
  82863. leadingBufferZone: 200,
  82864. /**
  82865. * @cfg {Number} pageSize
  82866. * The number of records considered to form a 'page'. This is used to power the built-in
  82867. * paging using the nextPage and previousPage functions when the grid is paged using a
  82868. * {@link Ext.toolbar.Paging PagingScroller} Defaults to 25.
  82869. *
  82870. * If this Store is {@link #buffered}, pages are loaded into a page cache before the Store's
  82871. * data is updated from the cache. The pageSize is the number of rows loaded into the cache in one request.
  82872. * This will not affect the rendering of a buffered grid, but a larger page size will mean fewer loads.
  82873. *
  82874. * In a buffered grid, scrolling is monitored, and the page cache is kept primed with data ahead of the
  82875. * direction of scroll to provide rapid access to data when scrolling causes it to be required. Several pages
  82876. * in advance may be requested depending on various parameters.
  82877. *
  82878. * It is recommended to tune the {@link #pageSize}, {@link #trailingBufferZone} and
  82879. * {@link #leadingBufferZone} configurations based upon the conditions pertaining in your deployed application.
  82880. *
  82881. * The provided SDK example `examples/grid/infinite-scroll-grid-tuner.html` can be used to experiment with
  82882. * different settings including simulating Ajax latency.
  82883. */
  82884. pageSize: undefined,
  82885. /**
  82886. * @property {Number} currentPage
  82887. * The page that the Store has most recently loaded (see {@link #loadPage})
  82888. */
  82889. currentPage: 1,
  82890. /**
  82891. * @cfg {Boolean} clearOnPageLoad
  82892. * True to empty the store when loading another page via {@link #loadPage},
  82893. * {@link #nextPage} or {@link #previousPage}. Setting to false keeps existing records, allowing
  82894. * large data sets to be loaded one page at a time but rendered all together.
  82895. */
  82896. clearOnPageLoad: true,
  82897. /**
  82898. * @property {Boolean} loading
  82899. * True if the Store is currently loading via its Proxy
  82900. * @private
  82901. */
  82902. loading: false,
  82903. /**
  82904. * @cfg {Boolean} sortOnFilter
  82905. * For local filtering only, causes {@link #sort} to be called whenever {@link #filter} is called,
  82906. * causing the sorters to be reapplied after filtering. Defaults to true
  82907. */
  82908. sortOnFilter: true,
  82909. /**
  82910. * @cfg {Boolean} buffered
  82911. * Allows the Store to prefetch and cache in a **page cache**, pages of Records, and to then satisfy
  82912. * loading requirements from this page cache.
  82913. *
  82914. * To use buffered Stores, initiate the process by loading the first page. The number of rows rendered are
  82915. * determined automatically, and the range of pages needed to keep the cache primed for scrolling is
  82916. * requested and cached.
  82917. * Example:
  82918. *
  82919. * // Load page 1
  82920. * myStore.loadPage(1);
  82921. *
  82922. * A {@link Ext.grid.PagingScroller PagingScroller} is instantiated which will monitor the scrolling in the grid, and
  82923. * refresh the view's rows from the page cache as needed. It will also pull new data into the page
  82924. * cache when scrolling of the view draws upon data near either end of the prefetched data.
  82925. *
  82926. * The margins which trigger view refreshing from the prefetched data are {@link Ext.grid.PagingScroller#numFromEdge},
  82927. * {@link Ext.grid.PagingScroller#leadingBufferZone} and {@link Ext.grid.PagingScroller#trailingBufferZone}.
  82928. *
  82929. * The margins which trigger loading more data into the page cache are, {@link #leadingBufferZone} and
  82930. * {@link #trailingBufferZone}.
  82931. *
  82932. * By defult, only 5 pages of data are cached in the page cache, with pages "scrolling" out of the buffer
  82933. * as the view moves down through the dataset.
  82934. * Setting this value to zero means that no pages are *ever* scrolled out of the page cache, and
  82935. * that eventually the whole dataset may become present in the page cache. This is sometimes desirable
  82936. * as long as datasets do not reach astronomical proportions.
  82937. *
  82938. * Selection state may be maintained across page boundaries by configuring the SelectionModel not to discard
  82939. * records from its collection when those Records cycle out of the Store's primary collection. This is done
  82940. * by configuring the SelectionModel like this:
  82941. *
  82942. * selModel: {
  82943. * pruneRemoved: false
  82944. * }
  82945. *
  82946. */
  82947. buffered: false,
  82948. /**
  82949. * @cfg {Number} purgePageCount
  82950. * *Valid only when used with a {@link Ext.data.Store#buffered buffered} Store.*
  82951. *
  82952. * The number of pages *additional to the required buffered range* to keep in the prefetch cache before purging least recently used records.
  82953. *
  82954. * For example, if the height of the view area and the configured {@link #trailingBufferZone} and {@link #leadingBufferZone} require that there
  82955. * are three pages in the cache, then a `purgePageCount` of 5 ensures that up to 8 pages can be in the page cache any any one time.
  82956. *
  82957. * A value of 0 indicates to never purge the prefetched data.
  82958. */
  82959. purgePageCount: 5,
  82960. /**
  82961. * @cfg {Boolean} [clearRemovedOnLoad=true]
  82962. * True to clear anything in the {@link #removed} record collection when the store loads.
  82963. */
  82964. clearRemovedOnLoad: true,
  82965. defaultPageSize: 25,
  82966. // Private. Used as parameter to loadRecords
  82967. addRecordsOptions: {
  82968. addRecords: true
  82969. },
  82970. statics: {
  82971. recordIdFn: function(record) {
  82972. return record.internalId;
  82973. },
  82974. recordIndexFn: function(record) {
  82975. return record.index;
  82976. }
  82977. },
  82978. onClassExtended: function(cls, data, hooks) {
  82979. var model = data.model,
  82980. onBeforeClassCreated;
  82981. if (typeof model == 'string') {
  82982. onBeforeClassCreated = hooks.onBeforeCreated;
  82983. hooks.onBeforeCreated = function() {
  82984. var me = this,
  82985. args = arguments;
  82986. Ext.require(model, function() {
  82987. onBeforeClassCreated.apply(me, args);
  82988. });
  82989. };
  82990. }
  82991. },
  82992. /**
  82993. * Creates the store.
  82994. * @param {Object} [config] Config object
  82995. */
  82996. constructor: function(config) {
  82997. // Clone the config so we don't modify the original config object
  82998. config = Ext.Object.merge({}, config);
  82999. var me = this,
  83000. groupers = config.groupers || me.groupers,
  83001. groupField = config.groupField || me.groupField,
  83002. proxy,
  83003. data;
  83004. /**
  83005. * @event beforeprefetch
  83006. * Fires before a prefetch occurs. Return false to cancel.
  83007. * @param {Ext.data.Store} this
  83008. * @param {Ext.data.Operation} operation The associated operation
  83009. */
  83010. /**
  83011. * @event groupchange
  83012. * Fired whenever the grouping in the grid changes
  83013. * @param {Ext.data.Store} store The store
  83014. * @param {Ext.util.Grouper[]} groupers The array of grouper objects
  83015. */
  83016. /**
  83017. * @event prefetch
  83018. * Fires whenever records have been prefetched
  83019. * @param {Ext.data.Store} this
  83020. * @param {Ext.data.Model[]} records An array of records.
  83021. * @param {Boolean} successful True if the operation was successful.
  83022. * @param {Ext.data.Operation} operation The associated operation
  83023. */
  83024. data = config.data || me.data;
  83025. /**
  83026. * @property {Ext.util.MixedCollection} data
  83027. * The MixedCollection that holds this store's local cache of records.
  83028. */
  83029. me.data = new Ext.util.MixedCollection(false, Ext.data.Store.recordIdFn);
  83030. if (data) {
  83031. me.inlineData = data;
  83032. delete config.data;
  83033. }
  83034. if (!groupers && groupField) {
  83035. groupers = [{
  83036. property : groupField,
  83037. direction: config.groupDir || me.groupDir
  83038. }];
  83039. }
  83040. delete config.groupers;
  83041. /**
  83042. * @property {Ext.util.MixedCollection} groupers
  83043. * The collection of {@link Ext.util.Grouper Groupers} currently applied to this Store.
  83044. */
  83045. me.groupers = new Ext.util.MixedCollection();
  83046. me.groupers.addAll(me.decodeGroupers(groupers));
  83047. this.callParent([config]);
  83048. // don't use *config* anymore from here on... use *me* instead...
  83049. if (me.buffered) {
  83050. /**
  83051. * @property {Ext.data.Store.PageMap} pageMap
  83052. * Internal PageMap instance.
  83053. * @private
  83054. */
  83055. me.pageMap = new me.PageMap({
  83056. pageSize: me.pageSize,
  83057. maxSize: me.purgePageCount,
  83058. listeners: {
  83059. // Whenever PageMap gets cleared, it means we re no longer interested in
  83060. // any outstanding page prefetches, so cancel tham all
  83061. clear: me.cancelAllPrefetches,
  83062. scope: me
  83063. }
  83064. });
  83065. me.pageRequests = {};
  83066. me.sortOnLoad = false;
  83067. me.filterOnLoad = false;
  83068. }
  83069. // Only sort by group fields if we are doing local grouping
  83070. if (me.remoteGroup) {
  83071. me.remoteSort = true;
  83072. }
  83073. if (me.groupers.items.length && !me.remoteGroup) {
  83074. me.sort(me.groupers.items, 'prepend', false);
  83075. }
  83076. proxy = me.proxy;
  83077. data = me.inlineData;
  83078. // Page size for non-buffered Store defaults to 25
  83079. // For a buffered Store, the default page size is taken from the initial call to prefetch.
  83080. if (!me.buffered && !me.pageSize) {
  83081. me.pageSize = me.defaultPageSize;
  83082. }
  83083. if (data) {
  83084. if (proxy instanceof Ext.data.proxy.Memory) {
  83085. proxy.data = data;
  83086. me.read();
  83087. } else {
  83088. me.add.apply(me, [data]);
  83089. }
  83090. me.sort();
  83091. delete me.inlineData;
  83092. } else if (me.autoLoad) {
  83093. Ext.defer(me.load, 10, me, [ typeof me.autoLoad === 'object' ? me.autoLoad : undefined ]);
  83094. // Remove the defer call, we may need reinstate this at some point, but currently it's not obvious why it's here.
  83095. // this.load(typeof this.autoLoad == 'object' ? this.autoLoad : undefined);
  83096. }
  83097. },
  83098. // private override
  83099. // After destroying the Store, clear the page prefetch cache
  83100. destroyStore: function() {
  83101. this.callParent(arguments);
  83102. // Release cached pages.
  83103. // Will also cancel outstanding prefetch requests, and cause a generation change
  83104. // so that incoming prefetch data will be ignored.
  83105. if (this.pageMap) {
  83106. this.pageMap.clear();
  83107. }
  83108. },
  83109. onBeforeSort: function() {
  83110. var groupers = this.groupers;
  83111. if (groupers.getCount() > 0) {
  83112. this.sort(groupers.items, 'prepend', false);
  83113. }
  83114. },
  83115. /**
  83116. * @private
  83117. * Normalizes an array of grouper objects, ensuring that they are all Ext.util.Grouper instances
  83118. * @param {Object[]} groupers The groupers array
  83119. * @return {Ext.util.Grouper[]} Array of Ext.util.Grouper objects
  83120. */
  83121. decodeGroupers: function(groupers) {
  83122. if (!Ext.isArray(groupers)) {
  83123. if (groupers === undefined) {
  83124. groupers = [];
  83125. } else {
  83126. groupers = [groupers];
  83127. }
  83128. }
  83129. var length = groupers.length,
  83130. Grouper = Ext.util.Grouper,
  83131. config, i, result = [];
  83132. for (i = 0; i < length; i++) {
  83133. config = groupers[i];
  83134. if (!(config instanceof Grouper)) {
  83135. if (Ext.isString(config)) {
  83136. config = {
  83137. property: config
  83138. };
  83139. }
  83140. config = Ext.apply({
  83141. root : 'data',
  83142. direction: "ASC"
  83143. }, config);
  83144. //support for 3.x style sorters where a function can be defined as 'fn'
  83145. if (config.fn) {
  83146. config.sorterFn = config.fn;
  83147. }
  83148. //support a function to be passed as a sorter definition
  83149. if (typeof config == 'function') {
  83150. config = {
  83151. sorterFn: config
  83152. };
  83153. }
  83154. // return resulting Groupers in a separate array so as not to mutate passed in data objects.
  83155. result.push(new Grouper(config));
  83156. } else {
  83157. result.push(config);
  83158. }
  83159. }
  83160. return result;
  83161. },
  83162. /**
  83163. * Groups data inside the store.
  83164. * @param {String/Object[]} groupers Either a string name of one of the fields in this Store's
  83165. * configured {@link Ext.data.Model Model}, or an Array of grouper configurations.
  83166. * @param {String} [direction="ASC"] The overall direction to group the data by.
  83167. */
  83168. group: function(groupers, direction) {
  83169. var me = this,
  83170. hasNew = false,
  83171. grouper,
  83172. newGroupers;
  83173. if (Ext.isArray(groupers)) {
  83174. newGroupers = groupers;
  83175. } else if (Ext.isObject(groupers)) {
  83176. newGroupers = [groupers];
  83177. } else if (Ext.isString(groupers)) {
  83178. grouper = me.groupers.get(groupers);
  83179. if (!grouper) {
  83180. grouper = {
  83181. property : groupers,
  83182. direction: direction
  83183. };
  83184. newGroupers = [grouper];
  83185. } else if (direction === undefined) {
  83186. grouper.toggle();
  83187. } else {
  83188. grouper.setDirection(direction);
  83189. }
  83190. }
  83191. if (newGroupers && newGroupers.length) {
  83192. hasNew = true;
  83193. newGroupers = me.decodeGroupers(newGroupers);
  83194. me.groupers.clear();
  83195. me.groupers.addAll(newGroupers);
  83196. }
  83197. if (me.remoteGroup) {
  83198. if (me.buffered) {
  83199. me.pageMap.clear();
  83200. me.loadPage(1, { groupChange: true });
  83201. } else {
  83202. me.load({
  83203. scope: me,
  83204. callback: me.fireGroupChange
  83205. });
  83206. }
  83207. } else {
  83208. // need to explicitly force a sort if we have groupers
  83209. me.sort(null, null, null, hasNew);
  83210. me.fireGroupChange();
  83211. }
  83212. },
  83213. /**
  83214. * Clear any groupers in the store
  83215. */
  83216. clearGrouping: function() {
  83217. var me = this,
  83218. groupers = me.groupers.items,
  83219. gLen = groupers.length,
  83220. grouper, g;
  83221. for (g = 0; g < gLen; g++) {
  83222. grouper = groupers[g];
  83223. me.sorters.remove(grouper);
  83224. }
  83225. me.groupers.clear();
  83226. if (me.remoteGroup) {
  83227. if (me.buffered) {
  83228. me.pageMap.clear();
  83229. me.loadPage(1, { groupChange: true });
  83230. } else {
  83231. me.load({
  83232. scope: me,
  83233. callback: me.fireGroupChange
  83234. });
  83235. }
  83236. } else {
  83237. me.sort();
  83238. me.fireGroupChange();
  83239. }
  83240. },
  83241. /**
  83242. * Checks if the store is currently grouped
  83243. * @return {Boolean} True if the store is grouped.
  83244. */
  83245. isGrouped: function() {
  83246. return this.groupers.getCount() > 0;
  83247. },
  83248. /**
  83249. * Fires the groupchange event. Abstracted out so we can use it
  83250. * as a callback
  83251. * @private
  83252. */
  83253. fireGroupChange: function() {
  83254. this.fireEvent('groupchange', this, this.groupers);
  83255. },
  83256. /**
  83257. * Returns an array containing the result of applying grouping to the records in this store.
  83258. * See {@link #groupField}, {@link #groupDir} and {@link #getGroupString}. Example for a store
  83259. * containing records with a color field:
  83260. *
  83261. * var myStore = Ext.create('Ext.data.Store', {
  83262. * groupField: 'color',
  83263. * groupDir : 'DESC'
  83264. * });
  83265. *
  83266. * myStore.getGroups(); // returns:
  83267. * [
  83268. * {
  83269. * name: 'yellow',
  83270. * children: [
  83271. * // all records where the color field is 'yellow'
  83272. * ]
  83273. * },
  83274. * {
  83275. * name: 'red',
  83276. * children: [
  83277. * // all records where the color field is 'red'
  83278. * ]
  83279. * }
  83280. * ]
  83281. *
  83282. * Group contents are effected by filtering.
  83283. *
  83284. * @param {String} [groupName] Pass in an optional groupName argument to access a specific
  83285. * group as defined by {@link #getGroupString}.
  83286. * @return {Object/Object[]} The grouped data
  83287. */
  83288. getGroups: function(requestGroupString) {
  83289. var records = this.data.items,
  83290. length = records.length,
  83291. groups = [],
  83292. pointers = {},
  83293. record,
  83294. groupStr,
  83295. group,
  83296. i;
  83297. for (i = 0; i < length; i++) {
  83298. record = records[i];
  83299. groupStr = this.getGroupString(record);
  83300. group = pointers[groupStr];
  83301. if (group === undefined) {
  83302. group = {
  83303. name: groupStr,
  83304. children: []
  83305. };
  83306. groups.push(group);
  83307. pointers[groupStr] = group;
  83308. }
  83309. group.children.push(record);
  83310. }
  83311. return requestGroupString ? pointers[requestGroupString] : groups;
  83312. },
  83313. /**
  83314. * @private
  83315. * For a given set of records and a Grouper, returns an array of arrays - each of which is the set of records
  83316. * matching a certain group.
  83317. */
  83318. getGroupsForGrouper: function(records, grouper) {
  83319. var length = records.length,
  83320. groups = [],
  83321. oldValue,
  83322. newValue,
  83323. record,
  83324. group,
  83325. i;
  83326. for (i = 0; i < length; i++) {
  83327. record = records[i];
  83328. newValue = grouper.getGroupString(record);
  83329. if (newValue !== oldValue) {
  83330. group = {
  83331. name: newValue,
  83332. grouper: grouper,
  83333. records: []
  83334. };
  83335. groups.push(group);
  83336. }
  83337. group.records.push(record);
  83338. oldValue = newValue;
  83339. }
  83340. return groups;
  83341. },
  83342. /**
  83343. * @private
  83344. * This is used recursively to gather the records into the configured Groupers. The data MUST have been sorted for
  83345. * this to work properly (see {@link #getGroupData} and {@link #getGroupsForGrouper}) Most of the work is done by
  83346. * {@link #getGroupsForGrouper} - this function largely just handles the recursion.
  83347. *
  83348. * @param {Ext.data.Model[]} records The set or subset of records to group
  83349. * @param {Number} grouperIndex The grouper index to retrieve
  83350. * @return {Object[]} The grouped records
  83351. */
  83352. getGroupsForGrouperIndex: function(records, grouperIndex) {
  83353. var me = this,
  83354. groupers = me.groupers,
  83355. grouper = groupers.getAt(grouperIndex),
  83356. groups = me.getGroupsForGrouper(records, grouper),
  83357. length = groups.length,
  83358. i;
  83359. if (grouperIndex + 1 < groupers.length) {
  83360. for (i = 0; i < length; i++) {
  83361. groups[i].children = me.getGroupsForGrouperIndex(groups[i].records, grouperIndex + 1);
  83362. }
  83363. }
  83364. for (i = 0; i < length; i++) {
  83365. groups[i].depth = grouperIndex;
  83366. }
  83367. return groups;
  83368. },
  83369. /**
  83370. * @private
  83371. * Returns records grouped by the configured {@link #groupers grouper} configuration. Sample return value (in
  83372. * this case grouping by genre and then author in a fictional books dataset):
  83373. *
  83374. * [
  83375. * {
  83376. * name: 'Fantasy',
  83377. * depth: 0,
  83378. * records: [
  83379. * //book1, book2, book3, book4
  83380. * ],
  83381. * children: [
  83382. * {
  83383. * name: 'Rowling',
  83384. * depth: 1,
  83385. * records: [
  83386. * //book1, book2
  83387. * ]
  83388. * },
  83389. * {
  83390. * name: 'Tolkein',
  83391. * depth: 1,
  83392. * records: [
  83393. * //book3, book4
  83394. * ]
  83395. * }
  83396. * ]
  83397. * }
  83398. * ]
  83399. *
  83400. * @param {Boolean} [sort=true] True to call {@link #sort} before finding groups. Sorting is required to make grouping
  83401. * function correctly so this should only be set to false if the Store is known to already be sorted correctly.
  83402. * @return {Object[]} The group data
  83403. */
  83404. getGroupData: function(sort) {
  83405. var me = this;
  83406. if (sort !== false) {
  83407. me.sort();
  83408. }
  83409. return me.getGroupsForGrouperIndex(me.data.items, 0);
  83410. },
  83411. /**
  83412. * Returns the string to group on for a given model instance. The default implementation of this method returns
  83413. * the model's {@link #groupField}, but this can be overridden to group by an arbitrary string. For example, to
  83414. * group by the first letter of a model's 'name' field, use the following code:
  83415. *
  83416. * Ext.create('Ext.data.Store', {
  83417. * groupDir: 'ASC',
  83418. * getGroupString: function(instance) {
  83419. * return instance.get('name')[0];
  83420. * }
  83421. * });
  83422. *
  83423. * @param {Ext.data.Model} instance The model instance
  83424. * @return {String} The string to compare when forming groups
  83425. */
  83426. getGroupString: function(instance) {
  83427. var group = this.groupers.first();
  83428. if (group) {
  83429. return instance.get(group.property);
  83430. }
  83431. return '';
  83432. },
  83433. /**
  83434. * Inserts Model instances into the Store at the given index and fires the {@link #event-add} event.
  83435. * See also {@link #method-add}.
  83436. *
  83437. * @param {Number} index The start index at which to insert the passed Records.
  83438. * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the store.
  83439. */
  83440. insert: function(index, records) {
  83441. var me = this,
  83442. sync = false,
  83443. i,
  83444. record,
  83445. len;
  83446. records = [].concat(records);
  83447. for (i = 0,len = records.length; i < len; i++) {
  83448. record = me.createModel(records[i]);
  83449. record.set(me.modelDefaults);
  83450. // reassign the model in the array in case it wasn't created yet
  83451. records[i] = record;
  83452. me.data.insert(index + i, record);
  83453. record.join(me);
  83454. sync = sync || record.phantom === true;
  83455. }
  83456. if (me.snapshot) {
  83457. me.snapshot.addAll(records);
  83458. }
  83459. if (me.requireSort) {
  83460. // suspend events so the usual data changed events don't get fired.
  83461. me.suspendEvents();
  83462. me.sort();
  83463. me.resumeEvents();
  83464. }
  83465. me.fireEvent('add', me, records, index);
  83466. me.fireEvent('datachanged', me);
  83467. if (me.autoSync && sync && !me.autoSyncSuspended) {
  83468. me.sync();
  83469. }
  83470. },
  83471. /**
  83472. * Adds Model instance to the Store. This method accepts either:
  83473. *
  83474. * - An array of Model instances or Model configuration objects.
  83475. * - Any number of Model instance or Model configuration object arguments.
  83476. *
  83477. * The new Model instances will be added at the end of the existing collection.
  83478. *
  83479. * Sample usage:
  83480. *
  83481. * myStore.add({some: 'data'}, {some: 'other data'});
  83482. *
  83483. * Note that if this Store is sorted, the new Model instances will be inserted
  83484. * at the correct point in the Store to maintain the sort order.
  83485. *
  83486. * @param {Ext.data.Model[]/Ext.data.Model...} model An array of Model instances
  83487. * or Model configuration objects, or variable number of Model instance or config arguments.
  83488. * @return {Ext.data.Model[]} The model instances that were added
  83489. */
  83490. add: function(records) {
  83491. //accept both a single-argument array of records, or any number of record arguments
  83492. if (!Ext.isArray(records)) {
  83493. records = Array.prototype.slice.apply(arguments);
  83494. } else {
  83495. // Create an array copy
  83496. records = records.slice(0);
  83497. }
  83498. var me = this,
  83499. i = 0,
  83500. length = records.length,
  83501. record,
  83502. isSorted = !me.remoteSort && me.sorters && me.sorters.items.length;
  83503. // If this Store is sorted, and they only passed one Record (99% or use cases)
  83504. // then it's much more efficient to add it sorted than to append and then sort.
  83505. if (isSorted && length === 1) {
  83506. return [ me.addSorted(me.createModel(records[0])) ];
  83507. }
  83508. for (; i < length; i++) {
  83509. record = me.createModel(records[i]);
  83510. // reassign the model in the array in case it wasn't created yet
  83511. records[i] = record;
  83512. }
  83513. // If this sort is sorted, set the flag used by the insert method to sort
  83514. // before firing events.
  83515. if (isSorted) {
  83516. me.requireSort = true;
  83517. }
  83518. me.insert(me.data.length, records);
  83519. delete me.requireSort;
  83520. return records;
  83521. },
  83522. /**
  83523. * (Local sort only) Inserts the passed Record into the Store at the index where it
  83524. * should go based on the current sort information.
  83525. *
  83526. * @param {Ext.data.Record} record
  83527. */
  83528. addSorted: function(record) {
  83529. var me = this,
  83530. index = me.data.findInsertionIndex(record, me.generateComparator());
  83531. me.insert(index, record);
  83532. return record;
  83533. },
  83534. /**
  83535. * Converts a literal to a model, if it's not a model already
  83536. * @private
  83537. * @param {Ext.data.Model/Object} record The record to create
  83538. * @return {Ext.data.Model}
  83539. */
  83540. createModel: function(record) {
  83541. if (!record.isModel) {
  83542. record = Ext.ModelManager.create(record, this.model);
  83543. }
  83544. return record;
  83545. },
  83546. /**
  83547. * Calls the specified function for each {@link Ext.data.Model record} in the store.
  83548. *
  83549. * When store is filtered, only loops over the filtered records.
  83550. *
  83551. * @param {Function} fn The function to call. The {@link Ext.data.Model Record} is passed as the first parameter.
  83552. * Returning `false` aborts and exits the iteration.
  83553. * @param {Object} [scope] The scope (this reference) in which the function is executed.
  83554. * Defaults to the current {@link Ext.data.Model record} in the iteration.
  83555. */
  83556. each: function(fn, scope) {
  83557. var data = this.data.items,
  83558. dLen = data.length,
  83559. record, d;
  83560. for (d = 0; d < dLen; d++) {
  83561. record = data[d];
  83562. if (fn.call(scope || record, record, d, dLen) === false) {
  83563. break;
  83564. }
  83565. }
  83566. },
  83567. /**
  83568. * Removes the given record from the Store, firing the 'remove' event for each instance that is removed,
  83569. * plus a single 'datachanged' event after removal.
  83570. *
  83571. * @param {Ext.data.Model/Ext.data.Model[]} records Model instance or array of instances to remove.
  83572. */
  83573. remove: function(records, /* private */ isMove) {
  83574. if (!Ext.isArray(records)) {
  83575. records = [records];
  83576. }
  83577. /*
  83578. * Pass the isMove parameter if we know we're going to be re-inserting this record
  83579. */
  83580. isMove = isMove === true;
  83581. var me = this,
  83582. sync = false,
  83583. i = 0,
  83584. length = records.length,
  83585. isNotPhantom,
  83586. index,
  83587. record;
  83588. for (; i < length; i++) {
  83589. record = records[i];
  83590. index = me.data.indexOf(record);
  83591. if (me.snapshot) {
  83592. me.snapshot.remove(record);
  83593. }
  83594. if (index > -1) {
  83595. isNotPhantom = record.phantom !== true;
  83596. // don't push phantom records onto removed
  83597. if (!isMove && isNotPhantom) {
  83598. // Store the index the record was removed from so that rejectChanges can re-insert at the correct place.
  83599. // The record's index property won't do, as that is the index in the overall dataset when Store is buffered.
  83600. record.removedFrom = index;
  83601. me.removed.push(record);
  83602. }
  83603. record.unjoin(me);
  83604. me.data.remove(record);
  83605. sync = sync || isNotPhantom;
  83606. me.fireEvent('remove', me, record, index);
  83607. }
  83608. }
  83609. me.fireEvent('datachanged', me);
  83610. if (!isMove && me.autoSync && sync && !me.autoSyncSuspended) {
  83611. me.sync();
  83612. }
  83613. },
  83614. /**
  83615. * Removes the model instance at the given index
  83616. * @param {Number} index The record index
  83617. */
  83618. removeAt: function(index) {
  83619. var record = this.getAt(index);
  83620. if (record) {
  83621. this.remove(record);
  83622. }
  83623. },
  83624. /**
  83625. * Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
  83626. * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
  83627. * instances into the Store and calling an optional callback if required. Example usage:
  83628. *
  83629. * store.load({
  83630. * scope: this,
  83631. * callback: function(records, operation, success) {
  83632. * // the {@link Ext.data.Operation operation} object
  83633. * // contains all of the details of the load operation
  83634. * console.log(records);
  83635. * }
  83636. * });
  83637. *
  83638. * If the callback scope does not need to be set, a function can simply be passed:
  83639. *
  83640. * store.load(function(records, operation, success) {
  83641. * console.log('loaded records');
  83642. * });
  83643. *
  83644. * @param {Object/Function} [options] config object, passed into the Ext.data.Operation object before loading.
  83645. * Additionally `addRecords: true` can be specified to add these records to the existing records, default is
  83646. * to remove the Store's existing records first.
  83647. */
  83648. load: function(options) {
  83649. var me = this;
  83650. options = options || {};
  83651. if (typeof options == 'function') {
  83652. options = {
  83653. callback: options
  83654. };
  83655. }
  83656. options.groupers = options.groupers || me.groupers.items;
  83657. options.page = options.page || me.currentPage;
  83658. options.start = (options.start !== undefined) ? options.start : (options.page - 1) * me.pageSize;
  83659. options.limit = options.limit || me.pageSize;
  83660. options.addRecords = options.addRecords || false;
  83661. if (me.buffered) {
  83662. return me.loadToPrefetch(options);
  83663. }
  83664. return me.callParent([options]);
  83665. },
  83666. reload: function(options) {
  83667. var me = this,
  83668. startIdx,
  83669. endIdx,
  83670. startPage,
  83671. endPage,
  83672. i,
  83673. waitForReload,
  83674. bufferZone,
  83675. records;
  83676. if (!options) {
  83677. options = {};
  83678. }
  83679. // If buffered, we have to clear the page cache and then
  83680. // cache the page range surrounding store's loaded range.
  83681. if (me.buffered) {
  83682. // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
  83683. delete me.totalCount;
  83684. waitForReload = function() {
  83685. if (me.rangeCached(startIdx, endIdx)) {
  83686. me.loading = false;
  83687. me.pageMap.un('pageAdded', waitForReload);
  83688. records = me.pageMap.getRange(startIdx, endIdx);
  83689. me.loadRecords(records, {
  83690. start: startIdx
  83691. });
  83692. me.fireEvent('load', me, records, true);
  83693. }
  83694. };
  83695. bufferZone = Math.ceil((me.leadingBufferZone + me.trailingBufferZone) / 2);
  83696. // Get our record index range in the dataset
  83697. startIdx = options.start || me.getAt(0).index;
  83698. endIdx = startIdx + (options.count || me.getCount()) - 1;
  83699. // Calculate a page range which encompasses the Store's loaded range plus both buffer zones
  83700. startPage = me.getPageFromRecordIndex(Math.max(startIdx - bufferZone, 0));
  83701. endPage = me.getPageFromRecordIndex(endIdx + bufferZone);
  83702. // Clear cache (with initial flag so that any listening PagingScroller does not reset to page 1).
  83703. me.pageMap.clear(true);
  83704. if (me.fireEvent('beforeload', me, options) !== false) {
  83705. me.loading = true;
  83706. // Recache the page range which encapsulates our visible records
  83707. for (i = startPage; i <= endPage; i++) {
  83708. me.prefetchPage(i, options);
  83709. }
  83710. // Wait for the requested range to become available in the page map
  83711. // Load the range as soon as the whole range is available
  83712. me.pageMap.on('pageAdded', waitForReload);
  83713. }
  83714. } else {
  83715. return me.callParent(arguments);
  83716. }
  83717. },
  83718. /**
  83719. * @private
  83720. * Called internally when a Proxy has completed a load request
  83721. */
  83722. onProxyLoad: function(operation) {
  83723. var me = this,
  83724. resultSet = operation.getResultSet(),
  83725. records = operation.getRecords(),
  83726. successful = operation.wasSuccessful();
  83727. if (resultSet) {
  83728. me.totalCount = resultSet.total;
  83729. }
  83730. if (successful) {
  83731. me.loadRecords(records, operation);
  83732. }
  83733. me.loading = false;
  83734. if (me.hasListeners.load) {
  83735. me.fireEvent('load', me, records, successful);
  83736. }
  83737. //TODO: deprecate this event, it should always have been 'load' instead. 'load' is now documented, 'read' is not.
  83738. //People are definitely using this so can't deprecate safely until 2.x
  83739. if (me.hasListeners.read) {
  83740. me.fireEvent('read', me, records, successful);
  83741. }
  83742. //this is a callback that would have been passed to the 'read' function and is optional
  83743. Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
  83744. },
  83745. //inherit docs
  83746. getNewRecords: function() {
  83747. return this.data.filterBy(this.filterNew).items;
  83748. },
  83749. //inherit docs
  83750. getUpdatedRecords: function() {
  83751. return this.data.filterBy(this.filterUpdated).items;
  83752. },
  83753. /**
  83754. * Filters the loaded set of records by a given set of filters.
  83755. *
  83756. * By default, the passed filter(s) are *added* to the collection of filters being used to filter this Store.
  83757. *
  83758. * To remove existing filters before applying a new set of filters use
  83759. *
  83760. * // Clear the filter collection without updating the UI
  83761. * store.clearFilter(true);
  83762. *
  83763. * see {@link #clearFilter}.
  83764. *
  83765. * Alternatively, if filters are configured with an `id`, then existing filters store may be *replaced* by new
  83766. * filters having the same `id`.
  83767. *
  83768. * Filtering by single field:
  83769. *
  83770. * store.filter("email", /\.com$/);
  83771. *
  83772. * Using multiple filters:
  83773. *
  83774. * store.filter([
  83775. * {property: "email", value: /\.com$/},
  83776. * {filterFn: function(item) { return item.get("age") > 10; }}
  83777. * ]);
  83778. *
  83779. * Using Ext.util.Filter instances instead of config objects
  83780. * (note that we need to specify the {@link Ext.util.Filter#root root} config option in this case):
  83781. *
  83782. * store.filter([
  83783. * Ext.create('Ext.util.Filter', {property: "email", value: /\.com$/, root: 'data'}),
  83784. * Ext.create('Ext.util.Filter', {filterFn: function(item) { return item.get("age") > 10; }, root: 'data'})
  83785. * ]);
  83786. *
  83787. * When store is filtered, most of the methods for accessing store data will be working only
  83788. * within the set of filtered records. Two notable exceptions are {@link #queryBy} and
  83789. * {@link #getById}.
  83790. *
  83791. * @param {Object[]/Ext.util.Filter[]/String} filters The set of filters to apply to the data.
  83792. * These are stored internally on the store, but the filtering itself is done on the Store's
  83793. * {@link Ext.util.MixedCollection MixedCollection}. See MixedCollection's
  83794. * {@link Ext.util.MixedCollection#filter filter} method for filter syntax.
  83795. * Alternatively, pass in a property string
  83796. * @param {String} [value] value to filter by (only if using a property string as the first argument)
  83797. */
  83798. filter: function(filters, value) {
  83799. if (Ext.isString(filters)) {
  83800. filters = {
  83801. property: filters,
  83802. value: value
  83803. };
  83804. }
  83805. var me = this,
  83806. decoded = me.decodeFilters(filters),
  83807. i = 0,
  83808. doLocalSort = me.sorters.length && me.sortOnFilter && !me.remoteSort,
  83809. length = decoded.length;
  83810. for (; i < length; i++) {
  83811. me.filters.replace(decoded[i]);
  83812. }
  83813. if (me.remoteFilter) {
  83814. // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
  83815. delete me.totalCount;
  83816. // For a buffered Store, we have to clear the prefetch cache because the dataset will change upon filtering.
  83817. // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
  83818. // via the guaranteedrange event
  83819. if (me.buffered) {
  83820. me.pageMap.clear();
  83821. me.loadPage(1);
  83822. } else {
  83823. // Reset to the first page, the filter is likely to produce a smaller data set
  83824. me.currentPage = 1;
  83825. //the load function will pick up the new filters and request the filtered data from the proxy
  83826. me.load();
  83827. }
  83828. } else {
  83829. /**
  83830. * @property {Ext.util.MixedCollection} snapshot
  83831. * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
  83832. * records when a filter is removed or changed
  83833. */
  83834. if (me.filters.getCount()) {
  83835. me.snapshot = me.snapshot || me.data.clone();
  83836. me.data = me.data.filter(me.filters.items);
  83837. if (doLocalSort) {
  83838. me.sort();
  83839. } else {
  83840. // fire datachanged event if it hasn't already been fired by doSort
  83841. me.fireEvent('datachanged', me);
  83842. me.fireEvent('refresh', me);
  83843. }
  83844. }
  83845. }
  83846. },
  83847. /**
  83848. * Reverts to a view of the Record cache with no filtering applied.
  83849. * @param {Boolean} suppressEvent If `true` the filter is cleared silently.
  83850. *
  83851. * For a locally filtered Store, this means that the filter collection is cleared without firing the
  83852. * {@link #datachanged} event.
  83853. *
  83854. * For a remotely filtered Store, this means that the filter collection is cleared, but the store
  83855. * is not reloaded from the server.
  83856. */
  83857. clearFilter: function(suppressEvent) {
  83858. var me = this;
  83859. me.filters.clear();
  83860. if (me.remoteFilter) {
  83861. // In a buffered Store, the meaing of suppressEvent is to simply clear the filters collection
  83862. if (suppressEvent) {
  83863. return;
  83864. }
  83865. // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
  83866. delete me.totalCount;
  83867. // For a buffered Store, we have to clear the prefetch cache because the dataset will change upon filtering.
  83868. // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
  83869. // via the guaranteedrange event
  83870. if (me.buffered) {
  83871. me.pageMap.clear();
  83872. me.loadPage(1);
  83873. } else {
  83874. // Reset to the first page, clearing a filter will destroy the context of the current dataset
  83875. me.currentPage = 1;
  83876. me.load();
  83877. }
  83878. } else if (me.isFiltered()) {
  83879. me.data = me.snapshot.clone();
  83880. delete me.snapshot;
  83881. if (suppressEvent !== true) {
  83882. me.fireEvent('datachanged', me);
  83883. me.fireEvent('refresh', me);
  83884. }
  83885. }
  83886. },
  83887. /**
  83888. * Returns true if this store is currently filtered
  83889. * @return {Boolean}
  83890. */
  83891. isFiltered: function() {
  83892. var snapshot = this.snapshot;
  83893. return !! snapshot && snapshot !== this.data;
  83894. },
  83895. /**
  83896. * Filters by a function. The specified function will be called for each
  83897. * Record in this Store. If the function returns `true` the Record is included,
  83898. * otherwise it is filtered out.
  83899. *
  83900. * When store is filtered, most of the methods for accessing store data will be working only
  83901. * within the set of filtered records. Two notable exceptions are {@link #queryBy} and
  83902. * {@link #getById}.
  83903. *
  83904. * @param {Function} fn The function to be called. It will be passed the following parameters:
  83905. * @param {Ext.data.Model} fn.record The record to test for filtering. Access field values
  83906. * using {@link Ext.data.Model#get}.
  83907. * @param {Object} fn.id The ID of the Record passed.
  83908. * @param {Object} [scope] The scope (this reference) in which the function is executed.
  83909. * Defaults to this Store.
  83910. */
  83911. filterBy: function(fn, scope) {
  83912. var me = this;
  83913. me.snapshot = me.snapshot || me.data.clone();
  83914. me.data = me.queryBy(fn, scope || me);
  83915. me.fireEvent('datachanged', me);
  83916. me.fireEvent('refresh', me);
  83917. },
  83918. /**
  83919. * Query all the cached records in this Store using a filtering function. The specified function
  83920. * will be called with each record in this Store. If the function returns `true` the record is
  83921. * included in the results.
  83922. *
  83923. * This method is not effected by filtering, it will always look from all records inside the store
  83924. * no matter if filter is applied or not.
  83925. *
  83926. * @param {Function} fn The function to be called. It will be passed the following parameters:
  83927. * @param {Ext.data.Model} fn.record The record to test for filtering. Access field values
  83928. * using {@link Ext.data.Model#get}.
  83929. * @param {Object} fn.id The ID of the Record passed.
  83930. * @param {Object} [scope] The scope (this reference) in which the function is executed
  83931. * Defaults to this Store.
  83932. * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records
  83933. */
  83934. queryBy: function(fn, scope) {
  83935. var me = this,
  83936. data = me.snapshot || me.data;
  83937. return data.filterBy(fn, scope || me);
  83938. },
  83939. /**
  83940. * Query all the cached records in this Store by name/value pair.
  83941. * The parameters will be used to generated a filter function that is given
  83942. * to the queryBy method.
  83943. *
  83944. * This method compliments queryBy by generating the query function automatically.
  83945. *
  83946. * @param {String} property The property to create the filter function for
  83947. * @param {String/RegExp} value The string/regex to compare the property value to
  83948. * @param {Boolean} [anyMatch=false] True if we don't care if the filter value is not the full value.
  83949. * @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
  83950. * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
  83951. * Ignored if anyMatch is true.
  83952. * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records
  83953. */
  83954. query: function(property, value, anyMatch, caseSensitive, exactMatch) {
  83955. var me = this,
  83956. queryFn = me.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch),
  83957. results = me.queryBy(queryFn);
  83958. //create an empty mixed collection for use if queryBy returns null
  83959. if(!results) {
  83960. results = new Ext.util.MixedCollection();
  83961. }
  83962. return results;
  83963. },
  83964. /**
  83965. * Loads an array of data straight into the Store.
  83966. *
  83967. * Using this method is great if the data is in the correct format already (e.g. it doesn't need to be
  83968. * processed by a reader). If your data requires processing to decode the data structure, use a
  83969. * {@link Ext.data.proxy.Memory MemoryProxy} instead.
  83970. *
  83971. * @param {Ext.data.Model[]/Object[]} data Array of data to load. Any non-model instances will be cast
  83972. * into model instances first.
  83973. * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
  83974. * to remove the old ones first.
  83975. */
  83976. loadData: function(data, append) {
  83977. var me = this,
  83978. model = me.model,
  83979. length = data.length,
  83980. newData = [],
  83981. i,
  83982. record;
  83983. //make sure each data element is an Ext.data.Model instance
  83984. for (i = 0; i < length; i++) {
  83985. record = data[i];
  83986. if (!(record.isModel)) {
  83987. record = Ext.ModelManager.create(record, model);
  83988. }
  83989. newData.push(record);
  83990. }
  83991. me.loadRecords(newData, append ? me.addRecordsOptions : undefined);
  83992. },
  83993. /**
  83994. * Loads data via the bound Proxy's reader
  83995. *
  83996. * Use this method if you are attempting to load data and want to utilize the configured data reader.
  83997. *
  83998. * @param {Object[]} data The full JSON object you'd like to load into the Data store.
  83999. * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
  84000. * to remove the old ones first.
  84001. */
  84002. loadRawData : function(data, append) {
  84003. var me = this,
  84004. result = me.proxy.reader.read(data),
  84005. records = result.records;
  84006. if (result.success) {
  84007. me.totalCount = result.total;
  84008. me.loadRecords(records, append ? me.addRecordsOptions : undefined);
  84009. me.fireEvent('load', me, records, true);
  84010. }
  84011. },
  84012. /**
  84013. * Loads an array of {@link Ext.data.Model model} instances into the store, fires the datachanged event. This should only usually
  84014. * be called internally when loading from the {@link Ext.data.proxy.Proxy Proxy}, when adding records manually use {@link #method-add} instead
  84015. * @param {Ext.data.Model[]} records The array of records to load
  84016. * @param {Object} options
  84017. * @param {Boolean} [options.addRecords=false] Pass `true` to add these records to the existing records, `false` to remove the Store's existing records first.
  84018. * @param {Number} [options.start] Only used by buffered Stores. The index *within the overall dataset* of the first record in the array.
  84019. */
  84020. loadRecords: function(records, options) {
  84021. var me = this,
  84022. i = 0,
  84023. length = records.length,
  84024. start,
  84025. addRecords,
  84026. snapshot = me.snapshot;
  84027. if (options) {
  84028. start = options.start;
  84029. addRecords = options.addRecords;
  84030. }
  84031. if (!addRecords) {
  84032. delete me.snapshot;
  84033. me.clearData(true);
  84034. } else if (snapshot) {
  84035. snapshot.addAll(records);
  84036. }
  84037. me.data.addAll(records);
  84038. if (start !== undefined) {
  84039. for (; i < length; i++) {
  84040. records[i].index = start + i;
  84041. records[i].join(me);
  84042. }
  84043. } else {
  84044. for (; i < length; i++) {
  84045. records[i].join(me);
  84046. }
  84047. }
  84048. /*
  84049. * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
  84050. * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
  84051. * datachanged event is fired by the call to this.add, above.
  84052. */
  84053. me.suspendEvents();
  84054. if (me.filterOnLoad && !me.remoteFilter) {
  84055. me.filter();
  84056. }
  84057. if (me.sortOnLoad && !me.remoteSort) {
  84058. me.sort(undefined, undefined, undefined, true);
  84059. }
  84060. me.resumeEvents();
  84061. me.fireEvent('datachanged', me);
  84062. me.fireEvent('refresh', me);
  84063. },
  84064. // PAGING METHODS
  84065. /**
  84066. * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
  84067. * load operation, passing in calculated 'start' and 'limit' params
  84068. * @param {Number} page The number of the page to load
  84069. * @param {Object} options See options for {@link #method-load}
  84070. */
  84071. loadPage: function(page, options) {
  84072. var me = this;
  84073. me.currentPage = page;
  84074. // Copy options into a new object so as not to mutate passed in objects
  84075. options = Ext.apply({
  84076. page: page,
  84077. start: (page - 1) * me.pageSize,
  84078. limit: me.pageSize,
  84079. addRecords: !me.clearOnPageLoad
  84080. }, options);
  84081. if (me.buffered) {
  84082. return me.loadToPrefetch(options);
  84083. }
  84084. me.read(options);
  84085. },
  84086. /**
  84087. * Loads the next 'page' in the current data set
  84088. * @param {Object} options See options for {@link #method-load}
  84089. */
  84090. nextPage: function(options) {
  84091. this.loadPage(this.currentPage + 1, options);
  84092. },
  84093. /**
  84094. * Loads the previous 'page' in the current data set
  84095. * @param {Object} options See options for {@link #method-load}
  84096. */
  84097. previousPage: function(options) {
  84098. this.loadPage(this.currentPage - 1, options);
  84099. },
  84100. // private
  84101. clearData: function(isLoad) {
  84102. var me = this,
  84103. records = me.data.items,
  84104. i = records.length;
  84105. while (i--) {
  84106. records[i].unjoin(me);
  84107. }
  84108. me.data.clear();
  84109. if (isLoad !== true || me.clearRemovedOnLoad) {
  84110. me.removed.length = 0;
  84111. }
  84112. },
  84113. loadToPrefetch: function(options) {
  84114. var me = this,
  84115. i,
  84116. records,
  84117. // Get the requested record index range in the dataset
  84118. startIdx = options.start,
  84119. endIdx = options.start + options.limit - 1,
  84120. // The end index to load into the store's live record collection
  84121. loadEndIdx = options.start + (me.viewSize || options.limit) - 1,
  84122. // Calculate a page range which encompasses the requested range plus both buffer zones
  84123. startPage = me.getPageFromRecordIndex(Math.max(startIdx - me.trailingBufferZone, 0)),
  84124. endPage = me.getPageFromRecordIndex(endIdx + me.leadingBufferZone),
  84125. // Wait for the viewable range to be available
  84126. waitForRequestedRange = function() {
  84127. if (me.rangeCached(startIdx, loadEndIdx)) {
  84128. me.loading = false;
  84129. records = me.pageMap.getRange(startIdx, loadEndIdx);
  84130. me.pageMap.un('pageAdded', waitForRequestedRange);
  84131. // If there is a listener for guranteedrange (PagingScroller uses this), then go through that event
  84132. if (me.hasListeners.guaranteedrange) {
  84133. me.guaranteeRange(startIdx, loadEndIdx, options.callback, options.scope);
  84134. }
  84135. // Otherwise load the records directly
  84136. else {
  84137. me.loadRecords(records, {
  84138. start: startIdx
  84139. });
  84140. }
  84141. me.fireEvent('load', me, records, true);
  84142. if (options.groupChange) {
  84143. me.fireGroupChange();
  84144. }
  84145. }
  84146. };
  84147. if (me.fireEvent('beforeload', me, options) !== false) {
  84148. // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
  84149. delete me.totalCount;
  84150. me.loading = true;
  84151. // Wait for the requested range to become available in the page map
  84152. me.pageMap.on('pageAdded', waitForRequestedRange);
  84153. // Load the first page in the range, which will give us the initial total count.
  84154. // Once it is loaded, go ahead and prefetch any subsequent pages, if necessary.
  84155. // The prefetchPage has a check to prevent us loading more than the totalCount,
  84156. // so we don't want to blindly load up <n> pages where it isn't required.
  84157. me.on('prefetch', function(){
  84158. for (i = startPage + 1; i <= endPage; ++i) {
  84159. me.prefetchPage(i, options);
  84160. }
  84161. }, null, {single: true});
  84162. me.prefetchPage(startPage, options);
  84163. }
  84164. },
  84165. // Buffering
  84166. /**
  84167. * Prefetches data into the store using its configured {@link #proxy}.
  84168. * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
  84169. * See {@link #method-load}
  84170. */
  84171. prefetch: function(options) {
  84172. var me = this,
  84173. pageSize = me.pageSize,
  84174. proxy,
  84175. operation;
  84176. // Check pageSize has not been tampered with. That would break page caching
  84177. if (pageSize) {
  84178. if (me.lastPageSize && pageSize != me.lastPageSize) {
  84179. Ext.error.raise("pageSize cannot be dynamically altered");
  84180. }
  84181. if (!me.pageMap.pageSize) {
  84182. me.pageMap.pageSize = pageSize;
  84183. }
  84184. }
  84185. // Allow first prefetch call to imply the required page size.
  84186. else {
  84187. me.pageSize = me.pageMap.pageSize = pageSize = options.limit;
  84188. }
  84189. // So that we can check for tampering next time through
  84190. me.lastPageSize = pageSize;
  84191. // Always get whole pages.
  84192. if (!options.page) {
  84193. options.page = me.getPageFromRecordIndex(options.start);
  84194. options.start = (options.page - 1) * pageSize;
  84195. options.limit = Math.ceil(options.limit / pageSize) * pageSize;
  84196. }
  84197. // Currently not requesting this page, then request it...
  84198. if (!me.pageRequests[options.page]) {
  84199. // Copy options into a new object so as not to mutate passed in objects
  84200. options = Ext.apply({
  84201. action : 'read',
  84202. filters: me.filters.items,
  84203. sorters: me.sorters.items,
  84204. groupers: me.groupers.items,
  84205. // Generation # of the page map to which the requested records belong.
  84206. // If page map is cleared while this request is in flight, the generation will increment and the payload will be rejected
  84207. generation: me.pageMap.generation
  84208. }, options);
  84209. operation = new Ext.data.Operation(options);
  84210. if (me.fireEvent('beforeprefetch', me, operation) !== false) {
  84211. me.loading = true;
  84212. proxy = me.proxy;
  84213. me.pageRequests[options.page] = proxy.read(operation, me.onProxyPrefetch, me);
  84214. if (proxy.isSynchronous) {
  84215. delete me.pageRequests[options.page];
  84216. }
  84217. }
  84218. }
  84219. return me;
  84220. },
  84221. /**
  84222. * @private
  84223. * Cancels all pending prefetch requests.
  84224. *
  84225. * This is called when the page map is cleared.
  84226. *
  84227. * Any requests which still make it through will be for the previous page map generation
  84228. * (generation is incremented upon clear), and so will be rejected upon arrival.
  84229. */
  84230. cancelAllPrefetches: function() {
  84231. var me = this,
  84232. reqs = me.pageRequests,
  84233. req,
  84234. page;
  84235. // If any requests return, we no longer respond to them.
  84236. if (me.pageMap.events.pageadded) {
  84237. me.pageMap.events.pageadded.clearListeners();
  84238. }
  84239. // Cancel all outstanding requests
  84240. for (page in reqs) {
  84241. if (reqs.hasOwnProperty(page)) {
  84242. req = reqs[page];
  84243. delete reqs[page];
  84244. delete req.callback;
  84245. }
  84246. }
  84247. },
  84248. /**
  84249. * Prefetches a page of data.
  84250. * @param {Number} page The page to prefetch
  84251. * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
  84252. * See {@link #method-load}
  84253. */
  84254. prefetchPage: function(page, options) {
  84255. var me = this,
  84256. pageSize = me.pageSize || me.defaultPageSize,
  84257. start = (page - 1) * me.pageSize,
  84258. total = me.totalCount;
  84259. // No more data to prefetch.
  84260. if (total !== undefined && me.getCount() === total) {
  84261. return;
  84262. }
  84263. // Copy options into a new object so as not to mutate passed in objects
  84264. me.prefetch(Ext.applyIf({
  84265. page : page,
  84266. start : start,
  84267. limit : pageSize
  84268. }, options));
  84269. },
  84270. /**
  84271. * Called after the configured proxy completes a prefetch operation.
  84272. * @private
  84273. * @param {Ext.data.Operation} operation The operation that completed
  84274. */
  84275. onProxyPrefetch: function(operation) {
  84276. var me = this,
  84277. resultSet = operation.getResultSet(),
  84278. records = operation.getRecords(),
  84279. successful = operation.wasSuccessful(),
  84280. page = operation.page;
  84281. // Only cache the data if the operation was invoked for the current generation of the page map.
  84282. // If the generation has changed since the request was fired off, it will have been cancelled.
  84283. if (operation.generation === me.pageMap.generation) {
  84284. if (resultSet) {
  84285. me.totalCount = resultSet.total;
  84286. me.fireEvent('totalcountchange', me.totalCount);
  84287. }
  84288. // Remove the loaded page from the outstanding pages hash
  84289. if (page !== undefined) {
  84290. delete me.pageRequests[page];
  84291. }
  84292. // Add the page into the page map.
  84293. // pageAdded event may trigger the onGuaranteedRange
  84294. if (successful) {
  84295. me.cachePage(records, operation.page);
  84296. }
  84297. me.loading = false;
  84298. me.fireEvent('prefetch', me, records, successful, operation);
  84299. //this is a callback that would have been passed to the 'read' function and is optional
  84300. Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
  84301. }
  84302. },
  84303. /**
  84304. * Caches the records in the prefetch and stripes them with their server-side
  84305. * index.
  84306. * @private
  84307. * @param {Ext.data.Model[]} records The records to cache
  84308. * @param {Ext.data.Operation} The associated operation
  84309. */
  84310. cachePage: function(records, page) {
  84311. var me = this;
  84312. if (!Ext.isDefined(me.totalCount)) {
  84313. me.totalCount = records.length;
  84314. me.fireEvent('totalcountchange', me.totalCount);
  84315. }
  84316. // Add the fetched page into the pageCache
  84317. me.pageMap.addPage(page, records);
  84318. },
  84319. /**
  84320. * Determines if the passed range is available in the page cache.
  84321. * @private
  84322. * @param {Number} start The start index
  84323. * @param {Number} end The end index in the range
  84324. */
  84325. rangeCached: function(start, end) {
  84326. return this.pageMap && this.pageMap.hasRange(start, end);
  84327. },
  84328. /**
  84329. * Determines if the passed page is available in the page cache.
  84330. * @private
  84331. * @param {Number} page The page to find in the page cache.
  84332. */
  84333. pageCached: function(page) {
  84334. return this.pageMap && this.pageMap.hasPage(page);
  84335. },
  84336. /**
  84337. * Determines if the passed range is available in the page cache.
  84338. * @private
  84339. * @deprecated 4.1.0 use {@link #rangeCached} instead
  84340. * @param {Number} start The start index
  84341. * @param {Number} end The end index in the range
  84342. */
  84343. rangeSatisfied: function(start, end) {
  84344. return this.rangeCached(start, end);
  84345. },
  84346. /**
  84347. * Determines the page from a record index
  84348. * @param {Number} index The record index
  84349. * @return {Number} The page the record belongs to
  84350. */
  84351. getPageFromRecordIndex: function(index) {
  84352. return Math.floor(index / this.pageSize) + 1;
  84353. },
  84354. /**
  84355. * Handles a guaranteed range being loaded
  84356. * @private
  84357. */
  84358. onGuaranteedRange: function(options) {
  84359. var me = this,
  84360. totalCount = me.getTotalCount(),
  84361. start = options.prefetchStart,
  84362. end = ((totalCount - 1) < options.prefetchEnd) ? totalCount - 1 : options.prefetchEnd,
  84363. range;
  84364. end = Math.max(0, end);
  84365. if (start > end) {
  84366. Ext.log({
  84367. level: 'warn',
  84368. msg: 'Start (' + start + ') was greater than end (' + end +
  84369. ') for the range of records requested (' + start + '-' +
  84370. options.prefetchEnd + ')' + (this.storeId ? ' from store "' + this.storeId + '"' : '')
  84371. });
  84372. }
  84373. range = me.pageMap.getRange(start, end);
  84374. me.fireEvent('guaranteedrange', range, start, end);
  84375. if (options.cb) {
  84376. options.cb.call(options.scope || me, range, start, end);
  84377. }
  84378. },
  84379. /**
  84380. * Ensures that the specified range of rows is present in the cache.
  84381. *
  84382. * Converts the row range to a page range and then only load pages which are not already
  84383. * present in the page cache.
  84384. */
  84385. prefetchRange: function(start, end) {
  84386. var me = this,
  84387. startPage, endPage, page;
  84388. if (!me.rangeCached(start, end)) {
  84389. startPage = me.getPageFromRecordIndex(start);
  84390. endPage = me.getPageFromRecordIndex(end);
  84391. // Ensure that the page cache's max size is correct.
  84392. // Our purgePageCount is the number of additional pages *outside of the required range* which
  84393. // may be kept in the cache. A purgePageCount of zero means unlimited.
  84394. me.pageMap.maxSize = me.purgePageCount ? (endPage - startPage + 1) + me.purgePageCount : 0;
  84395. // We have the range, but ensure that we have a "buffer" of pages around it.
  84396. for (page = startPage; page <= endPage; page++) {
  84397. if (!me.pageCached(page)) {
  84398. me.prefetchPage(page);
  84399. }
  84400. }
  84401. }
  84402. },
  84403. /**
  84404. * Guarantee a specific range, this will load the store with a range (that
  84405. * must be the pageSize or smaller) and take care of any loading that may
  84406. * be necessary.
  84407. */
  84408. guaranteeRange: function(start, end, cb, scope) {
  84409. // Sanity check end point to be within dataset range
  84410. end = (end > this.totalCount) ? this.totalCount - 1 : end;
  84411. var me = this,
  84412. lastRequestStart = me.lastRequestStart,
  84413. options = {
  84414. prefetchStart: start,
  84415. prefetchEnd: end,
  84416. cb: cb,
  84417. scope: scope
  84418. },
  84419. pageAddHandler;
  84420. me.lastRequestStart = start;
  84421. // If data request can be satisfied from the page cache
  84422. if (me.rangeCached(start, end)) {
  84423. // Attempt to keep the page cache primed with pages which encompass the live data range
  84424. if (start < lastRequestStart) {
  84425. start = Math.max(start - me.leadingBufferZone, 0);
  84426. end = Math.min(end + me.trailingBufferZone, me.totalCount - 1);
  84427. } else {
  84428. start = Math.max(Math.min(start - me.trailingBufferZone, me.totalCount - me.pageSize), 0);
  84429. end = Math.min(end + me.leadingBufferZone, me.totalCount - 1);
  84430. }
  84431. // If the prefetch window calculated round the requested range is not already satisfied in the page cache,
  84432. // then arrange to prefetch it.
  84433. if (!me.rangeCached(start, end)) {
  84434. // We have the range, but ensure that we have a "buffer" of pages around it.
  84435. me.prefetchRange(start, end);
  84436. }
  84437. me.onGuaranteedRange(options);
  84438. }
  84439. // At least some of the requested range needs loading from server
  84440. else {
  84441. // Private event used by the LoadMask class to perform masking when the range required for rendering is not found in the cache
  84442. me.fireEvent('cachemiss', me, start, end);
  84443. // Calculate a prefetch range which is centered on the requested data
  84444. start = Math.min(Math.max(Math.floor(start - ((me.leadingBufferZone + me.trailingBufferZone) / 2)), 0), me.totalCount - me.pageSize);
  84445. end = Math.min(Math.max(Math.ceil (end + ((me.leadingBufferZone + me.trailingBufferZone) / 2)), 0), me.totalCount - 1);
  84446. // Add a pageAdded listener, and as soon as the requested range is loaded, fire the guaranteedrange event
  84447. pageAddHandler = function(page, records) {
  84448. if (me.rangeCached(options.prefetchStart, options.prefetchEnd)) {
  84449. // Private event used by the LoadMask class to unmask when the range required for rendering has been loaded into the cache
  84450. me.fireEvent('cachefilled', me, start, end);
  84451. me.pageMap.un('pageAdded', pageAddHandler);
  84452. me.onGuaranteedRange(options);
  84453. }
  84454. };
  84455. me.pageMap.on('pageAdded', pageAddHandler);
  84456. // Prioritize the request for the *exact range that the UI is asking for*.
  84457. // When a page request is in flight, it will not be requested again by checking the me.pageRequests hash,
  84458. // so the request after this will only request the *remaining* unrequested pages .
  84459. me.prefetchRange(options.prefetchStart, options.prefetchEnd);
  84460. // Load the pages that need loading.
  84461. me.prefetchRange(start, end);
  84462. }
  84463. },
  84464. // because prefetchData is stored by index
  84465. // this invalidates all of the prefetchedData
  84466. sort: function() {
  84467. var me = this,
  84468. prefetchData = me.pageMap;
  84469. if (me.buffered) {
  84470. if (me.remoteSort) {
  84471. prefetchData.clear();
  84472. me.callParent(arguments);
  84473. } else {
  84474. me.callParent(arguments);
  84475. }
  84476. } else {
  84477. me.callParent(arguments);
  84478. }
  84479. },
  84480. // overriden to provide striping of the indexes as sorting occurs.
  84481. // this cannot be done inside of sort because datachanged has already
  84482. // fired and will trigger a repaint of the bound view.
  84483. doSort: function(sorterFn) {
  84484. var me = this,
  84485. range,
  84486. ln,
  84487. i;
  84488. if (me.remoteSort) {
  84489. // For a buffered Store, we have to clear the prefetch cache since it is keyed by the index within the dataset.
  84490. // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
  84491. // via the guaranteedrange event
  84492. if (me.buffered) {
  84493. me.pageMap.clear();
  84494. me.loadPage(1);
  84495. } else {
  84496. //the load function will pick up the new sorters and request the sorted data from the proxy
  84497. me.load();
  84498. }
  84499. } else {
  84500. me.data.sortBy(sorterFn);
  84501. if (!me.buffered) {
  84502. range = me.getRange();
  84503. ln = range.length;
  84504. for (i = 0; i < ln; i++) {
  84505. range[i].index = i;
  84506. }
  84507. }
  84508. me.fireEvent('datachanged', me);
  84509. me.fireEvent('refresh', me);
  84510. }
  84511. },
  84512. /**
  84513. * Finds the index of the first matching Record in this store by a specific field value.
  84514. *
  84515. * When store is filtered, finds records only within filter.
  84516. *
  84517. * @param {String} fieldName The name of the Record field to test.
  84518. * @param {String/RegExp} value Either a string that the field value
  84519. * should begin with, or a RegExp to test against the field.
  84520. * @param {Number} [startIndex=0] The index to start searching at
  84521. * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
  84522. * @param {Boolean} [caseSensitive=false] True for case sensitive comparison
  84523. * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
  84524. * @return {Number} The matched index or -1
  84525. */
  84526. find: function(property, value, start, anyMatch, caseSensitive, exactMatch) {
  84527. var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
  84528. return fn ? this.data.findIndexBy(fn, null, start) : -1;
  84529. },
  84530. /**
  84531. * Finds the first matching Record in this store by a specific field value.
  84532. *
  84533. * When store is filtered, finds records only within filter.
  84534. *
  84535. * @param {String} fieldName The name of the Record field to test.
  84536. * @param {String/RegExp} value Either a string that the field value
  84537. * should begin with, or a RegExp to test against the field.
  84538. * @param {Number} [startIndex=0] The index to start searching at
  84539. * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
  84540. * @param {Boolean} [caseSensitive=false] True for case sensitive comparison
  84541. * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
  84542. * @return {Ext.data.Model} The matched record or null
  84543. */
  84544. findRecord: function() {
  84545. var me = this,
  84546. index = me.find.apply(me, arguments);
  84547. return index !== -1 ? me.getAt(index) : null;
  84548. },
  84549. /**
  84550. * @private
  84551. * Returns a filter function used to test a the given property's value. Defers most of the work to
  84552. * Ext.util.MixedCollection's createValueMatcher function.
  84553. *
  84554. * @param {String} property The property to create the filter function for
  84555. * @param {String/RegExp} value The string/regex to compare the property value to
  84556. * @param {Boolean} [anyMatch=false] True if we don't care if the filter value is not the full value.
  84557. * @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
  84558. * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
  84559. * Ignored if anyMatch is true.
  84560. */
  84561. createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
  84562. if (Ext.isEmpty(value)) {
  84563. return false;
  84564. }
  84565. value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
  84566. return function(r) {
  84567. return value.test(r.data[property]);
  84568. };
  84569. },
  84570. /**
  84571. * Finds the index of the first matching Record in this store by a specific field value.
  84572. *
  84573. * When store is filtered, finds records only within filter.
  84574. *
  84575. * @param {String} fieldName The name of the Record field to test.
  84576. * @param {Object} value The value to match the field against.
  84577. * @param {Number} [startIndex=0] The index to start searching at
  84578. * @return {Number} The matched index or -1
  84579. */
  84580. findExact: function(property, value, start) {
  84581. return this.data.findIndexBy(function(rec) {
  84582. return rec.isEqual(rec.get(property), value);
  84583. },
  84584. this, start);
  84585. },
  84586. /**
  84587. * Find the index of the first matching Record in this Store by a function.
  84588. * If the function returns `true` it is considered a match.
  84589. *
  84590. * When store is filtered, finds records only within filter.
  84591. *
  84592. * @param {Function} fn The function to be called. It will be passed the following parameters:
  84593. * @param {Ext.data.Model} fn.record The record to test for filtering. Access field values
  84594. * using {@link Ext.data.Model#get}.
  84595. * @param {Object} fn.id The ID of the Record passed.
  84596. * @param {Object} [scope] The scope (this reference) in which the function is executed.
  84597. * Defaults to this Store.
  84598. * @param {Number} [startIndex=0] The index to start searching at
  84599. * @return {Number} The matched index or -1
  84600. */
  84601. findBy: function(fn, scope, start) {
  84602. return this.data.findIndexBy(fn, scope, start);
  84603. },
  84604. /**
  84605. * Collects unique values for a particular dataIndex from this store.
  84606. *
  84607. * @param {String} dataIndex The property to collect
  84608. * @param {Boolean} [allowNull] Pass true to allow null, undefined or empty string values
  84609. * @param {Boolean} [bypassFilter] Pass true to collect from all records, even ones which are filtered.
  84610. * @return {Object[]} An array of the unique values
  84611. */
  84612. collect: function(dataIndex, allowNull, bypassFilter) {
  84613. var me = this,
  84614. data = (bypassFilter === true && me.snapshot) ? me.snapshot : me.data;
  84615. return data.collect(dataIndex, 'data', allowNull);
  84616. },
  84617. /**
  84618. * Gets the number of records in store.
  84619. *
  84620. * If using paging, this may not be the total size of the dataset. If the data object
  84621. * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
  84622. * the dataset size. **Note**: see the Important note in {@link #method-load}.
  84623. *
  84624. * When store is filtered, it's the number of records matching the filter.
  84625. *
  84626. * @return {Number} The number of Records in the Store.
  84627. */
  84628. getCount: function() {
  84629. return this.data.length || 0;
  84630. },
  84631. /**
  84632. * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
  84633. * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
  84634. * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
  84635. * could be loaded into the Store if the Store contained all data
  84636. * @return {Number} The total number of Model instances available via the Proxy. 0 returned if
  84637. * no value has been set via the reader.
  84638. */
  84639. getTotalCount: function() {
  84640. return this.totalCount || 0;
  84641. },
  84642. /**
  84643. * Get the Record at the specified index.
  84644. *
  84645. * The index is effected by filtering.
  84646. *
  84647. * @param {Number} index The index of the Record to find.
  84648. * @return {Ext.data.Model} The Record at the passed index. Returns undefined if not found.
  84649. */
  84650. getAt: function(index) {
  84651. return this.data.getAt(index);
  84652. },
  84653. /**
  84654. * Returns a range of Records between specified indices.
  84655. *
  84656. * This method is effected by filtering.
  84657. *
  84658. * @param {Number} [startIndex=0] The starting index
  84659. * @param {Number} [endIndex] The ending index. Defaults to the last Record in the Store.
  84660. * @return {Ext.data.Model[]} An array of Records
  84661. */
  84662. getRange: function(start, end) {
  84663. return this.data.getRange(start, end);
  84664. },
  84665. /**
  84666. * Get the Record with the specified id.
  84667. *
  84668. * This method is not effected by filtering, lookup will be performed from all records
  84669. * inside the store, filtered or not.
  84670. *
  84671. * @param {Mixed} id The id of the Record to find.
  84672. * @return {Ext.data.Model} The Record with the passed id. Returns null if not found.
  84673. */
  84674. getById: function(id) {
  84675. return (this.snapshot || this.data).findBy(function(record) {
  84676. return record.getId() === id;
  84677. });
  84678. },
  84679. /**
  84680. * Get the index of the record within the store.
  84681. *
  84682. * When store is filtered, records outside of filter will not be found.
  84683. *
  84684. * @param {Ext.data.Model} record The Ext.data.Model object to find.
  84685. * @return {Number} The index of the passed Record. Returns -1 if not found.
  84686. */
  84687. indexOf: function(record) {
  84688. return this.data.indexOf(record);
  84689. },
  84690. /**
  84691. * Get the index within the entire dataset. From 0 to the totalCount.
  84692. *
  84693. * Like #indexOf, this method is effected by filtering.
  84694. *
  84695. * @param {Ext.data.Model} record The Ext.data.Model object to find.
  84696. * @return {Number} The index of the passed Record. Returns -1 if not found.
  84697. */
  84698. indexOfTotal: function(record) {
  84699. var index = record.index;
  84700. if (index || index === 0) {
  84701. return index;
  84702. }
  84703. return this.indexOf(record);
  84704. },
  84705. /**
  84706. * Get the index within the store of the Record with the passed id.
  84707. *
  84708. * Like #indexOf, this method is effected by filtering.
  84709. *
  84710. * @param {String} id The id of the Record to find.
  84711. * @return {Number} The index of the Record. Returns -1 if not found.
  84712. */
  84713. indexOfId: function(id) {
  84714. return this.indexOf(this.getById(id));
  84715. },
  84716. /**
  84717. * Removes all items from the store.
  84718. * @param {Boolean} silent Prevent the `clear` event from being fired.
  84719. */
  84720. removeAll: function(silent) {
  84721. var me = this;
  84722. me.clearData();
  84723. if (me.snapshot) {
  84724. me.snapshot.clear();
  84725. }
  84726. // Special handling to synch the PageMap only for removeAll
  84727. // TODO: handle other store/data modifications WRT buffered Stores.
  84728. if (me.pageMap) {
  84729. me.pageMap.clear();
  84730. }
  84731. if (silent !== true) {
  84732. me.fireEvent('clear', me);
  84733. }
  84734. },
  84735. /*
  84736. * Aggregation methods
  84737. */
  84738. /**
  84739. * Convenience function for getting the first model instance in the store.
  84740. *
  84741. * When store is filtered, will return first item within the filter.
  84742. *
  84743. * @param {Boolean} [grouped] True to perform the operation for each group
  84744. * in the store. The value returned will be an object literal with the key being the group
  84745. * name and the first record being the value. The grouped parameter is only honored if
  84746. * the store has a groupField.
  84747. * @return {Ext.data.Model/undefined} The first model instance in the store, or undefined
  84748. */
  84749. first: function(grouped) {
  84750. var me = this;
  84751. if (grouped && me.isGrouped()) {
  84752. return me.aggregate(function(records) {
  84753. return records.length ? records[0] : undefined;
  84754. }, me, true);
  84755. } else {
  84756. return me.data.first();
  84757. }
  84758. },
  84759. /**
  84760. * Convenience function for getting the last model instance in the store.
  84761. *
  84762. * When store is filtered, will return last item within the filter.
  84763. *
  84764. * @param {Boolean} [grouped] True to perform the operation for each group
  84765. * in the store. The value returned will be an object literal with the key being the group
  84766. * name and the last record being the value. The grouped parameter is only honored if
  84767. * the store has a groupField.
  84768. * @return {Ext.data.Model/undefined} The last model instance in the store, or undefined
  84769. */
  84770. last: function(grouped) {
  84771. var me = this;
  84772. if (grouped && me.isGrouped()) {
  84773. return me.aggregate(function(records) {
  84774. var len = records.length;
  84775. return len ? records[len - 1] : undefined;
  84776. }, me, true);
  84777. } else {
  84778. return me.data.last();
  84779. }
  84780. },
  84781. /**
  84782. * Sums the value of `property` for each {@link Ext.data.Model record} between `start`
  84783. * and `end` and returns the result.
  84784. *
  84785. * When store is filtered, only sums items within the filter.
  84786. *
  84787. * @param {String} field A field in each record
  84788. * @param {Boolean} [grouped] True to perform the operation for each group
  84789. * in the store. The value returned will be an object literal with the key being the group
  84790. * name and the sum for that group being the value. The grouped parameter is only honored if
  84791. * the store has a groupField.
  84792. * @return {Number} The sum
  84793. */
  84794. sum: function(field, grouped) {
  84795. var me = this;
  84796. if (grouped && me.isGrouped()) {
  84797. return me.aggregate(me.getSum, me, true, [field]);
  84798. } else {
  84799. return me.getSum(me.data.items, field);
  84800. }
  84801. },
  84802. // @private, see sum
  84803. getSum: function(records, field) {
  84804. var total = 0,
  84805. i = 0,
  84806. len = records.length;
  84807. for (; i < len; ++i) {
  84808. total += records[i].get(field);
  84809. }
  84810. return total;
  84811. },
  84812. /**
  84813. * Gets the count of items in the store.
  84814. *
  84815. * When store is filtered, only items within the filter are counted.
  84816. *
  84817. * @param {Boolean} [grouped] True to perform the operation for each group
  84818. * in the store. The value returned will be an object literal with the key being the group
  84819. * name and the count for each group being the value. The grouped parameter is only honored if
  84820. * the store has a groupField.
  84821. * @return {Number} the count
  84822. */
  84823. count: function(grouped) {
  84824. var me = this;
  84825. if (grouped && me.isGrouped()) {
  84826. return me.aggregate(function(records) {
  84827. return records.length;
  84828. }, me, true);
  84829. } else {
  84830. return me.getCount();
  84831. }
  84832. },
  84833. /**
  84834. * Gets the minimum value in the store.
  84835. *
  84836. * When store is filtered, only items within the filter are aggregated.
  84837. *
  84838. * @param {String} field The field in each record
  84839. * @param {Boolean} [grouped] True to perform the operation for each group
  84840. * in the store. The value returned will be an object literal with the key being the group
  84841. * name and the minimum in the group being the value. The grouped parameter is only honored if
  84842. * the store has a groupField.
  84843. * @return {Object} The minimum value, if no items exist, undefined.
  84844. */
  84845. min: function(field, grouped) {
  84846. var me = this;
  84847. if (grouped && me.isGrouped()) {
  84848. return me.aggregate(me.getMin, me, true, [field]);
  84849. } else {
  84850. return me.getMin(me.data.items, field);
  84851. }
  84852. },
  84853. // @private, see min
  84854. getMin: function(records, field) {
  84855. var i = 1,
  84856. len = records.length,
  84857. value, min;
  84858. if (len > 0) {
  84859. min = records[0].get(field);
  84860. }
  84861. for (; i < len; ++i) {
  84862. value = records[i].get(field);
  84863. if (value < min) {
  84864. min = value;
  84865. }
  84866. }
  84867. return min;
  84868. },
  84869. /**
  84870. * Gets the maximum value in the store.
  84871. *
  84872. * When store is filtered, only items within the filter are aggregated.
  84873. *
  84874. * @param {String} field The field in each record
  84875. * @param {Boolean} [grouped] True to perform the operation for each group
  84876. * in the store. The value returned will be an object literal with the key being the group
  84877. * name and the maximum in the group being the value. The grouped parameter is only honored if
  84878. * the store has a groupField.
  84879. * @return {Object} The maximum value, if no items exist, undefined.
  84880. */
  84881. max: function(field, grouped) {
  84882. var me = this;
  84883. if (grouped && me.isGrouped()) {
  84884. return me.aggregate(me.getMax, me, true, [field]);
  84885. } else {
  84886. return me.getMax(me.data.items, field);
  84887. }
  84888. },
  84889. // @private, see max
  84890. getMax: function(records, field) {
  84891. var i = 1,
  84892. len = records.length,
  84893. value,
  84894. max;
  84895. if (len > 0) {
  84896. max = records[0].get(field);
  84897. }
  84898. for (; i < len; ++i) {
  84899. value = records[i].get(field);
  84900. if (value > max) {
  84901. max = value;
  84902. }
  84903. }
  84904. return max;
  84905. },
  84906. /**
  84907. * Gets the average value in the store.
  84908. *
  84909. * When store is filtered, only items within the filter are aggregated.
  84910. *
  84911. * @param {String} field The field in each record
  84912. * @param {Boolean} [grouped] True to perform the operation for each group
  84913. * in the store. The value returned will be an object literal with the key being the group
  84914. * name and the group average being the value. The grouped parameter is only honored if
  84915. * the store has a groupField.
  84916. * @return {Object} The average value, if no items exist, 0.
  84917. */
  84918. average: function(field, grouped) {
  84919. var me = this;
  84920. if (grouped && me.isGrouped()) {
  84921. return me.aggregate(me.getAverage, me, true, [field]);
  84922. } else {
  84923. return me.getAverage(me.data.items, field);
  84924. }
  84925. },
  84926. // @private, see average
  84927. getAverage: function(records, field) {
  84928. var i = 0,
  84929. len = records.length,
  84930. sum = 0;
  84931. if (records.length > 0) {
  84932. for (; i < len; ++i) {
  84933. sum += records[i].get(field);
  84934. }
  84935. return sum / len;
  84936. }
  84937. return 0;
  84938. },
  84939. /**
  84940. * Runs the aggregate function for all the records in the store.
  84941. *
  84942. * When store is filtered, only items within the filter are aggregated.
  84943. *
  84944. * @param {Function} fn The function to execute. The function is called with a single parameter,
  84945. * an array of records for that group.
  84946. * @param {Object} [scope] The scope to execute the function in. Defaults to the store.
  84947. * @param {Boolean} [grouped] True to perform the operation for each group
  84948. * in the store. The value returned will be an object literal with the key being the group
  84949. * name and the group average being the value. The grouped parameter is only honored if
  84950. * the store has a groupField.
  84951. * @param {Array} [args] Any arguments to append to the function call
  84952. * @return {Object} An object literal with the group names and their appropriate values.
  84953. */
  84954. aggregate: function(fn, scope, grouped, args) {
  84955. args = args || [];
  84956. if (grouped && this.isGrouped()) {
  84957. var groups = this.getGroups(),
  84958. i = 0,
  84959. len = groups.length,
  84960. out = {},
  84961. group;
  84962. for (; i < len; ++i) {
  84963. group = groups[i];
  84964. out[group.name] = fn.apply(scope || this, [group.children].concat(args));
  84965. }
  84966. return out;
  84967. } else {
  84968. return fn.apply(scope || this, [this.data.items].concat(args));
  84969. }
  84970. },
  84971. /**
  84972. * Commits all Records with {@link #getModifiedRecords outstanding changes}. To handle updates for changes,
  84973. * subscribe to the Store's {@link #event-update update event}, and perform updating when the third parameter is
  84974. * Ext.data.Record.COMMIT.
  84975. */
  84976. commitChanges : function(){
  84977. var me = this,
  84978. recs = me.getModifiedRecords(),
  84979. len = recs.length,
  84980. i = 0;
  84981. for (; i < len; i++){
  84982. recs[i].commit();
  84983. }
  84984. // Since removals are cached in a simple array we can simply reset it here.
  84985. // Adds and updates are managed in the data MixedCollection and should already be current.
  84986. me.removed.length = 0;
  84987. },
  84988. filterNewOnly: function(item){
  84989. return item.phantom === true;
  84990. },
  84991. // Ideally in the future this will use getModifiedRecords, where there will be a param
  84992. // to getNewRecords & getUpdatedRecords to indicate whether to get only the valid
  84993. // records or grab all of them
  84994. getRejectRecords: function() {
  84995. // Return phantom records + updated records
  84996. return Ext.Array.push(this.data.filterBy(this.filterNewOnly).items, this.getUpdatedRecords());
  84997. },
  84998. /**
  84999. * {@link Ext.data.Model#reject Rejects} outstanding changes on all {@link #getModifiedRecords modified records}
  85000. * and re-insert any records that were removed locally. Any phantom records will be removed.
  85001. */
  85002. rejectChanges : function() {
  85003. var me = this,
  85004. recs = me.getRejectRecords(),
  85005. len = recs.length,
  85006. i = 0,
  85007. rec;
  85008. for (; i < len; i++) {
  85009. rec = recs[i];
  85010. rec.reject();
  85011. if (rec.phantom) {
  85012. me.remove(rec);
  85013. }
  85014. }
  85015. // Restore removed records back to their original positions
  85016. recs = me.removed;
  85017. len = recs.length;
  85018. for (i = 0; i < len; i++) {
  85019. rec = recs[i];
  85020. me.insert(rec.removedFrom || 0, rec);
  85021. rec.reject();
  85022. }
  85023. // Since removals are cached in a simple array we can simply reset it here.
  85024. // Adds and updates are managed in the data MixedCollection and should already be current.
  85025. me.removed.length = 0;
  85026. }
  85027. }, function() {
  85028. // A dummy empty store with a fieldless Model defined in it.
  85029. // Just for binding to Views which are instantiated with no Store defined.
  85030. // They will be able to run and render fine, and be bound to a generated Store later.
  85031. Ext.regStore('ext-empty-store', {fields: [], proxy: 'memory'});
  85032. /**
  85033. * @class Ext.data.Store.PageMap
  85034. * @extends Ext.util.LruCache
  85035. * Private class for use by only Store when configured `buffered: true`.
  85036. * @private
  85037. */
  85038. this.prototype.PageMap = new Ext.Class({
  85039. extend: 'Ext.util.LruCache',
  85040. // Maintain a generation counter, so that the Store can reject incoming pages destined for the previous generation
  85041. clear: function(initial) {
  85042. this.generation = (this.generation ||0) + 1;
  85043. this.callParent(arguments);
  85044. },
  85045. getPageFromRecordIndex: this.prototype.getPageFromRecordIndex,
  85046. addPage: function(page, records) {
  85047. this.add(page, records);
  85048. this.fireEvent('pageAdded', page, records);
  85049. },
  85050. getPage: function(page) {
  85051. return this.get(page);
  85052. },
  85053. hasRange: function(start, end) {
  85054. var page = this.getPageFromRecordIndex(start),
  85055. endPage = this.getPageFromRecordIndex(end);
  85056. for (; page <= endPage; page++) {
  85057. if (!this.hasPage(page)) {
  85058. return false;
  85059. }
  85060. }
  85061. return true;
  85062. },
  85063. hasPage: function(page) {
  85064. // We must use this.get to trigger an access so that the page which is checked for presence is not eligible for pruning
  85065. return !!this.get(page);
  85066. },
  85067. getRange: function(start, end) {
  85068. if (!this.hasRange(start, end)) {
  85069. Ext.Error.raise('PageMap asked for range which it does not have');
  85070. }
  85071. var me = this,
  85072. startPage = me.getPageFromRecordIndex(start),
  85073. endPage = me.getPageFromRecordIndex(end),
  85074. dataStart = (startPage - 1) * me.pageSize,
  85075. dataEnd = (endPage * me.pageSize) - 1,
  85076. page = startPage,
  85077. result = [],
  85078. sliceBegin, sliceEnd, doSlice,
  85079. i = 0, len;
  85080. for (; page <= endPage; page++) {
  85081. // First and last pages will need slicing to cut into the actual wanted records
  85082. if (page == startPage) {
  85083. sliceBegin = start - dataStart;
  85084. doSlice = true;
  85085. } else {
  85086. sliceBegin = 0;
  85087. doSlice = false;
  85088. }
  85089. if (page == endPage) {
  85090. sliceEnd = me.pageSize - (dataEnd - end);
  85091. doSlice = true;
  85092. }
  85093. // First and last pages will need slicing
  85094. if (doSlice) {
  85095. Ext.Array.push(result, Ext.Array.slice(me.getPage(page), sliceBegin, sliceEnd));
  85096. } else {
  85097. Ext.Array.push(result, me.getPage(page));
  85098. }
  85099. }
  85100. // Inject the dataset ordinal position into the record as the index
  85101. for (len = result.length; i < len; i++) {
  85102. result[i].index = start++;
  85103. }
  85104. return result;
  85105. }
  85106. });
  85107. });
  85108. /**
  85109. * @author Ed Spencer
  85110. * @class Ext.data.reader.Array
  85111. *
  85112. * <p>Data reader class to create an Array of {@link Ext.data.Model} objects from an Array.
  85113. * Each element of that Array represents a row of data fields. The
  85114. * fields are pulled into a Record object using as a subscript, the <code>mapping</code> property
  85115. * of the field definition if it exists, or the field's ordinal position in the definition.</p>
  85116. *
  85117. * <p><u>Example code:</u></p>
  85118. *
  85119. <pre><code>
  85120. Employee = Ext.define('Employee', {
  85121. extend: 'Ext.data.Model',
  85122. fields: [
  85123. 'id',
  85124. {name: 'name', mapping: 1}, // "mapping" only needed if an "id" field is present which
  85125. {name: 'occupation', mapping: 2} // precludes using the ordinal position as the index.
  85126. ]
  85127. });
  85128. var myReader = new Ext.data.reader.Array({
  85129. model: 'Employee'
  85130. }, Employee);
  85131. </code></pre>
  85132. *
  85133. * <p>This would consume an Array like this:</p>
  85134. *
  85135. <pre><code>
  85136. [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
  85137. </code></pre>
  85138. *
  85139. * @constructor
  85140. * Create a new ArrayReader
  85141. * @param {Object} meta Metadata configuration options.
  85142. */
  85143. Ext.define('Ext.data.reader.Array', {
  85144. extend: 'Ext.data.reader.Json',
  85145. alternateClassName: 'Ext.data.ArrayReader',
  85146. alias : 'reader.array',
  85147. // For Array Reader, methods in the base which use these properties must not see the defaults
  85148. totalProperty: undefined,
  85149. successProperty: undefined,
  85150. /**
  85151. * @private
  85152. * Returns an accessor expression for the passed Field from an Array using either the Field's mapping, or
  85153. * its ordinal position in the fields collsction as the index.
  85154. * This is used by buildExtractors to create optimized on extractor function which converts raw data into model instances.
  85155. */
  85156. createFieldAccessExpression: function(field, fieldVarName, dataName) {
  85157. // In the absence of a mapping property, use the original ordinal position
  85158. // at which the Model inserted the field into its collection.
  85159. var index = (field.mapping == null) ? field.originalIndex : field.mapping,
  85160. result;
  85161. if (typeof index === 'function') {
  85162. result = fieldVarName + '.mapping(' + dataName + ', this)';
  85163. } else {
  85164. if (isNaN(index)) {
  85165. index = '"' + index + '"';
  85166. }
  85167. result = dataName + "[" + index + "]";
  85168. }
  85169. return result;
  85170. }
  85171. });
  85172. /**
  85173. * @author Ed Spencer
  85174. *
  85175. * Small helper class to make creating {@link Ext.data.Store}s from Array data easier. An ArrayStore will be
  85176. * automatically configured with a {@link Ext.data.reader.Array}.
  85177. *
  85178. * A store configuration would be something like:
  85179. *
  85180. * var store = Ext.create('Ext.data.ArrayStore', {
  85181. * // store configs
  85182. * storeId: 'myStore',
  85183. * // reader configs
  85184. * fields: [
  85185. * 'company',
  85186. * {name: 'price', type: 'float'},
  85187. * {name: 'change', type: 'float'},
  85188. * {name: 'pctChange', type: 'float'},
  85189. * {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
  85190. * ]
  85191. * });
  85192. *
  85193. * This store is configured to consume a returned object of the form:
  85194. *
  85195. * var myData = [
  85196. * ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
  85197. * ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
  85198. * ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
  85199. * ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
  85200. * ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
  85201. * ];
  85202. *
  85203. * An object literal of this form could also be used as the {@link #cfg-data} config option.
  85204. *
  85205. */
  85206. Ext.define('Ext.data.ArrayStore', {
  85207. extend: 'Ext.data.Store',
  85208. alias: 'store.array',
  85209. requires: [
  85210. 'Ext.data.proxy.Memory',
  85211. 'Ext.data.reader.Array'
  85212. ],
  85213. constructor: function(config) {
  85214. config = Ext.apply({
  85215. proxy: {
  85216. type: 'memory',
  85217. reader: 'array'
  85218. }
  85219. }, config);
  85220. this.callParent([config]);
  85221. },
  85222. loadData: function(data, append) {
  85223. if (this.expandData === true) {
  85224. var r = [],
  85225. i = 0,
  85226. ln = data.length;
  85227. for (; i < ln; i++) {
  85228. r[r.length] = [data[i]];
  85229. }
  85230. data = r;
  85231. }
  85232. this.callParent([data, append]);
  85233. }
  85234. }, function() {
  85235. // backwards compat
  85236. Ext.data.SimpleStore = Ext.data.ArrayStore;
  85237. // Ext.reg('simplestore', Ext.data.SimpleStore);
  85238. });
  85239. /**
  85240. * @author Ed Spencer
  85241. * @class Ext.data.Batch
  85242. *
  85243. * <p>Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the 'operationcomplete' event
  85244. * after the completion of each Operation, and the 'complete' event when all Operations have been successfully executed. Fires an 'exception'
  85245. * event if any of the Operations encounter an exception.</p>
  85246. *
  85247. * <p>Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes</p>
  85248. *
  85249. */
  85250. Ext.define('Ext.data.Batch', {
  85251. mixins: {
  85252. observable: 'Ext.util.Observable'
  85253. },
  85254. /**
  85255. * @cfg {Boolean} autoStart
  85256. * True to immediately start processing the batch as soon as it is constructed (defaults to false)
  85257. */
  85258. autoStart: false,
  85259. /**
  85260. * @cfg {Boolean} pauseOnException
  85261. * True to pause the execution of the batch if any operation encounters an exception
  85262. * (defaults to false). If you set this to true you are responsible for implementing the appropriate
  85263. * handling logic and restarting or discarding the batch as needed. There are different ways you could
  85264. * do this, e.g. by handling the batch's {@link #exception} event directly, or perhaps by overriding
  85265. * {@link Ext.data.AbstractStore#onBatchException onBatchException} at the store level. If you do pause
  85266. * and attempt to handle the exception you can call {@link #retry} to process the same operation again.
  85267. *
  85268. * Note that {@link Ext.data.Operation operations} are atomic, so any operations that may have succeeded
  85269. * prior to an exception (and up until pausing the batch) will be finalized at the server level and will
  85270. * not be automatically reversible. Any transactional / rollback behavior that might be desired would have
  85271. * to be implemented at the application level. Pausing on exception will likely be most beneficial when
  85272. * used in coordination with such a scheme, where an exception might actually affect subsequent operations
  85273. * in the same batch and so should be handled before continuing with the next operation.
  85274. *
  85275. * If you have not implemented transactional operation handling then this option should typically be left
  85276. * to the default of false (e.g. process as many operations as possible, and handle any exceptions
  85277. * asynchronously without holding up the rest of the batch).
  85278. */
  85279. pauseOnException: false,
  85280. /**
  85281. * @property {Number} current
  85282. * The index of the current operation being executed. Read only
  85283. */
  85284. current: -1,
  85285. /**
  85286. * @property {Number} total
  85287. * The total number of operations in this batch. Read only
  85288. */
  85289. total: 0,
  85290. /**
  85291. * @property {Boolean} isRunning
  85292. * True if the batch is currently running. Read only
  85293. */
  85294. isRunning: false,
  85295. /**
  85296. * @property {Boolean} isComplete
  85297. * True if this batch has been executed completely. Read only
  85298. */
  85299. isComplete: false,
  85300. /**
  85301. * @property {Boolean} hasException
  85302. * True if this batch has encountered an exception. This is cleared at the start of each operation. Read only
  85303. */
  85304. hasException: false,
  85305. /**
  85306. * Creates new Batch object.
  85307. * @param {Object} [config] Config object
  85308. */
  85309. constructor: function(config) {
  85310. var me = this;
  85311. /**
  85312. * @event complete
  85313. * Fired when all operations of this batch have been completed
  85314. * @param {Ext.data.Batch} batch The batch object
  85315. * @param {Object} operation The last operation that was executed
  85316. */
  85317. /**
  85318. * @event exception
  85319. * Fired when a operation encountered an exception
  85320. * @param {Ext.data.Batch} batch The batch object
  85321. * @param {Object} operation The operation that encountered the exception
  85322. */
  85323. /**
  85324. * @event operationcomplete
  85325. * Fired when each operation of the batch completes
  85326. * @param {Ext.data.Batch} batch The batch object
  85327. * @param {Object} operation The operation that just completed
  85328. */
  85329. me.mixins.observable.constructor.call(me, config);
  85330. /**
  85331. * Ordered array of operations that will be executed by this batch
  85332. * @property {Ext.data.Operation[]} operations
  85333. */
  85334. me.operations = [];
  85335. /**
  85336. * Ordered array of operations that raised an exception during the most recent
  85337. * batch execution and did not successfully complete
  85338. * @property {Ext.data.Operation[]} exceptions
  85339. */
  85340. me.exceptions = [];
  85341. },
  85342. /**
  85343. * Adds a new operation to this batch at the end of the {@link #operations} array
  85344. * @param {Object} operation The {@link Ext.data.Operation Operation} object
  85345. * @return {Ext.data.Batch} this
  85346. */
  85347. add: function(operation) {
  85348. this.total++;
  85349. operation.setBatch(this);
  85350. this.operations.push(operation);
  85351. return this;
  85352. },
  85353. /**
  85354. * Kicks off execution of the batch, continuing from the next operation if the previous
  85355. * operation encountered an exception, or if execution was paused. Use this method to start
  85356. * the batch for the first time or to restart a paused batch by skipping the current
  85357. * unsuccessful operation.
  85358. *
  85359. * To retry processing the current operation before continuing to the rest of the batch (e.g.
  85360. * because you explicitly handled the operation's exception), call {@link #retry} instead.
  85361. *
  85362. * Note that if the batch is already running any call to start will be ignored.
  85363. *
  85364. * @return {Ext.data.Batch} this
  85365. */
  85366. start: function(/* private */ index) {
  85367. var me = this;
  85368. if (me.isRunning) {
  85369. return me;
  85370. }
  85371. me.exceptions.length = 0;
  85372. me.hasException = false;
  85373. me.isRunning = true;
  85374. return me.runOperation(Ext.isDefined(index) ? index : me.current + 1);
  85375. },
  85376. /**
  85377. * Kicks off execution of the batch, continuing from the current operation. This is intended
  85378. * for restarting a {@link #pause paused} batch after an exception, and the operation that raised
  85379. * the exception will now be retried. The batch will then continue with its normal processing until
  85380. * all operations are complete or another exception is encountered.
  85381. *
  85382. * Note that if the batch is already running any call to retry will be ignored.
  85383. *
  85384. * @return {Ext.data.Batch} this
  85385. */
  85386. retry: function() {
  85387. return this.start(this.current);
  85388. },
  85389. /**
  85390. * @private
  85391. * Runs the next operation, relative to this.current.
  85392. * @return {Ext.data.Batch} this
  85393. */
  85394. runNextOperation: function() {
  85395. return this.runOperation(this.current + 1);
  85396. },
  85397. /**
  85398. * Pauses execution of the batch, but does not cancel the current operation
  85399. * @return {Ext.data.Batch} this
  85400. */
  85401. pause: function() {
  85402. this.isRunning = false;
  85403. return this;
  85404. },
  85405. /**
  85406. * Executes an operation by its numeric index in the {@link #operations} array
  85407. * @param {Number} index The operation index to run
  85408. * @return {Ext.data.Batch} this
  85409. */
  85410. runOperation: function(index) {
  85411. var me = this,
  85412. operations = me.operations,
  85413. operation = operations[index],
  85414. onProxyReturn;
  85415. if (operation === undefined) {
  85416. me.isRunning = false;
  85417. me.isComplete = true;
  85418. me.fireEvent('complete', me, operations[operations.length - 1]);
  85419. } else {
  85420. me.current = index;
  85421. onProxyReturn = function(operation) {
  85422. var hasException = operation.hasException();
  85423. if (hasException) {
  85424. me.hasException = true;
  85425. me.exceptions.push(operation);
  85426. me.fireEvent('exception', me, operation);
  85427. }
  85428. if (hasException && me.pauseOnException) {
  85429. me.pause();
  85430. } else {
  85431. operation.setCompleted();
  85432. me.fireEvent('operationcomplete', me, operation);
  85433. me.runNextOperation();
  85434. }
  85435. };
  85436. operation.setStarted();
  85437. me.proxy[operation.action](operation, onProxyReturn, me);
  85438. }
  85439. return me;
  85440. }
  85441. });
  85442. /**
  85443. * @private
  85444. */
  85445. Ext.define('Ext.data.BufferStore', {
  85446. extend: 'Ext.data.Store',
  85447. alias: 'store.buffer',
  85448. sortOnLoad: false,
  85449. filterOnLoad: false,
  85450. constructor: function() {
  85451. Ext.Error.raise('The BufferStore class has been deprecated. Instead, specify the buffered config option on Ext.data.Store');
  85452. }
  85453. });
  85454. /**
  85455. * Ext.Direct aims to streamline communication between the client and server by providing a single interface that
  85456. * reduces the amount of common code typically required to validate data and handle returned data packets (reading data,
  85457. * error conditions, etc).
  85458. *
  85459. * The Ext.direct namespace includes several classes for a closer integration with the server-side. The Ext.data
  85460. * namespace also includes classes for working with Ext.data.Stores which are backed by data from an Ext.Direct method.
  85461. *
  85462. * # Specification
  85463. *
  85464. * For additional information consult the [Ext.Direct Specification][1].
  85465. *
  85466. * # Providers
  85467. *
  85468. * Ext.Direct uses a provider architecture, where one or more providers are used to transport data to and from the
  85469. * server. There are several providers that exist in the core at the moment:
  85470. *
  85471. * - {@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations
  85472. * - {@link Ext.direct.PollingProvider PollingProvider} for repeated requests
  85473. * - {@link Ext.direct.RemotingProvider RemotingProvider} exposes server side on the client.
  85474. *
  85475. * A provider does not need to be invoked directly, providers are added via {@link Ext.direct.Manager}.{@link #addProvider}.
  85476. *
  85477. * # Router
  85478. *
  85479. * Ext.Direct utilizes a "router" on the server to direct requests from the client to the appropriate server-side
  85480. * method. Because the Ext.Direct API is completely platform-agnostic, you could completely swap out a Java based server
  85481. * solution and replace it with one that uses C# without changing the client side JavaScript at all.
  85482. *
  85483. * # Server side events
  85484. *
  85485. * Custom events from the server may be handled by the client by adding listeners, for example:
  85486. *
  85487. * {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
  85488. *
  85489. * // add a handler for a 'message' event sent by the server
  85490. * Ext.direct.Manager.on('message', function(e){
  85491. * out.append(String.format('<p><i>{0}</i></p>', e.data));
  85492. * out.el.scrollTo('t', 100000, true);
  85493. * });
  85494. *
  85495. * [1]: http://sencha.com/products/extjs/extdirect
  85496. *
  85497. * @singleton
  85498. * @alternateClassName Ext.Direct
  85499. */
  85500. Ext.define('Ext.direct.Manager', {
  85501. /* Begin Definitions */
  85502. singleton: true,
  85503. mixins: {
  85504. observable: 'Ext.util.Observable'
  85505. },
  85506. requires: ['Ext.util.MixedCollection'],
  85507. /**
  85508. * Exception types.
  85509. */
  85510. exceptions: {
  85511. TRANSPORT: 'xhr',
  85512. PARSE: 'parse',
  85513. LOGIN: 'login',
  85514. SERVER: 'exception'
  85515. },
  85516. /* End Definitions */
  85517. constructor: function(){
  85518. var me = this;
  85519. me.addEvents(
  85520. /**
  85521. * @event event
  85522. * Fires after an event.
  85523. * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
  85524. * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
  85525. */
  85526. 'event',
  85527. /**
  85528. * @event exception
  85529. * Fires after an event exception.
  85530. * @param {Ext.direct.Event} e The event type that occurred.
  85531. */
  85532. 'exception'
  85533. );
  85534. me.transactions = new Ext.util.MixedCollection();
  85535. me.providers = new Ext.util.MixedCollection();
  85536. me.mixins.observable.constructor.call(me);
  85537. },
  85538. /**
  85539. * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods. If the provider
  85540. * is not already connected, it will auto-connect.
  85541. *
  85542. * var pollProv = new Ext.direct.PollingProvider({
  85543. * url: 'php/poll2.php'
  85544. * });
  85545. *
  85546. * Ext.direct.Manager.addProvider({
  85547. * "type":"remoting", // create a {@link Ext.direct.RemotingProvider}
  85548. * "url":"php\/router.php", // url to connect to the Ext.Direct server-side router.
  85549. * "actions":{ // each property within the actions object represents a Class
  85550. * "TestAction":[ // array of methods within each server side Class
  85551. * {
  85552. * "name":"doEcho", // name of method
  85553. * "len":1
  85554. * },{
  85555. * "name":"multiply",
  85556. * "len":1
  85557. * },{
  85558. * "name":"doForm",
  85559. * "formHandler":true, // handle form on server with Ext.Direct.Transaction
  85560. * "len":1
  85561. * }]
  85562. * },
  85563. * "namespace":"myApplication",// namespace to create the Remoting Provider in
  85564. * },{
  85565. * type: 'polling', // create a {@link Ext.direct.PollingProvider}
  85566. * url: 'php/poll.php'
  85567. * }, pollProv); // reference to previously created instance
  85568. *
  85569. * @param {Ext.direct.Provider/Object...} provider
  85570. * Accepts any number of Provider descriptions (an instance or config object for
  85571. * a Provider). Each Provider description instructs Ext.Directhow to create
  85572. * client-side stub methods.
  85573. */
  85574. addProvider : function(provider){
  85575. var me = this,
  85576. args = arguments,
  85577. i = 0,
  85578. len;
  85579. if (args.length > 1) {
  85580. for (len = args.length; i < len; ++i) {
  85581. me.addProvider(args[i]);
  85582. }
  85583. return;
  85584. }
  85585. // if provider has not already been instantiated
  85586. if (!provider.isProvider) {
  85587. provider = Ext.create('direct.' + provider.type + 'provider', provider);
  85588. }
  85589. me.providers.add(provider);
  85590. provider.on('data', me.onProviderData, me);
  85591. if (!provider.isConnected()) {
  85592. provider.connect();
  85593. }
  85594. return provider;
  85595. },
  85596. /**
  85597. * Retrieves a {@link Ext.direct.Provider provider} by the **{@link Ext.direct.Provider#id id}** specified when the
  85598. * provider is {@link #addProvider added}.
  85599. * @param {String/Ext.direct.Provider} id The id of the provider, or the provider instance.
  85600. */
  85601. getProvider : function(id){
  85602. return id.isProvider ? id : this.providers.get(id);
  85603. },
  85604. /**
  85605. * Removes the provider.
  85606. * @param {String/Ext.direct.Provider} provider The provider instance or the id of the provider.
  85607. * @return {Ext.direct.Provider} The provider, null if not found.
  85608. */
  85609. removeProvider : function(provider){
  85610. var me = this,
  85611. providers = me.providers;
  85612. provider = provider.isProvider ? provider : providers.get(provider);
  85613. if (provider) {
  85614. provider.un('data', me.onProviderData, me);
  85615. providers.remove(provider);
  85616. return provider;
  85617. }
  85618. return null;
  85619. },
  85620. /**
  85621. * Adds a transaction to the manager.
  85622. * @private
  85623. * @param {Ext.direct.Transaction} transaction The transaction to add
  85624. * @return {Ext.direct.Transaction} transaction
  85625. */
  85626. addTransaction: function(transaction){
  85627. this.transactions.add(transaction);
  85628. return transaction;
  85629. },
  85630. /**
  85631. * Removes a transaction from the manager.
  85632. * @private
  85633. * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to remove
  85634. * @return {Ext.direct.Transaction} transaction
  85635. */
  85636. removeTransaction: function(transaction){
  85637. transaction = this.getTransaction(transaction);
  85638. this.transactions.remove(transaction);
  85639. return transaction;
  85640. },
  85641. /**
  85642. * Gets a transaction
  85643. * @private
  85644. * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to get
  85645. * @return {Ext.direct.Transaction}
  85646. */
  85647. getTransaction: function(transaction){
  85648. return Ext.isObject(transaction) ? transaction : this.transactions.get(transaction);
  85649. },
  85650. onProviderData : function(provider, event){
  85651. var me = this,
  85652. i = 0,
  85653. len;
  85654. if (Ext.isArray(event)) {
  85655. for (len = event.length; i < len; ++i) {
  85656. me.onProviderData(provider, event[i]);
  85657. }
  85658. return;
  85659. }
  85660. if (event.name && event.name != 'event' && event.name != 'exception') {
  85661. me.fireEvent(event.name, event);
  85662. } else if (event.status === false) {
  85663. me.fireEvent('exception', event);
  85664. }
  85665. me.fireEvent('event', event, provider);
  85666. },
  85667. /**
  85668. * Parses a direct function. It may be passed in a string format, for example:
  85669. * "MyApp.Person.read".
  85670. * @protected
  85671. * @param {String/Function} fn The direct function
  85672. * @return {Function} The function to use in the direct call. Null if not found
  85673. */
  85674. parseMethod: function(fn){
  85675. if (Ext.isString(fn)) {
  85676. var parts = fn.split('.'),
  85677. i = 0,
  85678. len = parts.length,
  85679. current = window;
  85680. while (current && i < len) {
  85681. current = current[parts[i]];
  85682. ++i;
  85683. }
  85684. fn = Ext.isFunction(current) ? current : null;
  85685. }
  85686. return fn || null;
  85687. }
  85688. }, function(){
  85689. // Backwards compatibility
  85690. Ext.Direct = Ext.direct.Manager;
  85691. });
  85692. /**
  85693. * This class is used to send requests to the server using {@link Ext.direct.Manager Ext.Direct}. When a
  85694. * request is made, the transport mechanism is handed off to the appropriate
  85695. * {@link Ext.direct.RemotingProvider Provider} to complete the call.
  85696. *
  85697. * # Specifying the function
  85698. *
  85699. * This proxy expects a Direct remoting method to be passed in order to be able to complete requests.
  85700. * This can be done by specifying the {@link #directFn} configuration. This will use the same direct
  85701. * method for all requests. Alternatively, you can provide an {@link #api} configuration. This
  85702. * allows you to specify a different remoting method for each CRUD action.
  85703. *
  85704. * # Parameters
  85705. *
  85706. * This proxy provides options to help configure which parameters will be sent to the server.
  85707. * By specifying the {@link #paramsAsHash} option, it will send an object literal containing each
  85708. * of the passed parameters. The {@link #paramOrder} option can be used to specify the order in which
  85709. * the remoting method parameters are passed.
  85710. *
  85711. * # Example Usage
  85712. *
  85713. * Ext.define('User', {
  85714. * extend: 'Ext.data.Model',
  85715. * fields: ['firstName', 'lastName'],
  85716. * proxy: {
  85717. * type: 'direct',
  85718. * directFn: MyApp.getUsers,
  85719. * paramOrder: 'id' // Tells the proxy to pass the id as the first parameter to the remoting method.
  85720. * }
  85721. * });
  85722. * User.load(1);
  85723. */
  85724. Ext.define('Ext.data.proxy.Direct', {
  85725. /* Begin Definitions */
  85726. extend: 'Ext.data.proxy.Server',
  85727. alternateClassName: 'Ext.data.DirectProxy',
  85728. alias: 'proxy.direct',
  85729. requires: ['Ext.direct.Manager'],
  85730. /* End Definitions */
  85731. /**
  85732. * @cfg {String/String[]} paramOrder
  85733. * Defaults to undefined. A list of params to be executed server side. Specify the params in the order in
  85734. * which they must be executed on the server-side as either (1) an Array of String values, or (2) a String
  85735. * of params delimited by either whitespace, comma, or pipe. For example, any of the following would be
  85736. * acceptable:
  85737. *
  85738. * paramOrder: ['param1','param2','param3']
  85739. * paramOrder: 'param1 param2 param3'
  85740. * paramOrder: 'param1,param2,param3'
  85741. * paramOrder: 'param1|param2|param'
  85742. */
  85743. paramOrder: undefined,
  85744. /**
  85745. * @cfg {Boolean} paramsAsHash
  85746. * Send parameters as a collection of named arguments.
  85747. * Providing a {@link #paramOrder} nullifies this configuration.
  85748. */
  85749. paramsAsHash: true,
  85750. /**
  85751. * @cfg {Function/String} directFn
  85752. * Function to call when executing a request. directFn is a simple alternative to defining the api configuration-parameter
  85753. * for Store's which will not implement a full CRUD api. The directFn may also be a string reference to the fully qualified
  85754. * name of the function, for example: 'MyApp.company.GetProfile'. This can be useful when using dynamic loading. The string
  85755. * will be looked up when the proxy is created.
  85756. */
  85757. directFn : undefined,
  85758. /**
  85759. * @cfg {Object} api
  85760. * The same as {@link Ext.data.proxy.Server#api}, however instead of providing urls, you should provide a direct
  85761. * function call. See {@link #directFn}.
  85762. */
  85763. /**
  85764. * @cfg {Object} extraParams
  85765. * Extra parameters that will be included on every read request. Individual requests with params
  85766. * of the same name will override these params when they are in conflict.
  85767. */
  85768. // private
  85769. paramOrderRe: /[\s,|]/,
  85770. constructor: function(config){
  85771. var me = this,
  85772. paramOrder,
  85773. fn,
  85774. api;
  85775. me.callParent(arguments);
  85776. paramOrder = me.paramOrder;
  85777. if (Ext.isString(paramOrder)) {
  85778. me.paramOrder = paramOrder.split(me.paramOrderRe);
  85779. }
  85780. fn = me.directFn;
  85781. if (fn) {
  85782. me.directFn = Ext.direct.Manager.parseMethod(fn);
  85783. }
  85784. api = me.api;
  85785. for (fn in api) {
  85786. if (api.hasOwnProperty(fn)) {
  85787. api[fn] = Ext.direct.Manager.parseMethod(api[fn]);
  85788. }
  85789. }
  85790. },
  85791. doRequest: function(operation, callback, scope) {
  85792. var me = this,
  85793. writer = me.getWriter(),
  85794. request = me.buildRequest(operation, callback, scope),
  85795. fn = me.api[request.action] || me.directFn,
  85796. params = request.params,
  85797. args = [],
  85798. method;
  85799. if (!fn) {
  85800. Ext.Error.raise('No direct function specified for this proxy');
  85801. }
  85802. if (operation.allowWrite()) {
  85803. request = writer.write(request);
  85804. }
  85805. if (operation.action == 'read') {
  85806. // We need to pass params
  85807. method = fn.directCfg.method;
  85808. args = method.getArgs(params, me.paramOrder, me.paramsAsHash);
  85809. } else {
  85810. args.push(request.jsonData);
  85811. }
  85812. Ext.apply(request, {
  85813. args: args,
  85814. directFn: fn
  85815. });
  85816. args.push(me.createRequestCallback(request, operation, callback, scope), me);
  85817. fn.apply(window, args);
  85818. },
  85819. /*
  85820. * Inherit docs. We don't apply any encoding here because
  85821. * all of the direct requests go out as jsonData
  85822. */
  85823. applyEncoding: function(value){
  85824. return value;
  85825. },
  85826. createRequestCallback: function(request, operation, callback, scope){
  85827. var me = this;
  85828. return function(data, event){
  85829. me.processResponse(event.status, operation, request, event, callback, scope);
  85830. };
  85831. },
  85832. // inherit docs
  85833. extractResponseData: function(response){
  85834. return Ext.isDefined(response.result) ? response.result : response.data;
  85835. },
  85836. // inherit docs
  85837. setException: function(operation, response) {
  85838. operation.setException(response.message);
  85839. },
  85840. // inherit docs
  85841. buildUrl: function(){
  85842. return '';
  85843. }
  85844. });
  85845. /**
  85846. * Small helper class to create an {@link Ext.data.Store} configured with an {@link Ext.data.proxy.Direct}
  85847. * and {@link Ext.data.reader.Json} to make interacting with an {@link Ext.direct.Manager} server-side
  85848. * {@link Ext.direct.Provider Provider} easier. To create a different proxy/reader combination create a basic
  85849. * {@link Ext.data.Store} configured as needed.
  85850. *
  85851. * **Note:** Although they are not listed, this class inherits all of the config options of:
  85852. *
  85853. * - **{@link Ext.data.Store Store}**
  85854. *
  85855. * - **{@link Ext.data.reader.Json JsonReader}**
  85856. *
  85857. * - **{@link Ext.data.reader.Json#cfg-root root}**
  85858. * - **{@link Ext.data.reader.Json#idProperty idProperty}**
  85859. * - **{@link Ext.data.reader.Json#totalProperty totalProperty}**
  85860. *
  85861. * - **{@link Ext.data.proxy.Direct DirectProxy}**
  85862. *
  85863. * - **{@link Ext.data.proxy.Direct#directFn directFn}**
  85864. * - **{@link Ext.data.proxy.Direct#paramOrder paramOrder}**
  85865. * - **{@link Ext.data.proxy.Direct#paramsAsHash paramsAsHash}**
  85866. *
  85867. */
  85868. Ext.define('Ext.data.DirectStore', {
  85869. /* Begin Definitions */
  85870. extend: 'Ext.data.Store',
  85871. alias: 'store.direct',
  85872. requires: ['Ext.data.proxy.Direct'],
  85873. /* End Definitions */
  85874. constructor : function(config){
  85875. config = Ext.apply({}, config);
  85876. if (!config.proxy) {
  85877. var proxy = {
  85878. type: 'direct',
  85879. reader: {
  85880. type: 'json'
  85881. }
  85882. };
  85883. Ext.copyTo(proxy, config, 'paramOrder,paramsAsHash,directFn,api,simpleSortMode');
  85884. Ext.copyTo(proxy.reader, config, 'totalProperty,root,idProperty');
  85885. config.proxy = proxy;
  85886. }
  85887. this.callParent([config]);
  85888. }
  85889. });
  85890. /**
  85891. * @class Ext.data.JsonP
  85892. * @singleton
  85893. * This class is used to create JSONP requests. JSONP is a mechanism that allows for making
  85894. * requests for data cross domain. More information is available <a href="http://en.wikipedia.org/wiki/JSONP">here</a>.
  85895. */
  85896. Ext.define('Ext.data.JsonP', {
  85897. /* Begin Definitions */
  85898. singleton: true,
  85899. /* End Definitions */
  85900. /**
  85901. * Number of requests done so far.
  85902. * @private
  85903. */
  85904. requestCount: 0,
  85905. /**
  85906. * Hash of pending requests.
  85907. * @private
  85908. */
  85909. requests: {},
  85910. /**
  85911. * @property timeout
  85912. * @type Number
  85913. * A default timeout for any JsonP requests. If the request has not completed in this time the
  85914. * failure callback will be fired. The timeout is in ms. Defaults to <tt>30000</tt>.
  85915. */
  85916. timeout: 30000,
  85917. /**
  85918. * @property disableCaching
  85919. * @type Boolean
  85920. * True to add a unique cache-buster param to requests. Defaults to <tt>true</tt>.
  85921. */
  85922. disableCaching: true,
  85923. /**
  85924. * @property disableCachingParam
  85925. * @type String
  85926. * Change the parameter which is sent went disabling caching through a cache buster. Defaults to <tt>'_dc'</tt>.
  85927. */
  85928. disableCachingParam: '_dc',
  85929. /**
  85930. * @property callbackKey
  85931. * @type String
  85932. * Specifies the GET parameter that will be sent to the server containing the function name to be executed when
  85933. * the request completes. Defaults to <tt>callback</tt>. Thus, a common request will be in the form of
  85934. * url?callback=Ext.data.JsonP.callback1
  85935. */
  85936. callbackKey: 'callback',
  85937. /**
  85938. * Makes a JSONP request.
  85939. * @param {Object} options An object which may contain the following properties. Note that options will
  85940. * take priority over any defaults that are specified in the class.
  85941. * <ul>
  85942. * <li><b>url</b> : String <div class="sub-desc">The URL to request.</div></li>
  85943. * <li><b>params</b> : Object (Optional)<div class="sub-desc">An object containing a series of
  85944. * key value pairs that will be sent along with the request.</div></li>
  85945. * <li><b>timeout</b> : Number (Optional) <div class="sub-desc">See {@link #timeout}</div></li>
  85946. * <li><b>callbackKey</b> : String (Optional) <div class="sub-desc">See {@link #callbackKey}</div></li>
  85947. * <li><b>callbackName</b> : String (Optional) <div class="sub-desc">The function name to use for this request.
  85948. * By default this name will be auto-generated: Ext.data.JsonP.callback1, Ext.data.JsonP.callback2, etc.
  85949. * Setting this option to "my_name" will force the function name to be Ext.data.JsonP.my_name.
  85950. * Use this if you want deterministic behavior, but be careful - the callbackName should be different
  85951. * in each JsonP request that you make.</div></li>
  85952. * <li><b>disableCaching</b> : Boolean (Optional) <div class="sub-desc">See {@link #disableCaching}</div></li>
  85953. * <li><b>disableCachingParam</b> : String (Optional) <div class="sub-desc">See {@link #disableCachingParam}</div></li>
  85954. * <li><b>success</b> : Function (Optional) <div class="sub-desc">A function to execute if the request succeeds.</div></li>
  85955. * <li><b>failure</b> : Function (Optional) <div class="sub-desc">A function to execute if the request fails.</div></li>
  85956. * <li><b>callback</b> : Function (Optional) <div class="sub-desc">A function to execute when the request
  85957. * completes, whether it is a success or failure.</div></li>
  85958. * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
  85959. * which to execute the callbacks: The "this" object for the callback function. Defaults to the browser window.</div></li>
  85960. * </ul>
  85961. * @return {Object} request An object containing the request details.
  85962. */
  85963. request: function(options){
  85964. options = Ext.apply({}, options);
  85965. if (!options.url) {
  85966. Ext.Error.raise('A url must be specified for a JSONP request.');
  85967. }
  85968. var me = this,
  85969. disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching,
  85970. cacheParam = options.disableCachingParam || me.disableCachingParam,
  85971. id = ++me.requestCount,
  85972. callbackName = options.callbackName || 'callback' + id,
  85973. callbackKey = options.callbackKey || me.callbackKey,
  85974. timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout,
  85975. params = Ext.apply({}, options.params),
  85976. url = options.url,
  85977. name = Ext.name,
  85978. request,
  85979. script;
  85980. params[callbackKey] = name + '.data.JsonP.' + callbackName;
  85981. if (disableCaching) {
  85982. params[cacheParam] = new Date().getTime();
  85983. }
  85984. script = me.createScript(url, params, options);
  85985. me.requests[id] = request = {
  85986. url: url,
  85987. params: params,
  85988. script: script,
  85989. id: id,
  85990. scope: options.scope,
  85991. success: options.success,
  85992. failure: options.failure,
  85993. callback: options.callback,
  85994. callbackKey: callbackKey,
  85995. callbackName: callbackName
  85996. };
  85997. if (timeout > 0) {
  85998. request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
  85999. }
  86000. me.setupErrorHandling(request);
  86001. me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
  86002. me.loadScript(request);
  86003. return request;
  86004. },
  86005. /**
  86006. * Abort a request. If the request parameter is not specified all open requests will
  86007. * be aborted.
  86008. * @param {Object/String} request (Optional) The request to abort
  86009. */
  86010. abort: function(request){
  86011. var me = this,
  86012. requests = me.requests,
  86013. key;
  86014. if (request) {
  86015. if (!request.id) {
  86016. request = requests[request];
  86017. }
  86018. me.handleAbort(request);
  86019. } else {
  86020. for (key in requests) {
  86021. if (requests.hasOwnProperty(key)) {
  86022. me.abort(requests[key]);
  86023. }
  86024. }
  86025. }
  86026. },
  86027. /**
  86028. * Sets up error handling for the script
  86029. * @private
  86030. * @param {Object} request The request
  86031. */
  86032. setupErrorHandling: function(request){
  86033. request.script.onerror = Ext.bind(this.handleError, this, [request]);
  86034. },
  86035. /**
  86036. * Handles any aborts when loading the script
  86037. * @private
  86038. * @param {Object} request The request
  86039. */
  86040. handleAbort: function(request){
  86041. request.errorType = 'abort';
  86042. this.handleResponse(null, request);
  86043. },
  86044. /**
  86045. * Handles any script errors when loading the script
  86046. * @private
  86047. * @param {Object} request The request
  86048. */
  86049. handleError: function(request){
  86050. request.errorType = 'error';
  86051. this.handleResponse(null, request);
  86052. },
  86053. /**
  86054. * Cleans up anu script handling errors
  86055. * @private
  86056. * @param {Object} request The request
  86057. */
  86058. cleanupErrorHandling: function(request){
  86059. request.script.onerror = null;
  86060. },
  86061. /**
  86062. * Handle any script timeouts
  86063. * @private
  86064. * @param {Object} request The request
  86065. */
  86066. handleTimeout: function(request){
  86067. request.errorType = 'timeout';
  86068. this.handleResponse(null, request);
  86069. },
  86070. /**
  86071. * Handle a successful response
  86072. * @private
  86073. * @param {Object} result The result from the request
  86074. * @param {Object} request The request
  86075. */
  86076. handleResponse: function(result, request){
  86077. var success = true;
  86078. if (request.timeout) {
  86079. clearTimeout(request.timeout);
  86080. }
  86081. delete this[request.callbackName];
  86082. delete this.requests[request.id];
  86083. this.cleanupErrorHandling(request);
  86084. Ext.fly(request.script).remove();
  86085. if (request.errorType) {
  86086. success = false;
  86087. Ext.callback(request.failure, request.scope, [request.errorType]);
  86088. } else {
  86089. Ext.callback(request.success, request.scope, [result]);
  86090. }
  86091. Ext.callback(request.callback, request.scope, [success, result, request.errorType]);
  86092. },
  86093. /**
  86094. * Create the script tag given the specified url, params and options. The options
  86095. * parameter is passed to allow an override to access it.
  86096. * @private
  86097. * @param {String} url The url of the request
  86098. * @param {Object} params Any extra params to be sent
  86099. * @param {Object} options The object passed to {@link #request}.
  86100. */
  86101. createScript: function(url, params, options) {
  86102. var script = document.createElement('script');
  86103. script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params)));
  86104. script.setAttribute("async", true);
  86105. script.setAttribute("type", "text/javascript");
  86106. return script;
  86107. },
  86108. /**
  86109. * Loads the script for the given request by appending it to the HEAD element. This is
  86110. * its own method so that users can override it (as well as {@link #createScript}).
  86111. * @private
  86112. * @param request The request object.
  86113. */
  86114. loadScript: function (request) {
  86115. Ext.getHead().appendChild(request.script);
  86116. }
  86117. });
  86118. /**
  86119. * @author Ed Spencer
  86120. *
  86121. * The JsonP proxy is useful when you need to load data from a domain other than the one your application is running on. If
  86122. * your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its data
  86123. * from http://domainB.com because cross-domain ajax requests are prohibited by the browser.
  86124. *
  86125. * We can get around this using a JsonP proxy. JsonP proxy injects a `<script>` tag into the DOM whenever an AJAX request
  86126. * would usually be made. Let's say we want to load data from http://domainB.com/users - the script tag that would be
  86127. * injected might look like this:
  86128. *
  86129. * <script src="http://domainB.com/users?callback=someCallback"></script>
  86130. *
  86131. * When we inject the tag above, the browser makes a request to that url and includes the response as if it was any
  86132. * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we want
  86133. * to be notified when the result comes in and that it should call our callback function with the data it sends back. So
  86134. * long as the server formats the response to look like this, everything will work:
  86135. *
  86136. * someCallback({
  86137. * users: [
  86138. * {
  86139. * id: 1,
  86140. * name: "Ed Spencer",
  86141. * email: "ed@sencha.com"
  86142. * }
  86143. * ]
  86144. * });
  86145. *
  86146. * As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the JSON
  86147. * object that the server returned.
  86148. *
  86149. * JsonP proxy takes care of all of this automatically. It formats the url you pass, adding the callback parameter
  86150. * automatically. It even creates a temporary callback function, waits for it to be called and then puts the data into
  86151. * the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}. Here's how
  86152. * we might set that up:
  86153. *
  86154. * Ext.define('User', {
  86155. * extend: 'Ext.data.Model',
  86156. * fields: ['id', 'name', 'email']
  86157. * });
  86158. *
  86159. * var store = Ext.create('Ext.data.Store', {
  86160. * model: 'User',
  86161. * proxy: {
  86162. * type: 'jsonp',
  86163. * url : 'http://domainB.com/users'
  86164. * }
  86165. * });
  86166. *
  86167. * store.load();
  86168. *
  86169. * That's all we need to do - JsonP proxy takes care of the rest. In this case the Proxy will have injected a script tag
  86170. * like this:
  86171. *
  86172. * <script src="http://domainB.com/users?callback=callback1"></script>
  86173. *
  86174. * # Customization
  86175. *
  86176. * This script tag can be customized using the {@link #callbackKey} configuration. For example:
  86177. *
  86178. * var store = Ext.create('Ext.data.Store', {
  86179. * model: 'User',
  86180. * proxy: {
  86181. * type: 'jsonp',
  86182. * url : 'http://domainB.com/users',
  86183. * callbackKey: 'theCallbackFunction'
  86184. * }
  86185. * });
  86186. *
  86187. * store.load();
  86188. *
  86189. * Would inject a script tag like this:
  86190. *
  86191. * <script src="http://domainB.com/users?theCallbackFunction=callback1"></script>
  86192. *
  86193. * # Implementing on the server side
  86194. *
  86195. * The remote server side needs to be configured to return data in this format. Here are suggestions for how you might
  86196. * achieve this using Java, PHP and ASP.net:
  86197. *
  86198. * Java:
  86199. *
  86200. * boolean jsonP = false;
  86201. * String cb = request.getParameter("callback");
  86202. * if (cb != null) {
  86203. * jsonP = true;
  86204. * response.setContentType("text/javascript");
  86205. * } else {
  86206. * response.setContentType("application/x-json");
  86207. * }
  86208. * Writer out = response.getWriter();
  86209. * if (jsonP) {
  86210. * out.write(cb + "(");
  86211. * }
  86212. * out.print(dataBlock.toJsonString());
  86213. * if (jsonP) {
  86214. * out.write(");");
  86215. * }
  86216. *
  86217. * PHP:
  86218. *
  86219. * $callback = $_REQUEST['callback'];
  86220. *
  86221. * // Create the output object.
  86222. * $output = array('a' => 'Apple', 'b' => 'Banana');
  86223. *
  86224. * //start output
  86225. * if ($callback) {
  86226. * header('Content-Type: text/javascript');
  86227. * echo $callback . '(' . json_encode($output) . ');';
  86228. * } else {
  86229. * header('Content-Type: application/x-json');
  86230. * echo json_encode($output);
  86231. * }
  86232. *
  86233. * ASP.net:
  86234. *
  86235. * String jsonString = "{success: true}";
  86236. * String cb = Request.Params.Get("callback");
  86237. * String responseString = "";
  86238. * if (!String.IsNullOrEmpty(cb)) {
  86239. * responseString = cb + "(" + jsonString + ")";
  86240. * } else {
  86241. * responseString = jsonString;
  86242. * }
  86243. * Response.Write(responseString);
  86244. */
  86245. Ext.define('Ext.data.proxy.JsonP', {
  86246. extend: 'Ext.data.proxy.Server',
  86247. alternateClassName: 'Ext.data.ScriptTagProxy',
  86248. alias: ['proxy.jsonp', 'proxy.scripttag'],
  86249. requires: ['Ext.data.JsonP'],
  86250. defaultWriterType: 'base',
  86251. /**
  86252. * @cfg {String} callbackKey
  86253. * See {@link Ext.data.JsonP#callbackKey}.
  86254. */
  86255. callbackKey : 'callback',
  86256. /**
  86257. * @cfg {String} recordParam
  86258. * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString'). Defaults to
  86259. * 'records'
  86260. */
  86261. recordParam: 'records',
  86262. /**
  86263. * @cfg {Boolean} autoAppendParams
  86264. * True to automatically append the request's params to the generated url. Defaults to true
  86265. */
  86266. autoAppendParams: true,
  86267. constructor: function(){
  86268. this.addEvents(
  86269. /**
  86270. * @event
  86271. * Fires when the server returns an exception
  86272. * @param {Ext.data.proxy.Proxy} this
  86273. * @param {Ext.data.Request} request The request that was sent
  86274. * @param {Ext.data.Operation} operation The operation that triggered the request
  86275. */
  86276. 'exception'
  86277. );
  86278. this.callParent(arguments);
  86279. },
  86280. /**
  86281. * @private
  86282. * Performs the read request to the remote domain. JsonP proxy does not actually create an Ajax request,
  86283. * instead we write out a `<script>` tag based on the configuration of the internal Ext.data.Request object
  86284. * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
  86285. * @param {Function} callback A callback function to execute when the Operation has been completed
  86286. * @param {Object} scope The scope to execute the callback in
  86287. */
  86288. doRequest: function(operation, callback, scope) {
  86289. //generate the unique IDs for this request
  86290. var me = this,
  86291. writer = me.getWriter(),
  86292. request = me.buildRequest(operation),
  86293. params = request.params;
  86294. if (operation.allowWrite()) {
  86295. request = writer.write(request);
  86296. }
  86297. // apply JsonP proxy-specific attributes to the Request
  86298. Ext.apply(request, {
  86299. callbackKey: me.callbackKey,
  86300. timeout: me.timeout,
  86301. scope: me,
  86302. disableCaching: false, // handled by the proxy
  86303. callback: me.createRequestCallback(request, operation, callback, scope)
  86304. });
  86305. // prevent doubling up
  86306. if (me.autoAppendParams) {
  86307. request.params = {};
  86308. }
  86309. request.jsonp = Ext.data.JsonP.request(request);
  86310. // restore on the request
  86311. request.params = params;
  86312. operation.setStarted();
  86313. me.lastRequest = request;
  86314. return request;
  86315. },
  86316. /**
  86317. * @private
  86318. * Creates and returns the function that is called when the request has completed. The returned function
  86319. * should accept a Response object, which contains the response to be read by the configured Reader.
  86320. * The third argument is the callback that should be called after the request has been completed and the Reader has decoded
  86321. * the response. This callback will typically be the callback passed by a store, e.g. in proxy.read(operation, theCallback, scope)
  86322. * theCallback refers to the callback argument received by this function.
  86323. * See {@link #doRequest} for details.
  86324. * @param {Ext.data.Request} request The Request object
  86325. * @param {Ext.data.Operation} operation The Operation being executed
  86326. * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
  86327. * passed to doRequest
  86328. * @param {Object} scope The scope in which to execute the callback function
  86329. * @return {Function} The callback function
  86330. */
  86331. createRequestCallback: function(request, operation, callback, scope) {
  86332. var me = this;
  86333. return function(success, response, errorType) {
  86334. delete me.lastRequest;
  86335. me.processResponse(success, operation, request, response, callback, scope);
  86336. };
  86337. },
  86338. // inherit docs
  86339. setException: function(operation, response) {
  86340. operation.setException(operation.request.jsonp.errorType);
  86341. },
  86342. /**
  86343. * Generates a url based on a given Ext.data.Request object. Adds the params and callback function name to the url
  86344. * @param {Ext.data.Request} request The request object
  86345. * @return {String} The url
  86346. */
  86347. buildUrl: function(request) {
  86348. var me = this,
  86349. url = me.callParent(arguments),
  86350. params = Ext.apply({}, request.params),
  86351. filters = params.filters,
  86352. records,
  86353. filter, i;
  86354. delete params.filters;
  86355. if (me.autoAppendParams) {
  86356. url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
  86357. }
  86358. if (filters && filters.length) {
  86359. for (i = 0; i < filters.length; i++) {
  86360. filter = filters[i];
  86361. if (filter.value) {
  86362. url = Ext.urlAppend(url, filter.property + "=" + filter.value);
  86363. }
  86364. }
  86365. }
  86366. //if there are any records present, append them to the url also
  86367. records = request.records;
  86368. if (Ext.isArray(records) && records.length > 0) {
  86369. url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.recordParam, me.encodeRecords(records)));
  86370. }
  86371. return url;
  86372. },
  86373. //inherit docs
  86374. destroy: function() {
  86375. this.abort();
  86376. this.callParent(arguments);
  86377. },
  86378. /**
  86379. * Aborts the current server request if one is currently running
  86380. */
  86381. abort: function() {
  86382. var lastRequest = this.lastRequest;
  86383. if (lastRequest) {
  86384. Ext.data.JsonP.abort(lastRequest.jsonp);
  86385. }
  86386. },
  86387. /**
  86388. * Encodes an array of records into a string suitable to be appended to the script src url. This is broken out into
  86389. * its own function so that it can be easily overridden.
  86390. * @param {Ext.data.Model[]} records The records array
  86391. * @return {String} The encoded records string
  86392. */
  86393. encodeRecords: function(records) {
  86394. var encoded = "",
  86395. i = 0,
  86396. len = records.length;
  86397. for (; i < len; i++) {
  86398. encoded += Ext.Object.toQueryString(records[i].getData());
  86399. }
  86400. return encoded;
  86401. }
  86402. });
  86403. /**
  86404. * @class Ext.data.JsonPStore
  86405. * @extends Ext.data.Store
  86406. * <p>Small helper class to make creating {@link Ext.data.Store}s from different domain JSON data easier.
  86407. * A JsonPStore will be automatically configured with a {@link Ext.data.reader.Json} and a {@link Ext.data.proxy.JsonP JsonPProxy}.</p>
  86408. * <p>A store configuration would be something like:<pre><code>
  86409. var store = new Ext.data.JsonPStore({
  86410. // store configs
  86411. storeId: 'myStore',
  86412. // proxy configs
  86413. url: 'get-images.php',
  86414. // reader configs
  86415. root: 'images',
  86416. idProperty: 'name',
  86417. fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
  86418. });
  86419. * </code></pre></p>
  86420. * <p>This store is configured to consume a returned object of the form:<pre><code>
  86421. stcCallback({
  86422. images: [
  86423. {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
  86424. {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
  86425. ]
  86426. })
  86427. * </code></pre>
  86428. * <p>Where stcCallback is the callback name passed in the request to the remote domain. See {@link Ext.data.proxy.JsonP JsonPProxy}
  86429. * for details of how this works.</p>
  86430. * An object literal of this form could also be used as the {@link #cfg-data} config option.</p>
  86431. * @xtype jsonpstore
  86432. */
  86433. Ext.define('Ext.data.JsonPStore', {
  86434. extend: 'Ext.data.Store',
  86435. alias : 'store.jsonp',
  86436. requires: [
  86437. 'Ext.data.proxy.JsonP',
  86438. 'Ext.data.reader.Json'
  86439. ],
  86440. constructor: function(config) {
  86441. config = Ext.apply({
  86442. proxy: {
  86443. type: 'jsonp',
  86444. reader: 'json'
  86445. }
  86446. }, config);
  86447. this.callParent([config]);
  86448. }
  86449. });
  86450. /**
  86451. * @author Ed Spencer
  86452. *
  86453. * <p>Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
  86454. * A JsonStore will be automatically configured with a {@link Ext.data.reader.Json}.</p>
  86455. *
  86456. * <p>A store configuration would be something like:</p>
  86457. *
  86458. <pre><code>
  86459. var store = new Ext.data.JsonStore({
  86460. // store configs
  86461. storeId: 'myStore',
  86462. proxy: {
  86463. type: 'ajax',
  86464. url: 'get-images.php',
  86465. reader: {
  86466. type: 'json',
  86467. root: 'images',
  86468. idProperty: 'name'
  86469. }
  86470. },
  86471. //alternatively, a {@link Ext.data.Model} name can be given (see {@link Ext.data.Store} for an example)
  86472. fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
  86473. });
  86474. </code></pre>
  86475. *
  86476. * <p>This store is configured to consume a returned object of the form:<pre><code>
  86477. {
  86478. images: [
  86479. {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
  86480. {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
  86481. ]
  86482. }
  86483. </code></pre>
  86484. *
  86485. * <p>An object literal of this form could also be used as the {@link #cfg-data} config option.</p>
  86486. */
  86487. Ext.define('Ext.data.JsonStore', {
  86488. extend: 'Ext.data.Store',
  86489. alias: 'store.json',
  86490. requires: [
  86491. 'Ext.data.proxy.Ajax',
  86492. 'Ext.data.reader.Json',
  86493. 'Ext.data.writer.Json'
  86494. ],
  86495. constructor: function(config) {
  86496. config = Ext.apply({
  86497. proxy: {
  86498. type : 'ajax',
  86499. reader: 'json',
  86500. writer: 'json'
  86501. }
  86502. }, config);
  86503. this.callParent([config]);
  86504. }
  86505. });
  86506. /**
  86507. * This class is used as a set of methods that are applied to the prototype of a
  86508. * Model to decorate it with a Node API. This means that models used in conjunction with a tree
  86509. * will have all of the tree related methods available on the model. In general this class will
  86510. * not be used directly by the developer. This class also creates extra fields on the model if
  86511. * they do not exist, to help maintain the tree state and UI. These fields are documented as
  86512. * config options.
  86513. */
  86514. Ext.define('Ext.data.NodeInterface', {
  86515. requires: ['Ext.data.Field'],
  86516. /**
  86517. * @cfg {String} parentId
  86518. * ID of parent node.
  86519. */
  86520. /**
  86521. * @cfg {Number} index
  86522. * The position of the node inside its parent. When parent has 4 children and the node is third amongst them,
  86523. * index will be 2.
  86524. */
  86525. /**
  86526. * @cfg {Number} depth
  86527. * The number of parents this node has. A root node has depth 0, a child of it depth 1, and so on...
  86528. */
  86529. /**
  86530. * @cfg {Boolean} [expanded=false]
  86531. * True if the node is expanded.
  86532. */
  86533. /**
  86534. * @cfg {Boolean} [expandable=false]
  86535. * Set to true to allow for expanding/collapsing of this node.
  86536. */
  86537. /**
  86538. * @cfg {Boolean} [checked=null]
  86539. * Set to true or false to show a checkbox alongside this node.
  86540. */
  86541. /**
  86542. * @cfg {Boolean} [leaf=false]
  86543. * Set to true to indicate that this child can have no children. The expand icon/arrow will then not be
  86544. * rendered for this node.
  86545. */
  86546. /**
  86547. * @cfg {String} cls
  86548. * CSS class to apply for this node.
  86549. */
  86550. /**
  86551. * @cfg {String} iconCls
  86552. * CSS class to apply for this node's icon.
  86553. */
  86554. /**
  86555. * @cfg {String} icon
  86556. * URL for this node's icon.
  86557. */
  86558. /**
  86559. * @cfg {Boolean} root
  86560. * True if this is the root node.
  86561. */
  86562. /**
  86563. * @cfg {Boolean} isLast
  86564. * True if this is the last node.
  86565. */
  86566. /**
  86567. * @cfg {Boolean} isFirst
  86568. * True if this is the first node.
  86569. */
  86570. /**
  86571. * @cfg {Boolean} [allowDrop=true]
  86572. * Set to false to deny dropping on this node.
  86573. */
  86574. /**
  86575. * @cfg {Boolean} [allowDrag=true]
  86576. * Set to false to deny dragging of this node.
  86577. */
  86578. /**
  86579. * @cfg {Boolean} [loaded=false]
  86580. * True if the node has finished loading.
  86581. */
  86582. /**
  86583. * @cfg {Boolean} [loading=false]
  86584. * True if the node is currently loading.
  86585. */
  86586. /**
  86587. * @cfg {String} href
  86588. * An URL for a link that's created when this config is specified.
  86589. */
  86590. /**
  86591. * @cfg {String} hrefTarget
  86592. * Target for link. Only applicable when {@link #href} also specified.
  86593. */
  86594. /**
  86595. * @cfg {String} qtip
  86596. * Tooltip text to show on this node.
  86597. */
  86598. /**
  86599. * @cfg {String} qtitle
  86600. * Tooltip title.
  86601. */
  86602. /**
  86603. * @cfg {String} text
  86604. * The text for to show on node label.
  86605. */
  86606. /**
  86607. * @cfg {Ext.data.NodeInterface[]} children
  86608. * Array of child nodes.
  86609. */
  86610. /**
  86611. * @property nextSibling
  86612. * A reference to this node's next sibling node. `null` if this node does not have a next sibling.
  86613. */
  86614. /**
  86615. * @property previousSibling
  86616. * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling.
  86617. */
  86618. /**
  86619. * @property parentNode
  86620. * A reference to this node's parent node. `null` if this node is the root node.
  86621. */
  86622. /**
  86623. * @property lastChild
  86624. * A reference to this node's last child node. `null` if this node has no children.
  86625. */
  86626. /**
  86627. * @property firstChild
  86628. * A reference to this node's first child node. `null` if this node has no children.
  86629. */
  86630. /**
  86631. * @property childNodes
  86632. * An array of this nodes children. Array will be empty if this node has no chidren.
  86633. */
  86634. statics: {
  86635. /**
  86636. * This method allows you to decorate a Model's class to implement the NodeInterface.
  86637. * This adds a set of methods, new events, new properties and new fields on every Record.
  86638. * @param {Ext.Class/Ext.data.Model} modelClass The Model class or an instance of the Model class you want to
  86639. * decorate the prototype of.
  86640. * @static
  86641. */
  86642. decorate: function(modelClass) {
  86643. var idName, idType;
  86644. // get the reference to the model class, in case the argument was a string or a record
  86645. if (typeof modelClass == 'string') {
  86646. modelClass = Ext.ModelManager.getModel(modelClass);
  86647. } else if (modelClass.isModel) {
  86648. modelClass = Ext.ModelManager.getModel(modelClass.modelName);
  86649. }
  86650. // avoid unnecessary work in case the model was already decorated
  86651. if (modelClass.prototype.isNode) {
  86652. return;
  86653. }
  86654. idName = modelClass.prototype.idProperty;
  86655. idField = modelClass.prototype.fields.get(idName);
  86656. idType = modelClass.prototype.fields.get(idName).type.type;
  86657. modelClass.override(this.getPrototypeBody());
  86658. this.applyFields(modelClass, [
  86659. {name: 'parentId', type: idType, defaultValue: null, useNull: idField.useNull},
  86660. {name: 'index', type: 'int', defaultValue: null, persist: false},
  86661. {name: 'depth', type: 'int', defaultValue: 0, persist: false},
  86662. {name: 'expanded', type: 'bool', defaultValue: false, persist: false},
  86663. {name: 'expandable', type: 'bool', defaultValue: true, persist: false},
  86664. {name: 'checked', type: 'auto', defaultValue: null, persist: false},
  86665. {name: 'leaf', type: 'bool', defaultValue: false},
  86666. {name: 'cls', type: 'string', defaultValue: null, persist: false},
  86667. {name: 'iconCls', type: 'string', defaultValue: null, persist: false},
  86668. {name: 'icon', type: 'string', defaultValue: null, persist: false},
  86669. {name: 'root', type: 'boolean', defaultValue: false, persist: false},
  86670. {name: 'isLast', type: 'boolean', defaultValue: false, persist: false},
  86671. {name: 'isFirst', type: 'boolean', defaultValue: false, persist: false},
  86672. {name: 'allowDrop', type: 'boolean', defaultValue: true, persist: false},
  86673. {name: 'allowDrag', type: 'boolean', defaultValue: true, persist: false},
  86674. {name: 'loaded', type: 'boolean', defaultValue: false, persist: false},
  86675. {name: 'loading', type: 'boolean', defaultValue: false, persist: false},
  86676. {name: 'href', type: 'string', defaultValue: null, persist: false},
  86677. {name: 'hrefTarget', type: 'string', defaultValue: null, persist: false},
  86678. {name: 'qtip', type: 'string', defaultValue: null, persist: false},
  86679. {name: 'qtitle', type: 'string', defaultValue: null, persist: false},
  86680. {name: 'children', type: 'auto', defaultValue: null, persist: false}
  86681. ]);
  86682. },
  86683. applyFields: function(modelClass, addFields) {
  86684. var modelPrototype = modelClass.prototype,
  86685. fields = modelPrototype.fields,
  86686. keys = fields.keys,
  86687. ln = addFields.length,
  86688. addField, i;
  86689. for (i = 0; i < ln; i++) {
  86690. addField = addFields[i];
  86691. if (!Ext.Array.contains(keys, addField.name)) {
  86692. fields.add(new Ext.data.Field(addField));
  86693. }
  86694. }
  86695. },
  86696. getPrototypeBody: function() {
  86697. return {
  86698. /**
  86699. * @property {Boolean} isNode
  86700. * `true` in this class to identify an object as an instantiated Node, or subclass thereof.
  86701. */
  86702. isNode: true,
  86703. constructor: function() {
  86704. var me = this;
  86705. this.callParent(arguments);
  86706. Ext.applyIf(me, {
  86707. firstChild: null,
  86708. lastChild: null,
  86709. parentNode: null,
  86710. previousSibling: null,
  86711. nextSibling: null,
  86712. childNodes: []
  86713. });
  86714. me.enableBubble([
  86715. /**
  86716. * @event append
  86717. * Fires when a new child node is appended
  86718. * @param {Ext.data.NodeInterface} this This node
  86719. * @param {Ext.data.NodeInterface} node The newly appended node
  86720. * @param {Number} index The index of the newly appended node
  86721. */
  86722. "append",
  86723. /**
  86724. * @event remove
  86725. * Fires when a child node is removed
  86726. * @param {Ext.data.NodeInterface} this This node
  86727. * @param {Ext.data.NodeInterface} node The removed node
  86728. * @param {Boolean} isMove `true` if the child node is being removed so it can be moved to another position in the tree.
  86729. * (a side effect of calling {@link Ext.data.NodeInterface#appendChild appendChild} or
  86730. * {@link Ext.data.NodeInterface#insertBefore insertBefore} with a node that already has a parentNode)
  86731. */
  86732. "remove",
  86733. /**
  86734. * @event move
  86735. * Fires when this node is moved to a new location in the tree
  86736. * @param {Ext.data.NodeInterface} this This node
  86737. * @param {Ext.data.NodeInterface} oldParent The old parent of this node
  86738. * @param {Ext.data.NodeInterface} newParent The new parent of this node
  86739. * @param {Number} index The index it was moved to
  86740. */
  86741. "move",
  86742. /**
  86743. * @event insert
  86744. * Fires when a new child node is inserted.
  86745. * @param {Ext.data.NodeInterface} this This node
  86746. * @param {Ext.data.NodeInterface} node The child node inserted
  86747. * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before
  86748. */
  86749. "insert",
  86750. /**
  86751. * @event beforeappend
  86752. * Fires before a new child is appended, return false to cancel the append.
  86753. * @param {Ext.data.NodeInterface} this This node
  86754. * @param {Ext.data.NodeInterface} node The child node to be appended
  86755. */
  86756. "beforeappend",
  86757. /**
  86758. * @event beforeremove
  86759. * Fires before a child is removed, return false to cancel the remove.
  86760. * @param {Ext.data.NodeInterface} this This node
  86761. * @param {Ext.data.NodeInterface} node The child node to be removed
  86762. * @param {Boolean} isMove `true` if the child node is being removed so it can be moved to another position in the tree.
  86763. * (a side effect of calling {@link Ext.data.NodeInterface#appendChild appendChild} or
  86764. * {@link Ext.data.NodeInterface#insertBefore insertBefore} with a node that already has a parentNode)
  86765. */
  86766. "beforeremove",
  86767. /**
  86768. * @event beforemove
  86769. * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
  86770. * @param {Ext.data.NodeInterface} this This node
  86771. * @param {Ext.data.NodeInterface} oldParent The parent of this node
  86772. * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to
  86773. * @param {Number} index The index it is being moved to
  86774. */
  86775. "beforemove",
  86776. /**
  86777. * @event beforeinsert
  86778. * Fires before a new child is inserted, return false to cancel the insert.
  86779. * @param {Ext.data.NodeInterface} this This node
  86780. * @param {Ext.data.NodeInterface} node The child node to be inserted
  86781. * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before
  86782. */
  86783. "beforeinsert",
  86784. /**
  86785. * @event expand
  86786. * Fires when this node is expanded.
  86787. * @param {Ext.data.NodeInterface} this The expanding node
  86788. */
  86789. "expand",
  86790. /**
  86791. * @event collapse
  86792. * Fires when this node is collapsed.
  86793. * @param {Ext.data.NodeInterface} this The collapsing node
  86794. */
  86795. "collapse",
  86796. /**
  86797. * @event beforeexpand
  86798. * Fires before this node is expanded.
  86799. * @param {Ext.data.NodeInterface} this The expanding node
  86800. */
  86801. "beforeexpand",
  86802. /**
  86803. * @event beforecollapse
  86804. * Fires before this node is collapsed.
  86805. * @param {Ext.data.NodeInterface} this The collapsing node
  86806. */
  86807. "beforecollapse",
  86808. /**
  86809. * @event sort
  86810. * Fires when this node's childNodes are sorted.
  86811. * @param {Ext.data.NodeInterface} this This node.
  86812. * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node.
  86813. */
  86814. "sort"
  86815. ]);
  86816. return me;
  86817. },
  86818. /**
  86819. * Ensures that the passed object is an instance of a Record with the NodeInterface applied
  86820. * @return {Ext.data.NodeInterface}
  86821. */
  86822. createNode: function(node) {
  86823. if (Ext.isObject(node) && !node.isModel) {
  86824. node = Ext.ModelManager.create(node, this.modelName);
  86825. }
  86826. // The node may already decorated, but may not have been
  86827. // so when the model constructor was called. If not,
  86828. // setup defaults here
  86829. if (!node.childNodes) {
  86830. Ext.applyIf(node, {
  86831. firstChild: null,
  86832. lastChild: null,
  86833. parentNode: null,
  86834. previousSibling: null,
  86835. nextSibling: null,
  86836. childNodes: []
  86837. });
  86838. }
  86839. return node;
  86840. },
  86841. /**
  86842. * Returns true if this node is a leaf
  86843. * @return {Boolean}
  86844. */
  86845. isLeaf : function() {
  86846. return this.get('leaf') === true;
  86847. },
  86848. /**
  86849. * Sets the first child of this node
  86850. * @private
  86851. * @param {Ext.data.NodeInterface} node
  86852. */
  86853. setFirstChild : function(node) {
  86854. this.firstChild = node;
  86855. },
  86856. /**
  86857. * Sets the last child of this node
  86858. * @private
  86859. * @param {Ext.data.NodeInterface} node
  86860. */
  86861. setLastChild : function(node) {
  86862. this.lastChild = node;
  86863. },
  86864. /**
  86865. * Updates general data of this node like isFirst, isLast, depth. This
  86866. * method is internally called after a node is moved. This shouldn't
  86867. * have to be called by the developer unless they are creating custom
  86868. * Tree plugins.
  86869. * @return {Boolean}
  86870. */
  86871. updateInfo: function(commit) {
  86872. var me = this,
  86873. isRoot = me.isRoot(),
  86874. parentNode = me.parentNode,
  86875. isFirst = (!parentNode || isRoot ? true : parentNode.firstChild === me),
  86876. isLast = (!parentNode || isRoot ? true : parentNode.lastChild === me),
  86877. depth = 0,
  86878. parent = me,
  86879. children = me.childNodes,
  86880. len = children.length,
  86881. i = 0,
  86882. phantom = me.phantom;
  86883. while (parent.parentNode) {
  86884. ++depth;
  86885. parent = parent.parentNode;
  86886. }
  86887. me.beginEdit();
  86888. me.set({
  86889. isFirst: isFirst,
  86890. isLast: isLast,
  86891. depth: depth,
  86892. index: parentNode ? parentNode.indexOf(me) : 0,
  86893. parentId: parentNode ? parentNode.getId() : null
  86894. });
  86895. me.endEdit(true);
  86896. if (commit) {
  86897. me.commit();
  86898. me.phantom = phantom;
  86899. }
  86900. for (i = 0; i < len; i++) {
  86901. children[i].updateInfo(commit);
  86902. }
  86903. },
  86904. /**
  86905. * Returns true if this node is the last child of its parent
  86906. * @return {Boolean}
  86907. */
  86908. isLast : function() {
  86909. return this.get('isLast');
  86910. },
  86911. /**
  86912. * Returns true if this node is the first child of its parent
  86913. * @return {Boolean}
  86914. */
  86915. isFirst : function() {
  86916. return this.get('isFirst');
  86917. },
  86918. /**
  86919. * Returns true if this node has one or more child nodes, else false.
  86920. * @return {Boolean}
  86921. */
  86922. hasChildNodes : function() {
  86923. return !this.isLeaf() && this.childNodes.length > 0;
  86924. },
  86925. /**
  86926. * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
  86927. * node attribute is explicitly specified as true, otherwise returns false.
  86928. * @return {Boolean}
  86929. */
  86930. isExpandable : function() {
  86931. var me = this;
  86932. if (me.get('expandable')) {
  86933. return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
  86934. }
  86935. return false;
  86936. },
  86937. triggerUIUpdate: function(){
  86938. // This isn't ideal, however none of the underlying fields have changed
  86939. // but we still need to update the UI
  86940. this.afterEdit([]);
  86941. },
  86942. /**
  86943. * Inserts node(s) as the last child node of this node.
  86944. *
  86945. * If the node was previously a child node of another parent node, it will be removed from that node first.
  86946. *
  86947. * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]} node The node or Array of nodes to append
  86948. * @return {Ext.data.NodeInterface} The appended node if single append, or null if an array was passed
  86949. */
  86950. appendChild : function(node, suppressEvents, commit) {
  86951. var me = this,
  86952. i, ln,
  86953. index,
  86954. oldParent,
  86955. ps;
  86956. // if passed an array do them one by one
  86957. if (Ext.isArray(node)) {
  86958. // suspend auto syncing while we append all the nodes
  86959. me.callStore('suspendAutoSync');
  86960. for (i = 0, ln = node.length - 1; i < ln; i++) {
  86961. me.appendChild(node[i]);
  86962. }
  86963. // resume auto syncing before we append the last node
  86964. me.callStore('resumeAutoSync');
  86965. me.appendChild(node[ln]);
  86966. } else {
  86967. // Make sure it is a record
  86968. node = me.createNode(node);
  86969. if (suppressEvents !== true && (!me.hasListeners.beforeappend || me.fireEvent("beforeappend", me, node) === false)) {
  86970. return false;
  86971. }
  86972. index = me.childNodes.length;
  86973. oldParent = node.parentNode;
  86974. // it's a move, make sure we move it cleanly
  86975. if (oldParent) {
  86976. if (suppressEvents !== true && (!me.hasListeners.beforeremove || node.fireEvent("beforemove", node, oldParent, me, index) === false)) {
  86977. return false;
  86978. }
  86979. oldParent.removeChild(node, false, false, true);
  86980. }
  86981. index = me.childNodes.length;
  86982. if (index === 0) {
  86983. me.setFirstChild(node);
  86984. }
  86985. me.childNodes.push(node);
  86986. node.parentNode = me;
  86987. node.nextSibling = null;
  86988. me.setLastChild(node);
  86989. ps = me.childNodes[index - 1];
  86990. if (ps) {
  86991. node.previousSibling = ps;
  86992. ps.nextSibling = node;
  86993. ps.updateInfo(commit);
  86994. } else {
  86995. node.previousSibling = null;
  86996. }
  86997. node.updateInfo(commit);
  86998. // As soon as we append a child to this node, we are loaded
  86999. if (!me.isLoaded()) {
  87000. me.set('loaded', true);
  87001. } else if (me.childNodes.length === 1) {
  87002. me.triggerUIUpdate();
  87003. }
  87004. if(!node.isLeaf() && node.phantom) {
  87005. node.set('loaded', true);
  87006. }
  87007. if (suppressEvents !== true) {
  87008. me.fireEvent("append", me, node, index);
  87009. if (oldParent) {
  87010. node.fireEvent("move", node, oldParent, me, index);
  87011. }
  87012. }
  87013. return node;
  87014. }
  87015. },
  87016. /**
  87017. * Returns the bubble target for this node
  87018. * @private
  87019. * @return {Object} The bubble target
  87020. */
  87021. getBubbleTarget: function() {
  87022. return this.parentNode;
  87023. },
  87024. /**
  87025. * Removes a child node from this node.
  87026. * @param {Ext.data.NodeInterface} node The node to remove
  87027. * @param {Boolean} [destroy=false] True to destroy the node upon removal.
  87028. * @return {Ext.data.NodeInterface} The removed node
  87029. */
  87030. removeChild : function(node, destroy, suppressEvents, isMove) {
  87031. var me = this,
  87032. index = me.indexOf(node),
  87033. i, childCount;
  87034. if (index == -1 || (suppressEvents !== true && (!me.hasListeners.beforeremove || me.fireEvent("beforeremove", me, node, !!isMove) === false))) {
  87035. return false;
  87036. }
  87037. // remove it from childNodes collection
  87038. Ext.Array.erase(me.childNodes, index, 1);
  87039. // update child refs
  87040. if (me.firstChild == node) {
  87041. me.setFirstChild(node.nextSibling);
  87042. }
  87043. if (me.lastChild == node) {
  87044. me.setLastChild(node.previousSibling);
  87045. }
  87046. // update siblings
  87047. if (node.previousSibling) {
  87048. node.previousSibling.nextSibling = node.nextSibling;
  87049. }
  87050. if (node.nextSibling) {
  87051. node.nextSibling.previousSibling = node.previousSibling;
  87052. }
  87053. // update the info for all siblings starting at the index before the node's old index (or 0 if the removed node was the firstChild)
  87054. for(i = index > 0 ? index - 1 : 0, childCount = me.childNodes.length; i < childCount; i++) {
  87055. me.childNodes[i].updateInfo();
  87056. }
  87057. // If this node suddenly doesnt have childnodes anymore, update myself
  87058. if (!me.childNodes.length) {
  87059. me.triggerUIUpdate();
  87060. }
  87061. if (suppressEvents !== true) {
  87062. if (me.hasListeners.remove) {
  87063. me.fireEvent("remove", me, node, !!isMove);
  87064. }
  87065. }
  87066. if (destroy) {
  87067. node.destroy(true);
  87068. } else {
  87069. node.clear();
  87070. }
  87071. return node;
  87072. },
  87073. /**
  87074. * Creates a copy (clone) of this Node.
  87075. * @param {String} [id] A new id, defaults to this Node's id.
  87076. * @param {Boolean} [deep=false] True to recursively copy all child Nodes into the new Node.
  87077. * False to copy without child Nodes.
  87078. * @return {Ext.data.NodeInterface} A copy of this Node.
  87079. */
  87080. copy: function(newId, deep) {
  87081. var me = this,
  87082. result = me.callOverridden(arguments),
  87083. len = me.childNodes ? me.childNodes.length : 0,
  87084. i;
  87085. // Move child nodes across to the copy if required
  87086. if (deep) {
  87087. for (i = 0; i < len; i++) {
  87088. result.appendChild(me.childNodes[i].copy(true));
  87089. }
  87090. }
  87091. return result;
  87092. },
  87093. /**
  87094. * Clears the node.
  87095. * @private
  87096. * @param {Boolean} [destroy=false] True to destroy the node.
  87097. */
  87098. clear : function(destroy) {
  87099. var me = this;
  87100. // clear any references from the node
  87101. me.parentNode = me.previousSibling = me.nextSibling = null;
  87102. if (destroy) {
  87103. me.firstChild = me.lastChild = null;
  87104. }
  87105. },
  87106. /**
  87107. * Destroys the node.
  87108. */
  87109. destroy : function(silent) {
  87110. /*
  87111. * Silent is to be used in a number of cases
  87112. * 1) When setRoot is called.
  87113. * 2) When destroy on the tree is called
  87114. * 3) For destroying child nodes on a node
  87115. */
  87116. var me = this,
  87117. options = me.destroyOptions,
  87118. nodes = me.childNodes,
  87119. nLen = nodes.length,
  87120. n;
  87121. if (silent === true) {
  87122. me.clear(true);
  87123. for (n = 0; n < nLen; n++) {
  87124. nodes[n].destroy(true);
  87125. }
  87126. me.childNodes = null;
  87127. delete me.destroyOptions;
  87128. me.callOverridden([options]);
  87129. } else {
  87130. me.destroyOptions = silent;
  87131. // overridden method will be called, since remove will end up calling destroy(true);
  87132. me.remove(true);
  87133. }
  87134. },
  87135. /**
  87136. * Inserts the first node before the second node in this nodes childNodes collection.
  87137. * @param {Ext.data.NodeInterface} node The node to insert
  87138. * @param {Ext.data.NodeInterface} refNode The node to insert before (if null the node is appended)
  87139. * @return {Ext.data.NodeInterface} The inserted node
  87140. */
  87141. insertBefore : function(node, refNode, suppressEvents) {
  87142. var me = this,
  87143. index = me.indexOf(refNode),
  87144. oldParent = node.parentNode,
  87145. refIndex = index,
  87146. childCount, ps, i;
  87147. if (!refNode) { // like standard Dom, refNode can be null for append
  87148. return me.appendChild(node);
  87149. }
  87150. // nothing to do
  87151. if (node == refNode) {
  87152. return false;
  87153. }
  87154. // Make sure it is a record with the NodeInterface
  87155. node = me.createNode(node);
  87156. if (suppressEvents !== true && (!me.hasListeners.beforeinsert || me.fireEvent("beforeinsert", me, node, refNode) === false)) {
  87157. return false;
  87158. }
  87159. // when moving internally, indexes will change after remove
  87160. if (oldParent == me && me.indexOf(node) < index) {
  87161. refIndex--;
  87162. }
  87163. // it's a move, make sure we move it cleanly
  87164. if (oldParent) {
  87165. if (suppressEvents !== true && (!me.hasListeners.beforeremove || node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false)) {
  87166. return false;
  87167. }
  87168. oldParent.removeChild(node, false, false, true);
  87169. }
  87170. if (refIndex === 0) {
  87171. me.setFirstChild(node);
  87172. }
  87173. Ext.Array.splice(me.childNodes, refIndex, 0, node);
  87174. node.parentNode = me;
  87175. node.nextSibling = refNode;
  87176. refNode.previousSibling = node;
  87177. ps = me.childNodes[refIndex - 1];
  87178. if (ps) {
  87179. node.previousSibling = ps;
  87180. ps.nextSibling = node;
  87181. } else {
  87182. node.previousSibling = null;
  87183. }
  87184. // update the info for all siblings starting at the index before the node's insertion point (or 0 if the inserted node is the firstChild)
  87185. for(i = refIndex > 0 ? refIndex - 1 : 0, childCount = me.childNodes.length; i < childCount; i++) {
  87186. me.childNodes[i].updateInfo();
  87187. }
  87188. if (!me.isLoaded()) {
  87189. me.set('loaded', true);
  87190. }
  87191. // If this node didnt have any childnodes before, update myself
  87192. else if (me.childNodes.length === 1) {
  87193. me.triggerUIUpdate();
  87194. }
  87195. if(!node.isLeaf() && node.phantom) {
  87196. node.set('loaded', true);
  87197. }
  87198. if (suppressEvents !== true) {
  87199. if (me.hasListeners.insert) {
  87200. me.fireEvent("insert", me, node, refNode);
  87201. }
  87202. if (oldParent && me.hasListeners.move) {
  87203. node.fireEvent("move", node, oldParent, me, refIndex, refNode);
  87204. }
  87205. }
  87206. return node;
  87207. },
  87208. /**
  87209. * Inserts a node into this node.
  87210. * @param {Number} index The zero-based index to insert the node at
  87211. * @param {Ext.data.NodeInterface} node The node to insert
  87212. * @return {Ext.data.NodeInterface} The node you just inserted
  87213. */
  87214. insertChild: function(index, node) {
  87215. var sibling = this.childNodes[index];
  87216. if (sibling) {
  87217. return this.insertBefore(node, sibling);
  87218. }
  87219. else {
  87220. return this.appendChild(node);
  87221. }
  87222. },
  87223. /**
  87224. * Removes this node from its parent
  87225. * @param {Boolean} [destroy=false] True to destroy the node upon removal.
  87226. * @return {Ext.data.NodeInterface} this
  87227. */
  87228. remove : function(destroy, suppressEvents) {
  87229. var parentNode = this.parentNode;
  87230. if (parentNode) {
  87231. parentNode.removeChild(this, destroy, suppressEvents);
  87232. }
  87233. return this;
  87234. },
  87235. /**
  87236. * Removes all child nodes from this node.
  87237. * @param {Boolean} [destroy=false] <True to destroy the node upon removal.
  87238. * @return {Ext.data.NodeInterface} this
  87239. */
  87240. removeAll : function(destroy, suppressEvents) {
  87241. var cn = this.childNodes,
  87242. n;
  87243. while ((n = cn[0])) {
  87244. this.removeChild(n, destroy, suppressEvents);
  87245. }
  87246. return this;
  87247. },
  87248. /**
  87249. * Returns the child node at the specified index.
  87250. * @param {Number} index
  87251. * @return {Ext.data.NodeInterface}
  87252. */
  87253. getChildAt : function(index) {
  87254. return this.childNodes[index];
  87255. },
  87256. /**
  87257. * Replaces one child node in this node with another.
  87258. * @param {Ext.data.NodeInterface} newChild The replacement node
  87259. * @param {Ext.data.NodeInterface} oldChild The node to replace
  87260. * @return {Ext.data.NodeInterface} The replaced node
  87261. */
  87262. replaceChild : function(newChild, oldChild, suppressEvents) {
  87263. var s = oldChild ? oldChild.nextSibling : null;
  87264. this.removeChild(oldChild, false, suppressEvents);
  87265. this.insertBefore(newChild, s, suppressEvents);
  87266. return oldChild;
  87267. },
  87268. /**
  87269. * Returns the index of a child node
  87270. * @param {Ext.data.NodeInterface} node
  87271. * @return {Number} The index of the node or -1 if it was not found
  87272. */
  87273. indexOf : function(child) {
  87274. return Ext.Array.indexOf(this.childNodes, child);
  87275. },
  87276. /**
  87277. * Returns the index of a child node that matches the id
  87278. * @param {String} id The id of the node to find
  87279. * @return {Number} The index of the node or -1 if it was not found
  87280. */
  87281. indexOfId: function(id) {
  87282. var childNodes = this.childNodes,
  87283. len = childNodes.length,
  87284. i = 0;
  87285. for (; i < len; ++i) {
  87286. if (childNodes[i].getId() === id) {
  87287. return i;
  87288. }
  87289. }
  87290. return -1;
  87291. },
  87292. /**
  87293. * Gets the hierarchical path from the root of the current node.
  87294. * @param {String} [field] The field to construct the path from. Defaults to the model idProperty.
  87295. * @param {String} [separator="/"] A separator to use.
  87296. * @return {String} The node path
  87297. */
  87298. getPath: function(field, separator) {
  87299. field = field || this.idProperty;
  87300. separator = separator || '/';
  87301. var path = [this.get(field)],
  87302. parent = this.parentNode;
  87303. while (parent) {
  87304. path.unshift(parent.get(field));
  87305. parent = parent.parentNode;
  87306. }
  87307. return separator + path.join(separator);
  87308. },
  87309. /**
  87310. * Returns depth of this node (the root node has a depth of 0)
  87311. * @return {Number}
  87312. */
  87313. getDepth : function() {
  87314. return this.get('depth');
  87315. },
  87316. /**
  87317. * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
  87318. * will be the args provided or the current node. If the function returns false at any point,
  87319. * the bubble is stopped.
  87320. * @param {Function} fn The function to call
  87321. * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
  87322. * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
  87323. */
  87324. bubble : function(fn, scope, args) {
  87325. var p = this;
  87326. while (p) {
  87327. if (fn.apply(scope || p, args || [p]) === false) {
  87328. break;
  87329. }
  87330. p = p.parentNode;
  87331. }
  87332. },
  87333. cascade: function() {
  87334. if (Ext.isDefined(Ext.global.console)) {
  87335. Ext.global.console.warn('Ext.data.Node: cascade has been deprecated. Please use cascadeBy instead.');
  87336. }
  87337. return this.cascadeBy.apply(this, arguments);
  87338. },
  87339. /**
  87340. * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
  87341. * will be the args provided or the current node. If the function returns false at any point,
  87342. * the cascade is stopped on that branch.
  87343. * @param {Function} fn The function to call
  87344. * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
  87345. * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
  87346. */
  87347. cascadeBy : function(fn, scope, args) {
  87348. if (fn.apply(scope || this, args || [this]) !== false) {
  87349. var childNodes = this.childNodes,
  87350. length = childNodes.length,
  87351. i;
  87352. for (i = 0; i < length; i++) {
  87353. childNodes[i].cascadeBy(fn, scope, args);
  87354. }
  87355. }
  87356. },
  87357. /**
  87358. * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
  87359. * will be the args provided or the current node. If the function returns false at any point,
  87360. * the iteration stops.
  87361. * @param {Function} fn The function to call
  87362. * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node in iteration.
  87363. * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
  87364. */
  87365. eachChild : function(fn, scope, args) {
  87366. var childNodes = this.childNodes,
  87367. length = childNodes.length,
  87368. i;
  87369. for (i = 0; i < length; i++) {
  87370. if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
  87371. break;
  87372. }
  87373. }
  87374. },
  87375. /**
  87376. * Finds the first child that has the attribute with the specified value.
  87377. * @param {String} attribute The attribute name
  87378. * @param {Object} value The value to search for
  87379. * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
  87380. * @return {Ext.data.NodeInterface} The found child or null if none was found
  87381. */
  87382. findChild : function(attribute, value, deep) {
  87383. return this.findChildBy(function() {
  87384. return this.get(attribute) == value;
  87385. }, null, deep);
  87386. },
  87387. /**
  87388. * Finds the first child by a custom function. The child matches if the function passed returns true.
  87389. * @param {Function} fn A function which must return true if the passed Node is the required Node.
  87390. * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the Node being tested.
  87391. * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
  87392. * @return {Ext.data.NodeInterface} The found child or null if none was found
  87393. */
  87394. findChildBy : function(fn, scope, deep) {
  87395. var cs = this.childNodes,
  87396. len = cs.length,
  87397. i = 0, n, res;
  87398. for (; i < len; i++) {
  87399. n = cs[i];
  87400. if (fn.call(scope || n, n) === true) {
  87401. return n;
  87402. }
  87403. else if (deep) {
  87404. res = n.findChildBy(fn, scope, deep);
  87405. if (res !== null) {
  87406. return res;
  87407. }
  87408. }
  87409. }
  87410. return null;
  87411. },
  87412. /**
  87413. * Returns true if this node is an ancestor (at any point) of the passed node.
  87414. * @param {Ext.data.NodeInterface} node
  87415. * @return {Boolean}
  87416. */
  87417. contains : function(node) {
  87418. return node.isAncestor(this);
  87419. },
  87420. /**
  87421. * Returns true if the passed node is an ancestor (at any point) of this node.
  87422. * @param {Ext.data.NodeInterface} node
  87423. * @return {Boolean}
  87424. */
  87425. isAncestor : function(node) {
  87426. var p = this.parentNode;
  87427. while (p) {
  87428. if (p == node) {
  87429. return true;
  87430. }
  87431. p = p.parentNode;
  87432. }
  87433. return false;
  87434. },
  87435. /**
  87436. * Sorts this nodes children using the supplied sort function.
  87437. * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
  87438. * @param {Boolean} [recursive=false] True to apply this sort recursively
  87439. * @param {Boolean} [suppressEvent=false] True to not fire a sort event.
  87440. */
  87441. sort : function(sortFn, recursive, suppressEvent) {
  87442. var cs = this.childNodes,
  87443. ln = cs.length,
  87444. i, n;
  87445. if (ln > 0) {
  87446. Ext.Array.sort(cs, sortFn);
  87447. for (i = 0; i < ln; i++) {
  87448. n = cs[i];
  87449. n.previousSibling = cs[i-1];
  87450. n.nextSibling = cs[i+1];
  87451. if (i === 0) {
  87452. this.setFirstChild(n);
  87453. }
  87454. if (i == ln - 1) {
  87455. this.setLastChild(n);
  87456. }
  87457. n.updateInfo();
  87458. if (recursive && !n.isLeaf()) {
  87459. n.sort(sortFn, true, true);
  87460. }
  87461. }
  87462. if (suppressEvent !== true) {
  87463. this.fireEvent('sort', this, cs);
  87464. }
  87465. }
  87466. },
  87467. /**
  87468. * Returns true if this node is expaned
  87469. * @return {Boolean}
  87470. */
  87471. isExpanded: function() {
  87472. return this.get('expanded');
  87473. },
  87474. /**
  87475. * Returns true if this node is loaded
  87476. * @return {Boolean}
  87477. */
  87478. isLoaded: function() {
  87479. return this.get('loaded');
  87480. },
  87481. /**
  87482. * Returns true if this node is loading
  87483. * @return {Boolean}
  87484. */
  87485. isLoading: function() {
  87486. return this.get('loading');
  87487. },
  87488. /**
  87489. * Returns true if this node is the root node
  87490. * @return {Boolean}
  87491. */
  87492. isRoot: function() {
  87493. return !this.parentNode;
  87494. },
  87495. /**
  87496. * Returns true if this node is visible
  87497. * @return {Boolean}
  87498. */
  87499. isVisible: function() {
  87500. var parent = this.parentNode;
  87501. while (parent) {
  87502. if (!parent.isExpanded()) {
  87503. return false;
  87504. }
  87505. parent = parent.parentNode;
  87506. }
  87507. return true;
  87508. },
  87509. /**
  87510. * Expand this node.
  87511. * @param {Boolean} [recursive=false] True to recursively expand all the children
  87512. * @param {Function} [callback] The function to execute once the expand completes
  87513. * @param {Object} [scope] The scope to run the callback in
  87514. */
  87515. expand: function(recursive, callback, scope) {
  87516. var me = this;
  87517. // all paths must call the callback (eventually) or things like
  87518. // selectPath fail
  87519. // First we start by checking if this node is a parent
  87520. if (!me.isLeaf()) {
  87521. // If it's loaded, wait until it loads before proceeding
  87522. if (me.isLoading()) {
  87523. me.on('expand', function(){
  87524. me.expand(recursive, callback, scope);
  87525. }, me, {single: true});
  87526. } else {
  87527. // Now we check if this record is already expanding or expanded
  87528. if (!me.isExpanded()) {
  87529. // The TreeStore actually listens for the beforeexpand method and checks
  87530. // whether we have to asynchronously load the children from the server
  87531. // first. Thats why we pass a callback function to the event that the
  87532. // store can call once it has loaded and parsed all the children.
  87533. me.fireEvent('beforeexpand', me, function() {
  87534. me.set('expanded', true);
  87535. if (me.hasListeners.expand) {
  87536. me.fireEvent('expand', me, me.childNodes, false);
  87537. }
  87538. // Call the expandChildren method if recursive was set to true
  87539. if (recursive) {
  87540. me.expandChildren(true, callback, scope);
  87541. } else {
  87542. Ext.callback(callback, scope || me, [me.childNodes]);
  87543. }
  87544. }, me);
  87545. } else if (recursive) {
  87546. // If it is is already expanded but we want to recursively expand then call expandChildren
  87547. me.expandChildren(true, callback, scope);
  87548. } else {
  87549. Ext.callback(callback, scope || me, [me.childNodes]);
  87550. }
  87551. }
  87552. } else {
  87553. // If it's not then we fire the callback right away
  87554. Ext.callback(callback, scope || me); // leaf = no childNodes
  87555. }
  87556. },
  87557. /**
  87558. * Expand all the children of this node.
  87559. * @param {Boolean} [recursive=false] True to recursively expand all the children
  87560. * @param {Function} [callback] The function to execute once all the children are expanded
  87561. * @param {Object} [scope] The scope to run the callback in
  87562. */
  87563. expandChildren: function(recursive, callback, scope) {
  87564. var me = this,
  87565. i = 0,
  87566. nodes = me.childNodes,
  87567. ln = nodes.length,
  87568. node,
  87569. expanding = 0;
  87570. for (; i < ln; ++i) {
  87571. node = nodes[i];
  87572. if (!node.isLeaf()) {
  87573. expanding++;
  87574. nodes[i].expand(recursive, function () {
  87575. expanding--;
  87576. if (callback && !expanding) {
  87577. Ext.callback(callback, scope || me, [me.childNodes]);
  87578. }
  87579. });
  87580. }
  87581. }
  87582. if (!expanding && callback) {
  87583. Ext.callback(callback, scope || me, [me.childNodes]); }
  87584. },
  87585. /**
  87586. * Collapse this node.
  87587. * @param {Boolean} [recursive=false] True to recursively collapse all the children
  87588. * @param {Function} [callback] The function to execute once the collapse completes
  87589. * @param {Object} [scope] The scope to run the callback in
  87590. */
  87591. collapse: function(recursive, callback, scope) {
  87592. var me = this;
  87593. // First we start by checking if this node is a parent
  87594. if (!me.isLeaf()) {
  87595. // Now we check if this record is already collapsing or collapsed
  87596. if (!me.collapsing && me.isExpanded()) {
  87597. me.fireEvent('beforecollapse', me, function() {
  87598. me.set('expanded', false);
  87599. if (me.hasListeners.collapse) {
  87600. me.fireEvent('collapse', me, me.childNodes, false);
  87601. }
  87602. // Call the collapseChildren method if recursive was set to true
  87603. if (recursive) {
  87604. me.collapseChildren(true, callback, scope);
  87605. }
  87606. else {
  87607. Ext.callback(callback, scope || me, [me.childNodes]);
  87608. }
  87609. }, me);
  87610. }
  87611. // If it is is already collapsed but we want to recursively collapse then call collapseChildren
  87612. else if (recursive) {
  87613. me.collapseChildren(true, callback, scope);
  87614. } else {
  87615. Ext.callback(callback, scope || me, [me.childNodes]);
  87616. }
  87617. }
  87618. // If it's not then we fire the callback right away
  87619. else {
  87620. Ext.callback(callback, scope || me, [me.childNodes]);
  87621. }
  87622. },
  87623. /**
  87624. * Collapse all the children of this node.
  87625. * @param {Function} [recursive=false] True to recursively collapse all the children
  87626. * @param {Function} [callback] The function to execute once all the children are collapsed
  87627. * @param {Object} [scope] The scope to run the callback in
  87628. */
  87629. collapseChildren: function(recursive, callback, scope) {
  87630. var me = this,
  87631. i = 0,
  87632. nodes = me.childNodes,
  87633. ln = nodes.length,
  87634. node,
  87635. collapsing = 0;
  87636. for (; i < ln; ++i) {
  87637. node = nodes[i];
  87638. if (!node.isLeaf()) {
  87639. collapsing++;
  87640. nodes[i].collapse(recursive, function () {
  87641. collapsing--;
  87642. if (callback && !collapsing) {
  87643. Ext.callback(callback, scope || me, [me.childNodes]);
  87644. }
  87645. });
  87646. }
  87647. }
  87648. if (!collapsing && callback) {
  87649. Ext.callback(callback, scope || me, [me.childNodes]);
  87650. }
  87651. }
  87652. };
  87653. }
  87654. }
  87655. });
  87656. /**
  87657. * Node Store
  87658. * @private
  87659. */
  87660. Ext.define('Ext.data.NodeStore', {
  87661. extend: 'Ext.data.Store',
  87662. alias: 'store.node',
  87663. requires: ['Ext.data.NodeInterface'],
  87664. /**
  87665. * @cfg {Ext.data.Model} node
  87666. * The Record you want to bind this Store to. Note that
  87667. * this record will be decorated with the Ext.data.NodeInterface if this is not the
  87668. * case yet.
  87669. */
  87670. node: null,
  87671. /**
  87672. * @cfg {Boolean} recursive
  87673. * Set this to true if you want this NodeStore to represent
  87674. * all the descendents of the node in its flat data collection. This is useful for
  87675. * rendering a tree structure to a DataView and is being used internally by
  87676. * the TreeView. Any records that are moved, removed, inserted or appended to the
  87677. * node at any depth below the node this store is bound to will be automatically
  87678. * updated in this Store's internal flat data structure.
  87679. */
  87680. recursive: false,
  87681. /**
  87682. * @cfg {Boolean} rootVisible
  87683. * False to not include the root node in this Stores collection.
  87684. */
  87685. rootVisible: false,
  87686. /**
  87687. * @cfg {Ext.data.TreeStore} treeStore
  87688. * The TreeStore that is used by this NodeStore's Ext.tree.View.
  87689. */
  87690. constructor: function(config) {
  87691. var me = this,
  87692. node;
  87693. config = config || {};
  87694. Ext.apply(me, config);
  87695. if (Ext.isDefined(me.proxy)) {
  87696. Ext.Error.raise("A NodeStore cannot be bound to a proxy. Instead bind it to a record " +
  87697. "decorated with the NodeInterface by setting the node config.");
  87698. }
  87699. me.useModelWarning = false;
  87700. config.proxy = {type: 'proxy'};
  87701. me.callParent([config]);
  87702. node = me.node;
  87703. if (node) {
  87704. me.node = null;
  87705. me.setNode(node);
  87706. }
  87707. },
  87708. setNode: function(node) {
  87709. var me = this;
  87710. if (me.node && me.node != node) {
  87711. // We want to unbind our listeners on the old node
  87712. me.mun(me.node, {
  87713. expand: me.onNodeExpand,
  87714. collapse: me.onNodeCollapse,
  87715. append: me.onNodeAppend,
  87716. insert: me.onNodeInsert,
  87717. remove: me.onNodeRemove,
  87718. sort: me.onNodeSort,
  87719. scope: me
  87720. });
  87721. me.node = null;
  87722. }
  87723. if (node) {
  87724. Ext.data.NodeInterface.decorate(node.self);
  87725. me.removeAll();
  87726. if (me.rootVisible) {
  87727. me.add(node);
  87728. } else if (!node.isExpanded() && me.treeStore.autoLoad !== false) {
  87729. node.expand();
  87730. }
  87731. me.mon(node, {
  87732. expand: me.onNodeExpand,
  87733. collapse: me.onNodeCollapse,
  87734. append: me.onNodeAppend,
  87735. insert: me.onNodeInsert,
  87736. remove: me.onNodeRemove,
  87737. sort: me.onNodeSort,
  87738. scope: me
  87739. });
  87740. me.node = node;
  87741. if (node.isExpanded() && node.isLoaded()) {
  87742. me.onNodeExpand(node, node.childNodes, true);
  87743. }
  87744. }
  87745. },
  87746. onNodeSort: function(node, childNodes) {
  87747. var me = this;
  87748. if ((me.indexOf(node) !== -1 || (node === me.node && !me.rootVisible) && node.isExpanded())) {
  87749. me.onNodeCollapse(node, childNodes, true);
  87750. me.onNodeExpand(node, childNodes, true);
  87751. }
  87752. },
  87753. onNodeExpand: function(parent, records, suppressEvent) {
  87754. var me = this,
  87755. insertIndex = me.indexOf(parent) + 1,
  87756. ln = records ? records.length : 0,
  87757. i, record;
  87758. if (!me.recursive && parent !== me.node) {
  87759. return;
  87760. }
  87761. if (parent !== this.node && !me.isVisible(parent)) {
  87762. return;
  87763. }
  87764. if (!suppressEvent && me.fireEvent('beforeexpand', parent, records, insertIndex) === false) {
  87765. return;
  87766. }
  87767. if (ln) {
  87768. me.insert(insertIndex, records);
  87769. for (i = 0; i < ln; i++) {
  87770. record = records[i];
  87771. if (record.isExpanded()) {
  87772. if (record.isLoaded()) {
  87773. // Take a shortcut
  87774. me.onNodeExpand(record, record.childNodes, true);
  87775. }
  87776. else {
  87777. record.set('expanded', false);
  87778. record.expand();
  87779. }
  87780. }
  87781. }
  87782. }
  87783. if (!suppressEvent) {
  87784. me.fireEvent('expand', parent, records);
  87785. }
  87786. },
  87787. onNodeCollapse: function(parent, records, suppressEvent) {
  87788. var me = this,
  87789. ln = records.length,
  87790. collapseIndex = me.indexOf(parent) + 1,
  87791. i, record;
  87792. if (!me.recursive && parent !== me.node) {
  87793. return;
  87794. }
  87795. if (!suppressEvent && me.fireEvent('beforecollapse', parent, records, collapseIndex) === false) {
  87796. return;
  87797. }
  87798. for (i = 0; i < ln; i++) {
  87799. record = records[i];
  87800. me.remove(record);
  87801. if (record.isExpanded()) {
  87802. me.onNodeCollapse(record, record.childNodes, true);
  87803. }
  87804. }
  87805. if (!suppressEvent) {
  87806. me.fireEvent('collapse', parent, records, collapseIndex);
  87807. }
  87808. },
  87809. onNodeAppend: function(parent, node, index) {
  87810. var me = this,
  87811. refNode, sibling;
  87812. if (me.isVisible(node)) {
  87813. if (index === 0) {
  87814. refNode = parent;
  87815. } else {
  87816. sibling = node.previousSibling;
  87817. while (sibling.isExpanded() && sibling.lastChild) {
  87818. sibling = sibling.lastChild;
  87819. }
  87820. refNode = sibling;
  87821. }
  87822. me.insert(me.indexOf(refNode) + 1, node);
  87823. if (!node.isLeaf() && node.isExpanded()) {
  87824. if (node.isLoaded()) {
  87825. // Take a shortcut
  87826. me.onNodeExpand(node, node.childNodes, true);
  87827. }
  87828. else {
  87829. node.set('expanded', false);
  87830. node.expand();
  87831. }
  87832. }
  87833. }
  87834. },
  87835. onNodeInsert: function(parent, node, refNode) {
  87836. var me = this,
  87837. index = this.indexOf(refNode);
  87838. if (index != -1 && me.isVisible(node)) {
  87839. me.insert(index, node);
  87840. if (!node.isLeaf() && node.isExpanded()) {
  87841. if (node.isLoaded()) {
  87842. // Take a shortcut
  87843. me.onNodeExpand(node, node.childNodes, true);
  87844. }
  87845. else {
  87846. node.set('expanded', false);
  87847. node.expand();
  87848. }
  87849. }
  87850. }
  87851. },
  87852. onNodeRemove: function(parent, node, index) {
  87853. var me = this;
  87854. if (me.indexOf(node) != -1) {
  87855. if (!node.isLeaf() && node.isExpanded()) {
  87856. me.onNodeCollapse(node, node.childNodes, true);
  87857. }
  87858. me.remove(node);
  87859. }
  87860. },
  87861. isVisible: function(node) {
  87862. var parent = node.parentNode;
  87863. while (parent) {
  87864. if (parent === this.node && !this.rootVisible && parent.isExpanded()) {
  87865. return true;
  87866. }
  87867. if (this.indexOf(parent) === -1 || !parent.isExpanded()) {
  87868. return false;
  87869. }
  87870. parent = parent.parentNode;
  87871. }
  87872. return true;
  87873. }
  87874. });
  87875. /**
  87876. * @author Ed Spencer
  87877. *
  87878. * Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
  87879. * All this class does is standardize the representation of a Request as used by any ServerProxy subclass,
  87880. * it does not contain any actual logic or perform the request itself.
  87881. */
  87882. Ext.define('Ext.data.Request', {
  87883. /**
  87884. * @cfg {String} action
  87885. * The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'.
  87886. */
  87887. action: undefined,
  87888. /**
  87889. * @cfg {Object} params
  87890. * HTTP request params. The Proxy and its Writer have access to and can modify this object.
  87891. */
  87892. params: undefined,
  87893. /**
  87894. * @cfg {String} method
  87895. * The HTTP method to use on this Request. Should be one of 'GET', 'POST', 'PUT' or 'DELETE'.
  87896. */
  87897. method: 'GET',
  87898. /**
  87899. * @cfg {String} url
  87900. * The url to access on this Request
  87901. */
  87902. url: undefined,
  87903. /**
  87904. * Creates the Request object.
  87905. * @param {Object} [config] Config object.
  87906. */
  87907. constructor: function(config) {
  87908. Ext.apply(this, config);
  87909. }
  87910. });
  87911. /**
  87912. * @author Don Griffin
  87913. *
  87914. * This class is a sequential id generator. A simple use of this class would be like so:
  87915. *
  87916. * Ext.define('MyApp.data.MyModel', {
  87917. * extend: 'Ext.data.Model',
  87918. * idgen: 'sequential'
  87919. * });
  87920. * // assign id's of 1, 2, 3, etc.
  87921. *
  87922. * An example of a configured generator would be:
  87923. *
  87924. * Ext.define('MyApp.data.MyModel', {
  87925. * extend: 'Ext.data.Model',
  87926. * idgen: {
  87927. * type: 'sequential',
  87928. * prefix: 'ID_',
  87929. * seed: 1000
  87930. * }
  87931. * });
  87932. * // assign id's of ID_1000, ID_1001, ID_1002, etc.
  87933. *
  87934. */
  87935. Ext.define('Ext.data.SequentialIdGenerator', {
  87936. extend: 'Ext.data.IdGenerator',
  87937. alias: 'idgen.sequential',
  87938. constructor: function() {
  87939. var me = this;
  87940. me.callParent(arguments);
  87941. me.parts = [ me.prefix, ''];
  87942. },
  87943. /**
  87944. * @cfg {String} prefix
  87945. * The string to place in front of the sequential number for each generated id. The
  87946. * default is blank.
  87947. */
  87948. prefix: '',
  87949. /**
  87950. * @cfg {Number} seed
  87951. * The number at which to start generating sequential id's. The default is 1.
  87952. */
  87953. seed: 1,
  87954. /**
  87955. * Generates and returns the next id.
  87956. * @return {String} The next id.
  87957. */
  87958. generate: function () {
  87959. var me = this,
  87960. parts = me.parts;
  87961. parts[1] = me.seed++;
  87962. return parts.join('');
  87963. }
  87964. });
  87965. /**
  87966. * @class Ext.data.Tree
  87967. *
  87968. * This class is used as a container for a series of nodes. The nodes themselves maintain
  87969. * the relationship between parent/child. The tree itself acts as a manager. It gives functionality
  87970. * to retrieve a node by its identifier: {@link #getNodeById}.
  87971. *
  87972. * The tree also relays events from any of it's child nodes, allowing them to be handled in a
  87973. * centralized fashion. In general this class is not used directly, rather used internally
  87974. * by other parts of the framework.
  87975. *
  87976. */
  87977. Ext.define('Ext.data.Tree', {
  87978. alias: 'data.tree',
  87979. mixins: {
  87980. observable: "Ext.util.Observable"
  87981. },
  87982. /**
  87983. * @property {Ext.data.NodeInterface}
  87984. * The root node for this tree
  87985. */
  87986. root: null,
  87987. /**
  87988. * Creates new Tree object.
  87989. * @param {Ext.data.NodeInterface} root (optional) The root node
  87990. */
  87991. constructor: function(root) {
  87992. var me = this;
  87993. me.mixins.observable.constructor.call(me);
  87994. if (root) {
  87995. me.setRootNode(root);
  87996. }
  87997. },
  87998. /**
  87999. * Returns the root node for this tree.
  88000. * @return {Ext.data.NodeInterface}
  88001. */
  88002. getRootNode : function() {
  88003. return this.root;
  88004. },
  88005. /**
  88006. * Sets the root node for this tree.
  88007. * @param {Ext.data.NodeInterface} node
  88008. * @return {Ext.data.NodeInterface} The root node
  88009. */
  88010. setRootNode : function(node) {
  88011. var me = this;
  88012. me.root = node;
  88013. if (me.fireEvent('beforeappend', null, node) !== false) {
  88014. node.set('root', true);
  88015. node.updateInfo();
  88016. // root node should never be phantom or dirty, so commit it
  88017. node.commit();
  88018. node.on({
  88019. scope: me,
  88020. insert: me.onNodeInsert,
  88021. append: me.onNodeAppend,
  88022. remove: me.onNodeRemove
  88023. });
  88024. me.relayEvents(node, [
  88025. /**
  88026. * @event append
  88027. * @inheritdoc Ext.data.NodeInterface#append
  88028. */
  88029. "append",
  88030. /**
  88031. * @event remove
  88032. * @inheritdoc Ext.data.NodeInterface#remove
  88033. */
  88034. "remove",
  88035. /**
  88036. * @event move
  88037. * @inheritdoc Ext.data.NodeInterface#move
  88038. */
  88039. "move",
  88040. /**
  88041. * @event insert
  88042. * @inheritdoc Ext.data.NodeInterface#insert
  88043. */
  88044. "insert",
  88045. /**
  88046. * @event beforeappend
  88047. * @inheritdoc Ext.data.NodeInterface#beforeappend
  88048. */
  88049. "beforeappend",
  88050. /**
  88051. * @event beforeremove
  88052. * @inheritdoc Ext.data.NodeInterface#beforeremove
  88053. */
  88054. "beforeremove",
  88055. /**
  88056. * @event beforemove
  88057. * @inheritdoc Ext.data.NodeInterface#beforemove
  88058. */
  88059. "beforemove",
  88060. /**
  88061. * @event beforeinsert
  88062. * @inheritdoc Ext.data.NodeInterface#beforeinsert
  88063. */
  88064. "beforeinsert",
  88065. /**
  88066. * @event expand
  88067. * @inheritdoc Ext.data.NodeInterface#expand
  88068. */
  88069. "expand",
  88070. /**
  88071. * @event collapse
  88072. * @inheritdoc Ext.data.NodeInterface#collapse
  88073. */
  88074. "collapse",
  88075. /**
  88076. * @event beforeexpand
  88077. * @inheritdoc Ext.data.NodeInterface#beforeexpand
  88078. */
  88079. "beforeexpand",
  88080. /**
  88081. * @event beforecollapse
  88082. * @inheritdoc Ext.data.NodeInterface#beforecollapse
  88083. */
  88084. "beforecollapse" ,
  88085. /**
  88086. * @event sort
  88087. * @inheritdoc Ext.data.NodeInterface#event-sort
  88088. */
  88089. "sort",
  88090. /**
  88091. * @event rootchange
  88092. * Fires whenever the root node is changed in the tree.
  88093. * @param {Ext.data.Model} root The new root
  88094. */
  88095. "rootchange"
  88096. ]);
  88097. me.nodeHash = {};
  88098. me.registerNode(node);
  88099. me.fireEvent('append', null, node);
  88100. me.fireEvent('rootchange', node);
  88101. }
  88102. return node;
  88103. },
  88104. /**
  88105. * Flattens all the nodes in the tree into an array.
  88106. * @private
  88107. * @return {Ext.data.NodeInterface[]} The flattened nodes.
  88108. */
  88109. flatten: function(){
  88110. return Ext.Object.getValues(this.nodeHash);
  88111. },
  88112. /**
  88113. * Fired when a node is inserted into the root or one of it's children
  88114. * @private
  88115. * @param {Ext.data.NodeInterface} parent The parent node
  88116. * @param {Ext.data.NodeInterface} node The inserted node
  88117. */
  88118. onNodeInsert: function(parent, node) {
  88119. this.registerNode(node, true);
  88120. },
  88121. /**
  88122. * Fired when a node is appended into the root or one of it's children
  88123. * @private
  88124. * @param {Ext.data.NodeInterface} parent The parent node
  88125. * @param {Ext.data.NodeInterface} node The appended node
  88126. */
  88127. onNodeAppend: function(parent, node) {
  88128. this.registerNode(node, true);
  88129. },
  88130. /**
  88131. * Fired when a node is removed from the root or one of it's children
  88132. * @private
  88133. * @param {Ext.data.NodeInterface} parent The parent node
  88134. * @param {Ext.data.NodeInterface} node The removed node
  88135. */
  88136. onNodeRemove: function(parent, node) {
  88137. this.unregisterNode(node, true);
  88138. },
  88139. /**
  88140. * Fired when a node's id changes. Updates the node's id in the node hash.
  88141. * @private
  88142. * @param {Ext.data.NodeInterface} node
  88143. * @param {Number} oldId The old id
  88144. * @param {Number} newId The new id
  88145. */
  88146. onNodeIdChanged: function(node, oldId, newId) {
  88147. var nodeHash = this.nodeHash;
  88148. nodeHash[newId] = node;
  88149. delete nodeHash[oldId || node.internalId];
  88150. },
  88151. /**
  88152. * Gets a node in this tree by its id.
  88153. * @param {String} id
  88154. * @return {Ext.data.NodeInterface} The match node.
  88155. */
  88156. getNodeById : function(id) {
  88157. return this.nodeHash[id];
  88158. },
  88159. /**
  88160. * Registers a node with the tree
  88161. * @private
  88162. * @param {Ext.data.NodeInterface} The node to register
  88163. * @param {Boolean} [includeChildren] True to unregister any child nodes
  88164. */
  88165. registerNode : function(node, includeChildren) {
  88166. var me = this;
  88167. me.nodeHash[node.getId() || node.internalId] = node;
  88168. node.on('idchanged', me.onNodeIdChanged, me);
  88169. if (includeChildren === true) {
  88170. node.eachChild(function(child){
  88171. me.registerNode(child, true);
  88172. });
  88173. }
  88174. },
  88175. /**
  88176. * Unregisters a node with the tree
  88177. * @private
  88178. * @param {Ext.data.NodeInterface} The node to unregister
  88179. * @param {Boolean} [includeChildren] True to unregister any child nodes
  88180. */
  88181. unregisterNode : function(node, includeChildren) {
  88182. delete this.nodeHash[node.getId() || node.internalId];
  88183. if (includeChildren === true) {
  88184. node.eachChild(function(child){
  88185. this.unregisterNode(child, true);
  88186. }, this);
  88187. }
  88188. },
  88189. /**
  88190. * Sorts this tree
  88191. * @private
  88192. * @param {Function} sorterFn The function to use for sorting
  88193. * @param {Boolean} recursive True to perform recursive sorting
  88194. */
  88195. sort: function(sorterFn, recursive) {
  88196. this.getRootNode().sort(sorterFn, recursive);
  88197. },
  88198. /**
  88199. * Filters this tree
  88200. * @private
  88201. * @param {Function} sorterFn The function to use for filtering
  88202. * @param {Boolean} recursive True to perform recursive filtering
  88203. */
  88204. filter: function(filters, recursive) {
  88205. this.getRootNode().filter(filters, recursive);
  88206. }
  88207. });
  88208. /**
  88209. * The TreeStore is a store implementation that is backed by by an {@link Ext.data.Tree}.
  88210. * It provides convenience methods for loading nodes, as well as the ability to use
  88211. * the hierarchical tree structure combined with a store. This class is generally used
  88212. * in conjunction with {@link Ext.tree.Panel}. This class also relays many events from
  88213. * the Tree for convenience.
  88214. *
  88215. * # Using Models
  88216. *
  88217. * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
  88218. * The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed
  88219. * in the {@link Ext.data.NodeInterface} documentation.
  88220. *
  88221. * # Reading Nested Data
  88222. *
  88223. * For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
  88224. * so the reader can find nested data for each node (if a root is not specified, it will default to
  88225. * 'children'). This will tell the tree to look for any nested tree nodes by the same keyword, i.e., 'children'.
  88226. * If a root is specified in the config make sure that any nested nodes with children have the same name.
  88227. * Note that setting {@link #defaultRootProperty} accomplishes the same thing.
  88228. */
  88229. Ext.define('Ext.data.TreeStore', {
  88230. extend: 'Ext.data.AbstractStore',
  88231. alias: 'store.tree',
  88232. requires: [
  88233. 'Ext.util.Sorter',
  88234. 'Ext.data.Tree',
  88235. 'Ext.data.NodeInterface'
  88236. ],
  88237. /**
  88238. * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
  88239. * The root node for this store. For example:
  88240. *
  88241. * root: {
  88242. * expanded: true,
  88243. * text: "My Root",
  88244. * children: [
  88245. * { text: "Child 1", leaf: true },
  88246. * { text: "Child 2", expanded: true, children: [
  88247. * { text: "GrandChild", leaf: true }
  88248. * ] }
  88249. * ]
  88250. * }
  88251. *
  88252. * Setting the `root` config option is the same as calling {@link #setRootNode}.
  88253. */
  88254. /**
  88255. * @cfg {Boolean} [clearOnLoad=true]
  88256. * Remove previously existing child nodes before loading.
  88257. */
  88258. clearOnLoad : true,
  88259. /**
  88260. * @cfg {Boolean} [clearRemovedOnLoad=true]
  88261. * If `true`, when a node is reloaded, any records in the {@link #removed} record collection that were previously descendants of the node being reloaded will be cleared from the {@link #removed} collection.
  88262. * Only applicable if {@link #clearOnLoad} is `true`.
  88263. */
  88264. clearRemovedOnLoad: true,
  88265. /**
  88266. * @cfg {String} [nodeParam="node"]
  88267. * The name of the parameter sent to the server which contains the identifier of the node.
  88268. */
  88269. nodeParam: 'node',
  88270. /**
  88271. * @cfg {String} [defaultRootId="root"]
  88272. * The default root id.
  88273. */
  88274. defaultRootId: 'root',
  88275. /**
  88276. * @cfg {String} [defaultRootProperty="children"]
  88277. * The root property to specify on the reader if one is not explicitly defined.
  88278. */
  88279. defaultRootProperty: 'children',
  88280. // Keep a copy of the default so we know if it's been changed in a subclass/config
  88281. rootProperty: 'children',
  88282. /**
  88283. * @cfg {Boolean} [folderSort=false]
  88284. * Set to true to automatically prepend a leaf sorter.
  88285. */
  88286. folderSort: false,
  88287. constructor: function(config) {
  88288. var me = this,
  88289. root,
  88290. fields,
  88291. defaultRoot;
  88292. config = Ext.apply({}, config);
  88293. /**
  88294. * If we have no fields declare for the store, add some defaults.
  88295. * These will be ignored if a model is explicitly specified.
  88296. */
  88297. fields = config.fields || me.fields;
  88298. if (!fields) {
  88299. config.fields = [
  88300. {name: 'text', type: 'string'}
  88301. ];
  88302. defaultRoot = config.defaultRootProperty || me.defaultRootProperty;
  88303. if (defaultRoot !== me.defaultRootProperty) {
  88304. config.fields.push({
  88305. name: defaultRoot,
  88306. type: 'auto',
  88307. defaultValue: null,
  88308. persist: false
  88309. });
  88310. }
  88311. }
  88312. me.callParent([config]);
  88313. // We create our data tree.
  88314. me.tree = new Ext.data.Tree();
  88315. me.relayEvents(me.tree, [
  88316. /**
  88317. * @event append
  88318. * @inheritdoc Ext.data.Tree#append
  88319. */
  88320. "append",
  88321. /**
  88322. * @event remove
  88323. * @inheritdoc Ext.data.Tree#remove
  88324. */
  88325. "remove",
  88326. /**
  88327. * @event move
  88328. * @inheritdoc Ext.data.Tree#move
  88329. */
  88330. "move",
  88331. /**
  88332. * @event insert
  88333. * @inheritdoc Ext.data.Tree#insert
  88334. */
  88335. "insert",
  88336. /**
  88337. * @event beforeappend
  88338. * @inheritdoc Ext.data.Tree#beforeappend
  88339. */
  88340. "beforeappend",
  88341. /**
  88342. * @event beforeremove
  88343. * @inheritdoc Ext.data.Tree#beforeremove
  88344. */
  88345. "beforeremove",
  88346. /**
  88347. * @event beforemove
  88348. * @inheritdoc Ext.data.Tree#beforemove
  88349. */
  88350. "beforemove",
  88351. /**
  88352. * @event beforeinsert
  88353. * @inheritdoc Ext.data.Tree#beforeinsert
  88354. */
  88355. "beforeinsert",
  88356. /**
  88357. * @event expand
  88358. * @inheritdoc Ext.data.Tree#expand
  88359. */
  88360. "expand",
  88361. /**
  88362. * @event collapse
  88363. * @inheritdoc Ext.data.Tree#collapse
  88364. */
  88365. "collapse",
  88366. /**
  88367. * @event beforeexpand
  88368. * @inheritdoc Ext.data.Tree#beforeexpand
  88369. */
  88370. "beforeexpand",
  88371. /**
  88372. * @event beforecollapse
  88373. * @inheritdoc Ext.data.Tree#beforecollapse
  88374. */
  88375. "beforecollapse",
  88376. /**
  88377. * @event sort
  88378. * @inheritdoc Ext.data.Tree#sort
  88379. */
  88380. "sort",
  88381. /**
  88382. * @event rootchange
  88383. * @inheritdoc Ext.data.Tree#rootchange
  88384. */
  88385. "rootchange"
  88386. ]);
  88387. me.tree.on({
  88388. scope: me,
  88389. remove: me.onNodeRemove,
  88390. // this event must follow the relay to beforeitemexpand to allow users to
  88391. // cancel the expand:
  88392. beforeexpand: me.onBeforeNodeExpand,
  88393. beforecollapse: me.onBeforeNodeCollapse,
  88394. append: me.onNodeAdded,
  88395. insert: me.onNodeAdded,
  88396. sort: me.onNodeSort
  88397. });
  88398. me.onBeforeSort();
  88399. root = me.root;
  88400. if (root) {
  88401. delete me.root;
  88402. me.setRootNode(root);
  88403. }
  88404. if (Ext.isDefined(me.nodeParameter)) {
  88405. if (Ext.isDefined(Ext.global.console)) {
  88406. Ext.global.console.warn('Ext.data.TreeStore: nodeParameter has been deprecated. Please use nodeParam instead.');
  88407. }
  88408. me.nodeParam = me.nodeParameter;
  88409. delete me.nodeParameter;
  88410. }
  88411. },
  88412. // inherit docs
  88413. setProxy: function(proxy) {
  88414. var reader,
  88415. needsRoot;
  88416. if (proxy instanceof Ext.data.proxy.Proxy) {
  88417. // proxy instance, check if a root was set
  88418. needsRoot = Ext.isEmpty(proxy.getReader().root);
  88419. } else if (Ext.isString(proxy)) {
  88420. // string type, means a reader can't be set
  88421. needsRoot = true;
  88422. } else {
  88423. // object, check if a reader and a root were specified.
  88424. reader = proxy.reader;
  88425. needsRoot = !(reader && !Ext.isEmpty(reader.root));
  88426. }
  88427. proxy = this.callParent(arguments);
  88428. if (needsRoot) {
  88429. reader = proxy.getReader();
  88430. reader.root = this.defaultRootProperty;
  88431. // force rebuild
  88432. reader.buildExtractors(true);
  88433. }
  88434. },
  88435. // inherit docs
  88436. onBeforeSort: function() {
  88437. if (this.folderSort) {
  88438. this.sort({
  88439. property: 'leaf',
  88440. direction: 'ASC'
  88441. }, 'prepend', false);
  88442. }
  88443. },
  88444. /**
  88445. * Called before a node is expanded.
  88446. * @private
  88447. * @param {Ext.data.NodeInterface} node The node being expanded.
  88448. * @param {Function} callback The function to run after the expand finishes
  88449. * @param {Object} scope The scope in which to run the callback function
  88450. */
  88451. onBeforeNodeExpand: function(node, callback, scope) {
  88452. if (node.isLoaded()) {
  88453. Ext.callback(callback, scope || node, [node.childNodes]);
  88454. }
  88455. else if (node.isLoading()) {
  88456. this.on('load', function() {
  88457. Ext.callback(callback, scope || node, [node.childNodes]);
  88458. }, this, {single: true});
  88459. }
  88460. else {
  88461. this.read({
  88462. node: node,
  88463. callback: function() {
  88464. Ext.callback(callback, scope || node, [node.childNodes]);
  88465. }
  88466. });
  88467. }
  88468. },
  88469. //inherit docs
  88470. getNewRecords: function() {
  88471. return Ext.Array.filter(this.tree.flatten(), this.filterNew);
  88472. },
  88473. //inherit docs
  88474. getUpdatedRecords: function() {
  88475. return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
  88476. },
  88477. /**
  88478. * Called before a node is collapsed.
  88479. * @private
  88480. * @param {Ext.data.NodeInterface} node The node being collapsed.
  88481. * @param {Function} callback The function to run after the collapse finishes
  88482. * @param {Object} scope The scope in which to run the callback function
  88483. */
  88484. onBeforeNodeCollapse: function(node, callback, scope) {
  88485. callback.call(scope || node, node.childNodes);
  88486. },
  88487. onNodeRemove: function(parent, node, isMove) {
  88488. var me = this,
  88489. removed = me.removed;
  88490. if (!node.isReplace && Ext.Array.indexOf(removed, node) == -1) {
  88491. removed.push(node);
  88492. }
  88493. if (me.autoSync && !me.autoSyncSuspended && !isMove) {
  88494. me.sync();
  88495. }
  88496. },
  88497. onNodeAdded: function(parent, node) {
  88498. var me = this,
  88499. proxy = me.getProxy(),
  88500. reader = proxy.getReader(),
  88501. data = node.raw || node[node.persistenceProperty],
  88502. dataRoot;
  88503. Ext.Array.remove(me.removed, node);
  88504. if (!node.isLeaf()) {
  88505. dataRoot = reader.getRoot(data);
  88506. if (dataRoot) {
  88507. me.fillNode(node, reader.extractData(dataRoot));
  88508. delete data[reader.root];
  88509. }
  88510. }
  88511. if (me.autoSync && !me.autoSyncSuspended && (node.phantom || node.dirty)) {
  88512. me.sync();
  88513. }
  88514. },
  88515. onNodeSort: function() {
  88516. if(this.autoSync && !this.autoSyncSuspended) {
  88517. this.sync();
  88518. }
  88519. },
  88520. /**
  88521. * Sets the root node for this store. See also the {@link #root} config option.
  88522. * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
  88523. * @return {Ext.data.NodeInterface} The new root
  88524. */
  88525. setRootNode: function(root, /* private */ preventLoad) {
  88526. var me = this,
  88527. model = me.model,
  88528. idProperty = model.prototype.idProperty
  88529. root = root || {};
  88530. if (!root.isModel) {
  88531. // create a default rootNode and create internal data struct.
  88532. Ext.applyIf(root, {
  88533. id: me.defaultRootId,
  88534. text: 'Root',
  88535. allowDrag: false
  88536. });
  88537. if (root[idProperty] === undefined) {
  88538. root[idProperty] = me.defaultRootId;
  88539. }
  88540. Ext.data.NodeInterface.decorate(model);
  88541. root = Ext.ModelManager.create(root, model);
  88542. } else if (root.isModel && !root.isNode) {
  88543. Ext.data.NodeInterface.decorate(model);
  88544. }
  88545. // Because we have decorated the model with new fields,
  88546. // we need to build new extactor functions on the reader.
  88547. me.getProxy().getReader().buildExtractors(true);
  88548. // When we add the root to the tree, it will automaticaly get the NodeInterface
  88549. me.tree.setRootNode(root);
  88550. // If the user has set expanded: true on the root, we want to call the expand function
  88551. if (preventLoad !== true && !root.isLoaded() && (me.autoLoad === true || root.isExpanded())) {
  88552. me.load({
  88553. node: root
  88554. });
  88555. }
  88556. return root;
  88557. },
  88558. /**
  88559. * Returns the root node for this tree.
  88560. * @return {Ext.data.NodeInterface}
  88561. */
  88562. getRootNode: function() {
  88563. return this.tree.getRootNode();
  88564. },
  88565. /**
  88566. * Returns the record node by id
  88567. * @return {Ext.data.NodeInterface}
  88568. */
  88569. getNodeById: function(id) {
  88570. return this.tree.getNodeById(id);
  88571. },
  88572. // inherit docs
  88573. getById: function(id) {
  88574. return this.getNodeById(id);
  88575. },
  88576. /**
  88577. * Loads the Store using its configured {@link #proxy}.
  88578. * @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}
  88579. * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
  88580. * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
  88581. * default to the root node.
  88582. */
  88583. load: function(options) {
  88584. options = options || {};
  88585. options.params = options.params || {};
  88586. var me = this,
  88587. node = options.node || me.tree.getRootNode();
  88588. // If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just
  88589. // create one for them.
  88590. if (!node) {
  88591. node = me.setRootNode({
  88592. expanded: true
  88593. }, true);
  88594. }
  88595. // Assign the ID of the Operation so that a REST proxy can create the correct URL
  88596. options.id = node.getId();
  88597. if (me.clearOnLoad) {
  88598. if(me.clearRemovedOnLoad) {
  88599. // clear from the removed array any nodes that were descendants of the node being reloaded so that they do not get saved on next sync.
  88600. me.clearRemoved(node);
  88601. }
  88602. // temporarily remove the onNodeRemove event listener so that when removeAll is called, the removed nodes do not get added to the removed array
  88603. me.tree.un('remove', me.onNodeRemove, me);
  88604. // remove all the nodes
  88605. node.removeAll(false);
  88606. // reattach the onNodeRemove listener
  88607. me.tree.on('remove', me.onNodeRemove, me);
  88608. }
  88609. Ext.applyIf(options, {
  88610. node: node
  88611. });
  88612. options.params[me.nodeParam] = node ? node.getId() : 'root';
  88613. if (node) {
  88614. node.set('loading', true);
  88615. }
  88616. return me.callParent([options]);
  88617. },
  88618. /**
  88619. * Removes all records that used to be descendants of the passed node from the removed array
  88620. * @private
  88621. * @param {Ext.data.NodeInterface} node
  88622. */
  88623. clearRemoved: function(node) {
  88624. var me = this,
  88625. removed = me.removed,
  88626. id = node.getId(),
  88627. removedLength = removed.length,
  88628. i = removedLength,
  88629. recordsToClear = {},
  88630. newRemoved = [],
  88631. removedHash = {},
  88632. removedNode,
  88633. targetNode,
  88634. targetId;
  88635. if(node === me.getRootNode()) {
  88636. // if the passed node is the root node, just reset the removed array
  88637. me.removed = [];
  88638. return;
  88639. }
  88640. // add removed records to a hash so they can be easily retrieved by id later
  88641. for(; i--;) {
  88642. removedNode = removed[i];
  88643. removedHash[removedNode.getId()] = removedNode;
  88644. }
  88645. for(i = removedLength; i--;) {
  88646. removedNode = removed[i];
  88647. targetNode = removedNode;
  88648. while(targetNode && targetNode.getId() !== id) {
  88649. // walk up the parent hierarchy until we find the passed node or until we get to the root node
  88650. targetId = targetNode.get('parentId');
  88651. targetNode = targetNode.parentNode || me.getNodeById(targetId) || removedHash[targetId];
  88652. }
  88653. if(targetNode) {
  88654. // removed node was previously a descendant of the passed node - add it to the records to clear from "removed" later
  88655. recordsToClear[removedNode.getId()] = removedNode;
  88656. }
  88657. }
  88658. // create a new removed array containing only the records that are not in recordsToClear
  88659. for(i = 0; i < removedLength; i++) {
  88660. removedNode = removed[i];
  88661. if(!recordsToClear[removedNode.getId()]) {
  88662. newRemoved.push(removedNode);
  88663. }
  88664. }
  88665. me.removed = newRemoved;
  88666. },
  88667. /**
  88668. * Fills a node with a series of child records.
  88669. * @private
  88670. * @param {Ext.data.NodeInterface} node The node to fill
  88671. * @param {Ext.data.Model[]} newNodes The records to add
  88672. */
  88673. fillNode: function(node, newNodes) {
  88674. var me = this,
  88675. ln = newNodes ? newNodes.length : 0,
  88676. sorters = me.sorters,
  88677. i, sortCollection,
  88678. needsIndexSort = false,
  88679. performLocalSort = ln && me.sortOnLoad && !me.remoteSort && sorters && sorters.items && sorters.items.length,
  88680. node1, node2;
  88681. // See if there are any differing index values in the new nodes. If not, then we do not have to sortByIndex
  88682. for (i = 1; i < ln; i++) {
  88683. node1 = newNodes[i];
  88684. node2 = newNodes[i - 1];
  88685. needsIndexSort = node1[node1.persistenceProperty].index != node2[node2.persistenceProperty].index;
  88686. if (needsIndexSort) {
  88687. break;
  88688. }
  88689. }
  88690. // If there is a set of local sorters defined.
  88691. if (performLocalSort) {
  88692. // If sorting by index is needed, sort by index first
  88693. if (needsIndexSort) {
  88694. me.sorters.insert(0, me.indexSorter);
  88695. }
  88696. sortCollection = new Ext.util.MixedCollection();
  88697. sortCollection.addAll(newNodes);
  88698. sortCollection.sort(me.sorters.items);
  88699. newNodes = sortCollection.items;
  88700. // Remove the index sorter
  88701. me.sorters.remove(me.indexSorter);
  88702. } else if (needsIndexSort) {
  88703. Ext.Array.sort(newNodes, me.sortByIndex);
  88704. }
  88705. node.set('loaded', true);
  88706. for (i = 0; i < ln; i++) {
  88707. node.appendChild(newNodes[i], undefined, true);
  88708. }
  88709. return newNodes;
  88710. },
  88711. /**
  88712. * Sorter function for sorting records in index order
  88713. * @private
  88714. * @param {Ext.data.NodeInterface} node1
  88715. * @param {Ext.data.NodeInterface} node2
  88716. * @return {Number}
  88717. */
  88718. sortByIndex: function(node1, node2) {
  88719. return node1[node1.persistenceProperty].index - node2[node2.persistenceProperty].index;
  88720. },
  88721. // inherit docs
  88722. onProxyLoad: function(operation) {
  88723. var me = this,
  88724. successful = operation.wasSuccessful(),
  88725. records = operation.getRecords(),
  88726. node = operation.node;
  88727. me.loading = false;
  88728. node.set('loading', false);
  88729. if (successful) {
  88730. if (!me.clearOnLoad) {
  88731. records = me.cleanRecords(node, records);
  88732. }
  88733. records = me.fillNode(node, records);
  88734. }
  88735. // The load event has an extra node parameter
  88736. // (differing from the load event described in AbstractStore)
  88737. /**
  88738. * @event load
  88739. * Fires whenever the store reads data from a remote data source.
  88740. * @param {Ext.data.TreeStore} this
  88741. * @param {Ext.data.NodeInterface} node The node that was loaded.
  88742. * @param {Ext.data.Model[]} records An array of records.
  88743. * @param {Boolean} successful True if the operation was successful.
  88744. */
  88745. // deprecate read?
  88746. me.fireEvent('read', me, operation.node, records, successful);
  88747. me.fireEvent('load', me, operation.node, records, successful);
  88748. //this is a callback that would have been passed to the 'read' function and is optional
  88749. Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
  88750. },
  88751. onCreateRecords: function(records) {
  88752. this.callParent(arguments);
  88753. var i = 0,
  88754. len = records.length,
  88755. tree = this.tree,
  88756. node;
  88757. for (; i < len; ++i) {
  88758. node = records[i];
  88759. tree.onNodeIdChanged(node, null, node.getId());
  88760. }
  88761. },
  88762. cleanRecords: function(node, records){
  88763. var nodeHash = {},
  88764. childNodes = node.childNodes,
  88765. i = 0,
  88766. len = childNodes.length,
  88767. out = [],
  88768. rec;
  88769. // build a hash of all the childNodes under the current node for performance
  88770. for (; i < len; ++i) {
  88771. nodeHash[childNodes[i].getId()] = true;
  88772. }
  88773. for (i = 0, len = records.length; i < len; ++i) {
  88774. rec = records[i];
  88775. if (!nodeHash[rec.getId()]) {
  88776. out.push(rec);
  88777. }
  88778. }
  88779. return out;
  88780. },
  88781. // inherit docs
  88782. removeAll: function() {
  88783. var root = this.getRootNode();
  88784. if (root) {
  88785. root.destroy(true);
  88786. }
  88787. this.fireEvent('clear', this);
  88788. },
  88789. // inherit docs
  88790. doSort: function(sorterFn) {
  88791. var me = this;
  88792. if (me.remoteSort) {
  88793. //the load function will pick up the new sorters and request the sorted data from the proxy
  88794. me.load();
  88795. } else {
  88796. me.tree.sort(sorterFn, true);
  88797. me.fireEvent('datachanged', me);
  88798. me.fireEvent('refresh', me);
  88799. }
  88800. me.fireEvent('sort', me);
  88801. }
  88802. }, function() {
  88803. var proto = this.prototype;
  88804. proto.indexSorter = new Ext.util.Sorter({
  88805. sorterFn: proto.sortByIndex
  88806. });
  88807. });
  88808. /**
  88809. * @extend Ext.data.IdGenerator
  88810. * @author Don Griffin
  88811. *
  88812. * This class generates UUID's according to RFC 4122. This class has a default id property.
  88813. * This means that a single instance is shared unless the id property is overridden. Thus,
  88814. * two {@link Ext.data.Model} instances configured like the following share one generator:
  88815. *
  88816. * Ext.define('MyApp.data.MyModelX', {
  88817. * extend: 'Ext.data.Model',
  88818. * idgen: 'uuid'
  88819. * });
  88820. *
  88821. * Ext.define('MyApp.data.MyModelY', {
  88822. * extend: 'Ext.data.Model',
  88823. * idgen: 'uuid'
  88824. * });
  88825. *
  88826. * This allows all models using this class to share a commonly configured instance.
  88827. *
  88828. * # Using Version 1 ("Sequential") UUID's
  88829. *
  88830. * If a server can provide a proper timestamp and a "cryptographic quality random number"
  88831. * (as described in RFC 4122), the shared instance can be configured as follows:
  88832. *
  88833. * Ext.data.IdGenerator.get('uuid').reconfigure({
  88834. * version: 1,
  88835. * clockSeq: clock, // 14 random bits
  88836. * salt: salt, // 48 secure random bits (the Node field)
  88837. * timestamp: ts // timestamp per Section 4.1.4
  88838. * });
  88839. *
  88840. * // or these values can be split into 32-bit chunks:
  88841. *
  88842. * Ext.data.IdGenerator.get('uuid').reconfigure({
  88843. * version: 1,
  88844. * clockSeq: clock,
  88845. * salt: { lo: saltLow32, hi: saltHigh32 },
  88846. * timestamp: { lo: timestampLow32, hi: timestamptHigh32 }
  88847. * });
  88848. *
  88849. * This approach improves the generator's uniqueness by providing a valid timestamp and
  88850. * higher quality random data. Version 1 UUID's should not be used unless this information
  88851. * can be provided by a server and care should be taken to avoid caching of this data.
  88852. *
  88853. * See http://www.ietf.org/rfc/rfc4122.txt for details.
  88854. */
  88855. Ext.define('Ext.data.UuidGenerator', (function () {
  88856. var twoPow14 = Math.pow(2, 14),
  88857. twoPow16 = Math.pow(2, 16),
  88858. twoPow28 = Math.pow(2, 28),
  88859. twoPow32 = Math.pow(2, 32);
  88860. function toHex (value, length) {
  88861. var ret = value.toString(16);
  88862. if (ret.length > length) {
  88863. ret = ret.substring(ret.length - length); // right-most digits
  88864. } else if (ret.length < length) {
  88865. ret = Ext.String.leftPad(ret, length, '0');
  88866. }
  88867. return ret;
  88868. }
  88869. function rand (lo, hi) {
  88870. var v = Math.random() * (hi - lo + 1);
  88871. return Math.floor(v) + lo;
  88872. }
  88873. function split (bignum) {
  88874. if (typeof(bignum) == 'number') {
  88875. var hi = Math.floor(bignum / twoPow32);
  88876. return {
  88877. lo: Math.floor(bignum - hi * twoPow32),
  88878. hi: hi
  88879. };
  88880. }
  88881. return bignum;
  88882. }
  88883. return {
  88884. extend: 'Ext.data.IdGenerator',
  88885. alias: 'idgen.uuid',
  88886. id: 'uuid', // shared by default
  88887. /**
  88888. * @property {Number/Object} salt
  88889. * When created, this value is a 48-bit number. For computation, this value is split
  88890. * into 32-bit parts and stored in an object with `hi` and `lo` properties.
  88891. */
  88892. /**
  88893. * @property {Number/Object} timestamp
  88894. * When created, this value is a 60-bit number. For computation, this value is split
  88895. * into 32-bit parts and stored in an object with `hi` and `lo` properties.
  88896. */
  88897. /**
  88898. * @cfg {Number} version
  88899. * The Version of UUID. Supported values are:
  88900. *
  88901. * * 1 : Time-based, "sequential" UUID.
  88902. * * 4 : Pseudo-random UUID.
  88903. *
  88904. * The default is 4.
  88905. */
  88906. version: 4,
  88907. constructor: function() {
  88908. var me = this;
  88909. me.callParent(arguments);
  88910. me.parts = [];
  88911. me.init();
  88912. },
  88913. generate: function () {
  88914. var me = this,
  88915. parts = me.parts,
  88916. ts = me.timestamp;
  88917. /*
  88918. The magic decoder ring (derived from RFC 4122 Section 4.2.2):
  88919. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  88920. | time_low |
  88921. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  88922. | time_mid | ver | time_hi |
  88923. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  88924. |res| clock_hi | clock_low | salt 0 |M| salt 1 |
  88925. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  88926. | salt (2-5) |
  88927. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  88928. time_mid clock_hi (low 6 bits)
  88929. time_low | time_hi |clock_lo
  88930. | | | || salt[0]
  88931. | | | || | salt[1..5]
  88932. v v v vv v v
  88933. 0badf00d-aced-1def-b123-dfad0badbeef
  88934. ^ ^ ^
  88935. version | multicast (low bit)
  88936. |
  88937. reserved (upper 2 bits)
  88938. */
  88939. parts[0] = toHex(ts.lo, 8);
  88940. parts[1] = toHex(ts.hi & 0xFFFF, 4);
  88941. parts[2] = toHex(((ts.hi >>> 16) & 0xFFF) | (me.version << 12), 4);
  88942. parts[3] = toHex(0x80 | ((me.clockSeq >>> 8) & 0x3F), 2) +
  88943. toHex(me.clockSeq & 0xFF, 2);
  88944. parts[4] = toHex(me.salt.hi, 4) + toHex(me.salt.lo, 8);
  88945. if (me.version == 4) {
  88946. me.init(); // just regenerate all the random values...
  88947. } else {
  88948. // sequentially increment the timestamp...
  88949. ++ts.lo;
  88950. if (ts.lo >= twoPow32) { // if (overflow)
  88951. ts.lo = 0;
  88952. ++ts.hi;
  88953. }
  88954. }
  88955. return parts.join('-').toLowerCase();
  88956. },
  88957. getRecId: function (rec) {
  88958. return rec.getId();
  88959. },
  88960. /**
  88961. * @private
  88962. */
  88963. init: function () {
  88964. var me = this,
  88965. salt, time;
  88966. if (me.version == 4) {
  88967. // See RFC 4122 (Secion 4.4)
  88968. // o If the state was unavailable (e.g., non-existent or corrupted),
  88969. // or the saved node ID is different than the current node ID,
  88970. // generate a random clock sequence value.
  88971. me.clockSeq = rand(0, twoPow14-1);
  88972. // we run this on every id generation...
  88973. salt = me.salt || (me.salt = {});
  88974. time = me.timestamp || (me.timestamp = {});
  88975. // See RFC 4122 (Secion 4.4)
  88976. salt.lo = rand(0, twoPow32-1);
  88977. salt.hi = rand(0, twoPow16-1);
  88978. time.lo = rand(0, twoPow32-1);
  88979. time.hi = rand(0, twoPow28-1);
  88980. } else {
  88981. // this is run only once per-instance
  88982. me.salt = split(me.salt);
  88983. me.timestamp = split(me.timestamp);
  88984. // Set multicast bit: "the least significant bit of the first octet of the
  88985. // node ID" (nodeId = salt for this implementation):
  88986. me.salt.hi |= 0x100;
  88987. }
  88988. },
  88989. /**
  88990. * Reconfigures this generator given new config properties.
  88991. */
  88992. reconfigure: function (config) {
  88993. Ext.apply(this, config);
  88994. this.init();
  88995. }
  88996. };
  88997. }()));
  88998. /**
  88999. * @author Ed Spencer
  89000. *
  89001. * The XML Reader is used by a Proxy to read a server response that is sent back in XML format. This usually happens as
  89002. * a result of loading a Store - for example we might create something like this:
  89003. *
  89004. * Ext.define('User', {
  89005. * extend: 'Ext.data.Model',
  89006. * fields: ['id', 'name', 'email']
  89007. * });
  89008. *
  89009. * var store = Ext.create('Ext.data.Store', {
  89010. * model: 'User',
  89011. * proxy: {
  89012. * type: 'ajax',
  89013. * url : 'users.xml',
  89014. * reader: {
  89015. * type: 'xml',
  89016. * record: 'user',
  89017. * root: 'users'
  89018. * }
  89019. * }
  89020. * });
  89021. *
  89022. * The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're not
  89023. * already familiar with them.
  89024. *
  89025. * We created the simplest type of XML Reader possible by simply telling our {@link Ext.data.Store Store}'s {@link
  89026. * Ext.data.proxy.Proxy Proxy} that we want a XML Reader. The Store automatically passes the configured model to the
  89027. * Store, so it is as if we passed this instead:
  89028. *
  89029. * reader: {
  89030. * type : 'xml',
  89031. * model: 'User',
  89032. * record: 'user',
  89033. * root: 'users'
  89034. * }
  89035. *
  89036. * The reader we set up is ready to read data from our server - at the moment it will accept a response like this:
  89037. *
  89038. * <?xml version="1.0" encoding="UTF-8"?>
  89039. * <users>
  89040. * <user>
  89041. * <id>1</id>
  89042. * <name>Ed Spencer</name>
  89043. * <email>ed@sencha.com</email>
  89044. * </user>
  89045. * <user>
  89046. * <id>2</id>
  89047. * <name>Abe Elias</name>
  89048. * <email>abe@sencha.com</email>
  89049. * </user>
  89050. * </users>
  89051. *
  89052. * First off there's {@link #root} option to define the root node `<users>` (there should be only one in a well-formed
  89053. * XML document). Then the XML Reader uses the configured {@link #record} option to pull out the data for each record -
  89054. * in this case we set record to 'user', so each `<user>` above will be converted into a User model.
  89055. *
  89056. * Note that XmlReader doesn't care whether your {@link #root} and {@link #record} elements are nested deep inside a
  89057. * larger structure, so a response like this will still work:
  89058. *
  89059. * <?xml version="1.0" encoding="UTF-8"?>
  89060. * <deeply>
  89061. * <nested>
  89062. * <xml>
  89063. * <users>
  89064. * <user>
  89065. * <id>1</id>
  89066. * <name>Ed Spencer</name>
  89067. * <email>ed@sencha.com</email>
  89068. * </user>
  89069. * <user>
  89070. * <id>2</id>
  89071. * <name>Abe Elias</name>
  89072. * <email>abe@sencha.com</email>
  89073. * </user>
  89074. * </users>
  89075. * </xml>
  89076. * </nested>
  89077. * </deeply>
  89078. *
  89079. * # Response metadata
  89080. *
  89081. * The server can return additional data in its response, such as the {@link #totalProperty total number of records} and
  89082. * the {@link #successProperty success status of the response}. These are typically included in the XML response like
  89083. * this:
  89084. *
  89085. * <?xml version="1.0" encoding="UTF-8"?>
  89086. * <users>
  89087. * <total>100</total>
  89088. * <success>true</success>
  89089. * <user>
  89090. * <id>1</id>
  89091. * <name>Ed Spencer</name>
  89092. * <email>ed@sencha.com</email>
  89093. * </user>
  89094. * <user>
  89095. * <id>2</id>
  89096. * <name>Abe Elias</name>
  89097. * <email>abe@sencha.com</email>
  89098. * </user>
  89099. * </users>
  89100. *
  89101. * If these properties are present in the XML response they can be parsed out by the XmlReader and used by the Store
  89102. * that loaded it. We can set up the names of these properties by specifying a final pair of configuration options:
  89103. *
  89104. * reader: {
  89105. * type: 'xml',
  89106. * root: 'users',
  89107. * totalProperty : 'total',
  89108. * successProperty: 'success'
  89109. * }
  89110. *
  89111. * These final options are not necessary to make the Reader work, but can be useful when the server needs to report an
  89112. * error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
  89113. * returned.
  89114. *
  89115. * # Response format
  89116. *
  89117. * **Note:** in order for the browser to parse a returned XML document, the Content-Type header in the HTTP response
  89118. * must be set to "text/xml" or "application/xml". This is very important - the XmlReader will not work correctly
  89119. * otherwise.
  89120. */
  89121. Ext.define('Ext.data.reader.Xml', {
  89122. extend: 'Ext.data.reader.Reader',
  89123. alternateClassName: 'Ext.data.XmlReader',
  89124. alias : 'reader.xml',
  89125. /**
  89126. * @cfg {String} record (required)
  89127. * The DomQuery path to the repeated element which contains record information.
  89128. */
  89129. /**
  89130. * @private
  89131. * Creates a function to return some particular key of data from a response. The totalProperty and
  89132. * successProperty are treated as special cases for type casting, everything else is just a simple selector.
  89133. * @param {String} key
  89134. * @return {Function}
  89135. */
  89136. createAccessor: function(expr) {
  89137. var me = this;
  89138. if (Ext.isEmpty(expr)) {
  89139. return Ext.emptyFn;
  89140. }
  89141. if (Ext.isFunction(expr)) {
  89142. return expr;
  89143. }
  89144. return function(root) {
  89145. return me.getNodeValue(Ext.DomQuery.selectNode(expr, root));
  89146. };
  89147. },
  89148. getNodeValue: function(node) {
  89149. if (node && node.firstChild) {
  89150. return node.firstChild.nodeValue;
  89151. }
  89152. return undefined;
  89153. },
  89154. //inherit docs
  89155. getResponseData: function(response) {
  89156. var xml = response.responseXML,
  89157. error,
  89158. msg;
  89159. if (!xml) {
  89160. msg = 'XML data not found in the response';
  89161. error = new Ext.data.ResultSet({
  89162. total : 0,
  89163. count : 0,
  89164. records: [],
  89165. success: false,
  89166. message: msg
  89167. });
  89168. this.fireEvent('exception', this, response, error);
  89169. Ext.Logger.warn(msg);
  89170. return error;
  89171. }
  89172. return this.readRecords(xml);
  89173. },
  89174. /**
  89175. * Normalizes the data object.
  89176. * @param {Object} data The raw data object
  89177. * @return {Object} The documentElement property of the data object if present, or the same object if not.
  89178. */
  89179. getData: function(data) {
  89180. return data.documentElement || data;
  89181. },
  89182. /**
  89183. * @private
  89184. * Given an XML object, returns the Element that represents the root as configured by the Reader's meta data.
  89185. * @param {Object} data The XML data object
  89186. * @return {XMLElement} The root node element
  89187. */
  89188. getRoot: function(data) {
  89189. var nodeName = data.nodeName,
  89190. root = this.root;
  89191. if (!root || (nodeName && nodeName == root)) {
  89192. return data;
  89193. } else if (Ext.DomQuery.isXml(data)) {
  89194. // This fix ensures we have XML data
  89195. // Related to TreeStore calling getRoot with the root node, which isn't XML
  89196. // Probably should be resolved in TreeStore at some point
  89197. return Ext.DomQuery.selectNode(root, data);
  89198. }
  89199. },
  89200. /**
  89201. * @private
  89202. * We're just preparing the data for the superclass by pulling out the record nodes we want.
  89203. * @param {XMLElement} root The XML root node
  89204. * @return {Ext.data.Model[]} The records
  89205. */
  89206. extractData: function(root) {
  89207. var recordName = this.record;
  89208. if (!recordName) {
  89209. Ext.Error.raise('Record is a required parameter');
  89210. }
  89211. if (recordName != root.nodeName) {
  89212. root = Ext.DomQuery.select(recordName, root);
  89213. } else {
  89214. root = [root];
  89215. }
  89216. return this.callParent([root]);
  89217. },
  89218. /**
  89219. * @private
  89220. * See Ext.data.reader.Reader's getAssociatedDataRoot docs.
  89221. * @param {Object} data The raw data object
  89222. * @param {String} associationName The name of the association to get data for (uses associationKey if present)
  89223. * @return {XMLElement} The root
  89224. */
  89225. getAssociatedDataRoot: function(data, associationName) {
  89226. return Ext.DomQuery.select(associationName, data)[0];
  89227. },
  89228. /**
  89229. * Parses an XML document and returns a ResultSet containing the model instances.
  89230. * @param {Object} doc Parsed XML document
  89231. * @return {Ext.data.ResultSet} The parsed result set
  89232. */
  89233. readRecords: function(doc) {
  89234. // it's possible that we get passed an array here by associations.
  89235. // Make sure we strip that out (see Ext.data.reader.Reader#readAssociated)
  89236. if (Ext.isArray(doc)) {
  89237. doc = doc[0];
  89238. }
  89239. /**
  89240. * @property {Object} xmlData
  89241. * Copy of {@link #rawData}.
  89242. * @deprecated Will be removed in Ext JS 5.0. Use {@link #rawData} instead.
  89243. */
  89244. this.xmlData = doc;
  89245. return this.callParent([doc]);
  89246. },
  89247. /**
  89248. * @private
  89249. * Returns an accessor expression for the passed Field from an XML element using either the Field's mapping, or
  89250. * its ordinal position in the fields collsction as the index.
  89251. * This is used by buildExtractors to create optimized on extractor function which converts raw data into model instances.
  89252. */
  89253. createFieldAccessExpression: function(field, fieldVarName, dataName) {
  89254. var selector = field.mapping || field.name,
  89255. result;
  89256. if (typeof selector === 'function') {
  89257. result = fieldVarName + '.mapping(' + dataName + ', this)';
  89258. } else {
  89259. result = 'me.getNodeValue(Ext.DomQuery.selectNode("' + selector + '", ' + dataName + '))';
  89260. }
  89261. return result;
  89262. }
  89263. });
  89264. /**
  89265. * @author Ed Spencer
  89266. * @class Ext.data.writer.Xml
  89267. This class is used to write {@link Ext.data.Model} data to the server in an XML format.
  89268. The {@link #documentRoot} property is used to specify the root element in the XML document.
  89269. The {@link #record} option is used to specify the element name for each record that will make
  89270. up the XML document.
  89271. * @markdown
  89272. */
  89273. Ext.define('Ext.data.writer.Xml', {
  89274. /* Begin Definitions */
  89275. extend: 'Ext.data.writer.Writer',
  89276. alternateClassName: 'Ext.data.XmlWriter',
  89277. alias: 'writer.xml',
  89278. /* End Definitions */
  89279. /**
  89280. * @cfg {String} documentRoot The name of the root element of the document. Defaults to <tt>'xmlData'</tt>.
  89281. * If there is more than 1 record and the root is not specified, the default document root will still be used
  89282. * to ensure a valid XML document is created.
  89283. */
  89284. documentRoot: 'xmlData',
  89285. /**
  89286. * @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty and a root is required
  89287. * to form a valid XML document.
  89288. */
  89289. defaultDocumentRoot: 'xmlData',
  89290. /**
  89291. * @cfg {String} header A header to use in the XML document (such as setting the encoding or version).
  89292. * Defaults to <tt>''</tt>.
  89293. */
  89294. header: '',
  89295. /**
  89296. * @cfg {String} record The name of the node to use for each record. Defaults to <tt>'record'</tt>.
  89297. */
  89298. record: 'record',
  89299. //inherit docs
  89300. writeRecords: function(request, data) {
  89301. var me = this,
  89302. xml = [],
  89303. i = 0,
  89304. len = data.length,
  89305. root = me.documentRoot,
  89306. record = me.record,
  89307. needsRoot = data.length !== 1,
  89308. item,
  89309. key;
  89310. // may not exist
  89311. xml.push(me.header || '');
  89312. if (!root && needsRoot) {
  89313. root = me.defaultDocumentRoot;
  89314. }
  89315. if (root) {
  89316. xml.push('<', root, '>');
  89317. }
  89318. for (; i < len; ++i) {
  89319. item = data[i];
  89320. xml.push('<', record, '>');
  89321. for (key in item) {
  89322. if (item.hasOwnProperty(key)) {
  89323. xml.push('<', key, '>', item[key], '</', key, '>');
  89324. }
  89325. }
  89326. xml.push('</', record, '>');
  89327. }
  89328. if (root) {
  89329. xml.push('</', root, '>');
  89330. }
  89331. request.xmlData = xml.join('');
  89332. return request;
  89333. }
  89334. });
  89335. /**
  89336. * @author Ed Spencer
  89337. * <p>Small helper class to make creating {@link Ext.data.Store}s from XML data easier.
  89338. * A XmlStore will be automatically configured with a {@link Ext.data.reader.Xml}.</p>
  89339. * <p>A store configuration would be something like:<pre><code>
  89340. var store = new Ext.data.XmlStore({
  89341. // store configs
  89342. storeId: 'myStore',
  89343. url: 'sheldon.xml', // automatically configures a HttpProxy
  89344. // reader configs
  89345. record: 'Item', // records will have an "Item" tag
  89346. idPath: 'ASIN',
  89347. totalRecords: '@TotalResults'
  89348. fields: [
  89349. // set up the fields mapping into the xml doc
  89350. // The first needs mapping, the others are very basic
  89351. {name: 'Author', mapping: 'ItemAttributes > Author'},
  89352. 'Title', 'Manufacturer', 'ProductGroup'
  89353. ]
  89354. });
  89355. * </code></pre></p>
  89356. * <p>This store is configured to consume a returned object of the form:<pre><code>
  89357. &#60?xml version="1.0" encoding="UTF-8"?>
  89358. &#60ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2009-05-15">
  89359. &#60Items>
  89360. &#60Request>
  89361. &#60IsValid>True&#60/IsValid>
  89362. &#60ItemSearchRequest>
  89363. &#60Author>Sidney Sheldon&#60/Author>
  89364. &#60SearchIndex>Books&#60/SearchIndex>
  89365. &#60/ItemSearchRequest>
  89366. &#60/Request>
  89367. &#60TotalResults>203&#60/TotalResults>
  89368. &#60TotalPages>21&#60/TotalPages>
  89369. &#60Item>
  89370. &#60ASIN>0446355453&#60/ASIN>
  89371. &#60DetailPageURL>
  89372. http://www.amazon.com/
  89373. &#60/DetailPageURL>
  89374. &#60ItemAttributes>
  89375. &#60Author>Sidney Sheldon&#60/Author>
  89376. &#60Manufacturer>Warner Books&#60/Manufacturer>
  89377. &#60ProductGroup>Book&#60/ProductGroup>
  89378. &#60Title>Master of the Game&#60/Title>
  89379. &#60/ItemAttributes>
  89380. &#60/Item>
  89381. &#60/Items>
  89382. &#60/ItemSearchResponse>
  89383. * </code></pre>
  89384. * An object literal of this form could also be used as the {@link #cfg-data} config option.</p>
  89385. * <p><b>Note:</b> This class accepts all of the configuration options of
  89386. * <b>{@link Ext.data.reader.Xml XmlReader}</b>.</p>
  89387. */
  89388. Ext.define('Ext.data.XmlStore', {
  89389. extend: 'Ext.data.Store',
  89390. alias: 'store.xml',
  89391. requires: [
  89392. 'Ext.data.proxy.Ajax',
  89393. 'Ext.data.reader.Xml',
  89394. 'Ext.data.writer.Xml'
  89395. ],
  89396. constructor: function(config){
  89397. config = Ext.apply({
  89398. proxy: {
  89399. type: 'ajax',
  89400. reader: 'xml',
  89401. writer: 'xml'
  89402. }
  89403. }, config);
  89404. this.callParent([config]);
  89405. }
  89406. });
  89407. /**
  89408. * @author Ed Spencer
  89409. * @class Ext.data.association.BelongsTo
  89410. *
  89411. * Represents a many to one association with another model. The owner model is expected to have
  89412. * a foreign key which references the primary key of the associated model:
  89413. *
  89414. * Ext.define('Category', {
  89415. * extend: 'Ext.data.Model',
  89416. * fields: [
  89417. * { name: 'id', type: 'int' },
  89418. * { name: 'name', type: 'string' }
  89419. * ]
  89420. * });
  89421. *
  89422. * Ext.define('Product', {
  89423. * extend: 'Ext.data.Model',
  89424. * fields: [
  89425. * { name: 'id', type: 'int' },
  89426. * { name: 'category_id', type: 'int' },
  89427. * { name: 'name', type: 'string' }
  89428. * ],
  89429. * // we can use the belongsTo shortcut on the model to create a belongsTo association
  89430. * associations: [
  89431. * { type: 'belongsTo', model: 'Category' }
  89432. * ]
  89433. * });
  89434. *
  89435. * In the example above we have created models for Products and Categories, and linked them together
  89436. * by saying that each Product belongs to a Category. This automatically links each Product to a Category
  89437. * based on the Product's category_id, and provides new functions on the Product model:
  89438. *
  89439. * ## Generated getter function
  89440. *
  89441. * The first function that is added to the owner model is a getter function:
  89442. *
  89443. * var product = new Product({
  89444. * id: 100,
  89445. * category_id: 20,
  89446. * name: 'Sneakers'
  89447. * });
  89448. *
  89449. * product.getCategory(function(category, operation) {
  89450. * // do something with the category object
  89451. * alert(category.get('id')); // alerts 20
  89452. * }, this);
  89453. *
  89454. * The getCategory function was created on the Product model when we defined the association. This uses the
  89455. * Category's configured {@link Ext.data.proxy.Proxy proxy} to load the Category asynchronously, calling the provided
  89456. * callback when it has loaded.
  89457. *
  89458. * The new getCategory function will also accept an object containing success, failure and callback properties
  89459. * - callback will always be called, success will only be called if the associated model was loaded successfully
  89460. * and failure will only be called if the associatied model could not be loaded:
  89461. *
  89462. * product.getCategory({
  89463. * reload: true, // force a reload if the owner model is already cached
  89464. * callback: function(category, operation) {}, // a function that will always be called
  89465. * success : function(category, operation) {}, // a function that will only be called if the load succeeded
  89466. * failure : function(category, operation) {}, // a function that will only be called if the load did not succeed
  89467. * scope : this // optionally pass in a scope object to execute the callbacks in
  89468. * });
  89469. *
  89470. * In each case above the callbacks are called with two arguments - the associated model instance and the
  89471. * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
  89472. * useful when the instance could not be loaded.
  89473. *
  89474. * Once the getter has been called on the model, it will be cached if the getter is called a second time. To
  89475. * force the model to reload, specify reload: true in the options object.
  89476. *
  89477. * ## Generated setter function
  89478. *
  89479. * The second generated function sets the associated model instance - if only a single argument is passed to
  89480. * the setter then the following two calls are identical:
  89481. *
  89482. * // this call...
  89483. * product.setCategory(10);
  89484. *
  89485. * // is equivalent to this call:
  89486. * product.set('category_id', 10);
  89487. *
  89488. * An instance of the owner model can also be passed as a parameter.
  89489. *
  89490. * If we pass in a second argument, the model will be automatically saved and the second argument passed to
  89491. * the owner model's {@link Ext.data.Model#save save} method:
  89492. *
  89493. * product.setCategory(10, function(product, operation) {
  89494. * // the product has been saved
  89495. * alert(product.get('category_id')); //now alerts 10
  89496. * });
  89497. *
  89498. * //alternative syntax:
  89499. * product.setCategory(10, {
  89500. * callback: function(product, operation), // a function that will always be called
  89501. * success : function(product, operation), // a function that will only be called if the load succeeded
  89502. * failure : function(product, operation), // a function that will only be called if the load did not succeed
  89503. * scope : this //optionally pass in a scope object to execute the callbacks in
  89504. * })
  89505. *
  89506. * ## Customisation
  89507. *
  89508. * Associations reflect on the models they are linking to automatically set up properties such as the
  89509. * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
  89510. *
  89511. * Ext.define('Product', {
  89512. * fields: [...],
  89513. *
  89514. * associations: [
  89515. * { type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id' }
  89516. * ]
  89517. * });
  89518. *
  89519. * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'category_id')
  89520. * with our own settings. Usually this will not be needed.
  89521. */
  89522. Ext.define('Ext.data.association.BelongsTo', {
  89523. extend: 'Ext.data.association.Association',
  89524. alternateClassName: 'Ext.data.BelongsToAssociation',
  89525. alias: 'association.belongsto',
  89526. /**
  89527. * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
  89528. * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
  89529. * model called Product would set up a product_id foreign key.
  89530. *
  89531. * Ext.define('Order', {
  89532. * extend: 'Ext.data.Model',
  89533. * fields: ['id', 'date'],
  89534. * hasMany: 'Product'
  89535. * });
  89536. *
  89537. * Ext.define('Product', {
  89538. * extend: 'Ext.data.Model',
  89539. * fields: ['id', 'name', 'order_id'], // refers to the id of the order that this product belongs to
  89540. * belongsTo: 'Order'
  89541. * });
  89542. * var product = new Product({
  89543. * id: 1,
  89544. * name: 'Product 1',
  89545. * order_id: 22
  89546. * }, 1);
  89547. * product.getOrder(); // Will make a call to the server asking for order_id 22
  89548. *
  89549. */
  89550. /**
  89551. * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
  89552. * Defaults to 'get' + the name of the foreign model, e.g. getCategory
  89553. */
  89554. /**
  89555. * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
  89556. * Defaults to 'set' + the name of the foreign model, e.g. setCategory
  89557. */
  89558. /**
  89559. * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
  89560. * Use 'belongsTo' to create a BelongsTo association.
  89561. *
  89562. * associations: [{
  89563. * type: 'belongsTo',
  89564. * model: 'User'
  89565. * }]
  89566. */
  89567. constructor: function(config) {
  89568. this.callParent(arguments);
  89569. var me = this,
  89570. ownerProto = me.ownerModel.prototype,
  89571. associatedName = me.associatedName,
  89572. getterName = me.getterName || 'get' + associatedName,
  89573. setterName = me.setterName || 'set' + associatedName;
  89574. Ext.applyIf(me, {
  89575. name : associatedName,
  89576. foreignKey : associatedName.toLowerCase() + "_id",
  89577. instanceName: associatedName + 'BelongsToInstance',
  89578. associationKey: associatedName.toLowerCase()
  89579. });
  89580. ownerProto[getterName] = me.createGetter();
  89581. ownerProto[setterName] = me.createSetter();
  89582. },
  89583. /**
  89584. * @private
  89585. * Returns a setter function to be placed on the owner model's prototype
  89586. * @return {Function} The setter function
  89587. */
  89588. createSetter: function() {
  89589. var me = this,
  89590. foreignKey = me.foreignKey;
  89591. //'this' refers to the Model instance inside this function
  89592. return function(value, options, scope) {
  89593. // If we pass in an instance, pull the id out
  89594. if (value && value.isModel) {
  89595. value = value.getId();
  89596. }
  89597. this.set(foreignKey, value);
  89598. if (Ext.isFunction(options)) {
  89599. options = {
  89600. callback: options,
  89601. scope: scope || this
  89602. };
  89603. }
  89604. if (Ext.isObject(options)) {
  89605. return this.save(options);
  89606. }
  89607. };
  89608. },
  89609. /**
  89610. * @private
  89611. * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
  89612. * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
  89613. * @return {Function} The getter function
  89614. */
  89615. createGetter: function() {
  89616. var me = this,
  89617. associatedName = me.associatedName,
  89618. associatedModel = me.associatedModel,
  89619. foreignKey = me.foreignKey,
  89620. primaryKey = me.primaryKey,
  89621. instanceName = me.instanceName;
  89622. //'this' refers to the Model instance inside this function
  89623. return function(options, scope) {
  89624. options = options || {};
  89625. var model = this,
  89626. foreignKeyId = model.get(foreignKey),
  89627. success,
  89628. instance,
  89629. args;
  89630. if (options.reload === true || model[instanceName] === undefined) {
  89631. instance = Ext.ModelManager.create({}, associatedName);
  89632. instance.set(primaryKey, foreignKeyId);
  89633. if (typeof options == 'function') {
  89634. options = {
  89635. callback: options,
  89636. scope: scope || model
  89637. };
  89638. }
  89639. // Overwrite the success handler so we can assign the current instance
  89640. success = options.success;
  89641. options.success = function(rec){
  89642. model[instanceName] = rec;
  89643. if (success) {
  89644. success.apply(this, arguments);
  89645. }
  89646. };
  89647. associatedModel.load(foreignKeyId, options);
  89648. // assign temporarily while we wait for data to return
  89649. model[instanceName] = instance;
  89650. return instance;
  89651. } else {
  89652. instance = model[instanceName];
  89653. args = [instance];
  89654. scope = scope || options.scope || model;
  89655. //TODO: We're duplicating the callback invokation code that the instance.load() call above
  89656. //makes here - ought to be able to normalize this - perhaps by caching at the Model.load layer
  89657. //instead of the association layer.
  89658. Ext.callback(options, scope, args);
  89659. Ext.callback(options.success, scope, args);
  89660. Ext.callback(options.failure, scope, args);
  89661. Ext.callback(options.callback, scope, args);
  89662. return instance;
  89663. }
  89664. };
  89665. },
  89666. /**
  89667. * Read associated data
  89668. * @private
  89669. * @param {Ext.data.Model} record The record we're writing to
  89670. * @param {Ext.data.reader.Reader} reader The reader for the associated model
  89671. * @param {Object} associationData The raw associated data
  89672. */
  89673. read: function(record, reader, associationData){
  89674. record[this.instanceName] = reader.read([associationData]).records[0];
  89675. }
  89676. });
  89677. /**
  89678. * General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and
  89679. * {@link #ordinalize ordinalizes} words. Sample usage:
  89680. *
  89681. * //turning singular words into plurals
  89682. * Ext.util.Inflector.pluralize('word'); //'words'
  89683. * Ext.util.Inflector.pluralize('person'); //'people'
  89684. * Ext.util.Inflector.pluralize('sheep'); //'sheep'
  89685. *
  89686. * //turning plurals into singulars
  89687. * Ext.util.Inflector.singularize('words'); //'word'
  89688. * Ext.util.Inflector.singularize('people'); //'person'
  89689. * Ext.util.Inflector.singularize('sheep'); //'sheep'
  89690. *
  89691. * //ordinalizing numbers
  89692. * Ext.util.Inflector.ordinalize(11); //"11th"
  89693. * Ext.util.Inflector.ordinalize(21); //"21st"
  89694. * Ext.util.Inflector.ordinalize(1043); //"1043rd"
  89695. *
  89696. * # Customization
  89697. *
  89698. * The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
  89699. * rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
  89700. * Here is how we might add a rule that pluralizes "ox" to "oxen":
  89701. *
  89702. * Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
  89703. *
  89704. * Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string. In
  89705. * this case, the regular expression will only match the string "ox", and will replace that match with "oxen". Here's
  89706. * how we could add the inverse rule:
  89707. *
  89708. * Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
  89709. *
  89710. * Note that the ox/oxen rules are present by default.
  89711. */
  89712. Ext.define('Ext.util.Inflector', {
  89713. /* Begin Definitions */
  89714. singleton: true,
  89715. /* End Definitions */
  89716. /**
  89717. * @private
  89718. * The registered plural tuples. Each item in the array should contain two items - the first must be a regular
  89719. * expression that matchers the singular form of a word, the second must be a String that replaces the matched
  89720. * part of the regular expression. This is managed by the {@link #plural} method.
  89721. * @property {Array} plurals
  89722. */
  89723. plurals: [
  89724. [(/(quiz)$/i), "$1zes" ],
  89725. [(/^(ox)$/i), "$1en" ],
  89726. [(/([m|l])ouse$/i), "$1ice" ],
  89727. [(/(matr|vert|ind)ix|ex$/i), "$1ices" ],
  89728. [(/(x|ch|ss|sh)$/i), "$1es" ],
  89729. [(/([^aeiouy]|qu)y$/i), "$1ies" ],
  89730. [(/(hive)$/i), "$1s" ],
  89731. [(/(?:([^f])fe|([lr])f)$/i), "$1$2ves"],
  89732. [(/sis$/i), "ses" ],
  89733. [(/([ti])um$/i), "$1a" ],
  89734. [(/(buffal|tomat|potat)o$/i), "$1oes" ],
  89735. [(/(bu)s$/i), "$1ses" ],
  89736. [(/(alias|status|sex)$/i), "$1es" ],
  89737. [(/(octop|vir)us$/i), "$1i" ],
  89738. [(/(ax|test)is$/i), "$1es" ],
  89739. [(/^person$/), "people" ],
  89740. [(/^man$/), "men" ],
  89741. [(/^(child)$/), "$1ren" ],
  89742. [(/s$/i), "s" ],
  89743. [(/$/), "s" ]
  89744. ],
  89745. /**
  89746. * @private
  89747. * The set of registered singular matchers. Each item in the array should contain two items - the first must be a
  89748. * regular expression that matches the plural form of a word, the second must be a String that replaces the
  89749. * matched part of the regular expression. This is managed by the {@link #singular} method.
  89750. * @property {Array} singulars
  89751. */
  89752. singulars: [
  89753. [(/(quiz)zes$/i), "$1" ],
  89754. [(/(matr)ices$/i), "$1ix" ],
  89755. [(/(vert|ind)ices$/i), "$1ex" ],
  89756. [(/^(ox)en/i), "$1" ],
  89757. [(/(alias|status)es$/i), "$1" ],
  89758. [(/(octop|vir)i$/i), "$1us" ],
  89759. [(/(cris|ax|test)es$/i), "$1is" ],
  89760. [(/(shoe)s$/i), "$1" ],
  89761. [(/(o)es$/i), "$1" ],
  89762. [(/(bus)es$/i), "$1" ],
  89763. [(/([m|l])ice$/i), "$1ouse" ],
  89764. [(/(x|ch|ss|sh)es$/i), "$1" ],
  89765. [(/(m)ovies$/i), "$1ovie" ],
  89766. [(/(s)eries$/i), "$1eries"],
  89767. [(/([^aeiouy]|qu)ies$/i), "$1y" ],
  89768. [(/([lr])ves$/i), "$1f" ],
  89769. [(/(tive)s$/i), "$1" ],
  89770. [(/(hive)s$/i), "$1" ],
  89771. [(/([^f])ves$/i), "$1fe" ],
  89772. [(/(^analy)ses$/i), "$1sis" ],
  89773. [(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"],
  89774. [(/([ti])a$/i), "$1um" ],
  89775. [(/(n)ews$/i), "$1ews" ],
  89776. [(/people$/i), "person" ],
  89777. [(/s$/i), "" ]
  89778. ],
  89779. /**
  89780. * @private
  89781. * The registered uncountable words
  89782. * @property {String[]} uncountable
  89783. */
  89784. uncountable: [
  89785. "sheep",
  89786. "fish",
  89787. "series",
  89788. "species",
  89789. "money",
  89790. "rice",
  89791. "information",
  89792. "equipment",
  89793. "grass",
  89794. "mud",
  89795. "offspring",
  89796. "deer",
  89797. "means"
  89798. ],
  89799. /**
  89800. * Adds a new singularization rule to the Inflector. See the intro docs for more information
  89801. * @param {RegExp} matcher The matcher regex
  89802. * @param {String} replacer The replacement string, which can reference matches from the matcher argument
  89803. */
  89804. singular: function(matcher, replacer) {
  89805. this.singulars.unshift([matcher, replacer]);
  89806. },
  89807. /**
  89808. * Adds a new pluralization rule to the Inflector. See the intro docs for more information
  89809. * @param {RegExp} matcher The matcher regex
  89810. * @param {String} replacer The replacement string, which can reference matches from the matcher argument
  89811. */
  89812. plural: function(matcher, replacer) {
  89813. this.plurals.unshift([matcher, replacer]);
  89814. },
  89815. /**
  89816. * Removes all registered singularization rules
  89817. */
  89818. clearSingulars: function() {
  89819. this.singulars = [];
  89820. },
  89821. /**
  89822. * Removes all registered pluralization rules
  89823. */
  89824. clearPlurals: function() {
  89825. this.plurals = [];
  89826. },
  89827. /**
  89828. * Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
  89829. * @param {String} word The word to test
  89830. * @return {Boolean} True if the word is transnumeral
  89831. */
  89832. isTransnumeral: function(word) {
  89833. return Ext.Array.indexOf(this.uncountable, word) != -1;
  89834. },
  89835. /**
  89836. * Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words')
  89837. * @param {String} word The word to pluralize
  89838. * @return {String} The pluralized form of the word
  89839. */
  89840. pluralize: function(word) {
  89841. if (this.isTransnumeral(word)) {
  89842. return word;
  89843. }
  89844. var plurals = this.plurals,
  89845. length = plurals.length,
  89846. tuple, regex, i;
  89847. for (i = 0; i < length; i++) {
  89848. tuple = plurals[i];
  89849. regex = tuple[0];
  89850. if (regex == word || (regex.test && regex.test(word))) {
  89851. return word.replace(regex, tuple[1]);
  89852. }
  89853. }
  89854. return word;
  89855. },
  89856. /**
  89857. * Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
  89858. * @param {String} word The word to singularize
  89859. * @return {String} The singularized form of the word
  89860. */
  89861. singularize: function(word) {
  89862. if (this.isTransnumeral(word)) {
  89863. return word;
  89864. }
  89865. var singulars = this.singulars,
  89866. length = singulars.length,
  89867. tuple, regex, i;
  89868. for (i = 0; i < length; i++) {
  89869. tuple = singulars[i];
  89870. regex = tuple[0];
  89871. if (regex == word || (regex.test && regex.test(word))) {
  89872. return word.replace(regex, tuple[1]);
  89873. }
  89874. }
  89875. return word;
  89876. },
  89877. /**
  89878. * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data
  89879. * package
  89880. * @param {String} word The word to classify
  89881. * @return {String} The classified version of the word
  89882. */
  89883. classify: function(word) {
  89884. return Ext.String.capitalize(this.singularize(word));
  89885. },
  89886. /**
  89887. * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the
  89888. * number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
  89889. * @param {Number} number The number to ordinalize
  89890. * @return {String} The ordinalized number
  89891. */
  89892. ordinalize: function(number) {
  89893. var parsed = parseInt(number, 10),
  89894. mod10 = parsed % 10,
  89895. mod100 = parsed % 100;
  89896. //11 through 13 are a special case
  89897. if (11 <= mod100 && mod100 <= 13) {
  89898. return number + "th";
  89899. } else {
  89900. switch(mod10) {
  89901. case 1 : return number + "st";
  89902. case 2 : return number + "nd";
  89903. case 3 : return number + "rd";
  89904. default: return number + "th";
  89905. }
  89906. }
  89907. }
  89908. }, function() {
  89909. //aside from the rules above, there are a number of words that have irregular pluralization so we add them here
  89910. var irregulars = {
  89911. alumnus: 'alumni',
  89912. cactus : 'cacti',
  89913. focus : 'foci',
  89914. nucleus: 'nuclei',
  89915. radius: 'radii',
  89916. stimulus: 'stimuli',
  89917. ellipsis: 'ellipses',
  89918. paralysis: 'paralyses',
  89919. oasis: 'oases',
  89920. appendix: 'appendices',
  89921. index: 'indexes',
  89922. beau: 'beaux',
  89923. bureau: 'bureaux',
  89924. tableau: 'tableaux',
  89925. woman: 'women',
  89926. child: 'children',
  89927. man: 'men',
  89928. corpus: 'corpora',
  89929. criterion: 'criteria',
  89930. curriculum: 'curricula',
  89931. genus: 'genera',
  89932. memorandum: 'memoranda',
  89933. phenomenon: 'phenomena',
  89934. foot: 'feet',
  89935. goose: 'geese',
  89936. tooth: 'teeth',
  89937. antenna: 'antennae',
  89938. formula: 'formulae',
  89939. nebula: 'nebulae',
  89940. vertebra: 'vertebrae',
  89941. vita: 'vitae'
  89942. },
  89943. singular;
  89944. for (singular in irregulars) {
  89945. this.plural(singular, irregulars[singular]);
  89946. this.singular(irregulars[singular], singular);
  89947. }
  89948. });
  89949. /**
  89950. * @author Ed Spencer
  89951. * @class Ext.data.association.HasMany
  89952. *
  89953. * <p>Represents a one-to-many relationship between two models. Usually created indirectly via a model definition:</p>
  89954. *
  89955. <pre><code>
  89956. Ext.define('Product', {
  89957. extend: 'Ext.data.Model',
  89958. fields: [
  89959. {name: 'id', type: 'int'},
  89960. {name: 'user_id', type: 'int'},
  89961. {name: 'name', type: 'string'}
  89962. ]
  89963. });
  89964. Ext.define('User', {
  89965. extend: 'Ext.data.Model',
  89966. fields: [
  89967. {name: 'id', type: 'int'},
  89968. {name: 'name', type: 'string'}
  89969. ],
  89970. // we can use the hasMany shortcut on the model to create a hasMany association
  89971. hasMany: {model: 'Product', name: 'products'}
  89972. });
  89973. </pre></code>
  89974. *
  89975. * <p>Above we created Product and User models, and linked them by saying that a User hasMany Products. This gives
  89976. * us a new function on every User instance, in this case the function is called 'products' because that is the name
  89977. * we specified in the association configuration above.</p>
  89978. *
  89979. * <p>This new function returns a specialized {@link Ext.data.Store Store} which is automatically filtered to load
  89980. * only Products for the given model instance:</p>
  89981. *
  89982. <pre><code>
  89983. //first, we load up a User with id of 1
  89984. var user = Ext.create('User', {id: 1, name: 'Ed'});
  89985. //the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
  89986. //the created store is automatically scoped to the set of Products for the User with id of 1
  89987. var products = user.products();
  89988. //we still have all of the usual Store functions, for example it's easy to add a Product for this User
  89989. products.add({
  89990. name: 'Another Product'
  89991. });
  89992. //saves the changes to the store - this automatically sets the new Product's user_id to 1 before saving
  89993. products.sync();
  89994. </code></pre>
  89995. *
  89996. * <p>The new Store is only instantiated the first time you call products() to conserve memory and processing time,
  89997. * though calling products() a second time returns the same store instance.</p>
  89998. *
  89999. * <p><u>Custom filtering</u></p>
  90000. *
  90001. * <p>The Store is automatically furnished with a filter - by default this filter tells the store to only return
  90002. * records where the associated model's foreign key matches the owner model's primary key. For example, if a User
  90003. * with ID = 100 hasMany Products, the filter loads only Products with user_id == 100.</p>
  90004. *
  90005. * <p>Sometimes we want to filter by another field - for example in the case of a Twitter search application we may
  90006. * have models for Search and Tweet:</p>
  90007. *
  90008. <pre><code>
  90009. Ext.define('Search', {
  90010. extend: 'Ext.data.Model',
  90011. fields: [
  90012. 'id', 'query'
  90013. ],
  90014. hasMany: {
  90015. model: 'Tweet',
  90016. name : 'tweets',
  90017. filterProperty: 'query'
  90018. }
  90019. });
  90020. Ext.define('Tweet', {
  90021. extend: 'Ext.data.Model',
  90022. fields: [
  90023. 'id', 'text', 'from_user'
  90024. ]
  90025. });
  90026. //returns a Store filtered by the filterProperty
  90027. var store = new Search({query: 'Sencha Touch'}).tweets();
  90028. </code></pre>
  90029. *
  90030. * <p>The tweets association above is filtered by the query property by setting the {@link #filterProperty}, and is
  90031. * equivalent to this:</p>
  90032. *
  90033. <pre><code>
  90034. var store = Ext.create('Ext.data.Store', {
  90035. model: 'Tweet',
  90036. filters: [
  90037. {
  90038. property: 'query',
  90039. value : 'Sencha Touch'
  90040. }
  90041. ]
  90042. });
  90043. </code></pre>
  90044. */
  90045. Ext.define('Ext.data.association.HasMany', {
  90046. extend: 'Ext.data.association.Association',
  90047. alternateClassName: 'Ext.data.HasManyAssociation',
  90048. requires: ['Ext.util.Inflector'],
  90049. alias: 'association.hasmany',
  90050. /**
  90051. * @cfg {String} foreignKey The name of the foreign key on the associated model that links it to the owner
  90052. * model. Defaults to the lowercased name of the owner model plus "_id", e.g. an association with a where a
  90053. * model called Group hasMany Users would create 'group_id' as the foreign key. When the remote store is loaded,
  90054. * the store is automatically filtered so that only records with a matching foreign key are included in the
  90055. * resulting child store. This can be overridden by specifying the {@link #filterProperty}.
  90056. * <pre><code>
  90057. Ext.define('Group', {
  90058. extend: 'Ext.data.Model',
  90059. fields: ['id', 'name'],
  90060. hasMany: 'User'
  90061. });
  90062. Ext.define('User', {
  90063. extend: 'Ext.data.Model',
  90064. fields: ['id', 'name', 'group_id'], // refers to the id of the group that this user belongs to
  90065. belongsTo: 'Group'
  90066. });
  90067. * </code></pre>
  90068. */
  90069. /**
  90070. * @cfg {String} name The name of the function to create on the owner model to retrieve the child store.
  90071. * If not specified, the pluralized name of the child model is used.
  90072. * <pre><code>
  90073. // This will create a users() method on any Group model instance
  90074. Ext.define('Group', {
  90075. extend: 'Ext.data.Model',
  90076. fields: ['id', 'name'],
  90077. hasMany: 'User'
  90078. });
  90079. var group = new Group();
  90080. console.log(group.users());
  90081. // The method to retrieve the users will now be getUserList
  90082. Ext.define('Group', {
  90083. extend: 'Ext.data.Model',
  90084. fields: ['id', 'name'],
  90085. hasMany: {model: 'User', name: 'getUserList'}
  90086. });
  90087. var group = new Group();
  90088. console.log(group.getUserList());
  90089. * </code></pre>
  90090. */
  90091. /**
  90092. * @cfg {Object} storeConfig Optional configuration object that will be passed to the generated Store. Defaults to
  90093. * undefined.
  90094. */
  90095. /**
  90096. * @cfg {String} filterProperty Optionally overrides the default filter that is set up on the associated Store. If
  90097. * this is not set, a filter is automatically created which filters the association based on the configured
  90098. * {@link #foreignKey}. See intro docs for more details. Defaults to undefined
  90099. */
  90100. /**
  90101. * @cfg {Boolean} autoLoad True to automatically load the related store from a remote source when instantiated.
  90102. * Defaults to <tt>false</tt>.
  90103. */
  90104. /**
  90105. * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
  90106. * Use 'hasMany' to create a HasMany association
  90107. * <pre><code>
  90108. associations: [{
  90109. type: 'hasMany',
  90110. model: 'User'
  90111. }]
  90112. * </code></pre>
  90113. */
  90114. constructor: function(config) {
  90115. var me = this,
  90116. ownerProto,
  90117. name;
  90118. me.callParent(arguments);
  90119. me.name = me.name || Ext.util.Inflector.pluralize(me.associatedName.toLowerCase());
  90120. ownerProto = me.ownerModel.prototype;
  90121. name = me.name;
  90122. Ext.applyIf(me, {
  90123. storeName : name + "Store",
  90124. foreignKey: me.ownerName.toLowerCase() + "_id"
  90125. });
  90126. ownerProto[name] = me.createStore();
  90127. },
  90128. /**
  90129. * @private
  90130. * Creates a function that returns an Ext.data.Store which is configured to load a set of data filtered
  90131. * by the owner model's primary key - e.g. in a hasMany association where Group hasMany Users, this function
  90132. * returns a Store configured to return the filtered set of a single Group's Users.
  90133. * @return {Function} The store-generating function
  90134. */
  90135. createStore: function() {
  90136. var that = this,
  90137. associatedModel = that.associatedModel,
  90138. storeName = that.storeName,
  90139. foreignKey = that.foreignKey,
  90140. primaryKey = that.primaryKey,
  90141. filterProperty = that.filterProperty,
  90142. autoLoad = that.autoLoad,
  90143. storeConfig = that.storeConfig || {};
  90144. return function() {
  90145. var me = this,
  90146. config, filter,
  90147. modelDefaults = {};
  90148. if (me[storeName] === undefined) {
  90149. if (filterProperty) {
  90150. filter = {
  90151. property : filterProperty,
  90152. value : me.get(filterProperty),
  90153. exactMatch: true
  90154. };
  90155. } else {
  90156. filter = {
  90157. property : foreignKey,
  90158. value : me.get(primaryKey),
  90159. exactMatch: true
  90160. };
  90161. }
  90162. modelDefaults[foreignKey] = me.get(primaryKey);
  90163. config = Ext.apply({}, storeConfig, {
  90164. model : associatedModel,
  90165. filters : [filter],
  90166. remoteFilter : false,
  90167. modelDefaults: modelDefaults
  90168. });
  90169. me[storeName] = Ext.data.AbstractStore.create(config);
  90170. if (autoLoad) {
  90171. me[storeName].load();
  90172. }
  90173. }
  90174. return me[storeName];
  90175. };
  90176. },
  90177. /**
  90178. * Read associated data
  90179. * @private
  90180. * @param {Ext.data.Model} record The record we're writing to
  90181. * @param {Ext.data.reader.Reader} reader The reader for the associated model
  90182. * @param {Object} associationData The raw associated data
  90183. */
  90184. read: function(record, reader, associationData){
  90185. var store = record[this.name](),
  90186. inverse,
  90187. items, iLen, i;
  90188. store.add(reader.read(associationData).records);
  90189. //now that we've added the related records to the hasMany association, set the inverse belongsTo
  90190. //association on each of them if it exists
  90191. inverse = this.associatedModel.prototype.associations.findBy(function(assoc){
  90192. return assoc.type === 'belongsTo' && assoc.associatedName === record.$className;
  90193. });
  90194. //if the inverse association was found, set it now on each record we've just created
  90195. if (inverse) {
  90196. items = store.data.items;
  90197. iLen = items.length;
  90198. for (i = 0; i < iLen; i++) {
  90199. items[i][inverse.instanceName] = record;
  90200. }
  90201. }
  90202. }
  90203. });
  90204. /**
  90205. * @class Ext.data.association.HasOne
  90206. *
  90207. * Represents a one to one association with another model. The owner model is expected to have
  90208. * a foreign key which references the primary key of the associated model:
  90209. *
  90210. * Ext.define('Address', {
  90211. * extend: 'Ext.data.Model',
  90212. * fields: [
  90213. * { name: 'id', type: 'int' },
  90214. * { name: 'number', type: 'string' },
  90215. * { name: 'street', type: 'string' },
  90216. * { name: 'city', type: 'string' },
  90217. * { name: 'zip', type: 'string' },
  90218. * ]
  90219. * });
  90220. *
  90221. * Ext.define('Person', {
  90222. * extend: 'Ext.data.Model',
  90223. * fields: [
  90224. * { name: 'id', type: 'int' },
  90225. * { name: 'name', type: 'string' },
  90226. * { name: 'address_id', type: 'int'}
  90227. * ],
  90228. * // we can use the hasOne shortcut on the model to create a hasOne association
  90229. * associations: [{ type: 'hasOne', model: 'Address' }]
  90230. * });
  90231. *
  90232. * In the example above we have created models for People and Addresses, and linked them together
  90233. * by saying that each Person has a single Address. This automatically links each Person to an Address
  90234. * based on the Persons address_id, and provides new functions on the Person model:
  90235. *
  90236. * ## Generated getter function
  90237. *
  90238. * The first function that is added to the owner model is a getter function:
  90239. *
  90240. * var person = new Person({
  90241. * id: 100,
  90242. * address_id: 20,
  90243. * name: 'John Smith'
  90244. * });
  90245. *
  90246. * person.getAddress(function(address, operation) {
  90247. * // do something with the address object
  90248. * alert(address.get('id')); // alerts 20
  90249. * }, this);
  90250. *
  90251. * The getAddress function was created on the Person model when we defined the association. This uses the
  90252. * Persons configured {@link Ext.data.proxy.Proxy proxy} to load the Address asynchronously, calling the provided
  90253. * callback when it has loaded.
  90254. *
  90255. * The new getAddress function will also accept an object containing success, failure and callback properties
  90256. * - callback will always be called, success will only be called if the associated model was loaded successfully
  90257. * and failure will only be called if the associatied model could not be loaded:
  90258. *
  90259. * person.getAddress({
  90260. * reload: true, // force a reload if the owner model is already cached
  90261. * callback: function(address, operation) {}, // a function that will always be called
  90262. * success : function(address, operation) {}, // a function that will only be called if the load succeeded
  90263. * failure : function(address, operation) {}, // a function that will only be called if the load did not succeed
  90264. * scope : this // optionally pass in a scope object to execute the callbacks in
  90265. * });
  90266. *
  90267. * In each case above the callbacks are called with two arguments - the associated model instance and the
  90268. * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
  90269. * useful when the instance could not be loaded.
  90270. *
  90271. * Once the getter has been called on the model, it will be cached if the getter is called a second time. To
  90272. * force the model to reload, specify reload: true in the options object.
  90273. *
  90274. * ## Generated setter function
  90275. *
  90276. * The second generated function sets the associated model instance - if only a single argument is passed to
  90277. * the setter then the following two calls are identical:
  90278. *
  90279. * // this call...
  90280. * person.setAddress(10);
  90281. *
  90282. * // is equivalent to this call:
  90283. * person.set('address_id', 10);
  90284. *
  90285. * An instance of the owner model can also be passed as a parameter.
  90286. *
  90287. * If we pass in a second argument, the model will be automatically saved and the second argument passed to
  90288. * the owner model's {@link Ext.data.Model#save save} method:
  90289. *
  90290. * person.setAddress(10, function(address, operation) {
  90291. * // the address has been saved
  90292. * alert(address.get('address_id')); //now alerts 10
  90293. * });
  90294. *
  90295. * //alternative syntax:
  90296. * person.setAddress(10, {
  90297. * callback: function(address, operation), // a function that will always be called
  90298. * success : function(address, operation), // a function that will only be called if the load succeeded
  90299. * failure : function(address, operation), // a function that will only be called if the load did not succeed
  90300. * scope : this //optionally pass in a scope object to execute the callbacks in
  90301. * })
  90302. *
  90303. * ## Customisation
  90304. *
  90305. * Associations reflect on the models they are linking to automatically set up properties such as the
  90306. * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
  90307. *
  90308. * Ext.define('Person', {
  90309. * fields: [...],
  90310. *
  90311. * associations: [
  90312. * { type: 'hasOne', model: 'Address', primaryKey: 'unique_id', foreignKey: 'addr_id' }
  90313. * ]
  90314. * });
  90315. *
  90316. * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'address_id')
  90317. * with our own settings. Usually this will not be needed.
  90318. */
  90319. Ext.define('Ext.data.association.HasOne', {
  90320. extend: 'Ext.data.association.Association',
  90321. alternateClassName: 'Ext.data.HasOneAssociation',
  90322. alias: 'association.hasone',
  90323. /**
  90324. * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
  90325. * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
  90326. * model called Person would set up a address_id foreign key.
  90327. *
  90328. * Ext.define('Person', {
  90329. * extend: 'Ext.data.Model',
  90330. * fields: ['id', 'name', 'address_id'], // refers to the id of the address object
  90331. * hasOne: 'Address'
  90332. * });
  90333. *
  90334. * Ext.define('Address', {
  90335. * extend: 'Ext.data.Model',
  90336. * fields: ['id', 'number', 'street', 'city', 'zip'],
  90337. * belongsTo: 'Person'
  90338. * });
  90339. * var Person = new Person({
  90340. * id: 1,
  90341. * name: 'John Smith',
  90342. * address_id: 13
  90343. * }, 1);
  90344. * person.getAddress(); // Will make a call to the server asking for address_id 13
  90345. *
  90346. */
  90347. /**
  90348. * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
  90349. * Defaults to 'get' + the name of the foreign model, e.g. getAddress
  90350. */
  90351. /**
  90352. * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
  90353. * Defaults to 'set' + the name of the foreign model, e.g. setAddress
  90354. */
  90355. /**
  90356. * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
  90357. * Use 'hasOne' to create a HasOne association.
  90358. *
  90359. * associations: [{
  90360. * type: 'hasOne',
  90361. * model: 'Address'
  90362. * }]
  90363. */
  90364. constructor: function(config) {
  90365. this.callParent(arguments);
  90366. var me = this,
  90367. ownerProto = me.ownerModel.prototype,
  90368. associatedName = me.associatedName,
  90369. getterName = me.getterName || 'get' + associatedName,
  90370. setterName = me.setterName || 'set' + associatedName;
  90371. Ext.applyIf(me, {
  90372. name : associatedName,
  90373. foreignKey : associatedName.toLowerCase() + "_id",
  90374. instanceName: associatedName + 'HasOneInstance',
  90375. associationKey: associatedName.toLowerCase()
  90376. });
  90377. ownerProto[getterName] = me.createGetter();
  90378. ownerProto[setterName] = me.createSetter();
  90379. },
  90380. /**
  90381. * @private
  90382. * Returns a setter function to be placed on the owner model's prototype
  90383. * @return {Function} The setter function
  90384. */
  90385. createSetter: function() {
  90386. var me = this,
  90387. ownerModel = me.ownerModel,
  90388. foreignKey = me.foreignKey;
  90389. //'this' refers to the Model instance inside this function
  90390. return function(value, options, scope) {
  90391. if (value && value.isModel) {
  90392. value = value.getId();
  90393. }
  90394. this.set(foreignKey, value);
  90395. if (Ext.isFunction(options)) {
  90396. options = {
  90397. callback: options,
  90398. scope: scope || this
  90399. };
  90400. }
  90401. if (Ext.isObject(options)) {
  90402. return this.save(options);
  90403. }
  90404. };
  90405. },
  90406. /**
  90407. * @private
  90408. * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
  90409. * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
  90410. * @return {Function} The getter function
  90411. */
  90412. createGetter: function() {
  90413. var me = this,
  90414. ownerModel = me.ownerModel,
  90415. associatedName = me.associatedName,
  90416. associatedModel = me.associatedModel,
  90417. foreignKey = me.foreignKey,
  90418. primaryKey = me.primaryKey,
  90419. instanceName = me.instanceName;
  90420. //'this' refers to the Model instance inside this function
  90421. return function(options, scope) {
  90422. options = options || {};
  90423. var model = this,
  90424. foreignKeyId = model.get(foreignKey),
  90425. success,
  90426. instance,
  90427. args;
  90428. if (options.reload === true || model[instanceName] === undefined) {
  90429. instance = Ext.ModelManager.create({}, associatedName);
  90430. instance.set(primaryKey, foreignKeyId);
  90431. if (typeof options == 'function') {
  90432. options = {
  90433. callback: options,
  90434. scope: scope || model
  90435. };
  90436. }
  90437. // Overwrite the success handler so we can assign the current instance
  90438. success = options.success;
  90439. options.success = function(rec){
  90440. model[instanceName] = rec;
  90441. if (success) {
  90442. success.apply(this, arguments);
  90443. }
  90444. };
  90445. associatedModel.load(foreignKeyId, options);
  90446. // assign temporarily while we wait for data to return
  90447. model[instanceName] = instance;
  90448. return instance;
  90449. } else {
  90450. instance = model[instanceName];
  90451. args = [instance];
  90452. scope = scope || options.scope || model;
  90453. //TODO: We're duplicating the callback invokation code that the instance.load() call above
  90454. //makes here - ought to be able to normalize this - perhaps by caching at the Model.load layer
  90455. //instead of the association layer.
  90456. Ext.callback(options, scope, args);
  90457. Ext.callback(options.success, scope, args);
  90458. Ext.callback(options.failure, scope, args);
  90459. Ext.callback(options.callback, scope, args);
  90460. return instance;
  90461. }
  90462. };
  90463. },
  90464. /**
  90465. * Read associated data
  90466. * @private
  90467. * @param {Ext.data.Model} record The record we're writing to
  90468. * @param {Ext.data.reader.Reader} reader The reader for the associated model
  90469. * @param {Object} associationData The raw associated data
  90470. */
  90471. read: function(record, reader, associationData){
  90472. var inverse = this.associatedModel.prototype.associations.findBy(function(assoc){
  90473. return assoc.type === 'belongsTo' && assoc.associatedName === record.$className;
  90474. }), newRecord = reader.read([associationData]).records[0];
  90475. record[this.instanceName] = newRecord;
  90476. //if the inverse association was found, set it now on each record we've just created
  90477. if (inverse) {
  90478. newRecord[inverse.instanceName] = record;
  90479. }
  90480. }
  90481. });
  90482. /**
  90483. * @author Ed Spencer
  90484. *
  90485. * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} and {@link
  90486. * Ext.data.proxy.SessionStorage SessionStorage} proxies. It uses the new HTML5 key/value client-side storage objects to
  90487. * save {@link Ext.data.Model model instances} for offline use.
  90488. * @private
  90489. */
  90490. Ext.define('Ext.data.proxy.WebStorage', {
  90491. extend: 'Ext.data.proxy.Client',
  90492. alternateClassName: 'Ext.data.WebStorageProxy',
  90493. requires: [
  90494. 'Ext.data.SequentialIdGenerator'
  90495. ],
  90496. /**
  90497. * @cfg {String} id
  90498. * The unique ID used as the key in which all record data are stored in the local storage object.
  90499. */
  90500. id: undefined,
  90501. /**
  90502. * Creates the proxy, throws an error if local storage is not supported in the current browser.
  90503. * @param {Object} config (optional) Config object.
  90504. */
  90505. constructor: function(config) {
  90506. this.callParent(arguments);
  90507. /**
  90508. * @property {Object} cache
  90509. * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
  90510. */
  90511. this.cache = {};
  90512. if (this.getStorageObject() === undefined) {
  90513. Ext.Error.raise("Local Storage is not supported in this browser, please use another type of data proxy");
  90514. }
  90515. //if an id is not given, try to use the store's id instead
  90516. this.id = this.id || (this.store ? this.store.storeId : undefined);
  90517. if (this.id === undefined) {
  90518. Ext.Error.raise("No unique id was provided to the local storage proxy. See Ext.data.proxy.LocalStorage documentation for details");
  90519. }
  90520. this.initialize();
  90521. },
  90522. //inherit docs
  90523. create: function(operation, callback, scope) {
  90524. var me = this,
  90525. records = operation.records,
  90526. length = records.length,
  90527. ids = me.getIds(),
  90528. id, record, i;
  90529. operation.setStarted();
  90530. if(me.isHierarchical === undefined) {
  90531. // if the storage object does not yet contain any data, this is the first point at which we can determine whether or not this proxy deals with hierarchical data.
  90532. // it cannot be determined during initialization because the Model is not decorated with NodeInterface until it is used in a TreeStore
  90533. me.isHierarchical = !!records[0].isNode;
  90534. if(me.isHierarchical) {
  90535. me.getStorageObject().setItem(me.getTreeKey(), true);
  90536. }
  90537. }
  90538. for (i = 0; i < length; i++) {
  90539. record = records[i];
  90540. if (record.phantom) {
  90541. record.phantom = false;
  90542. id = me.getNextId();
  90543. } else {
  90544. id = record.getId();
  90545. }
  90546. me.setRecord(record, id);
  90547. record.commit();
  90548. ids.push(id);
  90549. }
  90550. me.setIds(ids);
  90551. operation.setCompleted();
  90552. operation.setSuccessful();
  90553. if (typeof callback == 'function') {
  90554. callback.call(scope || me, operation);
  90555. }
  90556. },
  90557. //inherit docs
  90558. read: function(operation, callback, scope) {
  90559. //TODO: respect sorters, filters, start and limit options on the Operation
  90560. var me = this,
  90561. records = [],
  90562. i = 0,
  90563. success = true,
  90564. Model = me.model,
  90565. ids, length, record, data, id;
  90566. operation.setStarted();
  90567. if(me.isHierarchical) {
  90568. records = me.getTreeData();
  90569. } else {
  90570. ids = me.getIds();
  90571. length = ids.length;
  90572. id = operation.id;
  90573. //read a single record
  90574. if (id) {
  90575. data = me.getRecord(id);
  90576. if (data !== null) {
  90577. record = new Model(data, id, data);
  90578. }
  90579. if (record) {
  90580. records.push(record);
  90581. } else {
  90582. success = false;
  90583. }
  90584. } else {
  90585. for (; i < length; i++) {
  90586. id = ids[i];
  90587. data = me.getRecord(id);
  90588. records.push(new Model(data, id, data));
  90589. }
  90590. }
  90591. }
  90592. if(success) {
  90593. operation.setSuccessful();
  90594. }
  90595. operation.setCompleted();
  90596. operation.resultSet = Ext.create('Ext.data.ResultSet', {
  90597. records: records,
  90598. total : records.length,
  90599. loaded : true
  90600. });
  90601. if (typeof callback == 'function') {
  90602. callback.call(scope || me, operation);
  90603. }
  90604. },
  90605. //inherit docs
  90606. update: function(operation, callback, scope) {
  90607. var records = operation.records,
  90608. length = records.length,
  90609. ids = this.getIds(),
  90610. record, id, i;
  90611. operation.setStarted();
  90612. for (i = 0; i < length; i++) {
  90613. record = records[i];
  90614. this.setRecord(record);
  90615. record.commit();
  90616. //we need to update the set of ids here because it's possible that a non-phantom record was added
  90617. //to this proxy - in which case the record's id would never have been added via the normal 'create' call
  90618. id = record.getId();
  90619. if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
  90620. ids.push(id);
  90621. }
  90622. }
  90623. this.setIds(ids);
  90624. operation.setCompleted();
  90625. operation.setSuccessful();
  90626. if (typeof callback == 'function') {
  90627. callback.call(scope || this, operation);
  90628. }
  90629. },
  90630. //inherit
  90631. destroy: function(operation, callback, scope) {
  90632. var me = this,
  90633. records = operation.records,
  90634. ids = me.getIds(),
  90635. idLength = ids.length,
  90636. newIds = [],
  90637. removedHash = {},
  90638. i = records.length,
  90639. id;
  90640. operation.setStarted();
  90641. for (; i--;) {
  90642. Ext.apply(removedHash, me.removeRecord(records[i]));
  90643. }
  90644. for(i = 0; i < idLength; i++) {
  90645. id = ids[i];
  90646. if(!removedHash[id]) {
  90647. newIds.push(id);
  90648. }
  90649. }
  90650. me.setIds(newIds);
  90651. operation.setCompleted();
  90652. operation.setSuccessful();
  90653. if (typeof callback == 'function') {
  90654. callback.call(scope || me, operation);
  90655. }
  90656. },
  90657. /**
  90658. * @private
  90659. * Fetches record data from the Proxy by ID.
  90660. * @param {String} id The record's unique ID
  90661. * @return {Object} The record data
  90662. */
  90663. getRecord: function(id) {
  90664. var me = this,
  90665. cache = me.cache,
  90666. data = !cache[id] ? Ext.decode(me.getStorageObject().getItem(me.getRecordKey(id))) : cache[id];
  90667. if(!data) {
  90668. return null;
  90669. }
  90670. cache[id] = data;
  90671. data[me.model.prototype.idProperty] = id;
  90672. return data;
  90673. },
  90674. /**
  90675. * Saves the given record in the Proxy.
  90676. * @param {Ext.data.Model} record The model instance
  90677. * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)
  90678. */
  90679. setRecord: function(record, id) {
  90680. if (id) {
  90681. record.setId(id);
  90682. } else {
  90683. id = record.getId();
  90684. }
  90685. var me = this,
  90686. rawData = record.data,
  90687. data = {},
  90688. model = me.model,
  90689. fields = model.prototype.fields.items,
  90690. length = fields.length,
  90691. i = 0,
  90692. field, name, obj, key;
  90693. for (; i < length; i++) {
  90694. field = fields[i];
  90695. name = field.name;
  90696. if(field.persist) {
  90697. data[name] = rawData[name];
  90698. }
  90699. }
  90700. // no need to store the id in the data, since it is already stored in the record key
  90701. delete data[me.model.prototype.idProperty];
  90702. // if the record is a tree node and it's a direct child of the root node, do not store the parentId
  90703. if(record.isNode && record.get('depth') === 1) {
  90704. delete data.parentId;
  90705. }
  90706. obj = me.getStorageObject();
  90707. key = me.getRecordKey(id);
  90708. //keep the cache up to date
  90709. me.cache[id] = data;
  90710. //iPad bug requires that we remove the item before setting it
  90711. obj.removeItem(key);
  90712. obj.setItem(key, Ext.encode(data));
  90713. },
  90714. /**
  90715. * @private
  90716. * Physically removes a given record from the local storage and recursively removes children if the record is a tree node. Used internally by {@link #destroy}.
  90717. * @param {Ext.data.Model} record The record to remove
  90718. * @return {Object} a hash with the ids of the records that were removed as keys and the records that were removed as values
  90719. */
  90720. removeRecord: function(record) {
  90721. var me = this,
  90722. id = record.getId(),
  90723. records = {},
  90724. i, childNodes;
  90725. records[id] = record;
  90726. me.getStorageObject().removeItem(me.getRecordKey(id));
  90727. delete me.cache[id];
  90728. if(record.childNodes) {
  90729. childNodes = record.childNodes;
  90730. for(i = childNodes.length; i--;) {
  90731. Ext.apply(records, me.removeRecord(childNodes[i]));
  90732. }
  90733. }
  90734. return records;
  90735. },
  90736. /**
  90737. * @private
  90738. * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
  90739. * storing data in the local storage object and should prevent naming collisions.
  90740. * @param {String/Number/Ext.data.Model} id The record id, or a Model instance
  90741. * @return {String} The unique key for this record
  90742. */
  90743. getRecordKey: function(id) {
  90744. if (id.isModel) {
  90745. id = id.getId();
  90746. }
  90747. return Ext.String.format("{0}-{1}", this.id, id);
  90748. },
  90749. /**
  90750. * @private
  90751. * Returns the unique key used to store the current record counter for this proxy. This is used internally when
  90752. * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.
  90753. * @return {String} The counter key
  90754. */
  90755. getRecordCounterKey: function() {
  90756. return Ext.String.format("{0}-counter", this.id);
  90757. },
  90758. /**
  90759. * @private
  90760. * Returns the unique key used to store the tree indicator. This is used internally to determine if the stored data is hierarchical
  90761. * @return {String} The counter key
  90762. */
  90763. getTreeKey: function() {
  90764. return Ext.String.format("{0}-tree", this.id);
  90765. },
  90766. /**
  90767. * @private
  90768. * Returns the array of record IDs stored in this Proxy
  90769. * @return {Number[]} The record IDs. Each is cast as a Number
  90770. */
  90771. getIds: function() {
  90772. var me = this,
  90773. ids = (me.getStorageObject().getItem(me.id) || "").split(","),
  90774. model = me.model,
  90775. length = ids.length,
  90776. isString = model.prototype.fields.get(model.prototype.idProperty).type.type === 'string',
  90777. i;
  90778. if (length == 1 && ids[0] === "") {
  90779. ids = [];
  90780. } else {
  90781. for (i = 0; i < length; i++) {
  90782. ids[i] = isString ? ids[i] : +ids[i];
  90783. }
  90784. }
  90785. return ids;
  90786. },
  90787. /**
  90788. * @private
  90789. * Saves the array of ids representing the set of all records in the Proxy
  90790. * @param {Number[]} ids The ids to set
  90791. */
  90792. setIds: function(ids) {
  90793. var obj = this.getStorageObject(),
  90794. str = ids.join(",");
  90795. obj.removeItem(this.id);
  90796. if (!Ext.isEmpty(str)) {
  90797. obj.setItem(this.id, str);
  90798. }
  90799. },
  90800. /**
  90801. * @private
  90802. * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey).
  90803. * Increments the counter.
  90804. * @return {Number} The id
  90805. */
  90806. getNextId: function() {
  90807. var me = this,
  90808. obj = me.getStorageObject(),
  90809. key = me.getRecordCounterKey(),
  90810. model = me.model,
  90811. isString = model.prototype.fields.get(model.prototype.idProperty).type.type === 'string',
  90812. id;
  90813. id = me.idGenerator.generate();
  90814. obj.setItem(key, id);
  90815. if(!isString) {
  90816. id = +id;
  90817. }
  90818. return id;
  90819. },
  90820. /**
  90821. * Gets tree data and transforms it from key value pairs into a hierarchical structure.
  90822. * @private
  90823. * @return {Ext.data.NodeInterface[]}
  90824. */
  90825. getTreeData: function() {
  90826. var me = this,
  90827. ids = me.getIds(),
  90828. length = ids.length,
  90829. records = [],
  90830. recordHash = {},
  90831. root = [],
  90832. i = 0,
  90833. Model = me.model,
  90834. idProperty = Model.prototype.idProperty,
  90835. rootLength, record, parent, parentId, children, id;
  90836. for(; i < length; i++) {
  90837. id = ids[i];
  90838. // get the record for each id
  90839. record = me.getRecord(id);
  90840. // push the record into the records array
  90841. records.push(record);
  90842. // add the record to the record hash so it can be easily retrieved by id later
  90843. recordHash[id] = record;
  90844. if(!record.parentId) {
  90845. // push records that are at the root level (those with no parent id) into the "root" array
  90846. root.push(record);
  90847. }
  90848. }
  90849. rootLength = root.length;
  90850. // sort the records by parent id for greater efficiency, so that each parent record only has to be found once for all of its children
  90851. Ext.Array.sort(records, me.sortByParentId);
  90852. // append each record to its parent, starting after the root node(s), since root nodes do not need to be attached to a parent
  90853. for(i = rootLength; i < length; i++) {
  90854. record = records[i];
  90855. parentId = record.parentId;
  90856. if(!parent || parent[idProperty] !== parentId) {
  90857. // if this record has a different parent id from the previous record, we need to look up the parent by id.
  90858. parent = recordHash[parentId];
  90859. parent.children = children = [];
  90860. }
  90861. // push the record onto its parent's children array
  90862. children.push(record);
  90863. }
  90864. for(i = length; i--;) {
  90865. record = records[i];
  90866. if(!record.children && !record.leaf) {
  90867. // set non-leaf nodes with no children to loaded so the proxy won't try to dynamically load their contents when they are expanded
  90868. record.loaded = true;
  90869. }
  90870. }
  90871. // Create model instances out of all the "root-level" nodes.
  90872. for(i = rootLength; i--;) {
  90873. record = root[i];
  90874. root[i] = new Model(record, record[idProperty], record);
  90875. }
  90876. return root;
  90877. },
  90878. /**
  90879. * Sorter function for sorting records by parentId
  90880. * @private
  90881. * @param {Object} node1
  90882. * @param {Object} node2
  90883. * @return {Number}
  90884. */
  90885. sortByParentId: function(node1, node2) {
  90886. return (node1.parentId || 0) - (node2.parentId || 0);
  90887. },
  90888. /**
  90889. * @private
  90890. * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
  90891. * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
  90892. */
  90893. initialize: function() {
  90894. var me = this,
  90895. storageObject = me.getStorageObject(),
  90896. lastId = +storageObject.getItem(me.getRecordCounterKey());
  90897. storageObject.setItem(me.id, storageObject.getItem(me.id) || "");
  90898. if(storageObject.getItem(me.getTreeKey())) {
  90899. me.isHierarchical = true;
  90900. }
  90901. me.idGenerator = new Ext.data.SequentialIdGenerator({
  90902. seed: lastId ? lastId + 1 : 1
  90903. });
  90904. },
  90905. /**
  90906. * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
  90907. * storage object.
  90908. */
  90909. clear: function() {
  90910. var me = this,
  90911. obj = me.getStorageObject(),
  90912. ids = me.getIds(),
  90913. len = ids.length,
  90914. i;
  90915. //remove all the records
  90916. for (i = 0; i < len; i++) {
  90917. obj.removeItem(me.getRecordKey(ids[i]));
  90918. }
  90919. //remove the supporting objects
  90920. obj.removeItem(me.getRecordCounterKey());
  90921. obj.removeItem(me.getTreeKey());
  90922. obj.removeItem(me.id);
  90923. // clear the cache
  90924. me.cache = {};
  90925. },
  90926. /**
  90927. * @private
  90928. * Abstract function which should return the storage object that data will be saved to. This must be implemented
  90929. * in each subclass.
  90930. * @return {Object} The storage object
  90931. */
  90932. getStorageObject: function() {
  90933. Ext.Error.raise("The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass");
  90934. }
  90935. });
  90936. /**
  90937. * @author Ed Spencer
  90938. *
  90939. * The LocalStorageProxy uses the new HTML5 localStorage API to save {@link Ext.data.Model Model} data locally on the
  90940. * client browser. HTML5 localStorage is a key-value store (e.g. cannot save complex objects like JSON), so
  90941. * LocalStorageProxy automatically serializes and deserializes data when saving and retrieving it.
  90942. *
  90943. * localStorage is extremely useful for saving user-specific information without needing to build server-side
  90944. * infrastructure to support it. Let's imagine we're writing a Twitter search application and want to save the user's
  90945. * searches locally so they can easily perform a saved search again later. We'd start by creating a Search model:
  90946. *
  90947. * Ext.define('Search', {
  90948. * fields: ['id', 'query'],
  90949. * extend: 'Ext.data.Model',
  90950. * proxy: {
  90951. * type: 'localstorage',
  90952. * id : 'twitter-Searches'
  90953. * }
  90954. * });
  90955. *
  90956. * Our Search model contains just two fields - id and query - plus a Proxy definition. The only configuration we need to
  90957. * pass to the LocalStorage proxy is an {@link #id}. This is important as it separates the Model data in this Proxy from
  90958. * all others. The localStorage API puts all data into a single shared namespace, so by setting an id we enable
  90959. * LocalStorageProxy to manage the saved Search data.
  90960. *
  90961. * Saving our data into localStorage is easy and would usually be done with a {@link Ext.data.Store Store}:
  90962. *
  90963. * //our Store automatically picks up the LocalStorageProxy defined on the Search model
  90964. * var store = Ext.create('Ext.data.Store', {
  90965. * model: "Search"
  90966. * });
  90967. *
  90968. * //loads any existing Search data from localStorage
  90969. * store.load();
  90970. *
  90971. * //now add some Searches
  90972. * store.add({query: 'Sencha Touch'});
  90973. * store.add({query: 'Ext JS'});
  90974. *
  90975. * //finally, save our Search data to localStorage
  90976. * store.sync();
  90977. *
  90978. * The LocalStorageProxy automatically gives our new Searches an id when we call store.sync(). It encodes the Model data
  90979. * and places it into localStorage. We can also save directly to localStorage, bypassing the Store altogether:
  90980. *
  90981. * var search = Ext.create('Search', {query: 'Sencha Animator'});
  90982. *
  90983. * //uses the configured LocalStorageProxy to save the new Search to localStorage
  90984. * search.save();
  90985. *
  90986. * # Limitations
  90987. *
  90988. * If this proxy is used in a browser where local storage is not supported, the constructor will throw an error. A local
  90989. * storage proxy requires a unique ID which is used as a key in which all record data are stored in the local storage
  90990. * object.
  90991. *
  90992. * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
  90993. * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
  90994. */
  90995. Ext.define('Ext.data.proxy.LocalStorage', {
  90996. extend: 'Ext.data.proxy.WebStorage',
  90997. alias: 'proxy.localstorage',
  90998. alternateClassName: 'Ext.data.LocalStorageProxy',
  90999. //inherit docs
  91000. getStorageObject: function() {
  91001. return window.localStorage;
  91002. }
  91003. });
  91004. /**
  91005. * @author Ed Spencer
  91006. *
  91007. * The Rest proxy is a specialization of the {@link Ext.data.proxy.Ajax AjaxProxy} which simply maps the four actions
  91008. * (create, read, update and destroy) to RESTful HTTP verbs. For example, let's set up a {@link Ext.data.Model Model}
  91009. * with an inline Rest proxy
  91010. *
  91011. * Ext.define('User', {
  91012. * extend: 'Ext.data.Model',
  91013. * fields: ['id', 'name', 'email'],
  91014. *
  91015. * proxy: {
  91016. * type: 'rest',
  91017. * url : '/users'
  91018. * }
  91019. * });
  91020. *
  91021. * Now we can create a new User instance and save it via the Rest proxy. Doing this will cause the Proxy to send a POST
  91022. * request to '/users':
  91023. *
  91024. * var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
  91025. *
  91026. * user.save(); //POST /users
  91027. *
  91028. * Let's expand this a little and provide a callback for the {@link Ext.data.Model#save} call to update the Model once
  91029. * it has been created. We'll assume the creation went successfully and that the server gave this user an ID of 123:
  91030. *
  91031. * user.save({
  91032. * success: function(user) {
  91033. * user.set('name', 'Khan Noonien Singh');
  91034. *
  91035. * user.save(); //PUT /users/123
  91036. * }
  91037. * });
  91038. *
  91039. * Now that we're no longer creating a new Model instance, the request method is changed to an HTTP PUT, targeting the
  91040. * relevant url for that user. Now let's delete this user, which will use the DELETE method:
  91041. *
  91042. * user.destroy(); //DELETE /users/123
  91043. *
  91044. * Finally, when we perform a load of a Model or Store, Rest proxy will use the GET method:
  91045. *
  91046. * //1. Load via Store
  91047. *
  91048. * //the Store automatically picks up the Proxy from the User model
  91049. * var store = Ext.create('Ext.data.Store', {
  91050. * model: 'User'
  91051. * });
  91052. *
  91053. * store.load(); //GET /users
  91054. *
  91055. * //2. Load directly from the Model
  91056. *
  91057. * //GET /users/123
  91058. * Ext.ModelManager.getModel('User').load(123, {
  91059. * success: function(user) {
  91060. * console.log(user.getId()); //outputs 123
  91061. * }
  91062. * });
  91063. *
  91064. * # Url generation
  91065. *
  91066. * The Rest proxy is able to automatically generate the urls above based on two configuration options - {@link #appendId} and
  91067. * {@link #format}. If appendId is true (it is by default) then Rest proxy will automatically append the ID of the Model
  91068. * instance in question to the configured url, resulting in the '/users/123' that we saw above.
  91069. *
  91070. * If the request is not for a specific Model instance (e.g. loading a Store), the url is not appended with an id.
  91071. * The Rest proxy will automatically insert a '/' before the ID if one is not already present.
  91072. *
  91073. * new Ext.data.proxy.Rest({
  91074. * url: '/users',
  91075. * appendId: true //default
  91076. * });
  91077. *
  91078. * // Collection url: /users
  91079. * // Instance url : /users/123
  91080. *
  91081. * The Rest proxy can also optionally append a format string to the end of any generated url:
  91082. *
  91083. * new Ext.data.proxy.Rest({
  91084. * url: '/users',
  91085. * format: 'json'
  91086. * });
  91087. *
  91088. * // Collection url: /users.json
  91089. * // Instance url : /users/123.json
  91090. *
  91091. * If further customization is needed, simply implement the {@link #buildUrl} method and add your custom generated url
  91092. * onto the {@link Ext.data.Request Request} object that is passed to buildUrl. See [Rest proxy's implementation][1] for
  91093. * an example of how to achieve this.
  91094. *
  91095. * Note that Rest proxy inherits from {@link Ext.data.proxy.Ajax AjaxProxy}, which already injects all of the sorter,
  91096. * filter, group and paging options into the generated url. See the {@link Ext.data.proxy.Ajax AjaxProxy docs} for more
  91097. * details.
  91098. *
  91099. * [1]: source/Rest.html#Ext-data-proxy-Rest-method-buildUrl
  91100. */
  91101. Ext.define('Ext.data.proxy.Rest', {
  91102. extend: 'Ext.data.proxy.Ajax',
  91103. alternateClassName: 'Ext.data.RestProxy',
  91104. alias : 'proxy.rest',
  91105. /**
  91106. * @cfg {Boolean} appendId
  91107. * True to automatically append the ID of a Model instance when performing a request based on that single instance.
  91108. * See Rest proxy intro docs for more details. Defaults to true.
  91109. */
  91110. appendId: true,
  91111. /**
  91112. * @cfg {String} format
  91113. * Optional data format to send to the server when making any request (e.g. 'json'). See the Rest proxy intro docs
  91114. * for full details. Defaults to undefined.
  91115. */
  91116. /**
  91117. * @cfg {Boolean} batchActions
  91118. * True to batch actions of a particular type when synchronizing the store. Defaults to false.
  91119. */
  91120. batchActions: false,
  91121. /**
  91122. * Specialized version of buildUrl that incorporates the {@link #appendId} and {@link #format} options into the
  91123. * generated url. Override this to provide further customizations, but remember to call the superclass buildUrl so
  91124. * that additional parameters like the cache buster string are appended.
  91125. * @param {Object} request
  91126. */
  91127. buildUrl: function(request) {
  91128. var me = this,
  91129. operation = request.operation,
  91130. records = operation.records || [],
  91131. record = records[0],
  91132. format = me.format,
  91133. url = me.getUrl(request),
  91134. id = record ? record.getId() : operation.id;
  91135. if (me.appendId && id) {
  91136. if (!url.match(/\/$/)) {
  91137. url += '/';
  91138. }
  91139. url += id;
  91140. }
  91141. if (format) {
  91142. if (!url.match(/\.$/)) {
  91143. url += '.';
  91144. }
  91145. url += format;
  91146. }
  91147. request.url = url;
  91148. return me.callParent(arguments);
  91149. }
  91150. }, function() {
  91151. Ext.apply(this.prototype, {
  91152. /**
  91153. * @property {Object} actionMethods
  91154. * Mapping of action name to HTTP request method. These default to RESTful conventions for the 'create', 'read',
  91155. * 'update' and 'destroy' actions (which map to 'POST', 'GET', 'PUT' and 'DELETE' respectively). This object
  91156. * should not be changed except globally via {@link Ext#override Ext.override} - the {@link #getMethod} function
  91157. * can be overridden instead.
  91158. */
  91159. actionMethods: {
  91160. create : 'POST',
  91161. read : 'GET',
  91162. update : 'PUT',
  91163. destroy: 'DELETE'
  91164. }
  91165. });
  91166. });
  91167. /**
  91168. * @author Ed Spencer
  91169. *
  91170. * Proxy which uses HTML5 session storage as its data storage/retrieval mechanism. If this proxy is used in a browser
  91171. * where session storage is not supported, the constructor will throw an error. A session storage proxy requires a
  91172. * unique ID which is used as a key in which all record data are stored in the session storage object.
  91173. *
  91174. * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
  91175. * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
  91176. *
  91177. * Proxies are almost always used with a {@link Ext.data.Store store}:
  91178. *
  91179. * new Ext.data.Store({
  91180. * proxy: {
  91181. * type: 'sessionstorage',
  91182. * id : 'myProxyKey'
  91183. * }
  91184. * });
  91185. *
  91186. * Alternatively you can instantiate the Proxy directly:
  91187. *
  91188. * new Ext.data.proxy.SessionStorage({
  91189. * id : 'myOtherProxyKey'
  91190. * });
  91191. *
  91192. * Note that session storage is different to local storage (see {@link Ext.data.proxy.LocalStorage}) - if a browser
  91193. * session is ended (e.g. by closing the browser) then all data in a SessionStorageProxy are lost. Browser restarts
  91194. * don't affect the {@link Ext.data.proxy.LocalStorage} - the data are preserved.
  91195. */
  91196. Ext.define('Ext.data.proxy.SessionStorage', {
  91197. extend: 'Ext.data.proxy.WebStorage',
  91198. alias: 'proxy.sessionstorage',
  91199. alternateClassName: 'Ext.data.SessionStorageProxy',
  91200. //inherit docs
  91201. getStorageObject: function() {
  91202. return window.sessionStorage;
  91203. }
  91204. });
  91205. /*
  91206. * This is a derivative of the similarly named class in the YUI Library.
  91207. * The original license:
  91208. * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
  91209. * Code licensed under the BSD License:
  91210. * http://developer.yahoo.net/yui/license.txt
  91211. */
  91212. /**
  91213. * A DragDrop implementation that does not move, but can be a drop
  91214. * target. You would get the same result by simply omitting implementation
  91215. * for the event callbacks, but this way we reduce the processing cost of the
  91216. * event listener and the callbacks.
  91217. */
  91218. Ext.define('Ext.dd.DDTarget', {
  91219. extend: 'Ext.dd.DragDrop',
  91220. /**
  91221. * Creates new DDTarget.
  91222. * @param {String} id the id of the element that is a drop target
  91223. * @param {String} sGroup the group of related DragDrop objects
  91224. * @param {Object} config an object containing configurable attributes.
  91225. * Valid properties for DDTarget in addition to those in DragDrop: none.
  91226. */
  91227. constructor: function(id, sGroup, config) {
  91228. if (id) {
  91229. this.initTarget(id, sGroup, config);
  91230. }
  91231. },
  91232. /**
  91233. * Overridden and disabled. A DDTarget does not support being dragged.
  91234. * @method
  91235. */
  91236. getDragEl: Ext.emptyFn,
  91237. /**
  91238. * Overridden and disabled. A DDTarget does not support being dragged.
  91239. * @method
  91240. */
  91241. isValidHandleChild: Ext.emptyFn,
  91242. /**
  91243. * Overridden and disabled. A DDTarget does not support being dragged.
  91244. * @method
  91245. */
  91246. startDrag: Ext.emptyFn,
  91247. /**
  91248. * Overridden and disabled. A DDTarget does not support being dragged.
  91249. * @method
  91250. */
  91251. endDrag: Ext.emptyFn,
  91252. /**
  91253. * Overridden and disabled. A DDTarget does not support being dragged.
  91254. * @method
  91255. */
  91256. onDrag: Ext.emptyFn,
  91257. /**
  91258. * Overridden and disabled. A DDTarget does not support being dragged.
  91259. * @method
  91260. */
  91261. onDragDrop: Ext.emptyFn,
  91262. /**
  91263. * Overridden and disabled. A DDTarget does not support being dragged.
  91264. * @method
  91265. */
  91266. onDragEnter: Ext.emptyFn,
  91267. /**
  91268. * Overridden and disabled. A DDTarget does not support being dragged.
  91269. * @method
  91270. */
  91271. onDragOut: Ext.emptyFn,
  91272. /**
  91273. * Overridden and disabled. A DDTarget does not support being dragged.
  91274. * @method
  91275. */
  91276. onDragOver: Ext.emptyFn,
  91277. /**
  91278. * Overridden and disabled. A DDTarget does not support being dragged.
  91279. * @method
  91280. */
  91281. onInvalidDrop: Ext.emptyFn,
  91282. /**
  91283. * Overridden and disabled. A DDTarget does not support being dragged.
  91284. * @method
  91285. */
  91286. onMouseDown: Ext.emptyFn,
  91287. /**
  91288. * Overridden and disabled. A DDTarget does not support being dragged.
  91289. * @method
  91290. */
  91291. onMouseUp: Ext.emptyFn,
  91292. /**
  91293. * Overridden and disabled. A DDTarget does not support being dragged.
  91294. * @method
  91295. */
  91296. setXConstraint: Ext.emptyFn,
  91297. /**
  91298. * Overridden and disabled. A DDTarget does not support being dragged.
  91299. * @method
  91300. */
  91301. setYConstraint: Ext.emptyFn,
  91302. /**
  91303. * Overridden and disabled. A DDTarget does not support being dragged.
  91304. * @method
  91305. */
  91306. resetConstraints: Ext.emptyFn,
  91307. /**
  91308. * Overridden and disabled. A DDTarget does not support being dragged.
  91309. * @method
  91310. */
  91311. clearConstraints: Ext.emptyFn,
  91312. /**
  91313. * Overridden and disabled. A DDTarget does not support being dragged.
  91314. * @method
  91315. */
  91316. clearTicks: Ext.emptyFn,
  91317. /**
  91318. * Overridden and disabled. A DDTarget does not support being dragged.
  91319. * @method
  91320. */
  91321. setInitPosition: Ext.emptyFn,
  91322. /**
  91323. * Overridden and disabled. A DDTarget does not support being dragged.
  91324. * @method
  91325. */
  91326. setDragElId: Ext.emptyFn,
  91327. /**
  91328. * Overridden and disabled. A DDTarget does not support being dragged.
  91329. * @method
  91330. */
  91331. setHandleElId: Ext.emptyFn,
  91332. /**
  91333. * Overridden and disabled. A DDTarget does not support being dragged.
  91334. * @method
  91335. */
  91336. setOuterHandleElId: Ext.emptyFn,
  91337. /**
  91338. * Overridden and disabled. A DDTarget does not support being dragged.
  91339. * @method
  91340. */
  91341. addInvalidHandleClass: Ext.emptyFn,
  91342. /**
  91343. * Overridden and disabled. A DDTarget does not support being dragged.
  91344. * @method
  91345. */
  91346. addInvalidHandleId: Ext.emptyFn,
  91347. /**
  91348. * Overridden and disabled. A DDTarget does not support being dragged.
  91349. * @method
  91350. */
  91351. addInvalidHandleType: Ext.emptyFn,
  91352. /**
  91353. * Overridden and disabled. A DDTarget does not support being dragged.
  91354. * @method
  91355. */
  91356. removeInvalidHandleClass: Ext.emptyFn,
  91357. /**
  91358. * Overridden and disabled. A DDTarget does not support being dragged.
  91359. * @method
  91360. */
  91361. removeInvalidHandleId: Ext.emptyFn,
  91362. /**
  91363. * Overridden and disabled. A DDTarget does not support being dragged.
  91364. * @method
  91365. */
  91366. removeInvalidHandleType: Ext.emptyFn,
  91367. toString: function() {
  91368. return ("DDTarget " + this.id);
  91369. }
  91370. });
  91371. /**
  91372. * A DragTracker listens for drag events on an Element and fires events at the start and end of the drag,
  91373. * as well as during the drag. This is useful for components such as {@link Ext.slider.Multi}, where there is
  91374. * an element that can be dragged around to change the Slider's value.
  91375. *
  91376. * DragTracker provides a series of template methods that should be overridden to provide functionality
  91377. * in response to detected drag operations. These are onBeforeStart, onStart, onDrag and onEnd.
  91378. * See {@link Ext.slider.Multi}'s initEvents function for an example implementation.
  91379. */
  91380. Ext.define('Ext.dd.DragTracker', {
  91381. uses: ['Ext.util.Region'],
  91382. mixins: {
  91383. observable: 'Ext.util.Observable'
  91384. },
  91385. /**
  91386. * @property {Boolean} active
  91387. * Indicates whether the user is currently dragging this tracker.
  91388. * @readonly
  91389. */
  91390. active: false,
  91391. /**
  91392. * @property {HTMLElement} dragTarget
  91393. * The element being dragged.
  91394. *
  91395. * Only valid during drag operations.
  91396. *
  91397. * If the {@link #delegate} option is used, this will be the delegate element which was mousedowned.
  91398. * @readonly
  91399. */
  91400. /**
  91401. * @cfg {Boolean} trackOver
  91402. * Set to true to fire mouseover and mouseout events when the mouse enters or leaves the target element.
  91403. *
  91404. * This is implicitly set when an {@link #overCls} is specified.
  91405. *
  91406. * If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.
  91407. */
  91408. trackOver: false,
  91409. /**
  91410. * @cfg {String} overCls
  91411. * A CSS class to add to the DragTracker's target element when the element (or, if the {@link #delegate}
  91412. * option is used, when a delegate element) is mouseovered.
  91413. *
  91414. * If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.
  91415. */
  91416. /**
  91417. * @cfg {Ext.util.Region/Ext.Element} constrainTo
  91418. * A {@link Ext.util.Region Region} (Or an element from which a Region measurement will be read)
  91419. * which is used to constrain the result of the {@link #getOffset} call.
  91420. *
  91421. * This may be set any time during the DragTracker's lifecycle to set a dynamic constraining region.
  91422. */
  91423. /**
  91424. * @cfg {Number} tolerance
  91425. * Number of pixels the drag target must be moved before dragging is
  91426. * considered to have started.
  91427. */
  91428. tolerance: 5,
  91429. /**
  91430. * @cfg {Boolean/Number} autoStart
  91431. * Specify `true` to defer trigger start by 1000 ms.
  91432. * Specify a Number for the number of milliseconds to defer trigger start.
  91433. */
  91434. autoStart: false,
  91435. /**
  91436. * @cfg {String} delegate
  91437. * A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the DragTracker's encapsulating
  91438. * Element which are the tracked elements. This limits tracking to only begin when the matching elements are mousedowned.
  91439. *
  91440. * This may also be a specific child element within the DragTracker's encapsulating element to use as the tracked element.
  91441. */
  91442. /**
  91443. * @cfg {Boolean} [preventDefault=true]
  91444. * Specify `false` to enable default actions on onMouseDown events.
  91445. */
  91446. /**
  91447. * @cfg {Boolean} [stopEvent=false]
  91448. * Specify `true` to stop the `mousedown` event from bubbling to outer listeners from the target element (or its delegates).
  91449. */
  91450. constructor : function(config){
  91451. var me = this;
  91452. Ext.apply(me, config);
  91453. me.addEvents(
  91454. /**
  91455. * @event mouseover
  91456. * Fires when the mouse enters the DragTracker's target element (or if {@link #delegate} is
  91457. * used, when the mouse enters a delegate element).
  91458. *
  91459. * **Only available when {@link #trackOver} is `true`**
  91460. *
  91461. * @param {Object} this
  91462. * @param {Object} e event object
  91463. * @param {HTMLElement} target The element mouseovered.
  91464. */
  91465. 'mouseover',
  91466. /**
  91467. * @event mouseout
  91468. * Fires when the mouse exits the DragTracker's target element (or if {@link #delegate} is
  91469. * used, when the mouse exits a delegate element).
  91470. *
  91471. * **Only available when {@link #trackOver} is `true`**
  91472. *
  91473. * @param {Object} this
  91474. * @param {Object} e event object
  91475. */
  91476. 'mouseout',
  91477. /**
  91478. * @event mousedown
  91479. * Fires when the mouse button is pressed down, but before a drag operation begins. The
  91480. * drag operation begins after either the mouse has been moved by {@link #tolerance} pixels,
  91481. * or after the {@link #autoStart} timer fires.
  91482. *
  91483. * Return false to veto the drag operation.
  91484. *
  91485. * @param {Object} this
  91486. * @param {Object} e event object
  91487. */
  91488. 'mousedown',
  91489. /**
  91490. * @event mouseup
  91491. * @param {Object} this
  91492. * @param {Object} e event object
  91493. */
  91494. 'mouseup',
  91495. /**
  91496. * @event mousemove
  91497. * Fired when the mouse is moved. Returning false cancels the drag operation.
  91498. * @param {Object} this
  91499. * @param {Object} e event object
  91500. */
  91501. 'mousemove',
  91502. /**
  91503. * @event beforestart
  91504. * @param {Object} this
  91505. * @param {Object} e event object
  91506. */
  91507. 'beforedragstart',
  91508. /**
  91509. * @event dragstart
  91510. * @param {Object} this
  91511. * @param {Object} e event object
  91512. */
  91513. 'dragstart',
  91514. /**
  91515. * @event dragend
  91516. * @param {Object} this
  91517. * @param {Object} e event object
  91518. */
  91519. 'dragend',
  91520. /**
  91521. * @event drag
  91522. * @param {Object} this
  91523. * @param {Object} e event object
  91524. */
  91525. 'drag'
  91526. );
  91527. me.dragRegion = new Ext.util.Region(0,0,0,0);
  91528. if (me.el) {
  91529. me.initEl(me.el);
  91530. }
  91531. // Dont pass the config so that it is not applied to 'this' again
  91532. me.mixins.observable.constructor.call(me);
  91533. if (me.disabled) {
  91534. me.disable();
  91535. }
  91536. },
  91537. /**
  91538. * Initializes the DragTracker on a given element.
  91539. * @param {Ext.Element/HTMLElement} el The element
  91540. */
  91541. initEl: function(el) {
  91542. var me = this;
  91543. me.el = Ext.get(el);
  91544. // The delegate option may also be an element on which to listen
  91545. me.handle = Ext.get(me.delegate);
  91546. // If delegate specified an actual element to listen on, we do not use the delegate listener option
  91547. me.delegate = me.handle ? undefined : me.delegate;
  91548. if (!me.handle) {
  91549. me.handle = me.el;
  91550. }
  91551. // Add a mousedown listener which reacts only on the elements targeted by the delegate config.
  91552. // We process mousedown to begin tracking.
  91553. me.mon(me.handle, {
  91554. mousedown: me.onMouseDown,
  91555. delegate: me.delegate,
  91556. scope: me
  91557. });
  91558. // If configured to do so, track mouse entry and exit into the target (or delegate).
  91559. // The mouseover and mouseout CANNOT be replaced with mouseenter and mouseleave
  91560. // because delegate cannot work with those pseudoevents. Entry/exit checking is done in the handler.
  91561. if (me.trackOver || me.overCls) {
  91562. me.mon(me.handle, {
  91563. mouseover: me.onMouseOver,
  91564. mouseout: me.onMouseOut,
  91565. delegate: me.delegate,
  91566. scope: me
  91567. });
  91568. }
  91569. },
  91570. disable: function() {
  91571. this.disabled = true;
  91572. },
  91573. enable: function() {
  91574. this.disabled = false;
  91575. },
  91576. destroy : function() {
  91577. this.clearListeners();
  91578. delete this.el;
  91579. },
  91580. // When the pointer enters a tracking element, fire a mouseover if the mouse entered from outside.
  91581. // This is mouseenter functionality, but we cannot use mouseenter because we are using "delegate" to filter mouse targets
  91582. onMouseOver: function(e, target) {
  91583. var me = this;
  91584. if (!me.disabled) {
  91585. if (Ext.EventManager.contains(e) || me.delegate) {
  91586. me.mouseIsOut = false;
  91587. if (me.overCls) {
  91588. me.el.addCls(me.overCls);
  91589. }
  91590. me.fireEvent('mouseover', me, e, me.delegate ? e.getTarget(me.delegate, target) : me.handle);
  91591. }
  91592. }
  91593. },
  91594. // When the pointer exits a tracking element, fire a mouseout.
  91595. // This is mouseleave functionality, but we cannot use mouseleave because we are using "delegate" to filter mouse targets
  91596. onMouseOut: function(e) {
  91597. var me = this;
  91598. if (me.mouseIsDown) {
  91599. me.mouseIsOut = true;
  91600. } else {
  91601. if (me.overCls) {
  91602. me.el.removeCls(me.overCls);
  91603. }
  91604. me.fireEvent('mouseout', me, e);
  91605. }
  91606. },
  91607. onMouseDown: function(e, target){
  91608. var me = this,
  91609. el;
  91610. // If this is disabled, or the mousedown has been processed by an upstream DragTracker, return
  91611. if (me.disabled ||e.dragTracked) {
  91612. return;
  91613. }
  91614. // This information should be available in mousedown listener and onBeforeStart implementations
  91615. me.dragTarget = me.delegate ? target : me.handle.dom;
  91616. me.startXY = me.lastXY = e.getXY();
  91617. me.startRegion = Ext.fly(me.dragTarget).getRegion();
  91618. if (me.fireEvent('mousedown', me, e) === false ||
  91619. me.fireEvent('beforedragstart', me, e) === false ||
  91620. me.onBeforeStart(e) === false) {
  91621. return;
  91622. }
  91623. // Track when the mouse is down so that mouseouts while the mouse is down are not processed.
  91624. // The onMouseOut method will only ever be called after mouseup.
  91625. me.mouseIsDown = true;
  91626. // Flag for downstream DragTracker instances that the mouse is being tracked.
  91627. e.dragTracked = true;
  91628. // See Ext.dd.DragDropManager::handleMouseDown
  91629. el = me.el.dom;
  91630. if (Ext.isIE && el.setCapture) {
  91631. el.setCapture();
  91632. }
  91633. if (me.preventDefault !== false) {
  91634. e.preventDefault();
  91635. }
  91636. Ext.getDoc().on({
  91637. scope: me,
  91638. mouseup: me.onMouseUp,
  91639. mousemove: me.onMouseMove,
  91640. selectstart: me.stopSelect
  91641. });
  91642. if (me.autoStart) {
  91643. me.timer = Ext.defer(me.triggerStart, me.autoStart === true ? 1000 : me.autoStart, me, [e]);
  91644. }
  91645. },
  91646. onMouseMove: function(e, target){
  91647. var me = this,
  91648. xy = e.getXY(),
  91649. s = me.startXY;
  91650. e.preventDefault();
  91651. me.lastXY = xy;
  91652. if (!me.active) {
  91653. if (Math.max(Math.abs(s[0]-xy[0]), Math.abs(s[1]-xy[1])) > me.tolerance) {
  91654. me.triggerStart(e);
  91655. } else {
  91656. return;
  91657. }
  91658. }
  91659. // Returning false from a mousemove listener deactivates
  91660. if (me.fireEvent('mousemove', me, e) === false) {
  91661. me.onMouseUp(e);
  91662. } else {
  91663. me.onDrag(e);
  91664. me.fireEvent('drag', me, e);
  91665. }
  91666. },
  91667. onMouseUp: function(e) {
  91668. var me = this;
  91669. // Clear the flag which ensures onMouseOut fires only after the mouse button
  91670. // is lifted if the mouseout happens *during* a drag.
  91671. me.mouseIsDown = false;
  91672. // If we mouseouted the el *during* the drag, the onMouseOut method will not have fired. Ensure that it gets processed.
  91673. if (me.mouseIsOut) {
  91674. me.mouseIsOut = false;
  91675. me.onMouseOut(e);
  91676. }
  91677. e.preventDefault();
  91678. // See Ext.dd.DragDropManager::handleMouseDown
  91679. if (Ext.isIE && document.releaseCapture) {
  91680. document.releaseCapture();
  91681. }
  91682. me.fireEvent('mouseup', me, e);
  91683. me.endDrag(e);
  91684. },
  91685. /**
  91686. * @private
  91687. * Stop the drag operation, and remove active mouse listeners.
  91688. */
  91689. endDrag: function(e) {
  91690. var me = this,
  91691. doc = Ext.getDoc(),
  91692. wasActive = me.active;
  91693. doc.un('mousemove', me.onMouseMove, me);
  91694. doc.un('mouseup', me.onMouseUp, me);
  91695. doc.un('selectstart', me.stopSelect, me);
  91696. me.clearStart();
  91697. me.active = false;
  91698. if (wasActive) {
  91699. me.onEnd(e);
  91700. me.fireEvent('dragend', me, e);
  91701. }
  91702. // Private property calculated when first required and only cached during a drag
  91703. delete me._constrainRegion;
  91704. // Remove flag from event singleton. Using "Ext.EventObject" here since "endDrag" is called directly in some cases without an "e" param
  91705. delete Ext.EventObject.dragTracked;
  91706. },
  91707. triggerStart: function(e) {
  91708. var me = this;
  91709. me.clearStart();
  91710. me.active = true;
  91711. me.onStart(e);
  91712. me.fireEvent('dragstart', me, e);
  91713. },
  91714. clearStart : function() {
  91715. var timer = this.timer;
  91716. if (timer) {
  91717. clearTimeout(timer);
  91718. delete this.timer;
  91719. }
  91720. },
  91721. stopSelect : function(e) {
  91722. e.stopEvent();
  91723. return false;
  91724. },
  91725. /**
  91726. * Template method which should be overridden by each DragTracker instance. Called when the user first clicks and
  91727. * holds the mouse button down. Return false to disallow the drag
  91728. * @param {Ext.EventObject} e The event object
  91729. * @template
  91730. */
  91731. onBeforeStart : function(e) {
  91732. },
  91733. /**
  91734. * Template method which should be overridden by each DragTracker instance. Called when a drag operation starts
  91735. * (e.g. the user has moved the tracked element beyond the specified tolerance)
  91736. * @param {Ext.EventObject} e The event object
  91737. * @template
  91738. */
  91739. onStart : function(xy) {
  91740. },
  91741. /**
  91742. * Template method which should be overridden by each DragTracker instance. Called whenever a drag has been detected.
  91743. * @param {Ext.EventObject} e The event object
  91744. * @template
  91745. */
  91746. onDrag : function(e) {
  91747. },
  91748. /**
  91749. * Template method which should be overridden by each DragTracker instance. Called when a drag operation has been completed
  91750. * (e.g. the user clicked and held the mouse down, dragged the element and then released the mouse button)
  91751. * @param {Ext.EventObject} e The event object
  91752. * @template
  91753. */
  91754. onEnd : function(e) {
  91755. },
  91756. /**
  91757. * Returns the drag target. This is usually the DragTracker's encapsulating element.
  91758. *
  91759. * If the {@link #delegate} option is being used, this may be a child element which matches the
  91760. * {@link #delegate} selector.
  91761. *
  91762. * @return {Ext.Element} The element currently being tracked.
  91763. */
  91764. getDragTarget : function(){
  91765. return this.dragTarget;
  91766. },
  91767. /**
  91768. * @private
  91769. * @returns {Ext.Element} The DragTracker's encapsulating element.
  91770. */
  91771. getDragCt : function(){
  91772. return this.el;
  91773. },
  91774. /**
  91775. * @private
  91776. * Return the Region into which the drag operation is constrained.
  91777. * Either the XY pointer itself can be constrained, or the dragTarget element
  91778. * The private property _constrainRegion is cached until onMouseUp
  91779. */
  91780. getConstrainRegion: function() {
  91781. var me = this;
  91782. if (me.constrainTo) {
  91783. if (me.constrainTo instanceof Ext.util.Region) {
  91784. return me.constrainTo;
  91785. }
  91786. if (!me._constrainRegion) {
  91787. me._constrainRegion = Ext.fly(me.constrainTo).getViewRegion();
  91788. }
  91789. } else {
  91790. if (!me._constrainRegion) {
  91791. me._constrainRegion = me.getDragCt().getViewRegion();
  91792. }
  91793. }
  91794. return me._constrainRegion;
  91795. },
  91796. getXY : function(constrain){
  91797. return constrain ? this.constrainModes[constrain](this, this.lastXY) : this.lastXY;
  91798. },
  91799. /**
  91800. * Returns the X, Y offset of the current mouse position from the mousedown point.
  91801. *
  91802. * This method may optionally constrain the real offset values, and returns a point coerced in one
  91803. * of two modes:
  91804. *
  91805. * - `point`
  91806. * The current mouse position is coerced into the constrainRegion and the resulting position is returned.
  91807. * - `dragTarget`
  91808. * The new {@link Ext.util.Region Region} of the {@link #getDragTarget dragTarget} is calculated
  91809. * based upon the current mouse position, and then coerced into the constrainRegion. The returned
  91810. * mouse position is then adjusted by the same delta as was used to coerce the region.\
  91811. *
  91812. * @param constrainMode {String} (Optional) If omitted the true mouse position is returned. May be passed
  91813. * as `point` or `dragTarget`. See above.
  91814. * @returns {Number[]} The `X, Y` offset from the mousedown point, optionally constrained.
  91815. */
  91816. getOffset : function(constrain){
  91817. var xy = this.getXY(constrain),
  91818. s = this.startXY;
  91819. return [xy[0]-s[0], xy[1]-s[1]];
  91820. },
  91821. constrainModes: {
  91822. // Constrain the passed point to within the constrain region
  91823. point: function(me, xy) {
  91824. var dr = me.dragRegion,
  91825. constrainTo = me.getConstrainRegion();
  91826. // No constraint
  91827. if (!constrainTo) {
  91828. return xy;
  91829. }
  91830. dr.x = dr.left = dr[0] = dr.right = xy[0];
  91831. dr.y = dr.top = dr[1] = dr.bottom = xy[1];
  91832. dr.constrainTo(constrainTo);
  91833. return [dr.left, dr.top];
  91834. },
  91835. // Constrain the dragTarget to within the constrain region. Return the passed xy adjusted by the same delta.
  91836. dragTarget: function(me, xy) {
  91837. var s = me.startXY,
  91838. dr = me.startRegion.copy(),
  91839. constrainTo = me.getConstrainRegion(),
  91840. adjust;
  91841. // No constraint
  91842. if (!constrainTo) {
  91843. return xy;
  91844. }
  91845. // See where the passed XY would put the dragTarget if translated by the unconstrained offset.
  91846. // If it overflows, we constrain the passed XY to bring the potential
  91847. // region back within the boundary.
  91848. dr.translateBy(xy[0]-s[0], xy[1]-s[1]);
  91849. // Constrain the X coordinate by however much the dragTarget overflows
  91850. if (dr.right > constrainTo.right) {
  91851. xy[0] += adjust = (constrainTo.right - dr.right); // overflowed the right
  91852. dr.left += adjust;
  91853. }
  91854. if (dr.left < constrainTo.left) {
  91855. xy[0] += (constrainTo.left - dr.left); // overflowed the left
  91856. }
  91857. // Constrain the Y coordinate by however much the dragTarget overflows
  91858. if (dr.bottom > constrainTo.bottom) {
  91859. xy[1] += adjust = (constrainTo.bottom - dr.bottom); // overflowed the bottom
  91860. dr.top += adjust;
  91861. }
  91862. if (dr.top < constrainTo.top) {
  91863. xy[1] += (constrainTo.top - dr.top); // overflowed the top
  91864. }
  91865. return xy;
  91866. }
  91867. }
  91868. });
  91869. /**
  91870. * This class provides a container DD instance that allows dragging of multiple child source nodes.
  91871. *
  91872. * This class does not move the drag target nodes, but a proxy element which may contain any DOM structure you wish. The
  91873. * DOM element to show in the proxy is provided by either a provided implementation of {@link #getDragData}, or by
  91874. * registered draggables registered with {@link Ext.dd.Registry}
  91875. *
  91876. * If you wish to provide draggability for an arbitrary number of DOM nodes, each of which represent some application
  91877. * object (For example nodes in a {@link Ext.view.View DataView}) then use of this class is the most efficient way to
  91878. * "activate" those nodes.
  91879. *
  91880. * By default, this class requires that draggable child nodes are registered with {@link Ext.dd.Registry}. However a
  91881. * simpler way to allow a DragZone to manage any number of draggable elements is to configure the DragZone with an
  91882. * implementation of the {@link #getDragData} method which interrogates the passed mouse event to see if it has taken
  91883. * place within an element, or class of elements. This is easily done by using the event's {@link
  91884. * Ext.EventObject#getTarget getTarget} method to identify a node based on a {@link Ext.DomQuery} selector. For example,
  91885. * to make the nodes of a DataView draggable, use the following technique. Knowledge of the use of the DataView is
  91886. * required:
  91887. *
  91888. * myDataView.on('render', function(v) {
  91889. * myDataView.dragZone = new Ext.dd.DragZone(v.getEl(), {
  91890. *
  91891. * // On receipt of a mousedown event, see if it is within a DataView node.
  91892. * // Return a drag data object if so.
  91893. * getDragData: function(e) {
  91894. *
  91895. * // Use the DataView's own itemSelector (a mandatory property) to
  91896. * // test if the mousedown is within one of the DataView's nodes.
  91897. * var sourceEl = e.getTarget(v.itemSelector, 10);
  91898. *
  91899. * // If the mousedown is within a DataView node, clone the node to produce
  91900. * // a ddel element for use by the drag proxy. Also add application data
  91901. * // to the returned data object.
  91902. * if (sourceEl) {
  91903. * d = sourceEl.cloneNode(true);
  91904. * d.id = Ext.id();
  91905. * return {
  91906. * ddel: d,
  91907. * sourceEl: sourceEl,
  91908. * repairXY: Ext.fly(sourceEl).getXY(),
  91909. * sourceStore: v.store,
  91910. * draggedRecord: v.{@link Ext.view.View#getRecord getRecord}(sourceEl)
  91911. * }
  91912. * }
  91913. * },
  91914. *
  91915. * // Provide coordinates for the proxy to slide back to on failed drag.
  91916. * // This is the original XY coordinates of the draggable element captured
  91917. * // in the getDragData method.
  91918. * getRepairXY: function() {
  91919. * return this.dragData.repairXY;
  91920. * }
  91921. * });
  91922. * });
  91923. *
  91924. * See the {@link Ext.dd.DropZone DropZone} documentation for details about building a DropZone which cooperates with
  91925. * this DragZone.
  91926. */
  91927. Ext.define('Ext.dd.DragZone', {
  91928. extend: 'Ext.dd.DragSource',
  91929. /**
  91930. * Creates new DragZone.
  91931. * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
  91932. * @param {Object} config
  91933. */
  91934. constructor : function(el, config){
  91935. this.callParent([el, config]);
  91936. if (this.containerScroll) {
  91937. Ext.dd.ScrollManager.register(this.el);
  91938. }
  91939. },
  91940. /**
  91941. * @property {Object} dragData
  91942. * This property contains the data representing the dragged object. This data is set up by the implementation of the
  91943. * {@link #getDragData} method. It must contain a ddel property, but can contain any other data according to the
  91944. * application's needs.
  91945. */
  91946. /**
  91947. * @cfg {Boolean} containerScroll
  91948. * True to register this container with the Scrollmanager for auto scrolling during drag operations.
  91949. */
  91950. /**
  91951. * Called when a mousedown occurs in this container. Looks in {@link Ext.dd.Registry} for a valid target to drag
  91952. * based on the mouse down. Override this method to provide your own lookup logic (e.g. finding a child by class
  91953. * name). Make sure your returned object has a "ddel" attribute (with an HTML Element) for other functions to work.
  91954. * @param {Event} e The mouse down event
  91955. * @return {Object} The dragData
  91956. */
  91957. getDragData : function(e){
  91958. return Ext.dd.Registry.getHandleFromEvent(e);
  91959. },
  91960. /**
  91961. * Called once drag threshold has been reached to initialize the proxy element. By default, it clones the
  91962. * this.dragData.ddel
  91963. * @param {Number} x The x position of the click on the dragged object
  91964. * @param {Number} y The y position of the click on the dragged object
  91965. * @return {Boolean} true to continue the drag, false to cancel
  91966. * @template
  91967. */
  91968. onInitDrag : function(x, y){
  91969. this.proxy.update(this.dragData.ddel.cloneNode(true));
  91970. this.onStartDrag(x, y);
  91971. return true;
  91972. },
  91973. /**
  91974. * Called after a repair of an invalid drop. By default, highlights this.dragData.ddel
  91975. * @template
  91976. */
  91977. afterRepair : function(){
  91978. var me = this;
  91979. if (Ext.enableFx) {
  91980. Ext.fly(me.dragData.ddel).highlight(me.repairHighlightColor);
  91981. }
  91982. me.dragging = false;
  91983. },
  91984. /**
  91985. * Called before a repair of an invalid drop to get the XY to animate to. By default returns the XY of
  91986. * this.dragData.ddel
  91987. * @param {Event} e The mouse up event
  91988. * @return {Number[]} The xy location (e.g. `[100, 200]`)
  91989. * @template
  91990. */
  91991. getRepairXY : function(e){
  91992. return Ext.fly(this.dragData.ddel).getXY();
  91993. },
  91994. destroy : function(){
  91995. this.callParent();
  91996. if (this.containerScroll) {
  91997. Ext.dd.ScrollManager.unregister(this.el);
  91998. }
  91999. }
  92000. });
  92001. /**
  92002. * Provides automatic scrolling of overflow regions in the page during drag operations.
  92003. *
  92004. * The ScrollManager configs will be used as the defaults for any scroll container registered with it, but you can also
  92005. * override most of the configs per scroll container by adding a ddScrollConfig object to the target element that
  92006. * contains these properties: {@link #hthresh}, {@link #vthresh}, {@link #increment} and {@link #frequency}. Example
  92007. * usage:
  92008. *
  92009. * var el = Ext.get('scroll-ct');
  92010. * el.ddScrollConfig = {
  92011. * vthresh: 50,
  92012. * hthresh: -1,
  92013. * frequency: 100,
  92014. * increment: 200
  92015. * };
  92016. * Ext.dd.ScrollManager.register(el);
  92017. *
  92018. * Note: This class is designed to be used in "Point Mode
  92019. */
  92020. Ext.define('Ext.dd.ScrollManager', {
  92021. singleton: true,
  92022. requires: [
  92023. 'Ext.dd.DragDropManager'
  92024. ],
  92025. constructor: function() {
  92026. var ddm = Ext.dd.DragDropManager;
  92027. ddm.fireEvents = Ext.Function.createSequence(ddm.fireEvents, this.onFire, this);
  92028. ddm.stopDrag = Ext.Function.createSequence(ddm.stopDrag, this.onStop, this);
  92029. this.doScroll = Ext.Function.bind(this.doScroll, this);
  92030. this.ddmInstance = ddm;
  92031. this.els = {};
  92032. this.dragEl = null;
  92033. this.proc = {};
  92034. },
  92035. onStop: function(e){
  92036. var sm = Ext.dd.ScrollManager;
  92037. sm.dragEl = null;
  92038. sm.clearProc();
  92039. },
  92040. triggerRefresh: function() {
  92041. if (this.ddmInstance.dragCurrent) {
  92042. this.ddmInstance.refreshCache(this.ddmInstance.dragCurrent.groups);
  92043. }
  92044. },
  92045. doScroll: function() {
  92046. if (this.ddmInstance.dragCurrent) {
  92047. var proc = this.proc,
  92048. procEl = proc.el,
  92049. ddScrollConfig = proc.el.ddScrollConfig,
  92050. inc = ddScrollConfig ? ddScrollConfig.increment : this.increment;
  92051. if (!this.animate) {
  92052. if (procEl.scroll(proc.dir, inc)) {
  92053. this.triggerRefresh();
  92054. }
  92055. } else {
  92056. procEl.scroll(proc.dir, inc, true, this.animDuration, this.triggerRefresh);
  92057. }
  92058. }
  92059. },
  92060. clearProc: function() {
  92061. var proc = this.proc;
  92062. if (proc.id) {
  92063. clearInterval(proc.id);
  92064. }
  92065. proc.id = 0;
  92066. proc.el = null;
  92067. proc.dir = "";
  92068. },
  92069. startProc: function(el, dir) {
  92070. this.clearProc();
  92071. this.proc.el = el;
  92072. this.proc.dir = dir;
  92073. var group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup : undefined,
  92074. freq = (el.ddScrollConfig && el.ddScrollConfig.frequency)
  92075. ? el.ddScrollConfig.frequency
  92076. : this.frequency;
  92077. if (group === undefined || this.ddmInstance.dragCurrent.ddGroup == group) {
  92078. this.proc.id = setInterval(this.doScroll, freq);
  92079. }
  92080. },
  92081. onFire: function(e, isDrop) {
  92082. if (isDrop || !this.ddmInstance.dragCurrent) {
  92083. return;
  92084. }
  92085. if (!this.dragEl || this.dragEl != this.ddmInstance.dragCurrent) {
  92086. this.dragEl = this.ddmInstance.dragCurrent;
  92087. // refresh regions on drag start
  92088. this.refreshCache();
  92089. }
  92090. var xy = e.getXY(),
  92091. pt = e.getPoint(),
  92092. proc = this.proc,
  92093. els = this.els,
  92094. id, el, r, c;
  92095. for (id in els) {
  92096. el = els[id];
  92097. r = el._region;
  92098. c = el.ddScrollConfig ? el.ddScrollConfig : this;
  92099. if (r && r.contains(pt) && el.isScrollable()) {
  92100. if (r.bottom - pt.y <= c.vthresh) {
  92101. if(proc.el != el){
  92102. this.startProc(el, "down");
  92103. }
  92104. return;
  92105. }else if (r.right - pt.x <= c.hthresh) {
  92106. if (proc.el != el) {
  92107. this.startProc(el, "left");
  92108. }
  92109. return;
  92110. } else if(pt.y - r.top <= c.vthresh) {
  92111. if (proc.el != el) {
  92112. this.startProc(el, "up");
  92113. }
  92114. return;
  92115. } else if(pt.x - r.left <= c.hthresh) {
  92116. if (proc.el != el) {
  92117. this.startProc(el, "right");
  92118. }
  92119. return;
  92120. }
  92121. }
  92122. }
  92123. this.clearProc();
  92124. },
  92125. /**
  92126. * Registers new overflow element(s) to auto scroll
  92127. * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
  92128. * The id of or the element to be scrolled or an array of either
  92129. */
  92130. register : function(el){
  92131. if (Ext.isArray(el)) {
  92132. for(var i = 0, len = el.length; i < len; i++) {
  92133. this.register(el[i]);
  92134. }
  92135. } else {
  92136. el = Ext.get(el);
  92137. this.els[el.id] = el;
  92138. }
  92139. },
  92140. /**
  92141. * Unregisters overflow element(s) so they are no longer scrolled
  92142. * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
  92143. * The id of or the element to be removed or an array of either
  92144. */
  92145. unregister : function(el){
  92146. if(Ext.isArray(el)){
  92147. for (var i = 0, len = el.length; i < len; i++) {
  92148. this.unregister(el[i]);
  92149. }
  92150. }else{
  92151. el = Ext.get(el);
  92152. delete this.els[el.id];
  92153. }
  92154. },
  92155. /**
  92156. * The number of pixels from the top or bottom edge of a container the pointer needs to be to trigger scrolling
  92157. */
  92158. vthresh : 25,
  92159. /**
  92160. * The number of pixels from the right or left edge of a container the pointer needs to be to trigger scrolling
  92161. */
  92162. hthresh : 25,
  92163. /**
  92164. * The number of pixels to scroll in each scroll increment
  92165. */
  92166. increment : 100,
  92167. /**
  92168. * The frequency of scrolls in milliseconds
  92169. */
  92170. frequency : 500,
  92171. /**
  92172. * True to animate the scroll
  92173. */
  92174. animate: true,
  92175. /**
  92176. * The animation duration in seconds - MUST BE less than Ext.dd.ScrollManager.frequency!
  92177. */
  92178. animDuration: 0.4,
  92179. /**
  92180. * @property {String} ddGroup
  92181. * The named drag drop {@link Ext.dd.DragSource#ddGroup group} to which this container belongs. If a ddGroup is
  92182. * specified, then container scrolling will only occur when a dragged object is in the same ddGroup.
  92183. */
  92184. ddGroup: undefined,
  92185. /**
  92186. * Manually trigger a cache refresh.
  92187. */
  92188. refreshCache : function(){
  92189. var els = this.els,
  92190. id;
  92191. for (id in els) {
  92192. if(typeof els[id] == 'object'){ // for people extending the object prototype
  92193. els[id]._region = els[id].getRegion();
  92194. }
  92195. }
  92196. }
  92197. });
  92198. /**
  92199. * A simple class that provides the basic implementation needed to make any element a drop target that can have
  92200. * draggable items dropped onto it. The drop has no effect until an implementation of notifyDrop is provided.
  92201. */
  92202. Ext.define('Ext.dd.DropTarget', {
  92203. extend: 'Ext.dd.DDTarget',
  92204. requires: ['Ext.dd.ScrollManager'],
  92205. /**
  92206. * Creates new DropTarget.
  92207. * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
  92208. * @param {Object} config
  92209. */
  92210. constructor : function(el, config){
  92211. this.el = Ext.get(el);
  92212. Ext.apply(this, config);
  92213. if(this.containerScroll){
  92214. Ext.dd.ScrollManager.register(this.el);
  92215. }
  92216. this.callParent([this.el.dom, this.ddGroup || this.group,
  92217. {isTarget: true}]);
  92218. },
  92219. /**
  92220. * @cfg {String} ddGroup
  92221. * A named drag drop group to which this object belongs. If a group is specified, then this object will only
  92222. * interact with other drag drop objects in the same group.
  92223. */
  92224. /**
  92225. * @cfg {String} [overClass=""]
  92226. * The CSS class applied to the drop target element while the drag source is over it.
  92227. */
  92228. /**
  92229. * @cfg {String} dropAllowed
  92230. * The CSS class returned to the drag source when drop is allowed.
  92231. */
  92232. dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
  92233. /**
  92234. * @cfg {String} dropNotAllowed
  92235. * The CSS class returned to the drag source when drop is not allowed.
  92236. */
  92237. dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
  92238. // private
  92239. isTarget : true,
  92240. // private
  92241. isNotifyTarget : true,
  92242. /**
  92243. * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source is now over the
  92244. * target. This default implementation adds the CSS class specified by overClass (if any) to the drop element
  92245. * and returns the dropAllowed config value. This method should be overridden if drop validation is required.
  92246. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
  92247. * @param {Event} e The event
  92248. * @param {Object} data An object containing arbitrary data supplied by the drag source
  92249. * @return {String} status The CSS class that communicates the drop status back to the source so that the
  92250. * underlying {@link Ext.dd.StatusProxy} can be updated
  92251. * @template
  92252. */
  92253. notifyEnter : function(dd, e, data){
  92254. if(this.overClass){
  92255. this.el.addCls(this.overClass);
  92256. }
  92257. return this.dropAllowed;
  92258. },
  92259. /**
  92260. * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the target.
  92261. * This method will be called on every mouse movement while the drag source is over the drop target.
  92262. * This default implementation simply returns the dropAllowed config value.
  92263. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
  92264. * @param {Event} e The event
  92265. * @param {Object} data An object containing arbitrary data supplied by the drag source
  92266. * @return {String} status The CSS class that communicates the drop status back to the source so that the
  92267. * underlying {@link Ext.dd.StatusProxy} can be updated
  92268. * @template
  92269. */
  92270. notifyOver : function(dd, e, data){
  92271. return this.dropAllowed;
  92272. },
  92273. /**
  92274. * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source has been dragged
  92275. * out of the target without dropping. This default implementation simply removes the CSS class specified by
  92276. * overClass (if any) from the drop element.
  92277. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
  92278. * @param {Event} e The event
  92279. * @param {Object} data An object containing arbitrary data supplied by the drag source
  92280. * @template
  92281. */
  92282. notifyOut : function(dd, e, data){
  92283. if(this.overClass){
  92284. this.el.removeCls(this.overClass);
  92285. }
  92286. },
  92287. /**
  92288. * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the dragged item has
  92289. * been dropped on it. This method has no default implementation and returns false, so you must provide an
  92290. * implementation that does something to process the drop event and returns true so that the drag source's
  92291. * repair action does not run.
  92292. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
  92293. * @param {Event} e The event
  92294. * @param {Object} data An object containing arbitrary data supplied by the drag source
  92295. * @return {Boolean} False if the drop was invalid.
  92296. * @template
  92297. */
  92298. notifyDrop : function(dd, e, data){
  92299. return false;
  92300. },
  92301. destroy : function(){
  92302. this.callParent();
  92303. if(this.containerScroll){
  92304. Ext.dd.ScrollManager.unregister(this.el);
  92305. }
  92306. }
  92307. });
  92308. /**
  92309. * Provides easy access to all drag drop components that are registered on a page. Items can be retrieved either
  92310. * directly by DOM node id, or by passing in the drag drop event that occurred and looking up the event target.
  92311. */
  92312. Ext.define('Ext.dd.Registry', {
  92313. singleton: true,
  92314. constructor: function() {
  92315. this.elements = {};
  92316. this.handles = {};
  92317. this.autoIdSeed = 0;
  92318. },
  92319. getId: function(el, autogen){
  92320. if(typeof el == "string"){
  92321. return el;
  92322. }
  92323. var id = el.id;
  92324. if(!id && autogen !== false){
  92325. id = "extdd-" + (++this.autoIdSeed);
  92326. el.id = id;
  92327. }
  92328. return id;
  92329. },
  92330. /**
  92331. * Registers a drag drop element.
  92332. *
  92333. * @param {String/HTMLElement} element The id or DOM node to register
  92334. * @param {Object} data An custom data object that will be passed between the elements that are involved in drag
  92335. * drop operations. You can populate this object with any arbitrary properties that your own code knows how to
  92336. * interpret, plus there are some specific properties known to the Registry that should be populated in the data
  92337. * object (if applicable):
  92338. * @param {HTMLElement[]} data.handles Array of DOM nodes that trigger dragging for the element being registered.
  92339. * @param {Boolean} data.isHandle True if the element passed in triggers dragging itself, else false.
  92340. */
  92341. register : function(el, data){
  92342. data = data || {};
  92343. if (typeof el == "string") {
  92344. el = document.getElementById(el);
  92345. }
  92346. data.ddel = el;
  92347. this.elements[this.getId(el)] = data;
  92348. if (data.isHandle !== false) {
  92349. this.handles[data.ddel.id] = data;
  92350. }
  92351. if (data.handles) {
  92352. var hs = data.handles,
  92353. i, len;
  92354. for (i = 0, len = hs.length; i < len; i++) {
  92355. this.handles[this.getId(hs[i])] = data;
  92356. }
  92357. }
  92358. },
  92359. /**
  92360. * Unregister a drag drop element
  92361. * @param {String/HTMLElement} element The id or DOM node to unregister
  92362. */
  92363. unregister : function(el){
  92364. var id = this.getId(el, false),
  92365. data = this.elements[id],
  92366. hs, i, len;
  92367. if(data){
  92368. delete this.elements[id];
  92369. if(data.handles){
  92370. hs = data.handles;
  92371. for (i = 0, len = hs.length; i < len; i++) {
  92372. delete this.handles[this.getId(hs[i], false)];
  92373. }
  92374. }
  92375. }
  92376. },
  92377. /**
  92378. * Returns the handle registered for a DOM Node by id
  92379. * @param {String/HTMLElement} id The DOM node or id to look up
  92380. * @return {Object} handle The custom handle data
  92381. */
  92382. getHandle : function(id){
  92383. if(typeof id != "string"){ // must be element?
  92384. id = id.id;
  92385. }
  92386. return this.handles[id];
  92387. },
  92388. /**
  92389. * Returns the handle that is registered for the DOM node that is the target of the event
  92390. * @param {Event} e The event
  92391. * @return {Object} handle The custom handle data
  92392. */
  92393. getHandleFromEvent : function(e){
  92394. var t = e.getTarget();
  92395. return t ? this.handles[t.id] : null;
  92396. },
  92397. /**
  92398. * Returns a custom data object that is registered for a DOM node by id
  92399. * @param {String/HTMLElement} id The DOM node or id to look up
  92400. * @return {Object} data The custom data
  92401. */
  92402. getTarget : function(id){
  92403. if(typeof id != "string"){ // must be element?
  92404. id = id.id;
  92405. }
  92406. return this.elements[id];
  92407. },
  92408. /**
  92409. * Returns a custom data object that is registered for the DOM node that is the target of the event
  92410. * @param {Event} e The event
  92411. * @return {Object} data The custom data
  92412. */
  92413. getTargetFromEvent : function(e){
  92414. var t = e.getTarget();
  92415. return t ? this.elements[t.id] || this.handles[t.id] : null;
  92416. }
  92417. });
  92418. /**
  92419. * This class provides a container DD instance that allows dropping on multiple child target nodes.
  92420. *
  92421. * By default, this class requires that child nodes accepting drop are registered with {@link Ext.dd.Registry}.
  92422. * However a simpler way to allow a DropZone to manage any number of target elements is to configure the
  92423. * DropZone with an implementation of {@link #getTargetFromEvent} which interrogates the passed
  92424. * mouse event to see if it has taken place within an element, or class of elements. This is easily done
  92425. * by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
  92426. * {@link Ext.DomQuery} selector.
  92427. *
  92428. * Once the DropZone has detected through calling getTargetFromEvent, that the mouse is over
  92429. * a drop target, that target is passed as the first parameter to {@link #onNodeEnter}, {@link #onNodeOver},
  92430. * {@link #onNodeOut}, {@link #onNodeDrop}. You may configure the instance of DropZone with implementations
  92431. * of these methods to provide application-specific behaviour for these events to update both
  92432. * application state, and UI state.
  92433. *
  92434. * For example to make a GridPanel a cooperating target with the example illustrated in
  92435. * {@link Ext.dd.DragZone DragZone}, the following technique might be used:
  92436. *
  92437. * myGridPanel.on('render', function() {
  92438. * myGridPanel.dropZone = new Ext.dd.DropZone(myGridPanel.getView().scroller, {
  92439. *
  92440. * // If the mouse is over a grid row, return that node. This is
  92441. * // provided as the "target" parameter in all "onNodeXXXX" node event handling functions
  92442. * getTargetFromEvent: function(e) {
  92443. * return e.getTarget(myGridPanel.getView().rowSelector);
  92444. * },
  92445. *
  92446. * // On entry into a target node, highlight that node.
  92447. * onNodeEnter : function(target, dd, e, data){
  92448. * Ext.fly(target).addCls('my-row-highlight-class');
  92449. * },
  92450. *
  92451. * // On exit from a target node, unhighlight that node.
  92452. * onNodeOut : function(target, dd, e, data){
  92453. * Ext.fly(target).removeCls('my-row-highlight-class');
  92454. * },
  92455. *
  92456. * // While over a target node, return the default drop allowed class which
  92457. * // places a "tick" icon into the drag proxy.
  92458. * onNodeOver : function(target, dd, e, data){
  92459. * return Ext.dd.DropZone.prototype.dropAllowed;
  92460. * },
  92461. *
  92462. * // On node drop we can interrogate the target to find the underlying
  92463. * // application object that is the real target of the dragged data.
  92464. * // In this case, it is a Record in the GridPanel's Store.
  92465. * // We can use the data set up by the DragZone's getDragData method to read
  92466. * // any data we decided to attach in the DragZone's getDragData method.
  92467. * onNodeDrop : function(target, dd, e, data){
  92468. * var rowIndex = myGridPanel.getView().findRowIndex(target);
  92469. * var r = myGridPanel.getStore().getAt(rowIndex);
  92470. * Ext.Msg.alert('Drop gesture', 'Dropped Record id ' + data.draggedRecord.id +
  92471. * ' on Record id ' + r.id);
  92472. * return true;
  92473. * }
  92474. * });
  92475. * }
  92476. *
  92477. * See the {@link Ext.dd.DragZone DragZone} documentation for details about building a DragZone which
  92478. * cooperates with this DropZone.
  92479. */
  92480. Ext.define('Ext.dd.DropZone', {
  92481. extend: 'Ext.dd.DropTarget',
  92482. requires: ['Ext.dd.Registry'],
  92483. /**
  92484. * Returns a custom data object associated with the DOM node that is the target of the event. By default
  92485. * this looks up the event target in the {@link Ext.dd.Registry}, although you can override this method to
  92486. * provide your own custom lookup.
  92487. * @param {Event} e The event
  92488. * @return {Object} data The custom data
  92489. */
  92490. getTargetFromEvent : function(e){
  92491. return Ext.dd.Registry.getTargetFromEvent(e);
  92492. },
  92493. /**
  92494. * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
  92495. * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
  92496. * This method has no default implementation and should be overridden to provide
  92497. * node-specific processing if necessary.
  92498. * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
  92499. * {@link #getTargetFromEvent} for this node)
  92500. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
  92501. * @param {Event} e The event
  92502. * @param {Object} data An object containing arbitrary data supplied by the drag source
  92503. */
  92504. onNodeEnter : function(n, dd, e, data){
  92505. },
  92506. /**
  92507. * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
  92508. * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
  92509. * The default implementation returns this.dropAllowed, so it should be
  92510. * overridden to provide the proper feedback.
  92511. * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
  92512. * {@link #getTargetFromEvent} for this node)
  92513. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
  92514. * @param {Event} e The event
  92515. * @param {Object} data An object containing arbitrary data supplied by the drag source
  92516. * @return {String} status The CSS class that communicates the drop status back to the source so that the
  92517. * underlying {@link Ext.dd.StatusProxy} can be updated
  92518. * @template
  92519. */
  92520. onNodeOver : function(n, dd, e, data){
  92521. return this.dropAllowed;
  92522. },
  92523. /**
  92524. * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged out of
  92525. * the drop node without dropping. This method has no default implementation and should be overridden to provide
  92526. * node-specific processing if necessary.
  92527. * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
  92528. * {@link #getTargetFromEvent} for this node)
  92529. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
  92530. * @param {Event} e The event
  92531. * @param {Object} data An object containing arbitrary data supplied by the drag source
  92532. * @template
  92533. */
  92534. onNodeOut : function(n, dd, e, data){
  92535. },
  92536. /**
  92537. * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped onto
  92538. * the drop node. The default implementation returns false, so it should be overridden to provide the
  92539. * appropriate processing of the drop event and return true so that the drag source's repair action does not run.
  92540. * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
  92541. * {@link #getTargetFromEvent} for this node)
  92542. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
  92543. * @param {Event} e The event
  92544. * @param {Object} data An object containing arbitrary data supplied by the drag source
  92545. * @return {Boolean} True if the drop was valid, else false
  92546. * @template
  92547. */
  92548. onNodeDrop : function(n, dd, e, data){
  92549. return false;
  92550. },
  92551. /**
  92552. * Called while the DropZone determines that a {@link Ext.dd.DragSource} is being dragged over it,
  92553. * but not over any of its registered drop nodes. The default implementation returns this.dropNotAllowed, so
  92554. * it should be overridden to provide the proper feedback if necessary.
  92555. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
  92556. * @param {Event} e The event
  92557. * @param {Object} data An object containing arbitrary data supplied by the drag source
  92558. * @return {String} status The CSS class that communicates the drop status back to the source so that the
  92559. * underlying {@link Ext.dd.StatusProxy} can be updated
  92560. * @template
  92561. */
  92562. onContainerOver : function(dd, e, data){
  92563. return this.dropNotAllowed;
  92564. },
  92565. /**
  92566. * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped on it,
  92567. * but not on any of its registered drop nodes. The default implementation returns false, so it should be
  92568. * overridden to provide the appropriate processing of the drop event if you need the drop zone itself to
  92569. * be able to accept drops. It should return true when valid so that the drag source's repair action does not run.
  92570. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
  92571. * @param {Event} e The event
  92572. * @param {Object} data An object containing arbitrary data supplied by the drag source
  92573. * @return {Boolean} True if the drop was valid, else false
  92574. * @template
  92575. */
  92576. onContainerDrop : function(dd, e, data){
  92577. return false;
  92578. },
  92579. /**
  92580. * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source is now over
  92581. * the zone. The default implementation returns this.dropNotAllowed and expects that only registered drop
  92582. * nodes can process drag drop operations, so if you need the drop zone itself to be able to process drops
  92583. * you should override this method and provide a custom implementation.
  92584. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
  92585. * @param {Event} e The event
  92586. * @param {Object} data An object containing arbitrary data supplied by the drag source
  92587. * @return {String} status The CSS class that communicates the drop status back to the source so that the
  92588. * underlying {@link Ext.dd.StatusProxy} can be updated
  92589. * @template
  92590. */
  92591. notifyEnter : function(dd, e, data){
  92592. return this.dropNotAllowed;
  92593. },
  92594. /**
  92595. * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the drop zone.
  92596. * This method will be called on every mouse movement while the drag source is over the drop zone.
  92597. * It will call {@link #onNodeOver} while the drag source is over a registered node, and will also automatically
  92598. * delegate to the appropriate node-specific methods as necessary when the drag source enters and exits
  92599. * registered nodes ({@link #onNodeEnter}, {@link #onNodeOut}). If the drag source is not currently over a
  92600. * registered node, it will call {@link #onContainerOver}.
  92601. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
  92602. * @param {Event} e The event
  92603. * @param {Object} data An object containing arbitrary data supplied by the drag source
  92604. * @return {String} status The CSS class that communicates the drop status back to the source so that the
  92605. * underlying {@link Ext.dd.StatusProxy} can be updated
  92606. * @template
  92607. */
  92608. notifyOver : function(dd, e, data){
  92609. var n = this.getTargetFromEvent(e);
  92610. if(!n) { // not over valid drop target
  92611. if(this.lastOverNode){
  92612. this.onNodeOut(this.lastOverNode, dd, e, data);
  92613. this.lastOverNode = null;
  92614. }
  92615. return this.onContainerOver(dd, e, data);
  92616. }
  92617. if(this.lastOverNode != n){
  92618. if(this.lastOverNode){
  92619. this.onNodeOut(this.lastOverNode, dd, e, data);
  92620. }
  92621. this.onNodeEnter(n, dd, e, data);
  92622. this.lastOverNode = n;
  92623. }
  92624. return this.onNodeOver(n, dd, e, data);
  92625. },
  92626. /**
  92627. * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source has been dragged
  92628. * out of the zone without dropping. If the drag source is currently over a registered node, the notification
  92629. * will be delegated to {@link #onNodeOut} for node-specific handling, otherwise it will be ignored.
  92630. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
  92631. * @param {Event} e The event
  92632. * @param {Object} data An object containing arbitrary data supplied by the drag zone
  92633. * @template
  92634. */
  92635. notifyOut : function(dd, e, data){
  92636. if(this.lastOverNode){
  92637. this.onNodeOut(this.lastOverNode, dd, e, data);
  92638. this.lastOverNode = null;
  92639. }
  92640. },
  92641. /**
  92642. * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the dragged item has
  92643. * been dropped on it. The drag zone will look up the target node based on the event passed in, and if there
  92644. * is a node registered for that event, it will delegate to {@link #onNodeDrop} for node-specific handling,
  92645. * otherwise it will call {@link #onContainerDrop}.
  92646. * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
  92647. * @param {Event} e The event
  92648. * @param {Object} data An object containing arbitrary data supplied by the drag source
  92649. * @return {Boolean} False if the drop was invalid.
  92650. * @template
  92651. */
  92652. notifyDrop : function(dd, e, data){
  92653. if(this.lastOverNode){
  92654. this.onNodeOut(this.lastOverNode, dd, e, data);
  92655. this.lastOverNode = null;
  92656. }
  92657. var n = this.getTargetFromEvent(e);
  92658. return n ?
  92659. this.onNodeDrop(n, dd, e, data) :
  92660. this.onContainerDrop(dd, e, data);
  92661. },
  92662. // private
  92663. triggerCacheRefresh : function() {
  92664. Ext.dd.DDM.refreshCache(this.groups);
  92665. }
  92666. });
  92667. /**
  92668. * @class Ext.direct.Event
  92669. * A base class for all Ext.direct events. An event is
  92670. * created after some kind of interaction with the server.
  92671. * The event class is essentially just a data structure
  92672. * to hold a Direct response.
  92673. */
  92674. Ext.define('Ext.direct.Event', {
  92675. /* Begin Definitions */
  92676. alias: 'direct.event',
  92677. requires: ['Ext.direct.Manager'],
  92678. /* End Definitions */
  92679. status: true,
  92680. /**
  92681. * Creates new Event.
  92682. * @param {Object} config (optional) Config object.
  92683. */
  92684. constructor: function(config) {
  92685. Ext.apply(this, config);
  92686. },
  92687. /**
  92688. * Return the raw data for this event.
  92689. * @return {Object} The data from the event
  92690. */
  92691. getData: function(){
  92692. return this.data;
  92693. }
  92694. });
  92695. /**
  92696. * @class Ext.direct.RemotingEvent
  92697. * An event that is fired when data is received from a
  92698. * {@link Ext.direct.RemotingProvider}. Contains a method to the
  92699. * related transaction for the direct request, see {@link #getTransaction}
  92700. */
  92701. Ext.define('Ext.direct.RemotingEvent', {
  92702. /* Begin Definitions */
  92703. extend: 'Ext.direct.Event',
  92704. alias: 'direct.rpc',
  92705. /* End Definitions */
  92706. /**
  92707. * Get the transaction associated with this event.
  92708. * @return {Ext.direct.Transaction} The transaction
  92709. */
  92710. getTransaction: function(){
  92711. return this.transaction || Ext.direct.Manager.getTransaction(this.tid);
  92712. }
  92713. });
  92714. /**
  92715. * @class Ext.direct.ExceptionEvent
  92716. * An event that is fired when an exception is received from a {@link Ext.direct.RemotingProvider}
  92717. */
  92718. Ext.define('Ext.direct.ExceptionEvent', {
  92719. /* Begin Definitions */
  92720. extend: 'Ext.direct.RemotingEvent',
  92721. alias: 'direct.exception',
  92722. /* End Definitions */
  92723. status: false
  92724. });
  92725. /**
  92726. * Ext.direct.Provider is an abstract class meant to be extended.
  92727. *
  92728. * For example Ext JS implements the following subclasses:
  92729. *
  92730. * Provider
  92731. * |
  92732. * +---{@link Ext.direct.JsonProvider JsonProvider}
  92733. * |
  92734. * +---{@link Ext.direct.PollingProvider PollingProvider}
  92735. * |
  92736. * +---{@link Ext.direct.RemotingProvider RemotingProvider}
  92737. *
  92738. * @abstract
  92739. */
  92740. Ext.define('Ext.direct.Provider', {
  92741. /* Begin Definitions */
  92742. alias: 'direct.provider',
  92743. mixins: {
  92744. observable: 'Ext.util.Observable'
  92745. },
  92746. /* End Definitions */
  92747. /**
  92748. * @cfg {String} id
  92749. * The unique id of the provider (defaults to an {@link Ext#id auto-assigned id}).
  92750. * You should assign an id if you need to be able to access the provider later and you do
  92751. * not have an object reference available, for example:
  92752. *
  92753. * Ext.direct.Manager.addProvider({
  92754. * type: 'polling',
  92755. * url: 'php/poll.php',
  92756. * id: 'poll-provider'
  92757. * });
  92758. * var p = {@link Ext.direct.Manager}.{@link Ext.direct.Manager#getProvider getProvider}('poll-provider');
  92759. * p.disconnect();
  92760. *
  92761. */
  92762. constructor : function(config){
  92763. var me = this;
  92764. Ext.apply(me, config);
  92765. me.addEvents(
  92766. /**
  92767. * @event connect
  92768. * Fires when the Provider connects to the server-side
  92769. * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
  92770. */
  92771. 'connect',
  92772. /**
  92773. * @event disconnect
  92774. * Fires when the Provider disconnects from the server-side
  92775. * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
  92776. */
  92777. 'disconnect',
  92778. /**
  92779. * @event data
  92780. * Fires when the Provider receives data from the server-side
  92781. * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
  92782. * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
  92783. */
  92784. 'data',
  92785. /**
  92786. * @event exception
  92787. * Fires when the Provider receives an exception from the server-side
  92788. */
  92789. 'exception'
  92790. );
  92791. me.mixins.observable.constructor.call(me, config);
  92792. },
  92793. /**
  92794. * Returns whether or not the server-side is currently connected.
  92795. * Abstract method for subclasses to implement.
  92796. */
  92797. isConnected: function(){
  92798. return false;
  92799. },
  92800. /**
  92801. * Abstract methods for subclasses to implement.
  92802. * @method
  92803. */
  92804. connect: Ext.emptyFn,
  92805. /**
  92806. * Abstract methods for subclasses to implement.
  92807. * @method
  92808. */
  92809. disconnect: Ext.emptyFn
  92810. });
  92811. /**
  92812. * @class Ext.direct.JsonProvider
  92813. A base provider for communicating using JSON. This is an abstract class
  92814. and should not be instanced directly.
  92815. * @markdown
  92816. * @abstract
  92817. */
  92818. Ext.define('Ext.direct.JsonProvider', {
  92819. /* Begin Definitions */
  92820. extend: 'Ext.direct.Provider',
  92821. alias: 'direct.jsonprovider',
  92822. uses: ['Ext.direct.ExceptionEvent'],
  92823. /* End Definitions */
  92824. /**
  92825. * Parse the JSON response
  92826. * @private
  92827. * @param {Object} response The XHR response object
  92828. * @return {Object} The data in the response.
  92829. */
  92830. parseResponse: function(response){
  92831. if (!Ext.isEmpty(response.responseText)) {
  92832. if (Ext.isObject(response.responseText)) {
  92833. return response.responseText;
  92834. }
  92835. return Ext.decode(response.responseText);
  92836. }
  92837. return null;
  92838. },
  92839. /**
  92840. * Creates a set of events based on the XHR response
  92841. * @private
  92842. * @param {Object} response The XHR response
  92843. * @return {Ext.direct.Event[]} An array of Ext.direct.Event
  92844. */
  92845. createEvents: function(response){
  92846. var data = null,
  92847. events = [],
  92848. event,
  92849. i = 0,
  92850. len;
  92851. try{
  92852. data = this.parseResponse(response);
  92853. } catch(e) {
  92854. event = new Ext.direct.ExceptionEvent({
  92855. data: e,
  92856. xhr: response,
  92857. code: Ext.direct.Manager.exceptions.PARSE,
  92858. message: 'Error parsing json response: \n\n ' + data
  92859. });
  92860. return [event];
  92861. }
  92862. if (Ext.isArray(data)) {
  92863. for (len = data.length; i < len; ++i) {
  92864. events.push(this.createEvent(data[i]));
  92865. }
  92866. } else {
  92867. events.push(this.createEvent(data));
  92868. }
  92869. return events;
  92870. },
  92871. /**
  92872. * Create an event from a response object
  92873. * @param {Object} response The XHR response object
  92874. * @return {Ext.direct.Event} The event
  92875. */
  92876. createEvent: function(response){
  92877. return Ext.create('direct.' + response.type, response);
  92878. }
  92879. });
  92880. /**
  92881. * @class Ext.direct.PollingProvider
  92882. *
  92883. * <p>Provides for repetitive polling of the server at distinct {@link #interval intervals}.
  92884. * The initial request for data originates from the client, and then is responded to by the
  92885. * server.</p>
  92886. *
  92887. * <p>All configurations for the PollingProvider should be generated by the server-side
  92888. * API portion of the Ext.Direct stack.</p>
  92889. *
  92890. * <p>An instance of PollingProvider may be created directly via the new keyword or by simply
  92891. * specifying <tt>type = 'polling'</tt>. For example:</p>
  92892. * <pre><code>
  92893. var pollA = new Ext.direct.PollingProvider({
  92894. type:'polling',
  92895. url: 'php/pollA.php',
  92896. });
  92897. Ext.direct.Manager.addProvider(pollA);
  92898. pollA.disconnect();
  92899. Ext.direct.Manager.addProvider(
  92900. {
  92901. type:'polling',
  92902. url: 'php/pollB.php',
  92903. id: 'pollB-provider'
  92904. }
  92905. );
  92906. var pollB = Ext.direct.Manager.getProvider('pollB-provider');
  92907. * </code></pre>
  92908. */
  92909. Ext.define('Ext.direct.PollingProvider', {
  92910. /* Begin Definitions */
  92911. extend: 'Ext.direct.JsonProvider',
  92912. alias: 'direct.pollingprovider',
  92913. uses: ['Ext.direct.ExceptionEvent'],
  92914. requires: ['Ext.Ajax', 'Ext.util.DelayedTask'],
  92915. /* End Definitions */
  92916. /**
  92917. * @cfg {Number} interval
  92918. * How often to poll the server-side in milliseconds. Defaults to every 3 seconds.
  92919. */
  92920. interval: 3000,
  92921. /**
  92922. * @cfg {Object} baseParams
  92923. * An object containing properties which are to be sent as parameters on every polling request
  92924. */
  92925. /**
  92926. * @cfg {String/Function} url
  92927. * The url which the PollingProvider should contact with each request. This can also be
  92928. * an imported Ext.Direct method which will accept the baseParams as its only argument.
  92929. */
  92930. // private
  92931. constructor : function(config){
  92932. this.callParent(arguments);
  92933. this.addEvents(
  92934. /**
  92935. * @event beforepoll
  92936. * Fired immediately before a poll takes place, an event handler can return false
  92937. * in order to cancel the poll.
  92938. * @param {Ext.direct.PollingProvider} this
  92939. */
  92940. 'beforepoll',
  92941. /**
  92942. * @event poll
  92943. * This event has not yet been implemented.
  92944. * @param {Ext.direct.PollingProvider} this
  92945. */
  92946. 'poll'
  92947. );
  92948. },
  92949. // inherited
  92950. isConnected: function(){
  92951. return !!this.pollTask;
  92952. },
  92953. /**
  92954. * Connect to the server-side and begin the polling process. To handle each
  92955. * response subscribe to the data event.
  92956. */
  92957. connect: function(){
  92958. var me = this, url = me.url;
  92959. if (url && !me.pollTask) {
  92960. me.pollTask = Ext.TaskManager.start({
  92961. run: function(){
  92962. if (me.fireEvent('beforepoll', me) !== false) {
  92963. if (Ext.isFunction(url)) {
  92964. url(me.baseParams);
  92965. } else {
  92966. Ext.Ajax.request({
  92967. url: url,
  92968. callback: me.onData,
  92969. scope: me,
  92970. params: me.baseParams
  92971. });
  92972. }
  92973. }
  92974. },
  92975. interval: me.interval,
  92976. scope: me
  92977. });
  92978. me.fireEvent('connect', me);
  92979. } else if (!url) {
  92980. Ext.Error.raise('Error initializing PollingProvider, no url configured.');
  92981. }
  92982. },
  92983. /**
  92984. * Disconnect from the server-side and stop the polling process. The disconnect
  92985. * event will be fired on a successful disconnect.
  92986. */
  92987. disconnect: function(){
  92988. var me = this;
  92989. if (me.pollTask) {
  92990. Ext.TaskManager.stop(me.pollTask);
  92991. delete me.pollTask;
  92992. me.fireEvent('disconnect', me);
  92993. }
  92994. },
  92995. // private
  92996. onData: function(opt, success, response){
  92997. var me = this,
  92998. i = 0,
  92999. len,
  93000. events;
  93001. if (success) {
  93002. events = me.createEvents(response);
  93003. for (len = events.length; i < len; ++i) {
  93004. me.fireEvent('data', me, events[i]);
  93005. }
  93006. } else {
  93007. me.fireEvent('data', me, new Ext.direct.ExceptionEvent({
  93008. data: null,
  93009. code: Ext.direct.Manager.exceptions.TRANSPORT,
  93010. message: 'Unable to connect to the server.',
  93011. xhr: response
  93012. }));
  93013. }
  93014. }
  93015. });
  93016. /**
  93017. * Small utility class used internally to represent a Direct method.
  93018. * @private
  93019. */
  93020. Ext.define('Ext.direct.RemotingMethod', {
  93021. constructor: function(config){
  93022. var me = this,
  93023. params = Ext.isDefined(config.params) ? config.params : config.len,
  93024. name, pLen, p, param;
  93025. me.name = config.name;
  93026. me.formHandler = config.formHandler;
  93027. if (Ext.isNumber(params)) {
  93028. // given only the number of parameters
  93029. me.len = params;
  93030. me.ordered = true;
  93031. } else {
  93032. /*
  93033. * Given an array of either
  93034. * a) String
  93035. * b) Objects with a name property. We may want to encode extra info in here later
  93036. */
  93037. me.params = [];
  93038. pLen = params.length;
  93039. for (p = 0; p < pLen; p++) {
  93040. param = params[p];
  93041. name = Ext.isObject(param) ? param.name : param;
  93042. me.params.push(name);
  93043. }
  93044. }
  93045. },
  93046. getArgs: function(params, paramOrder, paramsAsHash){
  93047. var args = [],
  93048. i,
  93049. len;
  93050. if (this.ordered) {
  93051. if (this.len > 0) {
  93052. // If a paramOrder was specified, add the params into the argument list in that order.
  93053. if (paramOrder) {
  93054. for (i = 0, len = paramOrder.length; i < len; i++) {
  93055. args.push(params[paramOrder[i]]);
  93056. }
  93057. } else if (paramsAsHash) {
  93058. // If paramsAsHash was specified, add all the params as a single object argument.
  93059. args.push(params);
  93060. }
  93061. }
  93062. } else {
  93063. args.push(params);
  93064. }
  93065. return args;
  93066. },
  93067. /**
  93068. * Takes the arguments for the Direct function and splits the arguments
  93069. * from the scope and the callback.
  93070. * @param {Array} args The arguments passed to the direct call
  93071. * @return {Object} An object with 3 properties, args, callback & scope.
  93072. */
  93073. getCallData: function(args){
  93074. var me = this,
  93075. data = null,
  93076. len = me.len,
  93077. params = me.params,
  93078. callback,
  93079. scope,
  93080. name;
  93081. if (me.ordered) {
  93082. callback = args[len];
  93083. scope = args[len + 1];
  93084. if (len !== 0) {
  93085. data = args.slice(0, len);
  93086. }
  93087. } else {
  93088. data = Ext.apply({}, args[0]);
  93089. callback = args[1];
  93090. scope = args[2];
  93091. // filter out any non-existent properties
  93092. for (name in data) {
  93093. if (data.hasOwnProperty(name)) {
  93094. if (!Ext.Array.contains(params, name)) {
  93095. delete data[name];
  93096. }
  93097. }
  93098. }
  93099. }
  93100. return {
  93101. data: data,
  93102. callback: callback,
  93103. scope: scope
  93104. };
  93105. }
  93106. });
  93107. /**
  93108. * Supporting Class for Ext.Direct (not intended to be used directly).
  93109. */
  93110. Ext.define('Ext.direct.Transaction', {
  93111. /* Begin Definitions */
  93112. alias: 'direct.transaction',
  93113. alternateClassName: 'Ext.Direct.Transaction',
  93114. statics: {
  93115. TRANSACTION_ID: 0
  93116. },
  93117. /* End Definitions */
  93118. /**
  93119. * Creates new Transaction.
  93120. * @param {Object} [config] Config object.
  93121. */
  93122. constructor: function(config){
  93123. var me = this;
  93124. Ext.apply(me, config);
  93125. me.id = me.tid = ++me.self.TRANSACTION_ID;
  93126. me.retryCount = 0;
  93127. },
  93128. send: function(){
  93129. this.provider.queueTransaction(this);
  93130. },
  93131. retry: function(){
  93132. this.retryCount++;
  93133. this.send();
  93134. },
  93135. getProvider: function(){
  93136. return this.provider;
  93137. }
  93138. });
  93139. /**
  93140. * @class Ext.direct.RemotingProvider
  93141. *
  93142. * <p>The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
  93143. * server side methods on the client (a remote procedure call (RPC) type of
  93144. * connection where the client can initiate a procedure on the server).</p>
  93145. *
  93146. * <p>This allows for code to be organized in a fashion that is maintainable,
  93147. * while providing a clear path between client and server, something that is
  93148. * not always apparent when using URLs.</p>
  93149. *
  93150. * <p>To accomplish this the server-side needs to describe what classes and methods
  93151. * are available on the client-side. This configuration will typically be
  93152. * outputted by the server-side Ext.Direct stack when the API description is built.</p>
  93153. */
  93154. Ext.define('Ext.direct.RemotingProvider', {
  93155. /* Begin Definitions */
  93156. alias: 'direct.remotingprovider',
  93157. extend: 'Ext.direct.JsonProvider',
  93158. requires: [
  93159. 'Ext.util.MixedCollection',
  93160. 'Ext.util.DelayedTask',
  93161. 'Ext.direct.Transaction',
  93162. 'Ext.direct.RemotingMethod'
  93163. ],
  93164. /* End Definitions */
  93165. /**
  93166. * @cfg {Object} actions
  93167. * Object literal defining the server side actions and methods. For example, if
  93168. * the Provider is configured with:
  93169. * <pre><code>
  93170. "actions":{ // each property within the 'actions' object represents a server side Class
  93171. "TestAction":[ // array of methods within each server side Class to be
  93172. { // stubbed out on client
  93173. "name":"doEcho",
  93174. "len":1
  93175. },{
  93176. "name":"multiply",// name of method
  93177. "len":2 // The number of parameters that will be used to create an
  93178. // array of data to send to the server side function.
  93179. // Ensure the server sends back a Number, not a String.
  93180. },{
  93181. "name":"doForm",
  93182. "formHandler":true, // direct the client to use specialized form handling method
  93183. "len":1
  93184. }]
  93185. }
  93186. * </code></pre>
  93187. * <p>Note that a Store is not required, a server method can be called at any time.
  93188. * In the following example a <b>client side</b> handler is used to call the
  93189. * server side method "multiply" in the server-side "TestAction" Class:</p>
  93190. * <pre><code>
  93191. TestAction.multiply(
  93192. 2, 4, // pass two arguments to server, so specify len=2
  93193. // callback function after the server is called
  93194. // result: the result returned by the server
  93195. // e: Ext.direct.RemotingEvent object
  93196. function(result, e){
  93197. var t = e.getTransaction();
  93198. var action = t.action; // server side Class called
  93199. var method = t.method; // server side method called
  93200. if(e.status){
  93201. var answer = Ext.encode(result); // 8
  93202. }else{
  93203. var msg = e.message; // failure message
  93204. }
  93205. }
  93206. );
  93207. * </code></pre>
  93208. * In the example above, the server side "multiply" function will be passed two
  93209. * arguments (2 and 4). The "multiply" method should return the value 8 which will be
  93210. * available as the <tt>result</tt> in the example above.
  93211. */
  93212. /**
  93213. * @cfg {String/Object} namespace
  93214. * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).
  93215. * Explicitly specify the namespace Object, or specify a String to have a
  93216. * {@link Ext#namespace namespace created} implicitly.
  93217. */
  93218. /**
  93219. * @cfg {String} url
  93220. * <b>Required</b>. The url to connect to the {@link Ext.direct.Manager} server-side router.
  93221. */
  93222. /**
  93223. * @cfg {String} enableUrlEncode
  93224. * Specify which param will hold the arguments for the method.
  93225. * Defaults to <tt>'data'</tt>.
  93226. */
  93227. /**
  93228. * @cfg {Number/Boolean} enableBuffer
  93229. * <p><tt>true</tt> or <tt>false</tt> to enable or disable combining of method
  93230. * calls. If a number is specified this is the amount of time in milliseconds
  93231. * to wait before sending a batched request.</p>
  93232. * <br><p>Calls which are received within the specified timeframe will be
  93233. * concatenated together and sent in a single request, optimizing the
  93234. * application by reducing the amount of round trips that have to be made
  93235. * to the server.</p>
  93236. */
  93237. enableBuffer: 10,
  93238. /**
  93239. * @cfg {Number} maxRetries
  93240. * Number of times to re-attempt delivery on failure of a call.
  93241. */
  93242. maxRetries: 1,
  93243. /**
  93244. * @cfg {Number} timeout
  93245. * The timeout to use for each request.
  93246. */
  93247. timeout: undefined,
  93248. constructor : function(config){
  93249. var me = this;
  93250. me.callParent(arguments);
  93251. me.addEvents(
  93252. /**
  93253. * @event beforecall
  93254. * Fires immediately before the client-side sends off the RPC call.
  93255. * By returning false from an event handler you can prevent the call from
  93256. * executing.
  93257. * @param {Ext.direct.RemotingProvider} provider
  93258. * @param {Ext.direct.Transaction} transaction
  93259. * @param {Object} meta The meta data
  93260. */
  93261. 'beforecall',
  93262. /**
  93263. * @event call
  93264. * Fires immediately after the request to the server-side is sent. This does
  93265. * NOT fire after the response has come back from the call.
  93266. * @param {Ext.direct.RemotingProvider} provider
  93267. * @param {Ext.direct.Transaction} transaction
  93268. * @param {Object} meta The meta data
  93269. */
  93270. 'call'
  93271. );
  93272. me.namespace = (Ext.isString(me.namespace)) ? Ext.ns(me.namespace) : me.namespace || window;
  93273. me.transactions = new Ext.util.MixedCollection();
  93274. me.callBuffer = [];
  93275. },
  93276. /**
  93277. * Initialize the API
  93278. * @private
  93279. */
  93280. initAPI : function(){
  93281. var actions = this.actions,
  93282. namespace = this.namespace,
  93283. action,
  93284. cls,
  93285. methods,
  93286. i,
  93287. len,
  93288. method;
  93289. for (action in actions) {
  93290. if (actions.hasOwnProperty(action)) {
  93291. cls = namespace[action];
  93292. if (!cls) {
  93293. cls = namespace[action] = {};
  93294. }
  93295. methods = actions[action];
  93296. for (i = 0, len = methods.length; i < len; ++i) {
  93297. method = new Ext.direct.RemotingMethod(methods[i]);
  93298. cls[method.name] = this.createHandler(action, method);
  93299. }
  93300. }
  93301. }
  93302. },
  93303. /**
  93304. * Create a handler function for a direct call.
  93305. * @private
  93306. * @param {String} action The action the call is for
  93307. * @param {Object} method The details of the method
  93308. * @return {Function} A JS function that will kick off the call
  93309. */
  93310. createHandler : function(action, method){
  93311. var me = this,
  93312. handler;
  93313. if (!method.formHandler) {
  93314. handler = function(){
  93315. me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
  93316. };
  93317. } else {
  93318. handler = function(form, callback, scope){
  93319. me.configureFormRequest(action, method, form, callback, scope);
  93320. };
  93321. }
  93322. handler.directCfg = {
  93323. action: action,
  93324. method: method
  93325. };
  93326. return handler;
  93327. },
  93328. // inherit docs
  93329. isConnected: function(){
  93330. return !!this.connected;
  93331. },
  93332. // inherit docs
  93333. connect: function(){
  93334. var me = this;
  93335. if (me.url) {
  93336. me.initAPI();
  93337. me.connected = true;
  93338. me.fireEvent('connect', me);
  93339. } else if(!me.url) {
  93340. Ext.Error.raise('Error initializing RemotingProvider, no url configured.');
  93341. }
  93342. },
  93343. // inherit docs
  93344. disconnect: function(){
  93345. var me = this;
  93346. if (me.connected) {
  93347. me.connected = false;
  93348. me.fireEvent('disconnect', me);
  93349. }
  93350. },
  93351. /**
  93352. * Run any callbacks related to the transaction.
  93353. * @private
  93354. * @param {Ext.direct.Transaction} transaction The transaction
  93355. * @param {Ext.direct.Event} event The event
  93356. */
  93357. runCallback: function(transaction, event){
  93358. var success = !!event.status,
  93359. funcName = success ? 'success' : 'failure',
  93360. callback,
  93361. result;
  93362. if (transaction && transaction.callback) {
  93363. callback = transaction.callback;
  93364. result = Ext.isDefined(event.result) ? event.result : event.data;
  93365. if (Ext.isFunction(callback)) {
  93366. callback(result, event, success);
  93367. } else {
  93368. Ext.callback(callback[funcName], callback.scope, [result, event, success]);
  93369. Ext.callback(callback.callback, callback.scope, [result, event, success]);
  93370. }
  93371. }
  93372. },
  93373. /**
  93374. * React to the ajax request being completed
  93375. * @private
  93376. */
  93377. onData: function(options, success, response){
  93378. var me = this,
  93379. i = 0,
  93380. len,
  93381. events,
  93382. event,
  93383. transaction,
  93384. transactions;
  93385. if (success) {
  93386. events = me.createEvents(response);
  93387. for (len = events.length; i < len; ++i) {
  93388. event = events[i];
  93389. transaction = me.getTransaction(event);
  93390. me.fireEvent('data', me, event);
  93391. if (transaction) {
  93392. me.runCallback(transaction, event, true);
  93393. Ext.direct.Manager.removeTransaction(transaction);
  93394. }
  93395. }
  93396. } else {
  93397. transactions = [].concat(options.transaction);
  93398. for (len = transactions.length; i < len; ++i) {
  93399. transaction = me.getTransaction(transactions[i]);
  93400. if (transaction && transaction.retryCount < me.maxRetries) {
  93401. transaction.retry();
  93402. } else {
  93403. event = new Ext.direct.ExceptionEvent({
  93404. data: null,
  93405. transaction: transaction,
  93406. code: Ext.direct.Manager.exceptions.TRANSPORT,
  93407. message: 'Unable to connect to the server.',
  93408. xhr: response
  93409. });
  93410. me.fireEvent('data', me, event);
  93411. if (transaction) {
  93412. me.runCallback(transaction, event, false);
  93413. Ext.direct.Manager.removeTransaction(transaction);
  93414. }
  93415. }
  93416. }
  93417. }
  93418. },
  93419. /**
  93420. * Get transaction from XHR options
  93421. * @private
  93422. * @param {Object} options The options sent to the Ajax request
  93423. * @return {Ext.direct.Transaction} The transaction, null if not found
  93424. */
  93425. getTransaction: function(options){
  93426. return options && options.tid ? Ext.direct.Manager.getTransaction(options.tid) : null;
  93427. },
  93428. /**
  93429. * Configure a direct request
  93430. * @private
  93431. * @param {String} action The action being executed
  93432. * @param {Object} method The being executed
  93433. */
  93434. configureRequest: function(action, method, args){
  93435. var me = this,
  93436. callData = method.getCallData(args),
  93437. data = callData.data,
  93438. callback = callData.callback,
  93439. scope = callData.scope,
  93440. transaction;
  93441. transaction = new Ext.direct.Transaction({
  93442. provider: me,
  93443. args: args,
  93444. action: action,
  93445. method: method.name,
  93446. data: data,
  93447. callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
  93448. });
  93449. if (me.fireEvent('beforecall', me, transaction, method) !== false) {
  93450. Ext.direct.Manager.addTransaction(transaction);
  93451. me.queueTransaction(transaction);
  93452. me.fireEvent('call', me, transaction, method);
  93453. }
  93454. },
  93455. /**
  93456. * Gets the Ajax call info for a transaction
  93457. * @private
  93458. * @param {Ext.direct.Transaction} transaction The transaction
  93459. * @return {Object} The call params
  93460. */
  93461. getCallData: function(transaction){
  93462. return {
  93463. action: transaction.action,
  93464. method: transaction.method,
  93465. data: transaction.data,
  93466. type: 'rpc',
  93467. tid: transaction.id
  93468. };
  93469. },
  93470. /**
  93471. * Sends a request to the server
  93472. * @private
  93473. * @param {Object/Array} data The data to send
  93474. */
  93475. sendRequest : function(data){
  93476. var me = this,
  93477. request = {
  93478. url: me.url,
  93479. callback: me.onData,
  93480. scope: me,
  93481. transaction: data,
  93482. timeout: me.timeout
  93483. }, callData,
  93484. enableUrlEncode = me.enableUrlEncode,
  93485. i = 0,
  93486. len,
  93487. params;
  93488. if (Ext.isArray(data)) {
  93489. callData = [];
  93490. for (len = data.length; i < len; ++i) {
  93491. callData.push(me.getCallData(data[i]));
  93492. }
  93493. } else {
  93494. callData = me.getCallData(data);
  93495. }
  93496. if (enableUrlEncode) {
  93497. params = {};
  93498. params[Ext.isString(enableUrlEncode) ? enableUrlEncode : 'data'] = Ext.encode(callData);
  93499. request.params = params;
  93500. } else {
  93501. request.jsonData = callData;
  93502. }
  93503. Ext.Ajax.request(request);
  93504. },
  93505. /**
  93506. * Add a new transaction to the queue
  93507. * @private
  93508. * @param {Ext.direct.Transaction} transaction The transaction
  93509. */
  93510. queueTransaction: function(transaction){
  93511. var me = this,
  93512. enableBuffer = me.enableBuffer;
  93513. if (transaction.form) {
  93514. me.sendFormRequest(transaction);
  93515. return;
  93516. }
  93517. me.callBuffer.push(transaction);
  93518. if (enableBuffer) {
  93519. if (!me.callTask) {
  93520. me.callTask = new Ext.util.DelayedTask(me.combineAndSend, me);
  93521. }
  93522. me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
  93523. } else {
  93524. me.combineAndSend();
  93525. }
  93526. },
  93527. /**
  93528. * Combine any buffered requests and send them off
  93529. * @private
  93530. */
  93531. combineAndSend : function(){
  93532. var buffer = this.callBuffer,
  93533. len = buffer.length;
  93534. if (len > 0) {
  93535. this.sendRequest(len == 1 ? buffer[0] : buffer);
  93536. this.callBuffer = [];
  93537. }
  93538. },
  93539. /**
  93540. * Configure a form submission request
  93541. * @private
  93542. * @param {String} action The action being executed
  93543. * @param {Object} method The method being executed
  93544. * @param {HTMLElement} form The form being submitted
  93545. * @param {Function} callback (optional) A callback to run after the form submits
  93546. * @param {Object} scope (optional) A scope to execute the callback in
  93547. */
  93548. configureFormRequest : function(action, method, form, callback, scope){
  93549. var me = this,
  93550. transaction = new Ext.direct.Transaction({
  93551. provider: me,
  93552. action: action,
  93553. method: method.name,
  93554. args: [form, callback, scope],
  93555. callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
  93556. isForm: true
  93557. }),
  93558. isUpload,
  93559. params;
  93560. if (me.fireEvent('beforecall', me, transaction, method) !== false) {
  93561. Ext.direct.Manager.addTransaction(transaction);
  93562. isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data';
  93563. params = {
  93564. extTID: transaction.id,
  93565. extAction: action,
  93566. extMethod: method.name,
  93567. extType: 'rpc',
  93568. extUpload: String(isUpload)
  93569. };
  93570. // change made from typeof callback check to callback.params
  93571. // to support addl param passing in DirectSubmit EAC 6/2
  93572. Ext.apply(transaction, {
  93573. form: Ext.getDom(form),
  93574. isUpload: isUpload,
  93575. params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
  93576. });
  93577. me.fireEvent('call', me, transaction, method);
  93578. me.sendFormRequest(transaction);
  93579. }
  93580. },
  93581. /**
  93582. * Sends a form request
  93583. * @private
  93584. * @param {Ext.direct.Transaction} transaction The transaction to send
  93585. */
  93586. sendFormRequest: function(transaction){
  93587. Ext.Ajax.request({
  93588. url: this.url,
  93589. params: transaction.params,
  93590. callback: this.onData,
  93591. scope: this,
  93592. form: transaction.form,
  93593. isUpload: transaction.isUpload,
  93594. transaction: transaction
  93595. });
  93596. }
  93597. });
  93598. /**
  93599. * @private
  93600. */
  93601. Ext.define('Ext.draw.Matrix', {
  93602. /* Begin Definitions */
  93603. requires: ['Ext.draw.Draw'],
  93604. /* End Definitions */
  93605. constructor: function(a, b, c, d, e, f) {
  93606. if (a != null) {
  93607. this.matrix = [[a, c, e], [b, d, f], [0, 0, 1]];
  93608. }
  93609. else {
  93610. this.matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
  93611. }
  93612. },
  93613. add: function(a, b, c, d, e, f) {
  93614. var me = this,
  93615. out = [[], [], []],
  93616. matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
  93617. x,
  93618. y,
  93619. z,
  93620. res;
  93621. for (x = 0; x < 3; x++) {
  93622. for (y = 0; y < 3; y++) {
  93623. res = 0;
  93624. for (z = 0; z < 3; z++) {
  93625. res += me.matrix[x][z] * matrix[z][y];
  93626. }
  93627. out[x][y] = res;
  93628. }
  93629. }
  93630. me.matrix = out;
  93631. },
  93632. prepend: function(a, b, c, d, e, f) {
  93633. var me = this,
  93634. out = [[], [], []],
  93635. matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
  93636. x,
  93637. y,
  93638. z,
  93639. res;
  93640. for (x = 0; x < 3; x++) {
  93641. for (y = 0; y < 3; y++) {
  93642. res = 0;
  93643. for (z = 0; z < 3; z++) {
  93644. res += matrix[x][z] * me.matrix[z][y];
  93645. }
  93646. out[x][y] = res;
  93647. }
  93648. }
  93649. me.matrix = out;
  93650. },
  93651. invert: function() {
  93652. var matrix = this.matrix,
  93653. a = matrix[0][0],
  93654. b = matrix[1][0],
  93655. c = matrix[0][1],
  93656. d = matrix[1][1],
  93657. e = matrix[0][2],
  93658. f = matrix[1][2],
  93659. x = a * d - b * c;
  93660. return new Ext.draw.Matrix(d / x, -b / x, -c / x, a / x, (c * f - d * e) / x, (b * e - a * f) / x);
  93661. },
  93662. clone: function() {
  93663. var matrix = this.matrix,
  93664. a = matrix[0][0],
  93665. b = matrix[1][0],
  93666. c = matrix[0][1],
  93667. d = matrix[1][1],
  93668. e = matrix[0][2],
  93669. f = matrix[1][2];
  93670. return new Ext.draw.Matrix(a, b, c, d, e, f);
  93671. },
  93672. translate: function(x, y) {
  93673. this.prepend(1, 0, 0, 1, x, y);
  93674. },
  93675. scale: function(x, y, cx, cy) {
  93676. var me = this;
  93677. if (y == null) {
  93678. y = x;
  93679. }
  93680. me.add(x, 0, 0, y, cx * (1 - x), cy * (1 - y));
  93681. },
  93682. rotate: function(a, x, y) {
  93683. a = Ext.draw.Draw.rad(a);
  93684. var me = this,
  93685. cos = +Math.cos(a).toFixed(9),
  93686. sin = +Math.sin(a).toFixed(9);
  93687. me.add(cos, sin, -sin, cos, x - cos * x + sin * y, -(sin * x) + y - cos * y);
  93688. },
  93689. x: function(x, y) {
  93690. var matrix = this.matrix;
  93691. return x * matrix[0][0] + y * matrix[0][1] + matrix[0][2];
  93692. },
  93693. y: function(x, y) {
  93694. var matrix = this.matrix;
  93695. return x * matrix[1][0] + y * matrix[1][1] + matrix[1][2];
  93696. },
  93697. get: function(i, j) {
  93698. return + this.matrix[i][j].toFixed(4);
  93699. },
  93700. toString: function() {
  93701. var me = this;
  93702. return [me.get(0, 0), me.get(0, 1), me.get(1, 0), me.get(1, 1), 0, 0].join();
  93703. },
  93704. toSvg: function() {
  93705. var me = this;
  93706. return "matrix(" + [me.get(0, 0), me.get(1, 0), me.get(0, 1), me.get(1, 1), me.get(0, 2), me.get(1, 2)].join() + ")";
  93707. },
  93708. toFilter: function(dx, dy) {
  93709. var me = this;
  93710. dx = dx || 0;
  93711. dy = dy || 0;
  93712. return "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', filterType='bilinear', M11=" + me.get(0, 0) +
  93713. ", M12=" + me.get(0, 1) + ", M21=" + me.get(1, 0) + ", M22=" + me.get(1, 1) +
  93714. ", Dx=" + (me.get(0, 2) + dx) + ", Dy=" + (me.get(1, 2) + dy) + ")";
  93715. },
  93716. offset: function() {
  93717. var matrix = this.matrix;
  93718. return [(matrix[0][2] || 0).toFixed(4), (matrix[1][2] || 0).toFixed(4)];
  93719. },
  93720. // Split matrix into Translate Scale, Shear, and Rotate
  93721. split: function () {
  93722. function norm(a) {
  93723. return a[0] * a[0] + a[1] * a[1];
  93724. }
  93725. function normalize(a) {
  93726. var mag = Math.sqrt(norm(a));
  93727. a[0] /= mag;
  93728. a[1] /= mag;
  93729. }
  93730. var matrix = this.matrix,
  93731. out = {
  93732. translateX: matrix[0][2],
  93733. translateY: matrix[1][2]
  93734. },
  93735. row;
  93736. // scale and shear
  93737. row = [[matrix[0][0], matrix[0][1]], [matrix[1][1], matrix[1][1]]];
  93738. out.scaleX = Math.sqrt(norm(row[0]));
  93739. normalize(row[0]);
  93740. out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
  93741. row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
  93742. out.scaleY = Math.sqrt(norm(row[1]));
  93743. normalize(row[1]);
  93744. out.shear /= out.scaleY;
  93745. // rotation
  93746. out.rotate = Math.asin(-row[0][1]);
  93747. out.isSimple = !+out.shear.toFixed(9) && (out.scaleX.toFixed(9) == out.scaleY.toFixed(9) || !out.rotate);
  93748. return out;
  93749. }
  93750. });
  93751. /**
  93752. * DD implementation for Panels.
  93753. * @private
  93754. */
  93755. Ext.define('Ext.draw.SpriteDD', {
  93756. extend: 'Ext.dd.DragSource',
  93757. constructor : function(sprite, cfg){
  93758. var me = this,
  93759. el = sprite.el;
  93760. me.sprite = sprite;
  93761. me.el = el;
  93762. me.dragData = {el: el, sprite: sprite};
  93763. me.callParent([el, cfg]);
  93764. me.sprite.setStyle('cursor', 'move');
  93765. },
  93766. showFrame: Ext.emptyFn,
  93767. createFrame : Ext.emptyFn,
  93768. getDragEl : function(e){
  93769. return this.el;
  93770. },
  93771. getRegion: function() {
  93772. var me = this,
  93773. el = me.el,
  93774. pos, x1, x2, y1, y2, t, r, b, l, bbox, sprite;
  93775. sprite = me.sprite;
  93776. bbox = sprite.getBBox();
  93777. try {
  93778. pos = Ext.Element.getXY(el);
  93779. } catch (e) { }
  93780. if (!pos) {
  93781. return null;
  93782. }
  93783. x1 = pos[0];
  93784. x2 = x1 + bbox.width;
  93785. y1 = pos[1];
  93786. y2 = y1 + bbox.height;
  93787. return new Ext.util.Region(y1, x2, y2, x1);
  93788. },
  93789. /*
  93790. TODO(nico): Cumulative translations in VML are handled
  93791. differently than in SVG. While in SVG we specify the translation
  93792. relative to the original x, y position attributes, in VML the translation
  93793. is a delta between the last position of the object (modified by the last
  93794. translation) and the new one.
  93795. In VML the translation alters the position
  93796. of the object, we should change that or alter the SVG impl.
  93797. */
  93798. startDrag: function(x, y) {
  93799. var me = this,
  93800. attr = me.sprite.attr;
  93801. me.prev = me.sprite.surface.transformToViewBox(x, y);
  93802. },
  93803. onDrag: function(e) {
  93804. var xy = e.getXY(),
  93805. me = this,
  93806. sprite = me.sprite,
  93807. attr = sprite.attr, dx, dy;
  93808. xy = me.sprite.surface.transformToViewBox(xy[0], xy[1]);
  93809. dx = xy[0] - me.prev[0];
  93810. dy = xy[1] - me.prev[1];
  93811. sprite.setAttributes({
  93812. translate: {
  93813. x: attr.translation.x + dx,
  93814. y: attr.translation.y + dy
  93815. }
  93816. }, true);
  93817. me.prev = xy;
  93818. },
  93819. setDragElPos: function () {
  93820. // Disable automatic DOM move in DD that spoils layout of VML engine.
  93821. return false;
  93822. }
  93823. });
  93824. /**
  93825. * A Sprite is an object rendered in a Drawing surface.
  93826. *
  93827. * ## Types
  93828. *
  93829. * The following sprite types are supported:
  93830. *
  93831. * ### Rect
  93832. *
  93833. * Rectangle requires `width` and `height` attributes:
  93834. *
  93835. * @example
  93836. * Ext.create('Ext.draw.Component', {
  93837. * renderTo: Ext.getBody(),
  93838. * width: 200,
  93839. * height: 200,
  93840. * items: [{
  93841. * type: 'rect',
  93842. * width: 100,
  93843. * height: 50,
  93844. * radius: 10,
  93845. * fill: 'green',
  93846. * opacity: 0.5,
  93847. * stroke: 'red',
  93848. * 'stroke-width': 2
  93849. * }]
  93850. * });
  93851. *
  93852. * ### Circle
  93853. *
  93854. * Circle requires `x`, `y` and `radius` attributes:
  93855. *
  93856. * @example
  93857. * Ext.create('Ext.draw.Component', {
  93858. * renderTo: Ext.getBody(),
  93859. * width: 200,
  93860. * height: 200,
  93861. * items: [{
  93862. * type: 'circle',
  93863. * radius: 90,
  93864. * x: 100,
  93865. * y: 100,
  93866. * fill: 'blue',
  93867. * }]
  93868. * });
  93869. *
  93870. * ### Ellipse
  93871. *
  93872. * Ellipse requires `x`, `y`, `radiusX` and `radiusY` attributes:
  93873. *
  93874. * @example
  93875. * Ext.create('Ext.draw.Component', {
  93876. * renderTo: Ext.getBody(),
  93877. * width: 200,
  93878. * height: 200,
  93879. * items: [{
  93880. * type: "ellipse",
  93881. * radiusX: 100,
  93882. * radiusY: 50,
  93883. * x: 100,
  93884. * y: 100,
  93885. * fill: 'red'
  93886. * }]
  93887. * });
  93888. *
  93889. * ### Path
  93890. *
  93891. * Path requires the `path` attribute:
  93892. *
  93893. * @example
  93894. * Ext.create('Ext.draw.Component', {
  93895. * renderTo: Ext.getBody(),
  93896. * width: 200,
  93897. * height: 200,
  93898. * items: [{
  93899. * type: "path",
  93900. * path: "M-66.6 26C-66.6 26 -75 22 -78.2 18.4C-81.4 14.8 -80.948 19.966 " +
  93901. * "-85.8 19.6C-91.647 19.159 -90.6 3.2 -90.6 3.2L-94.6 10.8C-94.6 " +
  93902. * "10.8 -95.8 25.2 -87.8 22.8C-83.893 21.628 -82.6 23.2 -84.2 " +
  93903. * "24C-85.8 24.8 -78.6 25.2 -81.4 26.8C-84.2 28.4 -69.8 23.2 -72.2 " +
  93904. * "33.6L-66.6 26z",
  93905. * fill: "purple"
  93906. * }]
  93907. * });
  93908. *
  93909. * ### Text
  93910. *
  93911. * Text requires the `text` attribute:
  93912. *
  93913. * @example
  93914. * Ext.create('Ext.draw.Component', {
  93915. * renderTo: Ext.getBody(),
  93916. * width: 200,
  93917. * height: 200,
  93918. * items: [{
  93919. * type: "text",
  93920. * text: "Hello, Sprite!",
  93921. * fill: "green",
  93922. * font: "18px monospace"
  93923. * }]
  93924. * });
  93925. *
  93926. * ### Image
  93927. *
  93928. * Image requires `width`, `height` and `src` attributes:
  93929. *
  93930. * @example
  93931. * Ext.create('Ext.draw.Component', {
  93932. * renderTo: Ext.getBody(),
  93933. * width: 200,
  93934. * height: 200,
  93935. * items: [{
  93936. * type: "image",
  93937. * src: "http://www.sencha.com/img/apple-touch-icon.png",
  93938. * width: 200,
  93939. * height: 200
  93940. * }]
  93941. * });
  93942. *
  93943. * ## Creating and adding a Sprite to a Surface
  93944. *
  93945. * See {@link Ext.draw.Surface} documentation.
  93946. *
  93947. * ## Transforming sprites
  93948. *
  93949. * See {@link #setAttributes} method documentation for examples on how to translate, scale and rotate the sprites.
  93950. *
  93951. */
  93952. Ext.define('Ext.draw.Sprite', {
  93953. /* Begin Definitions */
  93954. mixins: {
  93955. observable: 'Ext.util.Observable',
  93956. animate: 'Ext.util.Animate'
  93957. },
  93958. requires: ['Ext.draw.SpriteDD'],
  93959. /* End Definitions */
  93960. /**
  93961. * @cfg {String} type The type of the sprite.
  93962. * Possible options are 'circle', 'ellipse', 'path', 'rect', 'text', 'image'.
  93963. *
  93964. * See {@link Ext.draw.Sprite} class documentation for examples of all types.
  93965. */
  93966. /**
  93967. * @cfg {Number} width The width of the rect or image sprite.
  93968. */
  93969. /**
  93970. * @cfg {Number} height The height of the rect or image sprite.
  93971. */
  93972. /**
  93973. * @cfg {Number} radius The radius of the circle sprite. Or in case of rect sprite, the border radius.
  93974. */
  93975. /**
  93976. * @cfg {Number} radiusX The radius of the ellipse sprite along x-axis.
  93977. */
  93978. /**
  93979. * @cfg {Number} radiusY The radius of the ellipse sprite along y-axis.
  93980. */
  93981. /**
  93982. * @cfg {Number} x Sprite position along the x-axis.
  93983. */
  93984. /**
  93985. * @cfg {Number} y Sprite position along the y-axis.
  93986. */
  93987. /**
  93988. * @cfg {String} path The path of the path sprite written in SVG-like path syntax.
  93989. */
  93990. /**
  93991. * @cfg {Number} opacity The opacity of the sprite. A number between 0 and 1.
  93992. */
  93993. /**
  93994. * @cfg {String} fill The fill color.
  93995. */
  93996. /**
  93997. * @cfg {String} stroke The stroke color.
  93998. */
  93999. /**
  94000. * @cfg {Number} stroke-width The width of the stroke.
  94001. *
  94002. * Note that this attribute needs to be quoted when used. Like so:
  94003. *
  94004. * "stroke-width": 12,
  94005. */
  94006. /**
  94007. * @cfg {String} font Used with text type sprites. The full font description.
  94008. * Uses the same syntax as the CSS font parameter
  94009. */
  94010. /**
  94011. * @cfg {String} text The actual text to render in text sprites.
  94012. */
  94013. /**
  94014. * @cfg {String} src Path to the image to show in image sprites.
  94015. */
  94016. /**
  94017. * @cfg {String/String[]} group The group that this sprite belongs to, or an array of groups.
  94018. * Only relevant when added to a {@link Ext.draw.Surface Surface}.
  94019. */
  94020. /**
  94021. * @cfg {Boolean} draggable True to make the sprite draggable.
  94022. */
  94023. dirty: false,
  94024. dirtyHidden: false,
  94025. dirtyTransform: false,
  94026. dirtyPath: true,
  94027. dirtyFont: true,
  94028. zIndexDirty: true,
  94029. /**
  94030. * @property {Boolean} isSprite
  94031. * `true` in this class to identify an object as an instantiated Sprite, or subclass thereof.
  94032. */
  94033. isSprite: true,
  94034. zIndex: 0,
  94035. fontProperties: [
  94036. 'font',
  94037. 'font-size',
  94038. 'font-weight',
  94039. 'font-style',
  94040. 'font-family',
  94041. 'text-anchor',
  94042. 'text'
  94043. ],
  94044. pathProperties: [
  94045. 'x',
  94046. 'y',
  94047. 'd',
  94048. 'path',
  94049. 'height',
  94050. 'width',
  94051. 'radius',
  94052. 'r',
  94053. 'rx',
  94054. 'ry',
  94055. 'cx',
  94056. 'cy'
  94057. ],
  94058. constructor: function(config) {
  94059. var me = this;
  94060. config = Ext.merge({}, config || {});
  94061. me.id = Ext.id(null, 'ext-sprite-');
  94062. me.transformations = [];
  94063. Ext.copyTo(this, config, 'surface,group,type,draggable');
  94064. //attribute bucket
  94065. me.bbox = {};
  94066. me.attr = {
  94067. zIndex: 0,
  94068. translation: {
  94069. x: null,
  94070. y: null
  94071. },
  94072. rotation: {
  94073. degrees: null,
  94074. x: null,
  94075. y: null
  94076. },
  94077. scaling: {
  94078. x: null,
  94079. y: null,
  94080. cx: null,
  94081. cy: null
  94082. }
  94083. };
  94084. //delete not bucket attributes
  94085. delete config.surface;
  94086. delete config.group;
  94087. delete config.type;
  94088. delete config.draggable;
  94089. me.setAttributes(config);
  94090. me.addEvents(
  94091. /**
  94092. * @event
  94093. * Fires before the sprite is destroyed. Return false from an event handler to stop the destroy.
  94094. * @param {Ext.draw.Sprite} this
  94095. */
  94096. 'beforedestroy',
  94097. /**
  94098. * @event
  94099. * Fires after the sprite is destroyed.
  94100. * @param {Ext.draw.Sprite} this
  94101. */
  94102. 'destroy',
  94103. /**
  94104. * @event
  94105. * Fires after the sprite markup is rendered.
  94106. * @param {Ext.draw.Sprite} this
  94107. */
  94108. 'render',
  94109. /**
  94110. * @event
  94111. * @inheritdoc Ext.dom.Element#mousedown
  94112. */
  94113. 'mousedown',
  94114. /**
  94115. * @event
  94116. * @inheritdoc Ext.dom.Element#mouseup
  94117. */
  94118. 'mouseup',
  94119. /**
  94120. * @event
  94121. * @inheritdoc Ext.dom.Element#mouseover
  94122. */
  94123. 'mouseover',
  94124. /**
  94125. * @event
  94126. * @inheritdoc Ext.dom.Element#mouseout
  94127. */
  94128. 'mouseout',
  94129. /**
  94130. * @event
  94131. * @inheritdoc Ext.dom.Element#mousemove
  94132. */
  94133. 'mousemove',
  94134. /**
  94135. * @event
  94136. * @inheritdoc Ext.dom.Element#click
  94137. */
  94138. 'click'
  94139. );
  94140. me.mixins.observable.constructor.apply(this, arguments);
  94141. },
  94142. /**
  94143. * @property {Ext.dd.DragSource} dd
  94144. * If this Sprite is configured {@link #draggable}, this property will contain
  94145. * an instance of {@link Ext.dd.DragSource} which handles dragging the Sprite.
  94146. *
  94147. * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
  94148. * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
  94149. */
  94150. initDraggable: function() {
  94151. var me = this;
  94152. me.draggable = true;
  94153. //create element if it doesn't exist.
  94154. if (!me.el) {
  94155. me.surface.createSpriteElement(me);
  94156. }
  94157. me.dd = new Ext.draw.SpriteDD(me, Ext.isBoolean(me.draggable) ? null : me.draggable);
  94158. me.on('beforedestroy', me.dd.destroy, me.dd);
  94159. },
  94160. /**
  94161. * Change the attributes of the sprite.
  94162. *
  94163. * ## Translation
  94164. *
  94165. * For translate, the configuration object contains x and y attributes that indicate where to
  94166. * translate the object. For example:
  94167. *
  94168. * sprite.setAttributes({
  94169. * translate: {
  94170. * x: 10,
  94171. * y: 10
  94172. * }
  94173. * }, true);
  94174. *
  94175. *
  94176. * ## Rotation
  94177. *
  94178. * For rotation, the configuration object contains x and y attributes for the center of the rotation (which are optional),
  94179. * and a `degrees` attribute that specifies the rotation in degrees. For example:
  94180. *
  94181. * sprite.setAttributes({
  94182. * rotate: {
  94183. * degrees: 90
  94184. * }
  94185. * }, true);
  94186. *
  94187. * That example will create a 90 degrees rotation using the centroid of the Sprite as center of rotation, whereas:
  94188. *
  94189. * sprite.setAttributes({
  94190. * rotate: {
  94191. * x: 0,
  94192. * y: 0,
  94193. * degrees: 90
  94194. * }
  94195. * }, true);
  94196. *
  94197. * will create a rotation around the `(0, 0)` axis.
  94198. *
  94199. *
  94200. * ## Scaling
  94201. *
  94202. * For scaling, the configuration object contains x and y attributes for the x-axis and y-axis scaling. For example:
  94203. *
  94204. * sprite.setAttributes({
  94205. * scale: {
  94206. * x: 10,
  94207. * y: 3
  94208. * }
  94209. * }, true);
  94210. *
  94211. * You can also specify the center of scaling by adding `cx` and `cy` as properties:
  94212. *
  94213. * sprite.setAttributes({
  94214. * scale: {
  94215. * cx: 0,
  94216. * cy: 0,
  94217. * x: 10,
  94218. * y: 3
  94219. * }
  94220. * }, true);
  94221. *
  94222. * That last example will scale a sprite taking as centers of scaling the `(0, 0)` coordinate.
  94223. *
  94224. * @param {Object} attrs attributes to be changed on the sprite.
  94225. * @param {Boolean} redraw Flag to immediately draw the change.
  94226. * @return {Ext.draw.Sprite} this
  94227. */
  94228. setAttributes: function(attrs, redraw) {
  94229. var me = this,
  94230. fontProps = me.fontProperties,
  94231. fontPropsLength = fontProps.length,
  94232. pathProps = me.pathProperties,
  94233. pathPropsLength = pathProps.length,
  94234. hasSurface = !!me.surface,
  94235. custom = hasSurface && me.surface.customAttributes || {},
  94236. spriteAttrs = me.attr,
  94237. dirtyBBox = false,
  94238. attr, i, newTranslation, translation, newRotate, rotation, newScaling, scaling;
  94239. attrs = Ext.apply({}, attrs);
  94240. for (attr in custom) {
  94241. if (attrs.hasOwnProperty(attr) && typeof custom[attr] == "function") {
  94242. Ext.apply(attrs, custom[attr].apply(me, [].concat(attrs[attr])));
  94243. }
  94244. }
  94245. // Flag a change in hidden
  94246. if (!!attrs.hidden !== !!spriteAttrs.hidden) {
  94247. me.dirtyHidden = true;
  94248. }
  94249. // Flag path change
  94250. for (i = 0; i < pathPropsLength; i++) {
  94251. attr = pathProps[i];
  94252. if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
  94253. me.dirtyPath = true;
  94254. dirtyBBox = true;
  94255. break;
  94256. }
  94257. }
  94258. // Flag zIndex change
  94259. if ('zIndex' in attrs) {
  94260. me.zIndexDirty = true;
  94261. }
  94262. // Flag font/text change
  94263. if ('text' in attrs) {
  94264. me.dirtyFont = true;
  94265. dirtyBBox = true;
  94266. }
  94267. for (i = 0; i < fontPropsLength; i++) {
  94268. attr = fontProps[i];
  94269. if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
  94270. me.dirtyFont = true;
  94271. dirtyBBox = true;
  94272. break;
  94273. }
  94274. }
  94275. newTranslation = attrs.translation || attrs.translate;
  94276. delete attrs.translate;
  94277. delete attrs.translation;
  94278. translation = spriteAttrs.translation;
  94279. if (newTranslation) {
  94280. if (('x' in newTranslation && newTranslation.x !== translation.x) ||
  94281. ('y' in newTranslation && newTranslation.y !== translation.y)) {
  94282. me.dirtyTransform = true;
  94283. translation.x = newTranslation.x;
  94284. translation.y = newTranslation.y;
  94285. }
  94286. }
  94287. newRotate = attrs.rotation || attrs.rotate;
  94288. rotation = spriteAttrs.rotation;
  94289. delete attrs.rotate;
  94290. delete attrs.rotation;
  94291. if (newRotate) {
  94292. if (('x' in newRotate && newRotate.x !== rotation.x) ||
  94293. ('y' in newRotate && newRotate.y !== rotation.y) ||
  94294. ('degrees' in newRotate && newRotate.degrees !== rotation.degrees)) {
  94295. me.dirtyTransform = true;
  94296. rotation.x = newRotate.x;
  94297. rotation.y = newRotate.y;
  94298. rotation.degrees = newRotate.degrees;
  94299. }
  94300. }
  94301. newScaling = attrs.scaling || attrs.scale;
  94302. scaling = spriteAttrs.scaling;
  94303. delete attrs.scale;
  94304. delete attrs.scaling;
  94305. if (newScaling) {
  94306. if (('x' in newScaling && newScaling.x !== scaling.x) ||
  94307. ('y' in newScaling && newScaling.y !== scaling.y) ||
  94308. ('cx' in newScaling && newScaling.cx !== scaling.cx) ||
  94309. ('cy' in newScaling && newScaling.cy !== scaling.cy)) {
  94310. me.dirtyTransform = true;
  94311. scaling.x = newScaling.x;
  94312. scaling.y = newScaling.y;
  94313. scaling.cx = newScaling.cx;
  94314. scaling.cy = newScaling.cy;
  94315. }
  94316. }
  94317. // If the bbox is changed, then the bbox based transforms should be invalidated.
  94318. if (!me.dirtyTransform && dirtyBBox) {
  94319. if (spriteAttrs.scaling.x === null ||
  94320. spriteAttrs.scaling.y === null ||
  94321. spriteAttrs.rotation.y === null ||
  94322. spriteAttrs.rotation.y === null) {
  94323. me.dirtyTransform = true;
  94324. }
  94325. }
  94326. Ext.apply(spriteAttrs, attrs);
  94327. me.dirty = true;
  94328. if (redraw === true && hasSurface) {
  94329. me.redraw();
  94330. }
  94331. return this;
  94332. },
  94333. /**
  94334. * Retrieves the bounding box of the sprite.
  94335. * This will be returned as an object with x, y, width, and height properties.
  94336. * @return {Object} bbox
  94337. */
  94338. getBBox: function() {
  94339. return this.surface.getBBox(this);
  94340. },
  94341. setText: function(text) {
  94342. return this.surface.setText(this, text);
  94343. },
  94344. /**
  94345. * Hides the sprite.
  94346. * @param {Boolean} redraw Flag to immediately draw the change.
  94347. * @return {Ext.draw.Sprite} this
  94348. */
  94349. hide: function(redraw) {
  94350. this.setAttributes({
  94351. hidden: true
  94352. }, redraw);
  94353. return this;
  94354. },
  94355. /**
  94356. * Shows the sprite.
  94357. * @param {Boolean} redraw Flag to immediately draw the change.
  94358. * @return {Ext.draw.Sprite} this
  94359. */
  94360. show: function(redraw) {
  94361. this.setAttributes({
  94362. hidden: false
  94363. }, redraw);
  94364. return this;
  94365. },
  94366. /**
  94367. * Removes the sprite.
  94368. * @return {Boolean} True if sprite was successfully removed.
  94369. * False when there was no surface to remove it from.
  94370. */
  94371. remove: function() {
  94372. if (this.surface) {
  94373. this.surface.remove(this);
  94374. return true;
  94375. }
  94376. return false;
  94377. },
  94378. onRemove: function() {
  94379. this.surface.onRemove(this);
  94380. },
  94381. /**
  94382. * Removes the sprite and clears all listeners.
  94383. */
  94384. destroy: function() {
  94385. var me = this;
  94386. if (me.fireEvent('beforedestroy', me) !== false) {
  94387. me.remove();
  94388. me.surface.onDestroy(me);
  94389. me.clearListeners();
  94390. me.fireEvent('destroy');
  94391. }
  94392. },
  94393. /**
  94394. * Redraws the sprite.
  94395. * @return {Ext.draw.Sprite} this
  94396. */
  94397. redraw: function() {
  94398. this.surface.renderItem(this);
  94399. return this;
  94400. },
  94401. /**
  94402. * Wrapper for setting style properties, also takes single object parameter of multiple styles.
  94403. * @param {String/Object} property The style property to be set, or an object of multiple styles.
  94404. * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
  94405. * @return {Ext.draw.Sprite} this
  94406. */
  94407. setStyle: function() {
  94408. this.el.setStyle.apply(this.el, arguments);
  94409. return this;
  94410. },
  94411. /**
  94412. * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out. Note this method
  94413. * is severly limited in VML.
  94414. * @param {String/String[]} className The CSS class to add, or an array of classes
  94415. * @return {Ext.draw.Sprite} this
  94416. */
  94417. addCls: function(obj) {
  94418. this.surface.addCls(this, obj);
  94419. return this;
  94420. },
  94421. /**
  94422. * Removes one or more CSS classes from the element.
  94423. * @param {String/String[]} className The CSS class to remove, or an array of classes. Note this method
  94424. * is severly limited in VML.
  94425. * @return {Ext.draw.Sprite} this
  94426. */
  94427. removeCls: function(obj) {
  94428. this.surface.removeCls(this, obj);
  94429. return this;
  94430. }
  94431. });
  94432. /**
  94433. * This class encapsulates a drawn text item as rendered by the Ext.draw package within a Component which can be
  94434. * then used anywhere in an ExtJS application just like any other Component.
  94435. *
  94436. * ## Example usage
  94437. *
  94438. * @example
  94439. * Ext.create('Ext.panel.Panel', {
  94440. * title: 'Panel with VerticalTextItem',
  94441. * width: 300,
  94442. * height: 200,
  94443. * lbar: {
  94444. * layout: {
  94445. * align: 'center'
  94446. * },
  94447. * items: [{
  94448. * xtype: 'text',
  94449. * text: 'Sample VerticalTextItem',
  94450. * degrees: 90
  94451. * }]
  94452. * },
  94453. * renderTo: Ext.getBody()
  94454. * });
  94455. *
  94456. * @constructor
  94457. * Creates a new Text Component
  94458. * @param {Object} text A config object containing a `text` property, a `degrees` property,
  94459. * and, optionally, a `styleSelector` property which specifies a selector which provides CSS rules to
  94460. * give font family, size and color to the drawn text.
  94461. */
  94462. Ext.define('Ext.draw.Text', {
  94463. extend: 'Ext.draw.Component',
  94464. uses: ['Ext.util.CSS'],
  94465. alias: 'widget.text',
  94466. /**
  94467. * @cfg {String} text
  94468. * The text to display (html tags are <b>not</b> accepted)
  94469. */
  94470. text: '',
  94471. /**
  94472. * @cfg {String} styleSelector
  94473. * A CSS selector string which matches a style rule in the document stylesheet from which
  94474. * the text's font properties are read.
  94475. *
  94476. * **Drawn** text is not styled by CSS, but by properties set during its construction, so these styles
  94477. * must be programatically read from a stylesheet rule found via a selector at construction time.
  94478. */
  94479. /**
  94480. * @cfg {Number} degrees
  94481. * The angle by which to initially rotate the text clockwise. Defaults to zero.
  94482. */
  94483. focusable: false,
  94484. viewBox: false,
  94485. autoSize: true,
  94486. baseCls: Ext.baseCSSPrefix + 'surface ' + Ext.baseCSSPrefix + 'draw-text',
  94487. initComponent: function() {
  94488. var me = this;
  94489. me.textConfig = Ext.apply({
  94490. type: 'text',
  94491. text: me.text,
  94492. rotate: {
  94493. degrees: me.degrees || 0
  94494. }
  94495. }, me.textStyle);
  94496. Ext.apply(me.textConfig, me.getStyles(me.styleSelectors || me.styleSelector));
  94497. // Surface is created from the *initialConfig*, not the current object state,
  94498. // So the generated items must go into the initialConfig
  94499. me.initialConfig.items = [me.textConfig];
  94500. me.callParent(arguments);
  94501. },
  94502. /**
  94503. * @private
  94504. * Accumulates a style object based upon the styles specified in document stylesheets
  94505. * by an array of CSS selectors
  94506. */
  94507. getStyles: function(selectors) {
  94508. selectors = Ext.Array.from(selectors);
  94509. var i = 0,
  94510. len = selectors.length,
  94511. rule,
  94512. style,
  94513. prop,
  94514. result = {};
  94515. for (; i < len; i++) {
  94516. // Get the style rule which exactly matches the selector.
  94517. rule = Ext.util.CSS.getRule(selectors[i]);
  94518. if (rule) {
  94519. style = rule.style;
  94520. if (style) {
  94521. Ext.apply(result, {
  94522. 'font-family': style.fontFamily,
  94523. 'font-weight': style.fontWeight,
  94524. 'line-height': style.lineHeight,
  94525. 'font-size': style.fontSize,
  94526. fill: style.color
  94527. });
  94528. }
  94529. }
  94530. }
  94531. return result;
  94532. },
  94533. /**
  94534. * Sets the clockwise rotation angle relative to the horizontal axis.
  94535. * @param {Number} degrees The clockwise angle (in degrees) from the horizontal axis
  94536. * by which the text should be rotated.
  94537. */
  94538. setAngle: function(degrees) {
  94539. var me = this,
  94540. surface,
  94541. sprite;
  94542. if (me.rendered) {
  94543. surface = me.surface;
  94544. sprite = surface.items.items[0];
  94545. me.degrees = degrees;
  94546. sprite.setAttributes({
  94547. rotate: {
  94548. degrees: degrees
  94549. }
  94550. }, true);
  94551. if (me.autoSize || me.viewBox) {
  94552. me.updateLayout();
  94553. }
  94554. } else {
  94555. me.degrees = degrees;
  94556. }
  94557. },
  94558. /**
  94559. * Updates this item's text.
  94560. * @param {String} t The text to display (html **not** accepted).
  94561. */
  94562. setText: function(text) {
  94563. var me = this,
  94564. surface,
  94565. sprite;
  94566. if (me.rendered) {
  94567. surface = me.surface;
  94568. sprite = surface.items.items[0];
  94569. me.text = text || '';
  94570. surface.remove(sprite);
  94571. me.textConfig.type = 'text';
  94572. me.textConfig.text = me.text;
  94573. sprite = surface.add(me.textConfig);
  94574. sprite.setAttributes({
  94575. rotate: {
  94576. degrees: me.degrees
  94577. }
  94578. }, true);
  94579. if (me.autoSize || me.viewBox) {
  94580. me.updateLayout();
  94581. }
  94582. } else {
  94583. me.on({
  94584. render: function() {
  94585. me.setText(text);
  94586. },
  94587. single: true
  94588. });
  94589. }
  94590. }
  94591. });
  94592. /**
  94593. * Exports a {@link Ext.draw.Surface Surface} to an image. To do this,
  94594. * the svg string must be sent to a remote server and processed.
  94595. *
  94596. * # Sending the data
  94597. *
  94598. * A post request is made to the URL. The following fields are sent:
  94599. *
  94600. * + width: The width of the image
  94601. * + height: The height of the image
  94602. * + type: The image type to save as, see {@link #supportedTypes}
  94603. * + svg: The svg string for the surface
  94604. *
  94605. * # The response
  94606. *
  94607. * It is expected that the user will be prompted with an image download.
  94608. * As such, the following options should be set on the server:
  94609. *
  94610. * + Content-Disposition: 'attachment, filename="chart.png"'
  94611. * + Content-Type: 'image/png'
  94612. *
  94613. * **Important**: By default, chart data is sent to a server operated
  94614. * by Sencha to do data processing. You may change this default by
  94615. * setting the {@link #defaultUrl} of this class.
  94616. */
  94617. Ext.define('Ext.draw.engine.ImageExporter', {
  94618. singleton: true,
  94619. /**
  94620. * @property {String} [defaultUrl="http://svg.sencha.io"]
  94621. * The default URL to submit the form request.
  94622. */
  94623. defaultUrl: 'http://svg.sencha.io',
  94624. /**
  94625. * @property {Array} [supportedTypes=["image/png", "image/jpeg"]]
  94626. * A list of export types supported by the server
  94627. */
  94628. supportedTypes: ['image/png', 'image/jpeg'],
  94629. /**
  94630. * @property {String} [widthParam="width"]
  94631. * The name of the width parameter to be sent to the server.
  94632. * The Sencha IO server expects it to be the default value.
  94633. */
  94634. widthParam: 'width',
  94635. /**
  94636. * @property {String} [heightParam="height"]
  94637. * The name of the height parameter to be sent to the server.
  94638. * The Sencha IO server expects it to be the default value.
  94639. */
  94640. heightParam: 'height',
  94641. /**
  94642. * @property {String} [typeParam="type"]
  94643. * The name of the type parameter to be sent to the server.
  94644. * The Sencha IO server expects it to be the default value.
  94645. */
  94646. typeParam: 'type',
  94647. /**
  94648. * @property {String} [svgParam="svg"]
  94649. * The name of the svg parameter to be sent to the server.
  94650. * The Sencha IO server expects it to be the default value.
  94651. */
  94652. svgParam: 'svg',
  94653. formCls: Ext.baseCSSPrefix + 'hide-display',
  94654. /**
  94655. * Exports the surface to an image
  94656. * @param {Ext.draw.Surface} surface The surface to export
  94657. * @param {Object} [config] The following config options are supported:
  94658. *
  94659. * @param {Number} config.width A width to send to the server to for
  94660. * configuring the image height
  94661. *
  94662. * @param {Number} config.height A height to send to the server for
  94663. * configuring the image height
  94664. *
  94665. * @param {String} config.url The url to post the data to. Defaults to
  94666. * the {@link #defaultUrl} configuration on the class.
  94667. *
  94668. * @param {String} config.type The type of image to export. See the
  94669. * {@link #supportedTypes}
  94670. *
  94671. * @param {String} config.widthParam The name of the width parameter to send
  94672. * to the server. Defaults to {@link #widthParam}
  94673. *
  94674. * @param {String} config.heightParam The name of the height parameter to send
  94675. * to the server. Defaults to {@link #heightParam}
  94676. *
  94677. * @param {String} config.typeParam The name of the type parameter to send
  94678. * to the server. Defaults to {@link #typeParam}
  94679. *
  94680. * @param {String} config.svgParam The name of the svg parameter to send
  94681. * to the server. Defaults to {@link #svgParam}
  94682. *
  94683. * @return {Boolean} True if the surface was successfully sent to the server.
  94684. */
  94685. generate: function(surface, config) {
  94686. config = config || {};
  94687. var me = this,
  94688. type = config.type,
  94689. form;
  94690. if (Ext.Array.indexOf(me.supportedTypes, type) === -1) {
  94691. return false;
  94692. }
  94693. form = Ext.getBody().createChild({
  94694. tag: 'form',
  94695. method: 'POST',
  94696. action: config.url || me.defaultUrl,
  94697. cls: me.formCls,
  94698. children: [{
  94699. tag: 'input',
  94700. type: 'hidden',
  94701. name: config.widthParam || me.widthParam,
  94702. value: config.width || surface.width
  94703. }, {
  94704. tag: 'input',
  94705. type: 'hidden',
  94706. name: config.heightParam || me.heightParam,
  94707. value: config.height || surface.height
  94708. }, {
  94709. tag: 'input',
  94710. type: 'hidden',
  94711. name: config.typeParam || me.typeParam,
  94712. value: type
  94713. }, {
  94714. tag: 'input',
  94715. type: 'hidden',
  94716. name: config.svgParam || me.svgParam
  94717. }]
  94718. });
  94719. // Assign the data on the value so it doesn't get messed up in the html insertion
  94720. form.last(null, true).value = Ext.draw.engine.SvgExporter.generate(surface);
  94721. form.dom.submit();
  94722. form.remove();
  94723. return true;
  94724. }
  94725. });
  94726. /**
  94727. * Provides specific methods to draw with SVG.
  94728. */
  94729. Ext.define('Ext.draw.engine.Svg', {
  94730. /* Begin Definitions */
  94731. extend: 'Ext.draw.Surface',
  94732. requires: ['Ext.draw.Draw', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
  94733. /* End Definitions */
  94734. engine: 'Svg',
  94735. trimRe: /^\s+|\s+$/g,
  94736. spacesRe: /\s+/,
  94737. xlink: "http:/" + "/www.w3.org/1999/xlink",
  94738. translateAttrs: {
  94739. radius: "r",
  94740. radiusX: "rx",
  94741. radiusY: "ry",
  94742. path: "d",
  94743. lineWidth: "stroke-width",
  94744. fillOpacity: "fill-opacity",
  94745. strokeOpacity: "stroke-opacity",
  94746. strokeLinejoin: "stroke-linejoin"
  94747. },
  94748. parsers: {},
  94749. minDefaults: {
  94750. circle: {
  94751. cx: 0,
  94752. cy: 0,
  94753. r: 0,
  94754. fill: "none",
  94755. stroke: null,
  94756. "stroke-width": null,
  94757. opacity: null,
  94758. "fill-opacity": null,
  94759. "stroke-opacity": null
  94760. },
  94761. ellipse: {
  94762. cx: 0,
  94763. cy: 0,
  94764. rx: 0,
  94765. ry: 0,
  94766. fill: "none",
  94767. stroke: null,
  94768. "stroke-width": null,
  94769. opacity: null,
  94770. "fill-opacity": null,
  94771. "stroke-opacity": null
  94772. },
  94773. rect: {
  94774. x: 0,
  94775. y: 0,
  94776. width: 0,
  94777. height: 0,
  94778. rx: 0,
  94779. ry: 0,
  94780. fill: "none",
  94781. stroke: null,
  94782. "stroke-width": null,
  94783. opacity: null,
  94784. "fill-opacity": null,
  94785. "stroke-opacity": null
  94786. },
  94787. text: {
  94788. x: 0,
  94789. y: 0,
  94790. "text-anchor": "start",
  94791. "font-family": null,
  94792. "font-size": null,
  94793. "font-weight": null,
  94794. "font-style": null,
  94795. fill: "#000",
  94796. stroke: null,
  94797. "stroke-width": null,
  94798. opacity: null,
  94799. "fill-opacity": null,
  94800. "stroke-opacity": null
  94801. },
  94802. path: {
  94803. d: "M0,0",
  94804. fill: "none",
  94805. stroke: null,
  94806. "stroke-width": null,
  94807. opacity: null,
  94808. "fill-opacity": null,
  94809. "stroke-opacity": null
  94810. },
  94811. image: {
  94812. x: 0,
  94813. y: 0,
  94814. width: 0,
  94815. height: 0,
  94816. preserveAspectRatio: "none",
  94817. opacity: null
  94818. }
  94819. },
  94820. createSvgElement: function(type, attrs) {
  94821. var el = this.domRef.createElementNS("http:/" + "/www.w3.org/2000/svg", type),
  94822. key;
  94823. if (attrs) {
  94824. for (key in attrs) {
  94825. el.setAttribute(key, String(attrs[key]));
  94826. }
  94827. }
  94828. return el;
  94829. },
  94830. createSpriteElement: function(sprite) {
  94831. // Create svg element and append to the DOM.
  94832. var el = this.createSvgElement(sprite.type);
  94833. el.id = sprite.id;
  94834. if (el.style) {
  94835. el.style.webkitTapHighlightColor = "rgba(0,0,0,0)";
  94836. }
  94837. sprite.el = Ext.get(el);
  94838. this.applyZIndex(sprite); //performs the insertion
  94839. sprite.matrix = new Ext.draw.Matrix();
  94840. sprite.bbox = {
  94841. plain: 0,
  94842. transform: 0
  94843. };
  94844. this.applyAttrs(sprite);
  94845. this.applyTransformations(sprite);
  94846. sprite.fireEvent("render", sprite);
  94847. return el;
  94848. },
  94849. getBBoxText: function (sprite) {
  94850. var bbox = {},
  94851. bb, height, width, i, ln, el;
  94852. if (sprite && sprite.el) {
  94853. el = sprite.el.dom;
  94854. try {
  94855. bbox = el.getBBox();
  94856. return bbox;
  94857. } catch(e) {
  94858. // Firefox 3.0.x plays badly here
  94859. }
  94860. bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
  94861. ln = el.getNumberOfChars();
  94862. for (i = 0; i < ln; i++) {
  94863. bb = el.getExtentOfChar(i);
  94864. bbox.y = Math.min(bb.y, bbox.y);
  94865. height = bb.y + bb.height - bbox.y;
  94866. bbox.height = Math.max(bbox.height, height);
  94867. width = bb.x + bb.width - bbox.x;
  94868. bbox.width = Math.max(bbox.width, width);
  94869. }
  94870. return bbox;
  94871. }
  94872. },
  94873. hide: function() {
  94874. Ext.get(this.el).hide();
  94875. },
  94876. show: function() {
  94877. Ext.get(this.el).show();
  94878. },
  94879. hidePrim: function(sprite) {
  94880. this.addCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
  94881. },
  94882. showPrim: function(sprite) {
  94883. this.removeCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
  94884. },
  94885. getDefs: function() {
  94886. return this._defs || (this._defs = this.createSvgElement("defs"));
  94887. },
  94888. transform: function(sprite, matrixOnly) {
  94889. var me = this,
  94890. matrix = new Ext.draw.Matrix(),
  94891. transforms = sprite.transformations,
  94892. transformsLength = transforms.length,
  94893. i = 0,
  94894. transform, type;
  94895. for (; i < transformsLength; i++) {
  94896. transform = transforms[i];
  94897. type = transform.type;
  94898. if (type == "translate") {
  94899. matrix.translate(transform.x, transform.y);
  94900. }
  94901. else if (type == "rotate") {
  94902. matrix.rotate(transform.degrees, transform.x, transform.y);
  94903. }
  94904. else if (type == "scale") {
  94905. matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
  94906. }
  94907. }
  94908. sprite.matrix = matrix;
  94909. if (!matrixOnly) {
  94910. sprite.el.set({transform: matrix.toSvg()});
  94911. }
  94912. },
  94913. setSize: function(width, height) {
  94914. var me = this,
  94915. el = me.el;
  94916. width = +width || me.width;
  94917. height = +height || me.height;
  94918. me.width = width;
  94919. me.height = height;
  94920. el.setSize(width, height);
  94921. el.set({
  94922. width: width,
  94923. height: height
  94924. });
  94925. me.callParent([width, height]);
  94926. },
  94927. /**
  94928. * Get the region for the surface's canvas area
  94929. * @returns {Ext.util.Region}
  94930. */
  94931. getRegion: function() {
  94932. // Mozilla requires using the background rect because the svg element returns an
  94933. // incorrect region. Webkit gives no region for the rect and must use the svg element.
  94934. var svgXY = this.el.getXY(),
  94935. rectXY = this.bgRect.getXY(),
  94936. max = Math.max,
  94937. x = max(svgXY[0], rectXY[0]),
  94938. y = max(svgXY[1], rectXY[1]);
  94939. return {
  94940. left: x,
  94941. top: y,
  94942. right: x + this.width,
  94943. bottom: y + this.height
  94944. };
  94945. },
  94946. onRemove: function(sprite) {
  94947. if (sprite.el) {
  94948. sprite.el.destroy();
  94949. delete sprite.el;
  94950. }
  94951. this.callParent(arguments);
  94952. },
  94953. setViewBox: function(x, y, width, height) {
  94954. if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
  94955. this.callParent(arguments);
  94956. this.el.dom.setAttribute("viewBox", [x, y, width, height].join(" "));
  94957. }
  94958. },
  94959. render: function (container) {
  94960. var me = this,
  94961. width,
  94962. height,
  94963. el,
  94964. defs,
  94965. bgRect,
  94966. webkitRect;
  94967. if (!me.el) {
  94968. width = me.width || 0;
  94969. height = me.height || 0;
  94970. el = me.createSvgElement('svg', {
  94971. xmlns: "http:/" + "/www.w3.org/2000/svg",
  94972. version: 1.1,
  94973. width: width,
  94974. height: height
  94975. });
  94976. defs = me.getDefs();
  94977. // Create a rect that is always the same size as the svg root; this serves 2 purposes:
  94978. // (1) It allows mouse events to be fired over empty areas in Webkit, and (2) we can
  94979. // use it rather than the svg element for retrieving the correct client rect of the
  94980. // surface in Mozilla (see https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
  94981. bgRect = me.createSvgElement("rect", {
  94982. width: "100%",
  94983. height: "100%",
  94984. fill: "#000",
  94985. stroke: "none",
  94986. opacity: 0
  94987. });
  94988. if (Ext.isSafari3) {
  94989. // Rect that we will show/hide to fix old WebKit bug with rendering issues.
  94990. webkitRect = me.createSvgElement("rect", {
  94991. x: -10,
  94992. y: -10,
  94993. width: "110%",
  94994. height: "110%",
  94995. fill: "none",
  94996. stroke: "#000"
  94997. });
  94998. }
  94999. el.appendChild(defs);
  95000. if (Ext.isSafari3) {
  95001. el.appendChild(webkitRect);
  95002. }
  95003. el.appendChild(bgRect);
  95004. container.appendChild(el);
  95005. me.el = Ext.get(el);
  95006. me.bgRect = Ext.get(bgRect);
  95007. if (Ext.isSafari3) {
  95008. me.webkitRect = Ext.get(webkitRect);
  95009. me.webkitRect.hide();
  95010. }
  95011. me.el.on({
  95012. scope: me,
  95013. mouseup: me.onMouseUp,
  95014. mousedown: me.onMouseDown,
  95015. mouseover: me.onMouseOver,
  95016. mouseout: me.onMouseOut,
  95017. mousemove: me.onMouseMove,
  95018. mouseenter: me.onMouseEnter,
  95019. mouseleave: me.onMouseLeave,
  95020. click: me.onClick,
  95021. dblclick: me.onDblClick
  95022. });
  95023. }
  95024. me.renderAll();
  95025. },
  95026. // private
  95027. onMouseEnter: function(e) {
  95028. if (this.el.parent().getRegion().contains(e.getPoint())) {
  95029. this.fireEvent('mouseenter', e);
  95030. }
  95031. },
  95032. // private
  95033. onMouseLeave: function(e) {
  95034. if (!this.el.parent().getRegion().contains(e.getPoint())) {
  95035. this.fireEvent('mouseleave', e);
  95036. }
  95037. },
  95038. // @private - Normalize a delegated single event from the main container to each sprite and sprite group
  95039. processEvent: function(name, e) {
  95040. var target = e.getTarget(),
  95041. surface = this.surface,
  95042. sprite;
  95043. this.fireEvent(name, e);
  95044. // We wrap text types in a tspan, sprite is the parent.
  95045. if (target.nodeName == "tspan" && target.parentNode) {
  95046. target = target.parentNode;
  95047. }
  95048. sprite = this.items.get(target.id);
  95049. if (sprite) {
  95050. sprite.fireEvent(name, sprite, e);
  95051. }
  95052. },
  95053. /* @private - Wrap SVG text inside a tspan to allow for line wrapping. In addition this normallizes
  95054. * the baseline for text the vertical middle of the text to be the same as VML.
  95055. */
  95056. tuneText: function (sprite, attrs) {
  95057. var el = sprite.el.dom,
  95058. tspans = [],
  95059. height, tspan, text, i, ln, texts, factor, x;
  95060. if (attrs.hasOwnProperty("text")) {
  95061. //only create new tspans for text lines if the text has been
  95062. //updated or if it's the first time we're setting the text
  95063. //into the sprite.
  95064. //get the actual rendered text.
  95065. text = sprite.tspans && Ext.Array.map(sprite.tspans, function(t) { return t.textContent; }).join('');
  95066. if (!sprite.tspans || attrs.text != text) {
  95067. tspans = this.setText(sprite, attrs.text);
  95068. sprite.tspans = tspans;
  95069. //for all other cases reuse the tspans previously created.
  95070. } else {
  95071. tspans = sprite.tspans || [];
  95072. }
  95073. }
  95074. // Normalize baseline via a DY shift of first tspan. Shift other rows by height * line height (1.2)
  95075. if (tspans.length) {
  95076. height = this.getBBoxText(sprite).height;
  95077. x = sprite.el.dom.getAttribute("x");
  95078. for (i = 0, ln = tspans.length; i < ln; i++) {
  95079. // The text baseline for FireFox 3.0 and 3.5 is different than other SVG implementations
  95080. // so we are going to normalize that here
  95081. factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4;
  95082. tspans[i].setAttribute("x", x);
  95083. tspans[i].setAttribute("dy", i ? height * 1.2 : height / factor);
  95084. }
  95085. sprite.dirty = true;
  95086. }
  95087. },
  95088. setText: function(sprite, textString) {
  95089. var me = this,
  95090. el = sprite.el.dom,
  95091. tspans = [],
  95092. height, tspan, text, i, ln, texts;
  95093. while (el.firstChild) {
  95094. el.removeChild(el.firstChild);
  95095. }
  95096. // Wrap each row into tspan to emulate rows
  95097. texts = String(textString).split("\n");
  95098. for (i = 0, ln = texts.length; i < ln; i++) {
  95099. text = texts[i];
  95100. if (text) {
  95101. tspan = me.createSvgElement("tspan");
  95102. tspan.appendChild(document.createTextNode(Ext.htmlDecode(text)));
  95103. el.appendChild(tspan);
  95104. tspans[i] = tspan;
  95105. }
  95106. }
  95107. return tspans;
  95108. },
  95109. renderAll: function() {
  95110. this.items.each(this.renderItem, this);
  95111. },
  95112. renderItem: function (sprite) {
  95113. if (!this.el) {
  95114. return;
  95115. }
  95116. if (!sprite.el) {
  95117. this.createSpriteElement(sprite);
  95118. }
  95119. if (sprite.zIndexDirty) {
  95120. this.applyZIndex(sprite);
  95121. }
  95122. if (sprite.dirty) {
  95123. this.applyAttrs(sprite);
  95124. if (sprite.dirtyTransform) {
  95125. this.applyTransformations(sprite);
  95126. }
  95127. }
  95128. },
  95129. redraw: function(sprite) {
  95130. sprite.dirty = sprite.zIndexDirty = true;
  95131. this.renderItem(sprite);
  95132. },
  95133. applyAttrs: function (sprite) {
  95134. var me = this,
  95135. el = sprite.el,
  95136. group = sprite.group,
  95137. sattr = sprite.attr,
  95138. parsers = me.parsers,
  95139. //Safari does not handle linear gradients correctly in quirksmode
  95140. //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
  95141. //ref: EXTJSIV-1472
  95142. gradientsMap = me.gradientsMap || {},
  95143. safariFix = Ext.isSafari && !Ext.isStrict,
  95144. groups, i, ln, attrs, font, key, style, name, rect;
  95145. if (group) {
  95146. groups = [].concat(group);
  95147. ln = groups.length;
  95148. for (i = 0; i < ln; i++) {
  95149. group = groups[i];
  95150. me.getGroup(group).add(sprite);
  95151. }
  95152. delete sprite.group;
  95153. }
  95154. attrs = me.scrubAttrs(sprite) || {};
  95155. // if (sprite.dirtyPath) {
  95156. sprite.bbox.plain = 0;
  95157. sprite.bbox.transform = 0;
  95158. if (sprite.type == "circle" || sprite.type == "ellipse") {
  95159. attrs.cx = attrs.cx || attrs.x;
  95160. attrs.cy = attrs.cy || attrs.y;
  95161. }
  95162. else if (sprite.type == "rect") {
  95163. attrs.rx = attrs.ry = attrs.r;
  95164. }
  95165. else if (sprite.type == "path" && attrs.d) {
  95166. attrs.d = Ext.draw.Draw.pathToString(Ext.draw.Draw.pathToAbsolute(attrs.d));
  95167. }
  95168. sprite.dirtyPath = false;
  95169. // }
  95170. // else {
  95171. // delete attrs.d;
  95172. // }
  95173. if (attrs['clip-rect']) {
  95174. me.setClip(sprite, attrs);
  95175. delete attrs['clip-rect'];
  95176. }
  95177. if (sprite.type == 'text' && attrs.font && sprite.dirtyFont) {
  95178. el.set({ style: "font: " + attrs.font});
  95179. }
  95180. if (sprite.type == "image") {
  95181. el.dom.setAttributeNS(me.xlink, "href", attrs.src);
  95182. }
  95183. Ext.applyIf(attrs, me.minDefaults[sprite.type]);
  95184. if (sprite.dirtyHidden) {
  95185. (sattr.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
  95186. sprite.dirtyHidden = false;
  95187. }
  95188. for (key in attrs) {
  95189. if (attrs.hasOwnProperty(key) && attrs[key] != null) {
  95190. //Safari does not handle linear gradients correctly in quirksmode
  95191. //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
  95192. //ref: EXTJSIV-1472
  95193. //if we're Safari in QuirksMode and we're applying some color attribute and the value of that
  95194. //attribute is a reference to a gradient then assign a plain color to that value instead of the gradient.
  95195. if (safariFix && ('color|stroke|fill'.indexOf(key) > -1) && (attrs[key] in gradientsMap)) {
  95196. attrs[key] = gradientsMap[attrs[key]];
  95197. }
  95198. //hidden is not a proper SVG attribute.
  95199. if (key == 'hidden' && sprite.type == 'text') {
  95200. continue;
  95201. }
  95202. if (key in parsers) {
  95203. el.dom.setAttribute(key, parsers[key](attrs[key], sprite, me));
  95204. } else {
  95205. el.dom.setAttribute(key, attrs[key]);
  95206. }
  95207. }
  95208. }
  95209. if (sprite.type == 'text') {
  95210. me.tuneText(sprite, attrs);
  95211. }
  95212. sprite.dirtyFont = false;
  95213. //set styles
  95214. style = sattr.style;
  95215. if (style) {
  95216. el.setStyle(style);
  95217. }
  95218. sprite.dirty = false;
  95219. if (Ext.isSafari3) {
  95220. // Refreshing the view to fix bug EXTJSIV-1: rendering issue in old Safari 3
  95221. me.webkitRect.show();
  95222. setTimeout(function () {
  95223. me.webkitRect.hide();
  95224. });
  95225. }
  95226. },
  95227. setClip: function(sprite, params) {
  95228. var me = this,
  95229. rect = params["clip-rect"],
  95230. clipEl, clipPath;
  95231. if (rect) {
  95232. if (sprite.clip) {
  95233. sprite.clip.parentNode.parentNode.removeChild(sprite.clip.parentNode);
  95234. }
  95235. clipEl = me.createSvgElement('clipPath');
  95236. clipPath = me.createSvgElement('rect');
  95237. clipEl.id = Ext.id(null, 'ext-clip-');
  95238. clipPath.setAttribute("x", rect.x);
  95239. clipPath.setAttribute("y", rect.y);
  95240. clipPath.setAttribute("width", rect.width);
  95241. clipPath.setAttribute("height", rect.height);
  95242. clipEl.appendChild(clipPath);
  95243. me.getDefs().appendChild(clipEl);
  95244. sprite.el.dom.setAttribute("clip-path", "url(#" + clipEl.id + ")");
  95245. sprite.clip = clipPath;
  95246. }
  95247. // if (!attrs[key]) {
  95248. // var clip = Ext.getDoc().dom.getElementById(sprite.el.getAttribute("clip-path").replace(/(^url\(#|\)$)/g, ""));
  95249. // clip && clip.parentNode.removeChild(clip);
  95250. // sprite.el.setAttribute("clip-path", "");
  95251. // delete attrss.clip;
  95252. // }
  95253. },
  95254. /**
  95255. * Insert or move a given sprite's element to the correct place in the DOM list for its zIndex
  95256. * @param {Ext.draw.Sprite} sprite
  95257. */
  95258. applyZIndex: function(sprite) {
  95259. var me = this,
  95260. items = me.items,
  95261. idx = items.indexOf(sprite),
  95262. el = sprite.el,
  95263. prevEl;
  95264. if (me.el.dom.childNodes[idx + 2] !== el.dom) { //shift by 2 to account for defs and bg rect
  95265. if (idx > 0) {
  95266. // Find the first previous sprite which has its DOM element created already
  95267. do {
  95268. prevEl = items.getAt(--idx).el;
  95269. } while (!prevEl && idx > 0);
  95270. }
  95271. el.insertAfter(prevEl || me.bgRect);
  95272. }
  95273. sprite.zIndexDirty = false;
  95274. },
  95275. createItem: function (config) {
  95276. var sprite = new Ext.draw.Sprite(config);
  95277. sprite.surface = this;
  95278. return sprite;
  95279. },
  95280. addGradient: function(gradient) {
  95281. gradient = Ext.draw.Draw.parseGradient(gradient);
  95282. var me = this,
  95283. ln = gradient.stops.length,
  95284. vector = gradient.vector,
  95285. //Safari does not handle linear gradients correctly in quirksmode
  95286. //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
  95287. //ref: EXTJSIV-1472
  95288. usePlain = Ext.isSafari && !Ext.isStrict,
  95289. gradientEl, stop, stopEl, i, gradientsMap;
  95290. gradientsMap = me.gradientsMap || {};
  95291. if (!usePlain) {
  95292. if (gradient.type == "linear") {
  95293. gradientEl = me.createSvgElement("linearGradient");
  95294. gradientEl.setAttribute("x1", vector[0]);
  95295. gradientEl.setAttribute("y1", vector[1]);
  95296. gradientEl.setAttribute("x2", vector[2]);
  95297. gradientEl.setAttribute("y2", vector[3]);
  95298. }
  95299. else {
  95300. gradientEl = me.createSvgElement("radialGradient");
  95301. gradientEl.setAttribute("cx", gradient.centerX);
  95302. gradientEl.setAttribute("cy", gradient.centerY);
  95303. gradientEl.setAttribute("r", gradient.radius);
  95304. if (Ext.isNumber(gradient.focalX) && Ext.isNumber(gradient.focalY)) {
  95305. gradientEl.setAttribute("fx", gradient.focalX);
  95306. gradientEl.setAttribute("fy", gradient.focalY);
  95307. }
  95308. }
  95309. gradientEl.id = gradient.id;
  95310. me.getDefs().appendChild(gradientEl);
  95311. for (i = 0; i < ln; i++) {
  95312. stop = gradient.stops[i];
  95313. stopEl = me.createSvgElement("stop");
  95314. stopEl.setAttribute("offset", stop.offset + "%");
  95315. stopEl.setAttribute("stop-color", stop.color);
  95316. stopEl.setAttribute("stop-opacity",stop.opacity);
  95317. gradientEl.appendChild(stopEl);
  95318. }
  95319. } else {
  95320. gradientsMap['url(#' + gradient.id + ')'] = gradient.stops[0].color;
  95321. }
  95322. me.gradientsMap = gradientsMap;
  95323. },
  95324. /**
  95325. * Checks if the specified CSS class exists on this element's DOM node.
  95326. * @param {Ext.draw.Sprite} sprite The sprite to look into.
  95327. * @param {String} className The CSS class to check for
  95328. * @return {Boolean} True if the class exists, else false
  95329. */
  95330. hasCls: function(sprite, className) {
  95331. return className && (' ' + (sprite.el.dom.getAttribute('class') || '') + ' ').indexOf(' ' + className + ' ') != -1;
  95332. },
  95333. addCls: function(sprite, className) {
  95334. var el = sprite.el,
  95335. i,
  95336. len,
  95337. v,
  95338. cls = [],
  95339. curCls = el.getAttribute('class') || '';
  95340. // Separate case is for speed
  95341. if (!Ext.isArray(className)) {
  95342. if (typeof className == 'string' && !this.hasCls(sprite, className)) {
  95343. el.set({ 'class': curCls + ' ' + className });
  95344. }
  95345. }
  95346. else {
  95347. for (i = 0, len = className.length; i < len; i++) {
  95348. v = className[i];
  95349. if (typeof v == 'string' && (' ' + curCls + ' ').indexOf(' ' + v + ' ') == -1) {
  95350. cls.push(v);
  95351. }
  95352. }
  95353. if (cls.length) {
  95354. el.set({ 'class': ' ' + cls.join(' ') });
  95355. }
  95356. }
  95357. },
  95358. removeCls: function(sprite, className) {
  95359. var me = this,
  95360. el = sprite.el,
  95361. curCls = el.getAttribute('class') || '',
  95362. i, idx, len, cls, elClasses;
  95363. if (!Ext.isArray(className)){
  95364. className = [className];
  95365. }
  95366. if (curCls) {
  95367. elClasses = curCls.replace(me.trimRe, ' ').split(me.spacesRe);
  95368. for (i = 0, len = className.length; i < len; i++) {
  95369. cls = className[i];
  95370. if (typeof cls == 'string') {
  95371. cls = cls.replace(me.trimRe, '');
  95372. idx = Ext.Array.indexOf(elClasses, cls);
  95373. if (idx != -1) {
  95374. Ext.Array.erase(elClasses, idx, 1);
  95375. }
  95376. }
  95377. }
  95378. el.set({ 'class': elClasses.join(' ') });
  95379. }
  95380. },
  95381. destroy: function() {
  95382. var me = this;
  95383. me.callParent();
  95384. if (me.el) {
  95385. me.el.remove();
  95386. }
  95387. if (me._defs) {
  95388. Ext.get(me._defs).destroy();
  95389. }
  95390. if (me.bgRect) {
  95391. Ext.get(me.bgRect).destroy();
  95392. }
  95393. if (me.webkitRect) {
  95394. Ext.get(me.webkitRect).destroy();
  95395. }
  95396. delete me.el;
  95397. }
  95398. });
  95399. /**
  95400. * A utility class for exporting a {@link Ext.draw.Surface Surface} to a string
  95401. * that may be saved or used for processing on the server.
  95402. *
  95403. * @singleton
  95404. */
  95405. Ext.define('Ext.draw.engine.SvgExporter', function(){
  95406. var commaRe = /,/g,
  95407. fontRegex = /(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)\s('*.*'*)/,
  95408. rgbColorRe = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/g,
  95409. rgbaColorRe = /rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,([\d\.]+)\)/g,
  95410. surface, len, width, height,
  95411. init = function(s){
  95412. surface = s;
  95413. len = surface.length;
  95414. width = surface.width;
  95415. height = surface.height;
  95416. },
  95417. spriteProcessor = {
  95418. path: function(sprite){
  95419. var attr = sprite.attr,
  95420. path = attr.path,
  95421. pathString = '',
  95422. props, p, pLen;
  95423. if (Ext.isArray(path[0])) {
  95424. pLen = path.length;
  95425. for (p = 0; p < pLen; p++) {
  95426. pathString += path[p].join(' ');
  95427. }
  95428. } else if (Ext.isArray(path)) {
  95429. pathString = path.join(' ');
  95430. } else {
  95431. pathString = path.replace(commaRe,' ');
  95432. }
  95433. props = toPropertyString({
  95434. d: pathString,
  95435. fill: attr.fill || 'none',
  95436. stroke: attr.stroke,
  95437. 'fill-opacity': attr.opacity,
  95438. 'stroke-width': attr['stroke-width'],
  95439. 'stroke-opacity': attr['stroke-opacity'],
  95440. "z-index": attr.zIndex,
  95441. transform: sprite.matrix.toSvg()
  95442. });
  95443. return '<path ' + props + '/>';
  95444. },
  95445. text: function(sprite){
  95446. // TODO
  95447. // implement multi line support (@see Svg.js tuneText)
  95448. var attr = sprite.attr,
  95449. match = fontRegex.exec(attr.font),
  95450. size = (match && match[1]) || "12",
  95451. // default font family is Arial
  95452. family = (match && match[3]) || 'Arial',
  95453. text = attr.text,
  95454. factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4,
  95455. tspanString = '',
  95456. props;
  95457. sprite.getBBox();
  95458. tspanString += '<tspan x="' + (attr.x || '') + '" dy="';
  95459. tspanString += (size/factor)+'">';
  95460. tspanString += Ext.htmlEncode(text) + '</tspan>';
  95461. props = toPropertyString({
  95462. x: attr.x,
  95463. y: attr.y,
  95464. 'font-size': size,
  95465. 'font-family': family,
  95466. 'font-weight': attr['font-weight'],
  95467. 'text-anchor': attr['text-anchor'],
  95468. // if no fill property is set it will be black
  95469. fill: attr.fill || '#000',
  95470. 'fill-opacity': attr.opacity,
  95471. transform: sprite.matrix.toSvg()
  95472. });
  95473. return '<text '+ props + '>' + tspanString + '</text>';
  95474. },
  95475. rect: function(sprite){
  95476. var attr = sprite.attr,
  95477. props = toPropertyString({
  95478. x: attr.x,
  95479. y: attr.y,
  95480. rx: attr.rx,
  95481. ry: attr.ry,
  95482. width: attr.width,
  95483. height: attr.height,
  95484. fill: attr.fill || 'none',
  95485. 'fill-opacity': attr.opacity,
  95486. stroke: attr.stroke,
  95487. 'stroke-opacity': attr['stroke-opacity'],
  95488. 'stroke-width':attr['stroke-width'],
  95489. transform: sprite.matrix && sprite.matrix.toSvg()
  95490. });
  95491. return '<rect ' + props + '/>';
  95492. },
  95493. circle: function(sprite){
  95494. var attr = sprite.attr,
  95495. props = toPropertyString({
  95496. cx: attr.x,
  95497. cy: attr.y,
  95498. r: attr.radius,
  95499. fill: attr.translation.fill || attr.fill || 'none',
  95500. 'fill-opacity': attr.opacity,
  95501. stroke: attr.stroke,
  95502. 'stroke-opacity': attr['stroke-opacity'],
  95503. 'stroke-width':attr['stroke-width'],
  95504. transform: sprite.matrix.toSvg()
  95505. });
  95506. return '<circle ' + props + ' />';
  95507. },
  95508. image: function(sprite){
  95509. var attr = sprite.attr,
  95510. props = toPropertyString({
  95511. x: attr.x - (attr.width/2 >> 0),
  95512. y: attr.y - (attr.height/2 >> 0),
  95513. width: attr.width,
  95514. height: attr.height,
  95515. 'xlink:href': attr.src,
  95516. transform: sprite.matrix.toSvg()
  95517. });
  95518. return '<image ' + props + ' />';
  95519. }
  95520. },
  95521. svgHeader = function(){
  95522. var svg = '<?xml version="1.0" standalone="yes"?>';
  95523. svg += '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';
  95524. return svg;
  95525. },
  95526. svgContent = function(){
  95527. var svg = '<svg width="'+width+'px" height="'+height+'px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">',
  95528. defs = '', item, itemsLen, items, gradient,
  95529. getSvgString, colorstops, stop,
  95530. coll, keys, colls, k, kLen, key, collI, i, j, stopsLen, sortedItems, za, zb;
  95531. items = surface.items.items;
  95532. itemsLen = items.length;
  95533. getSvgString = function(node){
  95534. var childs = node.childNodes,
  95535. childLength = childs.length,
  95536. i = 0,
  95537. attrLength,
  95538. j,
  95539. svgString = '', child, attr, tagName, attrItem;
  95540. for(; i < childLength; i++){
  95541. child = childs[i];
  95542. attr = child.attributes;
  95543. tagName = child.tagName;
  95544. svgString += '<' +tagName;
  95545. for(j = 0, attrLength = attr.length; j < attrLength; j++){
  95546. attrItem = attr.item(j);
  95547. svgString += ' '+attrItem.name+'="'+attrItem.value+'"';
  95548. }
  95549. svgString += '>';
  95550. if(child.childNodes.length > 0){
  95551. svgString += getSvgString(child);
  95552. }
  95553. svgString += '</' + tagName + '>';
  95554. }
  95555. return svgString;
  95556. };
  95557. if(surface.getDefs){
  95558. defs = getSvgString(surface.getDefs());
  95559. }else{
  95560. // IE
  95561. coll = surface.gradientsColl;
  95562. if (coll) {
  95563. keys = coll.keys;
  95564. colls = coll.items;
  95565. k = 0;
  95566. kLen = keys.length;
  95567. }
  95568. for (; k < kLen; k++) {
  95569. key = keys[k];
  95570. collI = colls[k];
  95571. gradient = surface.gradientsColl.getByKey(key);
  95572. defs += '<linearGradient id="' + key + '" x1="0" y1="0" x2="1" y2="1">';
  95573. var color = gradient.colors.replace(rgbColorRe, 'rgb($1|$2|$3)');
  95574. color = color.replace(rgbaColorRe, 'rgba($1|$2|$3|$4)')
  95575. colorstops = color.split(',');
  95576. for(i=0, stopsLen = colorstops.length; i < stopsLen; i++){
  95577. stop = colorstops[i].split(' ');
  95578. color = Ext.draw.Color.fromString(stop[1].replace(/\|/g,','));
  95579. defs += '<stop offset="'+stop[0]+'" stop-color="' + color.toString() + '" stop-opacity="1"></stop>';
  95580. }
  95581. defs += '</linearGradient>';
  95582. }
  95583. }
  95584. svg += '<defs>' + defs + '</defs>';
  95585. // thats the background rectangle
  95586. svg += spriteProcessor.rect({
  95587. attr: {
  95588. width: '100%',
  95589. height: '100%',
  95590. fill: '#fff',
  95591. stroke: 'none',
  95592. opacity: '0'
  95593. }
  95594. });
  95595. // Sort the items (stable sort guaranteed)
  95596. sortedItems = new Array(itemsLen);
  95597. for(i = 0; i < itemsLen; i++){
  95598. sortedItems[i] = i;
  95599. }
  95600. sortedItems.sort(function (a, b) {
  95601. za = items[a].attr.zIndex || 0;
  95602. zb = items[b].attr.zIndex || 0;
  95603. if (za == zb) {
  95604. return a - b;
  95605. }
  95606. return za - zb;
  95607. });
  95608. for(i = 0; i < itemsLen; i++){
  95609. item = items[sortedItems[i]];
  95610. if(!item.attr.hidden){
  95611. svg += spriteProcessor[item.type](item);
  95612. }
  95613. }
  95614. svg += '</svg>';
  95615. return svg;
  95616. },
  95617. toPropertyString = function(obj){
  95618. var propString = '',
  95619. key;
  95620. for(key in obj){
  95621. if(obj.hasOwnProperty(key) && obj[key] != null){
  95622. propString += key +'="'+ obj[key]+'" ';
  95623. }
  95624. }
  95625. return propString;
  95626. };
  95627. return {
  95628. singleton: true,
  95629. /**
  95630. * Exports the passed surface to a SVG string representation
  95631. * @param {Ext.draw.Surface} surface The surface to export
  95632. * @param {Object} [config] Any configuration for the export. Currently this is
  95633. * unused but may provide more options in the future
  95634. * @return {String} The SVG as a string
  95635. */
  95636. generate: function(surface, config){
  95637. config = config || {};
  95638. init(surface);
  95639. return svgHeader() + svgContent();
  95640. }
  95641. };
  95642. });
  95643. /**
  95644. * Provides specific methods to draw with VML.
  95645. */
  95646. Ext.define('Ext.draw.engine.Vml', {
  95647. /* Begin Definitions */
  95648. extend: 'Ext.draw.Surface',
  95649. requires: ['Ext.draw.Draw', 'Ext.draw.Color', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
  95650. /* End Definitions */
  95651. engine: 'Vml',
  95652. map: {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
  95653. bitesRe: /([clmz]),?([^clmz]*)/gi,
  95654. valRe: /-?[^,\s\-]+/g,
  95655. fillUrlRe: /^url\(\s*['"]?([^\)]+?)['"]?\s*\)$/i,
  95656. pathlike: /^(path|rect)$/,
  95657. NonVmlPathRe: /[ahqstv]/ig, // Non-VML Pathing ops
  95658. partialPathRe: /[clmz]/g,
  95659. fontFamilyRe: /^['"]+|['"]+$/g,
  95660. baseVmlCls: Ext.baseCSSPrefix + 'vml-base',
  95661. vmlGroupCls: Ext.baseCSSPrefix + 'vml-group',
  95662. spriteCls: Ext.baseCSSPrefix + 'vml-sprite',
  95663. measureSpanCls: Ext.baseCSSPrefix + 'vml-measure-span',
  95664. zoom: 21600,
  95665. coordsize: 1000,
  95666. coordorigin: '0 0',
  95667. zIndexShift: 0,
  95668. // VML uses CSS z-index and therefore doesn't need sprites to be kept in zIndex order
  95669. orderSpritesByZIndex: false,
  95670. // @private
  95671. // Convert an SVG standard path into a VML path
  95672. path2vml: function (path) {
  95673. var me = this,
  95674. nonVML = me.NonVmlPathRe,
  95675. map = me.map,
  95676. val = me.valRe,
  95677. zoom = me.zoom,
  95678. bites = me.bitesRe,
  95679. command = Ext.Function.bind(Ext.draw.Draw.pathToAbsolute, Ext.draw.Draw),
  95680. res, pa, p, r, i, ii, j, jj;
  95681. if (String(path).match(nonVML)) {
  95682. command = Ext.Function.bind(Ext.draw.Draw.path2curve, Ext.draw.Draw);
  95683. } else if (!String(path).match(me.partialPathRe)) {
  95684. res = String(path).replace(bites, function (all, command, args) {
  95685. var vals = [],
  95686. isMove = command.toLowerCase() == "m",
  95687. res = map[command];
  95688. args.replace(val, function (value) {
  95689. if (isMove && vals.length === 2) {
  95690. res += vals + map[command == "m" ? "l" : "L"];
  95691. vals = [];
  95692. }
  95693. vals.push(Math.round(value * zoom));
  95694. });
  95695. return res + vals;
  95696. });
  95697. return res;
  95698. }
  95699. pa = command(path);
  95700. res = [];
  95701. for (i = 0, ii = pa.length; i < ii; i++) {
  95702. p = pa[i];
  95703. r = pa[i][0].toLowerCase();
  95704. if (r == "z") {
  95705. r = "x";
  95706. }
  95707. for (j = 1, jj = p.length; j < jj; j++) {
  95708. r += Math.round(p[j] * me.zoom) + (j != jj - 1 ? "," : "");
  95709. }
  95710. res.push(r);
  95711. }
  95712. return res.join(" ");
  95713. },
  95714. // @private - set of attributes which need to be translated from the sprite API to the native browser API
  95715. translateAttrs: {
  95716. radius: "r",
  95717. radiusX: "rx",
  95718. radiusY: "ry",
  95719. lineWidth: "stroke-width",
  95720. fillOpacity: "fill-opacity",
  95721. strokeOpacity: "stroke-opacity",
  95722. strokeLinejoin: "stroke-linejoin"
  95723. },
  95724. // @private - Minimun set of defaults for different types of sprites.
  95725. minDefaults: {
  95726. circle: {
  95727. fill: "none",
  95728. stroke: null,
  95729. "stroke-width": null,
  95730. opacity: null,
  95731. "fill-opacity": null,
  95732. "stroke-opacity": null
  95733. },
  95734. ellipse: {
  95735. cx: 0,
  95736. cy: 0,
  95737. rx: 0,
  95738. ry: 0,
  95739. fill: "none",
  95740. stroke: null,
  95741. "stroke-width": null,
  95742. opacity: null,
  95743. "fill-opacity": null,
  95744. "stroke-opacity": null
  95745. },
  95746. rect: {
  95747. x: 0,
  95748. y: 0,
  95749. width: 0,
  95750. height: 0,
  95751. rx: 0,
  95752. ry: 0,
  95753. fill: "none",
  95754. stroke: null,
  95755. "stroke-width": null,
  95756. opacity: null,
  95757. "fill-opacity": null,
  95758. "stroke-opacity": null
  95759. },
  95760. text: {
  95761. x: 0,
  95762. y: 0,
  95763. "text-anchor": "start",
  95764. font: '10px "Arial"',
  95765. fill: "#000",
  95766. stroke: null,
  95767. "stroke-width": null,
  95768. opacity: null,
  95769. "fill-opacity": null,
  95770. "stroke-opacity": null
  95771. },
  95772. path: {
  95773. d: "M0,0",
  95774. fill: "none",
  95775. stroke: null,
  95776. "stroke-width": null,
  95777. opacity: null,
  95778. "fill-opacity": null,
  95779. "stroke-opacity": null
  95780. },
  95781. image: {
  95782. x: 0,
  95783. y: 0,
  95784. width: 0,
  95785. height: 0,
  95786. preserveAspectRatio: "none",
  95787. opacity: null
  95788. }
  95789. },
  95790. // private
  95791. onMouseEnter: function (e) {
  95792. this.fireEvent("mouseenter", e);
  95793. },
  95794. // private
  95795. onMouseLeave: function (e) {
  95796. this.fireEvent("mouseleave", e);
  95797. },
  95798. // @private - Normalize a delegated single event from the main container to each sprite and sprite group
  95799. processEvent: function (name, e) {
  95800. var target = e.getTarget(),
  95801. surface = this.surface,
  95802. sprite;
  95803. this.fireEvent(name, e);
  95804. sprite = this.items.get(target.id);
  95805. if (sprite) {
  95806. sprite.fireEvent(name, sprite, e);
  95807. }
  95808. },
  95809. // Create the VML element/elements and append them to the DOM
  95810. createSpriteElement: function (sprite) {
  95811. var me = this,
  95812. attr = sprite.attr,
  95813. type = sprite.type,
  95814. zoom = me.zoom,
  95815. vml = sprite.vml || (sprite.vml = {}),
  95816. round = Math.round,
  95817. el = (type === 'image') ? me.createNode('image') : me.createNode('shape'),
  95818. path, skew, textPath;
  95819. el.coordsize = zoom + ' ' + zoom;
  95820. el.coordorigin = attr.coordorigin || "0 0";
  95821. Ext.get(el).addCls(me.spriteCls);
  95822. if (type == "text") {
  95823. vml.path = path = me.createNode("path");
  95824. path.textpathok = true;
  95825. vml.textpath = textPath = me.createNode("textpath");
  95826. textPath.on = true;
  95827. el.appendChild(textPath);
  95828. el.appendChild(path);
  95829. }
  95830. el.id = sprite.id;
  95831. sprite.el = Ext.get(el);
  95832. sprite.el.setStyle('zIndex', -me.zIndexShift);
  95833. me.el.appendChild(el);
  95834. if (type !== 'image') {
  95835. skew = me.createNode("skew");
  95836. skew.on = true;
  95837. el.appendChild(skew);
  95838. sprite.skew = skew;
  95839. }
  95840. sprite.matrix = new Ext.draw.Matrix();
  95841. sprite.bbox = {
  95842. plain: null,
  95843. transform: null
  95844. };
  95845. this.applyAttrs(sprite);
  95846. this.applyTransformations(sprite);
  95847. sprite.fireEvent("render", sprite);
  95848. return sprite.el;
  95849. },
  95850. getBBoxText: function (sprite) {
  95851. var vml = sprite.vml;
  95852. return {
  95853. x: vml.X + (vml.bbx || 0) - vml.W / 2,
  95854. y: vml.Y - vml.H / 2,
  95855. width: vml.W,
  95856. height: vml.H
  95857. };
  95858. },
  95859. applyAttrs: function (sprite) {
  95860. var me = this,
  95861. vml = sprite.vml,
  95862. group = sprite.group,
  95863. spriteAttr = sprite.attr,
  95864. el = sprite.el,
  95865. dom = el.dom,
  95866. style, name, groups, i, ln, scrubbedAttrs, font, key,
  95867. cx, cy, rx, ry;
  95868. if (group) {
  95869. groups = [].concat(group);
  95870. ln = groups.length;
  95871. for (i = 0; i < ln; i++) {
  95872. group = groups[i];
  95873. me.getGroup(group).add(sprite);
  95874. }
  95875. delete sprite.group;
  95876. }
  95877. scrubbedAttrs = me.scrubAttrs(sprite) || {};
  95878. if (sprite.zIndexDirty) {
  95879. me.setZIndex(sprite);
  95880. }
  95881. // Apply minimum default attributes
  95882. Ext.applyIf(scrubbedAttrs, me.minDefaults[sprite.type]);
  95883. if (sprite.type == 'image') {
  95884. Ext.apply(sprite.attr, {
  95885. x: scrubbedAttrs.x,
  95886. y: scrubbedAttrs.y,
  95887. width: scrubbedAttrs.width,
  95888. height: scrubbedAttrs.height
  95889. });
  95890. el.setStyle({
  95891. width: scrubbedAttrs.width + 'px',
  95892. height: scrubbedAttrs.height + 'px'
  95893. });
  95894. dom.src = scrubbedAttrs.src;
  95895. }
  95896. if (dom.href) {
  95897. dom.href = scrubbedAttrs.href;
  95898. }
  95899. if (dom.title) {
  95900. dom.title = scrubbedAttrs.title;
  95901. }
  95902. if (dom.target) {
  95903. dom.target = scrubbedAttrs.target;
  95904. }
  95905. if (dom.cursor) {
  95906. dom.cursor = scrubbedAttrs.cursor;
  95907. }
  95908. // Change visibility
  95909. if (sprite.dirtyHidden) {
  95910. (scrubbedAttrs.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
  95911. sprite.dirtyHidden = false;
  95912. }
  95913. // Update path
  95914. if (sprite.dirtyPath) {
  95915. if (sprite.type == "circle" || sprite.type == "ellipse") {
  95916. cx = scrubbedAttrs.x;
  95917. cy = scrubbedAttrs.y;
  95918. rx = scrubbedAttrs.rx || scrubbedAttrs.r || 0;
  95919. ry = scrubbedAttrs.ry || scrubbedAttrs.r || 0;
  95920. dom.path = Ext.String.format("ar{0},{1},{2},{3},{4},{1},{4},{1}",
  95921. Math.round((cx - rx) * me.zoom),
  95922. Math.round((cy - ry) * me.zoom),
  95923. Math.round((cx + rx) * me.zoom),
  95924. Math.round((cy + ry) * me.zoom),
  95925. Math.round(cx * me.zoom));
  95926. sprite.dirtyPath = false;
  95927. }
  95928. else if (sprite.type !== "text" && sprite.type !== 'image') {
  95929. sprite.attr.path = scrubbedAttrs.path = me.setPaths(sprite, scrubbedAttrs) || scrubbedAttrs.path;
  95930. dom.path = me.path2vml(scrubbedAttrs.path);
  95931. sprite.dirtyPath = false;
  95932. }
  95933. }
  95934. // Apply clipping
  95935. if ("clip-rect" in scrubbedAttrs) {
  95936. me.setClip(sprite, scrubbedAttrs);
  95937. }
  95938. // Handle text (special handling required)
  95939. if (sprite.type == "text") {
  95940. me.setTextAttributes(sprite, scrubbedAttrs);
  95941. }
  95942. // Handle fill and opacity
  95943. if (scrubbedAttrs.opacity || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
  95944. me.setFill(sprite, scrubbedAttrs);
  95945. }
  95946. // Handle stroke (all fills require a stroke element)
  95947. if (scrubbedAttrs.stroke || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
  95948. me.setStroke(sprite, scrubbedAttrs);
  95949. }
  95950. //set styles
  95951. style = spriteAttr.style;
  95952. if (style) {
  95953. el.setStyle(style);
  95954. }
  95955. sprite.dirty = false;
  95956. },
  95957. setZIndex: function (sprite) {
  95958. var me = this,
  95959. zIndex = sprite.attr.zIndex,
  95960. shift = me.zIndexShift,
  95961. items, iLen, item, i;
  95962. if (zIndex < shift) {
  95963. // This means bad thing happened.
  95964. // The algorithm below will guarantee O(n) time.
  95965. items = me.items.items;
  95966. iLen = items.length;
  95967. for (i = 0; i < iLen; i++) {
  95968. if ((zIndex = items[i].attr.zIndex) && zIndex < shift) { // zIndex is no longer useful this case
  95969. shift = zIndex;
  95970. }
  95971. }
  95972. me.zIndexShift = shift;
  95973. for (i = 0; i < iLen; i++) {
  95974. item = items[i];
  95975. if (item.el) {
  95976. item.el.setStyle('zIndex', item.attr.zIndex - shift);
  95977. }
  95978. item.zIndexDirty = false;
  95979. }
  95980. } else if (sprite.el) {
  95981. sprite.el.setStyle('zIndex', zIndex - shift);
  95982. sprite.zIndexDirty = false;
  95983. }
  95984. },
  95985. // Normalize all virtualized types into paths.
  95986. setPaths: function (sprite, params) {
  95987. var spriteAttr = sprite.attr, thickness = sprite.attr['stroke-width'] || 1;
  95988. // Clear bbox cache
  95989. sprite.bbox.plain = null;
  95990. sprite.bbox.transform = null;
  95991. if (sprite.type == 'circle') {
  95992. spriteAttr.rx = spriteAttr.ry = params.r;
  95993. return Ext.draw.Draw.ellipsePath(sprite);
  95994. }
  95995. else if (sprite.type == 'ellipse') {
  95996. spriteAttr.rx = params.rx;
  95997. spriteAttr.ry = params.ry;
  95998. return Ext.draw.Draw.ellipsePath(sprite);
  95999. }
  96000. else if (sprite.type == 'rect') {
  96001. spriteAttr.rx = spriteAttr.ry = params.r;
  96002. return Ext.draw.Draw.rectPath(sprite);
  96003. }
  96004. else if (sprite.type == 'path' && spriteAttr.path) {
  96005. return Ext.draw.Draw.pathToAbsolute(spriteAttr.path);
  96006. }
  96007. return false;
  96008. },
  96009. setFill: function (sprite, params) {
  96010. var me = this,
  96011. el = sprite.el.dom,
  96012. fillEl = el.fill,
  96013. newfill = false,
  96014. opacity, gradient, fillUrl, rotation, angle;
  96015. if (!fillEl) {
  96016. // NOT an expando (but it sure looks like one)...
  96017. fillEl = el.fill = me.createNode("fill");
  96018. newfill = true;
  96019. }
  96020. if (Ext.isArray(params.fill)) {
  96021. params.fill = params.fill[0];
  96022. }
  96023. if (params.fill == "none") {
  96024. fillEl.on = false;
  96025. }
  96026. else {
  96027. if (typeof params.opacity == "number") {
  96028. fillEl.opacity = params.opacity;
  96029. }
  96030. if (typeof params["fill-opacity"] == "number") {
  96031. fillEl.opacity = params["fill-opacity"];
  96032. }
  96033. fillEl.on = true;
  96034. if (typeof params.fill == "string") {
  96035. fillUrl = params.fill.match(me.fillUrlRe);
  96036. if (fillUrl) {
  96037. fillUrl = fillUrl[1];
  96038. // If the URL matches one of the registered gradients, render that gradient
  96039. if (fillUrl.charAt(0) == "#") {
  96040. gradient = me.gradientsColl.getByKey(fillUrl.substring(1));
  96041. }
  96042. if (gradient) {
  96043. // VML angle is offset and inverted from standard, and must be adjusted to match rotation transform
  96044. rotation = params.rotation;
  96045. angle = -(gradient.angle + 270 + (rotation ? rotation.degrees : 0)) % 360;
  96046. // IE will flip the angle at 0 degrees...
  96047. if (angle === 0) {
  96048. angle = 180;
  96049. }
  96050. fillEl.angle = angle;
  96051. fillEl.type = "gradient";
  96052. fillEl.method = "sigma";
  96053. if (fillEl.colors) {
  96054. fillEl.colors.value = gradient.colors;
  96055. } else {
  96056. fillEl.colors = gradient.colors;
  96057. }
  96058. }
  96059. // Otherwise treat it as an image
  96060. else {
  96061. fillEl.src = fillUrl;
  96062. fillEl.type = "tile";
  96063. }
  96064. }
  96065. else {
  96066. fillEl.color = Ext.draw.Color.toHex(params.fill);
  96067. fillEl.src = "";
  96068. fillEl.type = "solid";
  96069. }
  96070. }
  96071. }
  96072. if (newfill) {
  96073. el.appendChild(fillEl);
  96074. }
  96075. },
  96076. setStroke: function (sprite, params) {
  96077. var me = this,
  96078. el = sprite.el.dom,
  96079. strokeEl = sprite.strokeEl,
  96080. newStroke = false,
  96081. width, opacity;
  96082. if (!strokeEl) {
  96083. strokeEl = sprite.strokeEl = me.createNode("stroke");
  96084. newStroke = true;
  96085. }
  96086. if (Ext.isArray(params.stroke)) {
  96087. params.stroke = params.stroke[0];
  96088. }
  96089. if (!params.stroke || params.stroke == "none" || params.stroke == 0 || params["stroke-width"] == 0) {
  96090. strokeEl.on = false;
  96091. }
  96092. else {
  96093. strokeEl.on = true;
  96094. if (params.stroke && !params.stroke.match(me.fillUrlRe)) {
  96095. // VML does NOT support a gradient stroke :(
  96096. strokeEl.color = Ext.draw.Color.toHex(params.stroke);
  96097. }
  96098. strokeEl.dashstyle = params["stroke-dasharray"] ? "dash" : "solid";
  96099. strokeEl.joinstyle = params["stroke-linejoin"];
  96100. strokeEl.endcap = params["stroke-linecap"] || "round";
  96101. strokeEl.miterlimit = params["stroke-miterlimit"] || 8;
  96102. width = parseFloat(params["stroke-width"] || 1) * 0.75;
  96103. opacity = params["stroke-opacity"] || 1;
  96104. // VML Does not support stroke widths under 1, so we're going to fiddle with stroke-opacity instead.
  96105. if (Ext.isNumber(width) && width < 1) {
  96106. strokeEl.weight = 1;
  96107. strokeEl.opacity = opacity * width;
  96108. }
  96109. else {
  96110. strokeEl.weight = width;
  96111. strokeEl.opacity = opacity;
  96112. }
  96113. }
  96114. if (newStroke) {
  96115. el.appendChild(strokeEl);
  96116. }
  96117. },
  96118. setClip: function (sprite, params) {
  96119. var me = this,
  96120. el = sprite.el,
  96121. clipEl = sprite.clipEl,
  96122. rect = String(params["clip-rect"]).split(me.separatorRe);
  96123. if (!clipEl) {
  96124. clipEl = sprite.clipEl = me.el.insertFirst(Ext.getDoc().dom.createElement("div"));
  96125. clipEl.addCls(Ext.baseCSSPrefix + 'vml-sprite');
  96126. }
  96127. if (rect.length == 4) {
  96128. rect[2] = +rect[2] + (+rect[0]);
  96129. rect[3] = +rect[3] + (+rect[1]);
  96130. clipEl.setStyle("clip", Ext.String.format("rect({1}px {2}px {3}px {0}px)", rect[0], rect[1], rect[2], rect[3]));
  96131. clipEl.setSize(me.el.width, me.el.height);
  96132. }
  96133. else {
  96134. clipEl.setStyle("clip", "");
  96135. }
  96136. },
  96137. setTextAttributes: function (sprite, params) {
  96138. var me = this,
  96139. vml = sprite.vml,
  96140. textStyle = vml.textpath.style,
  96141. spanCacheStyle = me.span.style,
  96142. zoom = me.zoom,
  96143. round = Math.round,
  96144. fontObj = {
  96145. fontSize: "font-size",
  96146. fontWeight: "font-weight",
  96147. fontStyle: "font-style"
  96148. },
  96149. fontProp,
  96150. paramProp;
  96151. if (sprite.dirtyFont) {
  96152. if (params.font) {
  96153. textStyle.font = spanCacheStyle.font = params.font;
  96154. }
  96155. if (params["font-family"]) {
  96156. textStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(me.fontFamilyRe, "") + '"';
  96157. spanCacheStyle.fontFamily = params["font-family"];
  96158. }
  96159. for (fontProp in fontObj) {
  96160. paramProp = params[fontObj[fontProp]];
  96161. if (paramProp) {
  96162. textStyle[fontProp] = spanCacheStyle[fontProp] = paramProp;
  96163. }
  96164. }
  96165. me.setText(sprite, params.text);
  96166. if (vml.textpath.string) {
  96167. me.span.innerHTML = String(vml.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br/>");
  96168. }
  96169. vml.W = me.span.offsetWidth;
  96170. vml.H = me.span.offsetHeight + 2; // TODO handle baseline differences and offset in VML Textpath
  96171. // text-anchor emulation
  96172. if (params["text-anchor"] == "middle") {
  96173. textStyle["v-text-align"] = "center";
  96174. }
  96175. else if (params["text-anchor"] == "end") {
  96176. textStyle["v-text-align"] = "right";
  96177. vml.bbx = -Math.round(vml.W / 2);
  96178. }
  96179. else {
  96180. textStyle["v-text-align"] = "left";
  96181. vml.bbx = Math.round(vml.W / 2);
  96182. }
  96183. }
  96184. vml.X = params.x;
  96185. vml.Y = params.y;
  96186. vml.path.v = Ext.String.format("m{0},{1}l{2},{1}", Math.round(vml.X * zoom), Math.round(vml.Y * zoom), Math.round(vml.X * zoom) + 1);
  96187. // Clear bbox cache
  96188. sprite.bbox.plain = null;
  96189. sprite.bbox.transform = null;
  96190. sprite.dirtyFont = false;
  96191. },
  96192. setText: function (sprite, text) {
  96193. sprite.vml.textpath.string = Ext.htmlDecode(text);
  96194. },
  96195. hide: function () {
  96196. this.el.hide();
  96197. },
  96198. show: function () {
  96199. this.el.show();
  96200. },
  96201. hidePrim: function (sprite) {
  96202. sprite.el.addCls(Ext.baseCSSPrefix + 'hide-visibility');
  96203. },
  96204. showPrim: function (sprite) {
  96205. sprite.el.removeCls(Ext.baseCSSPrefix + 'hide-visibility');
  96206. },
  96207. setSize: function (width, height) {
  96208. var me = this;
  96209. width = width || me.width;
  96210. height = height || me.height;
  96211. me.width = width;
  96212. me.height = height;
  96213. if (me.el) {
  96214. // Size outer div
  96215. if (width != undefined) {
  96216. me.el.setWidth(width);
  96217. }
  96218. if (height != undefined) {
  96219. me.el.setHeight(height);
  96220. }
  96221. }
  96222. me.callParent(arguments);
  96223. },
  96224. /**
  96225. * @private Using the current viewBox property and the surface's width and height, calculate the
  96226. * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
  96227. */
  96228. applyViewBox: function () {
  96229. var me = this,
  96230. viewBox = me.viewBox,
  96231. width = me.width,
  96232. height = me.height,
  96233. items,
  96234. iLen,
  96235. i;
  96236. me.callParent();
  96237. if (viewBox && (width || height)) {
  96238. items = me.items.items;
  96239. iLen = items.length;
  96240. for (i = 0; i < iLen; i++) {
  96241. me.applyTransformations(items[i]);
  96242. }
  96243. }
  96244. },
  96245. onAdd: function (item) {
  96246. this.callParent(arguments);
  96247. if (this.el) {
  96248. this.renderItem(item);
  96249. }
  96250. },
  96251. onRemove: function (sprite) {
  96252. if (sprite.el) {
  96253. sprite.el.remove();
  96254. delete sprite.el;
  96255. }
  96256. this.callParent(arguments);
  96257. },
  96258. render: function (container) {
  96259. var me = this,
  96260. doc = Ext.getDoc().dom,
  96261. el;
  96262. // VML Node factory method (createNode)
  96263. if (!me.createNode) {
  96264. try {
  96265. if (!doc.namespaces.rvml) {
  96266. doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
  96267. }
  96268. me.createNode = function (tagName) {
  96269. return doc.createElement("<rvml:" + tagName + ' class="rvml">');
  96270. };
  96271. } catch (e) {
  96272. me.createNode = function (tagName) {
  96273. return doc.createElement("<" + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
  96274. };
  96275. }
  96276. }
  96277. if (!me.el) {
  96278. el = doc.createElement("div");
  96279. me.el = Ext.get(el);
  96280. me.el.addCls(me.baseVmlCls);
  96281. // Measuring span (offscrren)
  96282. me.span = doc.createElement("span");
  96283. Ext.get(me.span).addCls(me.measureSpanCls);
  96284. el.appendChild(me.span);
  96285. me.el.setSize(me.width || 0, me.height || 0);
  96286. container.appendChild(el);
  96287. me.el.on({
  96288. scope: me,
  96289. mouseup: me.onMouseUp,
  96290. mousedown: me.onMouseDown,
  96291. mouseover: me.onMouseOver,
  96292. mouseout: me.onMouseOut,
  96293. mousemove: me.onMouseMove,
  96294. mouseenter: me.onMouseEnter,
  96295. mouseleave: me.onMouseLeave,
  96296. click: me.onClick,
  96297. dblclick: me.onDblClick
  96298. });
  96299. }
  96300. me.renderAll();
  96301. },
  96302. renderAll: function () {
  96303. this.items.each(this.renderItem, this);
  96304. },
  96305. redraw: function (sprite) {
  96306. sprite.dirty = true;
  96307. this.renderItem(sprite);
  96308. },
  96309. renderItem: function (sprite) {
  96310. // Does the surface element exist?
  96311. if (!this.el) {
  96312. return;
  96313. }
  96314. // Create sprite element if necessary
  96315. if (!sprite.el) {
  96316. this.createSpriteElement(sprite);
  96317. }
  96318. if (sprite.dirty) {
  96319. this.applyAttrs(sprite);
  96320. if (sprite.dirtyTransform) {
  96321. this.applyTransformations(sprite);
  96322. }
  96323. }
  96324. },
  96325. rotationCompensation: function (deg, dx, dy) {
  96326. var matrix = new Ext.draw.Matrix();
  96327. matrix.rotate(-deg, 0.5, 0.5);
  96328. return {
  96329. x: matrix.x(dx, dy),
  96330. y: matrix.y(dx, dy)
  96331. };
  96332. },
  96333. transform: function (sprite, matrixOnly) {
  96334. var me = this,
  96335. bbox = me.getBBox(sprite, true),
  96336. cx = bbox.x + bbox.width * 0.5,
  96337. cy = bbox.y + bbox.height * 0.5,
  96338. matrix = new Ext.draw.Matrix(),
  96339. transforms = sprite.transformations,
  96340. transformsLength = transforms.length,
  96341. i = 0,
  96342. deltaDegrees = 0,
  96343. deltaScaleX = 1,
  96344. deltaScaleY = 1,
  96345. flip = "",
  96346. el = sprite.el,
  96347. dom = el.dom,
  96348. domStyle = dom.style,
  96349. zoom = me.zoom,
  96350. skew = sprite.skew,
  96351. shift = me.viewBoxShift,
  96352. deltaX, deltaY, transform, type, compensate, y, fill, newAngle, zoomScaleX, zoomScaleY, newOrigin, offset;
  96353. for (; i < transformsLength; i++) {
  96354. transform = transforms[i];
  96355. type = transform.type;
  96356. if (type == "translate") {
  96357. matrix.translate(transform.x, transform.y);
  96358. }
  96359. else if (type == "rotate") {
  96360. matrix.rotate(transform.degrees, transform.x, transform.y);
  96361. deltaDegrees += transform.degrees;
  96362. }
  96363. else if (type == "scale") {
  96364. matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
  96365. deltaScaleX *= transform.x;
  96366. deltaScaleY *= transform.y;
  96367. }
  96368. }
  96369. sprite.matrix = matrix.clone();
  96370. if (matrixOnly) {
  96371. return;
  96372. }
  96373. if (shift) {
  96374. matrix.prepend(shift.scale, 0, 0, shift.scale, shift.dx * shift.scale, shift.dy * shift.scale);
  96375. }
  96376. // Hide element while we transform
  96377. if (sprite.type != "image" && skew) {
  96378. skew.origin = "0,0";
  96379. // matrix transform via VML skew
  96380. skew.matrix = matrix.toString();
  96381. // skew.offset = '32767,1' OK
  96382. // skew.offset = '32768,1' Crash
  96383. // M$, R U kidding??
  96384. offset = matrix.offset();
  96385. if (offset[0] > 32767) {
  96386. offset[0] = 32767;
  96387. } else if (offset[0] < -32768) {
  96388. offset[0] = -32768;
  96389. }
  96390. if (offset[1] > 32767) {
  96391. offset[1] = 32767;
  96392. } else if (offset[1] < -32768) {
  96393. offset[1] = -32768;
  96394. }
  96395. skew.offset = offset;
  96396. }
  96397. else {
  96398. domStyle.filter = matrix.toFilter();
  96399. domStyle.left = Math.min(
  96400. matrix.x(bbox.x, bbox.y),
  96401. matrix.x(bbox.x + bbox.width, bbox.y),
  96402. matrix.x(bbox.x, bbox.y + bbox.height),
  96403. matrix.x(bbox.x + bbox.width, bbox.y + bbox.height)) + 'px';
  96404. domStyle.top = Math.min(
  96405. matrix.y(bbox.x, bbox.y),
  96406. matrix.y(bbox.x + bbox.width, bbox.y),
  96407. matrix.y(bbox.x, bbox.y + bbox.height),
  96408. matrix.y(bbox.x + bbox.width, bbox.y + bbox.height)) + 'px';
  96409. }
  96410. },
  96411. createItem: function (config) {
  96412. return Ext.create('Ext.draw.Sprite', config);
  96413. },
  96414. getRegion: function () {
  96415. return this.el.getRegion();
  96416. },
  96417. addCls: function (sprite, className) {
  96418. if (sprite && sprite.el) {
  96419. sprite.el.addCls(className);
  96420. }
  96421. },
  96422. removeCls: function (sprite, className) {
  96423. if (sprite && sprite.el) {
  96424. sprite.el.removeCls(className);
  96425. }
  96426. },
  96427. /**
  96428. * Adds a definition to this Surface for a linear gradient. We convert the gradient definition
  96429. * to its corresponding VML attributes and store it for later use by individual sprites.
  96430. * @param {Object} gradient
  96431. */
  96432. addGradient: function (gradient) {
  96433. var gradients = this.gradientsColl || (this.gradientsColl = Ext.create('Ext.util.MixedCollection')),
  96434. colors = [],
  96435. stops = Ext.create('Ext.util.MixedCollection'),
  96436. keys,
  96437. items,
  96438. iLen,
  96439. key,
  96440. item,
  96441. i;
  96442. // Build colors string
  96443. stops.addAll(gradient.stops);
  96444. stops.sortByKey("ASC", function (a, b) {
  96445. a = parseInt(a, 10);
  96446. b = parseInt(b, 10);
  96447. return a > b ? 1 : (a < b ? -1 : 0);
  96448. });
  96449. keys = stops.keys;
  96450. items = stops.items;
  96451. iLen = keys.length;
  96452. for (i = 0; i < iLen; i++) {
  96453. key = keys[i];
  96454. item = items[i];
  96455. colors.push(key + '% ' + item.color);
  96456. }
  96457. gradients.add(gradient.id, {
  96458. colors: colors.join(","),
  96459. angle: gradient.angle
  96460. });
  96461. },
  96462. destroy: function () {
  96463. var me = this;
  96464. me.callParent(arguments);
  96465. if (me.el) {
  96466. me.el.remove();
  96467. }
  96468. delete me.el;
  96469. }
  96470. });
  96471. /**
  96472. * A simple Component for displaying an Adobe Flash SWF movie. The movie will be sized and can participate
  96473. * in layout like any other Component.
  96474. *
  96475. * This component requires the third-party SWFObject library version 2.2 or above. It is not included within
  96476. * the ExtJS distribution, so you will have to include it into your page manually in order to use this component.
  96477. * The SWFObject library can be downloaded from the [SWFObject project page](http://code.google.com/p/swfobject)
  96478. * and then simply import it into the head of your HTML document:
  96479. *
  96480. * <script type="text/javascript" src="path/to/local/swfobject.js"></script>
  96481. *
  96482. * ## Configuration
  96483. *
  96484. * This component allows several options for configuring how the target Flash movie is embedded. The most
  96485. * important is the required {@link #url} which points to the location of the Flash movie to load. Other
  96486. * configurations include:
  96487. *
  96488. * - {@link #backgroundColor}
  96489. * - {@link #wmode}
  96490. * - {@link #flashVars}
  96491. * - {@link #flashParams}
  96492. * - {@link #flashAttributes}
  96493. *
  96494. * ## Example usage:
  96495. *
  96496. * var win = Ext.widget('window', {
  96497. * title: "It's a tiger!",
  96498. * layout: 'fit',
  96499. * width: 300,
  96500. * height: 300,
  96501. * x: 20,
  96502. * y: 20,
  96503. * resizable: true,
  96504. * items: {
  96505. * xtype: 'flash',
  96506. * url: 'tiger.swf'
  96507. * }
  96508. * });
  96509. * win.show();
  96510. *
  96511. * ## Express Install
  96512. *
  96513. * Adobe provides a tool called [Express Install](http://www.adobe.com/devnet/flashplayer/articles/express_install.html)
  96514. * that offers users an easy way to upgrade their Flash player. If you wish to make use of this, you should set
  96515. * the static EXPRESS\_INSTALL\_URL property to the location of your Express Install SWF file:
  96516. *
  96517. * Ext.flash.Component.EXPRESS_INSTALL_URL = 'path/to/local/expressInstall.swf';
  96518. *
  96519. * @docauthor Jason Johnston <jason@sencha.com>
  96520. */
  96521. Ext.define('Ext.flash.Component', {
  96522. extend: 'Ext.Component',
  96523. alternateClassName: 'Ext.FlashComponent',
  96524. alias: 'widget.flash',
  96525. /**
  96526. * @cfg {String} [flashVersion="9.0.115"]
  96527. * Indicates the version the flash content was published for.
  96528. */
  96529. flashVersion : '9.0.115',
  96530. /**
  96531. * @cfg {String} [backgroundColor="#ffffff"]
  96532. * The background color of the SWF movie.
  96533. */
  96534. backgroundColor: '#ffffff',
  96535. /**
  96536. * @cfg {String} [wmode="opaque"]
  96537. * The wmode of the flash object. This can be used to control layering.
  96538. * Set to 'transparent' to ignore the {@link #backgroundColor} and make the background of the Flash
  96539. * movie transparent.
  96540. */
  96541. wmode: 'opaque',
  96542. /**
  96543. * @cfg {Object} flashVars
  96544. * A set of key value pairs to be passed to the flash object as flash variables.
  96545. */
  96546. /**
  96547. * @cfg {Object} flashParams
  96548. * A set of key value pairs to be passed to the flash object as parameters. Possible parameters can be found here:
  96549. * http://kb2.adobe.com/cps/127/tn_12701.html
  96550. */
  96551. /**
  96552. * @cfg {Object} flashAttributes
  96553. * A set of key value pairs to be passed to the flash object as attributes.
  96554. */
  96555. /**
  96556. * @cfg {String} url (required)
  96557. * The URL of the SWF file to include.
  96558. */
  96559. /**
  96560. * @cfg {String/Number} [swfWidth="100%"]
  96561. * The width of the embedded SWF movie inside the component.
  96562. *
  96563. * Defaults to "100%" so that the movie matches the width of the component.
  96564. */
  96565. swfWidth: '100%',
  96566. /**
  96567. * @cfg {String/Number} [swfHeight="100%"]
  96568. * The height of the embedded SWF movie inside the component.
  96569. *
  96570. * Defaults to "100%" so that the movie matches the height of the component.
  96571. */
  96572. swfHeight: '100%',
  96573. /**
  96574. * @cfg {Boolean} [expressInstall=false]
  96575. * True to prompt the user to install flash if not installed. Note that this uses
  96576. * Ext.FlashComponent.EXPRESS_INSTALL_URL, which should be set to the local resource.
  96577. */
  96578. expressInstall: false,
  96579. /**
  96580. * @property {Ext.Element} swf
  96581. * A reference to the object or embed element into which the SWF file is loaded. Only
  96582. * populated after the component is rendered and the SWF has been successfully embedded.
  96583. */
  96584. // Have to create a placeholder div with the swfId, which SWFObject will replace with the object/embed element.
  96585. renderTpl: ['<div id="{swfId}"></div>'],
  96586. initComponent: function() {
  96587. if (!('swfobject' in window)) {
  96588. Ext.Error.raise('The SWFObject library is not loaded. Ext.flash.Component requires SWFObject version 2.2 or later: http://code.google.com/p/swfobject/');
  96589. }
  96590. if (!this.url) {
  96591. Ext.Error.raise('The "url" config is required for Ext.flash.Component');
  96592. }
  96593. this.callParent();
  96594. this.addEvents(
  96595. /**
  96596. * @event success
  96597. * Fired when the Flash movie has been successfully embedded
  96598. * @param {Ext.flash.Component} this
  96599. */
  96600. 'success',
  96601. /**
  96602. * @event failure
  96603. * Fired when the Flash movie embedding fails
  96604. * @param {Ext.flash.Component} this
  96605. */
  96606. 'failure'
  96607. );
  96608. },
  96609. beforeRender: function(){
  96610. this.callParent();
  96611. Ext.applyIf(this.renderData, {
  96612. swfId: this.getSwfId()
  96613. });
  96614. },
  96615. afterRender: function() {
  96616. var me = this,
  96617. flashParams = Ext.apply({}, me.flashParams),
  96618. flashVars = Ext.apply({}, me.flashVars);
  96619. me.callParent();
  96620. flashParams = Ext.apply({
  96621. allowScriptAccess: 'always',
  96622. bgcolor: me.backgroundColor,
  96623. wmode: me.wmode
  96624. }, flashParams);
  96625. flashVars = Ext.apply({
  96626. allowedDomain: document.location.hostname
  96627. }, flashVars);
  96628. new swfobject.embedSWF(
  96629. me.url,
  96630. me.getSwfId(),
  96631. me.swfWidth,
  96632. me.swfHeight,
  96633. me.flashVersion,
  96634. me.expressInstall ? me.statics.EXPRESS_INSTALL_URL : undefined,
  96635. flashVars,
  96636. flashParams,
  96637. me.flashAttributes,
  96638. Ext.bind(me.swfCallback, me)
  96639. );
  96640. },
  96641. /**
  96642. * @private
  96643. * The callback method for handling an embedding success or failure by SWFObject
  96644. * @param {Object} e The event object passed by SWFObject - see http://code.google.com/p/swfobject/wiki/api
  96645. */
  96646. swfCallback: function(e) {
  96647. var me = this;
  96648. if (e.success) {
  96649. me.swf = Ext.get(e.ref);
  96650. me.onSuccess();
  96651. me.fireEvent('success', me);
  96652. } else {
  96653. me.onFailure();
  96654. me.fireEvent('failure', me);
  96655. }
  96656. },
  96657. /**
  96658. * Retrieves the id of the SWF object/embed element.
  96659. */
  96660. getSwfId: function() {
  96661. return this.swfId || (this.swfId = "extswf" + this.getAutoId());
  96662. },
  96663. onSuccess: function() {
  96664. // swfobject forces visiblity:visible on the swf element, which prevents it
  96665. // from getting hidden when an ancestor is given visibility:hidden.
  96666. this.swf.setStyle('visibility', 'inherit');
  96667. },
  96668. onFailure: Ext.emptyFn,
  96669. beforeDestroy: function() {
  96670. var me = this,
  96671. swf = me.swf;
  96672. if (swf) {
  96673. swfobject.removeSWF(me.getSwfId());
  96674. Ext.destroy(swf);
  96675. delete me.swf;
  96676. }
  96677. me.callParent();
  96678. },
  96679. statics: {
  96680. /**
  96681. * @property {String}
  96682. * The url for installing flash if it doesn't exist. This should be set to a local resource.
  96683. * See http://www.adobe.com/devnet/flashplayer/articles/express_install.html for details.
  96684. * @static
  96685. */
  96686. EXPRESS_INSTALL_URL: 'http:/' + '/swfobject.googlecode.com/svn/trunk/swfobject/expressInstall.swf'
  96687. }
  96688. });
  96689. /**
  96690. * The subclasses of this class provide actions to perform upon {@link Ext.form.Basic Form}s.
  96691. *
  96692. * Instances of this class are only created by a {@link Ext.form.Basic Form} when the Form needs to perform an action
  96693. * such as submit or load. The Configuration options listed for this class are set through the Form's action methods:
  96694. * {@link Ext.form.Basic#submit submit}, {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}
  96695. *
  96696. * The instance of Action which performed the action is passed to the success and failure callbacks of the Form's action
  96697. * methods ({@link Ext.form.Basic#submit submit}, {@link Ext.form.Basic#load load} and
  96698. * {@link Ext.form.Basic#doAction doAction}), and to the {@link Ext.form.Basic#actioncomplete actioncomplete} and
  96699. * {@link Ext.form.Basic#actionfailed actionfailed} event handlers.
  96700. */
  96701. Ext.define('Ext.form.action.Action', {
  96702. alternateClassName: 'Ext.form.Action',
  96703. /**
  96704. * @cfg {Ext.form.Basic} form
  96705. * The {@link Ext.form.Basic BasicForm} instance that is invoking this Action. Required.
  96706. */
  96707. /**
  96708. * @cfg {String} url
  96709. * The URL that the Action is to invoke. Will default to the {@link Ext.form.Basic#url url} configured on the
  96710. * {@link #form}.
  96711. */
  96712. /**
  96713. * @cfg {Boolean} reset
  96714. * When set to **true**, causes the Form to be {@link Ext.form.Basic#reset reset} on Action success. If specified,
  96715. * this happens before the {@link #success} callback is called and before the Form's
  96716. * {@link Ext.form.Basic#actioncomplete actioncomplete} event fires.
  96717. */
  96718. /**
  96719. * @cfg {String} method
  96720. * The HTTP method to use to access the requested URL.
  96721. * Defaults to the {@link Ext.form.Basic#method BasicForm's method}, or 'POST' if not specified.
  96722. */
  96723. /**
  96724. * @cfg {Object/String} params
  96725. * Extra parameter values to pass. These are added to the Form's {@link Ext.form.Basic#baseParams} and passed to the
  96726. * specified URL along with the Form's input fields.
  96727. *
  96728. * Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.
  96729. */
  96730. /**
  96731. * @cfg {Object} headers
  96732. * Extra headers to be sent in the AJAX request for submit and load actions.
  96733. * See {@link Ext.data.proxy.Ajax#headers}.
  96734. */
  96735. /**
  96736. * @cfg {Number} timeout
  96737. * The number of seconds to wait for a server response before failing with the {@link #failureType} as
  96738. * {@link Ext.form.action.Action#CONNECT_FAILURE}. If not specified, defaults to the configured
  96739. * {@link Ext.form.Basic#timeout timeout} of the {@link #form}.
  96740. */
  96741. /**
  96742. * @cfg {Function} success
  96743. * The function to call when a valid success return packet is received.
  96744. * @cfg {Ext.form.Basic} success.form The form that requested the action
  96745. * @cfg {Ext.form.action.Action} success.action The Action class. The {@link #result} property of this object may
  96746. * be examined to perform custom postprocessing.
  96747. */
  96748. /**
  96749. * @cfg {Function} failure
  96750. * The function to call when a failure packet was received, or when an error ocurred in the Ajax communication.
  96751. * @cfg {Ext.form.Basic} failure.form The form that requested the action
  96752. * @cfg {Ext.form.action.Action} failure.action The Action class. If an Ajax error ocurred, the failure type will
  96753. * be in {@link #failureType}. The {@link #result} property of this object may be examined to perform custom
  96754. * postprocessing.
  96755. */
  96756. /**
  96757. * @cfg {Object} scope
  96758. * The scope in which to call the configured #success and #failure callback functions
  96759. * (the `this` reference for the callback functions).
  96760. */
  96761. /**
  96762. * @cfg {String} waitMsg
  96763. * The message to be displayed by a call to {@link Ext.window.MessageBox#wait} during the time the action is being
  96764. * processed.
  96765. */
  96766. /**
  96767. * @cfg {String} waitTitle
  96768. * The title to be displayed by a call to {@link Ext.window.MessageBox#wait} during the time the action is being
  96769. * processed.
  96770. */
  96771. /**
  96772. * @cfg {Boolean} submitEmptyText
  96773. * If set to true, the emptyText value will be sent with the form when it is submitted.
  96774. */
  96775. submitEmptyText : true,
  96776. /**
  96777. * @property {String} type
  96778. * The type of action this Action instance performs. Currently only "submit" and "load" are supported.
  96779. */
  96780. /**
  96781. * @property {String} failureType
  96782. * The type of failure detected will be one of these:
  96783. * {@link #CLIENT_INVALID}, {@link #SERVER_INVALID}, {@link #CONNECT_FAILURE}, or {@link #LOAD_FAILURE}.
  96784. *
  96785. * Usage:
  96786. *
  96787. * var fp = new Ext.form.Panel({
  96788. * ...
  96789. * buttons: [{
  96790. * text: 'Save',
  96791. * formBind: true,
  96792. * handler: function(){
  96793. * if(fp.getForm().isValid()){
  96794. * fp.getForm().submit({
  96795. * url: 'form-submit.php',
  96796. * waitMsg: 'Submitting your data...',
  96797. * success: function(form, action){
  96798. * // server responded with success = true
  96799. * var result = action.{@link #result};
  96800. * },
  96801. * failure: function(form, action){
  96802. * if (action.{@link #failureType} === Ext.form.action.Action.CONNECT_FAILURE) {
  96803. * Ext.Msg.alert('Error',
  96804. * 'Status:'+action.{@link #response}.status+': '+
  96805. * action.{@link #response}.statusText);
  96806. * }
  96807. * if (action.failureType === Ext.form.action.Action.SERVER_INVALID){
  96808. * // server responded with success = false
  96809. * Ext.Msg.alert('Invalid', action.{@link #result}.errormsg);
  96810. * }
  96811. * }
  96812. * });
  96813. * }
  96814. * }
  96815. * },{
  96816. * text: 'Reset',
  96817. * handler: function(){
  96818. * fp.getForm().reset();
  96819. * }
  96820. * }]
  96821. */
  96822. /**
  96823. * @property {Object} response
  96824. * The raw XMLHttpRequest object used to perform the action.
  96825. */
  96826. /**
  96827. * @property {Object} result
  96828. * The decoded response object containing a boolean `success` property and other, action-specific properties.
  96829. */
  96830. /**
  96831. * Creates new Action.
  96832. * @param {Object} [config] Config object.
  96833. */
  96834. constructor: function(config) {
  96835. if (config) {
  96836. Ext.apply(this, config);
  96837. }
  96838. // Normalize the params option to an Object
  96839. var params = config.params;
  96840. if (Ext.isString(params)) {
  96841. this.params = Ext.Object.fromQueryString(params);
  96842. }
  96843. },
  96844. /**
  96845. * @method
  96846. * Invokes this action using the current configuration.
  96847. */
  96848. run: Ext.emptyFn,
  96849. /**
  96850. * @private
  96851. * @method onSuccess
  96852. * Callback method that gets invoked when the action completes successfully. Must be implemented by subclasses.
  96853. * @param {Object} response
  96854. */
  96855. /**
  96856. * @private
  96857. * @method handleResponse
  96858. * Handles the raw response and builds a result object from it. Must be implemented by subclasses.
  96859. * @param {Object} response
  96860. */
  96861. /**
  96862. * @private
  96863. * Handles a failure response.
  96864. * @param {Object} response
  96865. */
  96866. onFailure : function(response){
  96867. this.response = response;
  96868. this.failureType = Ext.form.action.Action.CONNECT_FAILURE;
  96869. this.form.afterAction(this, false);
  96870. },
  96871. /**
  96872. * @private
  96873. * Validates that a response contains either responseText or responseXML and invokes
  96874. * {@link #handleResponse} to build the result object.
  96875. * @param {Object} response The raw response object.
  96876. * @return {Object/Boolean} The result object as built by handleResponse, or `true` if
  96877. * the response had empty responseText and responseXML.
  96878. */
  96879. processResponse : function(response){
  96880. this.response = response;
  96881. if (!response.responseText && !response.responseXML) {
  96882. return true;
  96883. }
  96884. return (this.result = this.handleResponse(response));
  96885. },
  96886. /**
  96887. * @private
  96888. * Build the URL for the AJAX request. Used by the standard AJAX submit and load actions.
  96889. * @return {String} The URL.
  96890. */
  96891. getUrl: function() {
  96892. return this.url || this.form.url;
  96893. },
  96894. /**
  96895. * @private
  96896. * Determine the HTTP method to be used for the request.
  96897. * @return {String} The HTTP method
  96898. */
  96899. getMethod: function() {
  96900. return (this.method || this.form.method || 'POST').toUpperCase();
  96901. },
  96902. /**
  96903. * @private
  96904. * Get the set of parameters specified in the BasicForm's baseParams and/or the params option.
  96905. * Items in params override items of the same name in baseParams.
  96906. * @return {Object} the full set of parameters
  96907. */
  96908. getParams: function() {
  96909. return Ext.apply({}, this.params, this.form.baseParams);
  96910. },
  96911. /**
  96912. * @private
  96913. * Creates a callback object.
  96914. */
  96915. createCallback: function() {
  96916. var me = this,
  96917. undef,
  96918. form = me.form;
  96919. return {
  96920. success: me.onSuccess,
  96921. failure: me.onFailure,
  96922. scope: me,
  96923. timeout: (this.timeout * 1000) || (form.timeout * 1000),
  96924. upload: form.fileUpload ? me.onSuccess : undef
  96925. };
  96926. },
  96927. statics: {
  96928. /**
  96929. * @property
  96930. * Failure type returned when client side validation of the Form fails thus aborting a submit action. Client
  96931. * side validation is performed unless {@link Ext.form.action.Submit#clientValidation} is explicitly set to
  96932. * false.
  96933. * @static
  96934. */
  96935. CLIENT_INVALID: 'client',
  96936. /**
  96937. * @property
  96938. * Failure type returned when server side processing fails and the {@link #result}'s `success` property is set to
  96939. * false.
  96940. *
  96941. * In the case of a form submission, field-specific error messages may be returned in the {@link #result}'s
  96942. * errors property.
  96943. * @static
  96944. */
  96945. SERVER_INVALID: 'server',
  96946. /**
  96947. * @property
  96948. * Failure type returned when a communication error happens when attempting to send a request to the remote
  96949. * server. The {@link #response} may be examined to provide further information.
  96950. * @static
  96951. */
  96952. CONNECT_FAILURE: 'connect',
  96953. /**
  96954. * @property
  96955. * Failure type returned when the response's `success` property is set to false, or no field values are returned
  96956. * in the response's data property.
  96957. * @static
  96958. */
  96959. LOAD_FAILURE: 'load'
  96960. }
  96961. });
  96962. /**
  96963. * A class which handles loading of data from a server into the Fields of an {@link Ext.form.Basic}.
  96964. *
  96965. * Instances of this class are only created by a {@link Ext.form.Basic Form} when {@link Ext.form.Basic#load load}ing.
  96966. *
  96967. * ## Response Packet Criteria
  96968. *
  96969. * A response packet **must** contain:
  96970. *
  96971. * - **`success`** property : Boolean
  96972. * - **`data`** property : Object
  96973. *
  96974. * The `data` property contains the values of Fields to load. The individual value object for each Field is passed to
  96975. * the Field's {@link Ext.form.field.Field#setValue setValue} method.
  96976. *
  96977. * ## JSON Packets
  96978. *
  96979. * By default, response packets are assumed to be JSON, so for the following form load call:
  96980. *
  96981. * var myFormPanel = new Ext.form.Panel({
  96982. * title: 'Client and routing info',
  96983. * renderTo: Ext.getBody(),
  96984. * defaults: {
  96985. * xtype: 'textfield'
  96986. * },
  96987. * items: [{
  96988. * fieldLabel: 'Client',
  96989. * name: 'clientName'
  96990. * }, {
  96991. * fieldLabel: 'Port of loading',
  96992. * name: 'portOfLoading'
  96993. * }, {
  96994. * fieldLabel: 'Port of discharge',
  96995. * name: 'portOfDischarge'
  96996. * }]
  96997. * });
  96998. * myFormPanel.{@link Ext.form.Panel#getForm getForm}().{@link Ext.form.Basic#load load}({
  96999. * url: '/getRoutingInfo.php',
  97000. * params: {
  97001. * consignmentRef: myConsignmentRef
  97002. * },
  97003. * failure: function(form, action) {
  97004. * Ext.Msg.alert("Load failed", action.result.errorMessage);
  97005. * }
  97006. * });
  97007. *
  97008. * a **success response** packet may look like this:
  97009. *
  97010. * {
  97011. * success: true,
  97012. * data: {
  97013. * clientName: "Fred. Olsen Lines",
  97014. * portOfLoading: "FXT",
  97015. * portOfDischarge: "OSL"
  97016. * }
  97017. * }
  97018. *
  97019. * while a **failure response** packet may look like this:
  97020. *
  97021. * {
  97022. * success: false,
  97023. * errorMessage: "Consignment reference not found"
  97024. * }
  97025. *
  97026. * Other data may be placed into the response for processing the {@link Ext.form.Basic Form}'s callback or event handler
  97027. * methods. The object decoded from this JSON is available in the {@link Ext.form.action.Action#result result} property.
  97028. */
  97029. Ext.define('Ext.form.action.Load', {
  97030. extend:'Ext.form.action.Action',
  97031. requires: ['Ext.data.Connection'],
  97032. alternateClassName: 'Ext.form.Action.Load',
  97033. alias: 'formaction.load',
  97034. type: 'load',
  97035. /**
  97036. * @private
  97037. */
  97038. run: function() {
  97039. Ext.Ajax.request(Ext.apply(
  97040. this.createCallback(),
  97041. {
  97042. method: this.getMethod(),
  97043. url: this.getUrl(),
  97044. headers: this.headers,
  97045. params: this.getParams()
  97046. }
  97047. ));
  97048. },
  97049. /**
  97050. * @private
  97051. */
  97052. onSuccess: function(response){
  97053. var result = this.processResponse(response),
  97054. form = this.form;
  97055. if (result === true || !result.success || !result.data) {
  97056. this.failureType = Ext.form.action.Action.LOAD_FAILURE;
  97057. form.afterAction(this, false);
  97058. return;
  97059. }
  97060. form.clearInvalid();
  97061. form.setValues(result.data);
  97062. form.afterAction(this, true);
  97063. },
  97064. /**
  97065. * @private
  97066. */
  97067. handleResponse: function(response) {
  97068. var reader = this.form.reader,
  97069. rs, data;
  97070. if (reader) {
  97071. rs = reader.read(response);
  97072. data = rs.records && rs.records[0] ? rs.records[0].data : null;
  97073. return {
  97074. success : rs.success,
  97075. data : data
  97076. };
  97077. }
  97078. return Ext.decode(response.responseText);
  97079. }
  97080. });
  97081. /**
  97082. * A class which handles submission of data from {@link Ext.form.Basic Form}s and processes the returned response.
  97083. *
  97084. * Instances of this class are only created by a {@link Ext.form.Basic Form} when
  97085. * {@link Ext.form.Basic#submit submit}ting.
  97086. *
  97087. * # Response Packet Criteria
  97088. *
  97089. * A response packet may contain:
  97090. *
  97091. * - **`success`** property : Boolean - required.
  97092. *
  97093. * - **`errors`** property : Object - optional, contains error messages for invalid fields.
  97094. *
  97095. * # JSON Packets
  97096. *
  97097. * By default, response packets are assumed to be JSON, so a typical response packet may look like this:
  97098. *
  97099. * {
  97100. * success: false,
  97101. * errors: {
  97102. * clientCode: "Client not found",
  97103. * portOfLoading: "This field must not be null"
  97104. * }
  97105. * }
  97106. *
  97107. * Other data may be placed into the response for processing by the {@link Ext.form.Basic}'s callback or event handler
  97108. * methods. The object decoded from this JSON is available in the {@link Ext.form.action.Action#result result} property.
  97109. *
  97110. * Alternatively, if an {@link Ext.form.Basic#errorReader errorReader} is specified as an
  97111. * {@link Ext.data.reader.Xml XmlReader}:
  97112. *
  97113. * errorReader: new Ext.data.reader.Xml({
  97114. * record : 'field',
  97115. * success: '@success'
  97116. * }, [
  97117. * 'id', 'msg'
  97118. * ]
  97119. * )
  97120. *
  97121. * then the results may be sent back in XML format:
  97122. *
  97123. * <?xml version="1.0" encoding="UTF-8"?>
  97124. * <message success="false">
  97125. * <errors>
  97126. * <field>
  97127. * <id>clientCode</id>
  97128. * <msg><![CDATA[Code not found. <br /><i>This is a test validation message from the server </i>]]></msg>
  97129. * </field>
  97130. * <field>
  97131. * <id>portOfLoading</id>
  97132. * <msg><![CDATA[Port not found. <br /><i>This is a test validation message from the server </i>]]></msg>
  97133. * </field>
  97134. * </errors>
  97135. * </message>
  97136. *
  97137. * Other elements may be placed into the response XML for processing by the {@link Ext.form.Basic}'s callback or event
  97138. * handler methods. The XML document is available in the {@link Ext.form.Basic#errorReader errorReader}'s
  97139. * {@link Ext.data.reader.Xml#xmlData xmlData} property.
  97140. */
  97141. Ext.define('Ext.form.action.Submit', {
  97142. extend:'Ext.form.action.Action',
  97143. alternateClassName: 'Ext.form.Action.Submit',
  97144. alias: 'formaction.submit',
  97145. type: 'submit',
  97146. /**
  97147. * @cfg {Boolean} [clientValidation=true]
  97148. * Determines whether a Form's fields are validated in a final call to {@link Ext.form.Basic#isValid isValid} prior
  97149. * to submission. Pass false in the Form's submit options to prevent this.
  97150. */
  97151. // inherit docs
  97152. run : function(){
  97153. var form = this.form;
  97154. if (this.clientValidation === false || form.isValid()) {
  97155. this.doSubmit();
  97156. } else {
  97157. // client validation failed
  97158. this.failureType = Ext.form.action.Action.CLIENT_INVALID;
  97159. form.afterAction(this, false);
  97160. }
  97161. },
  97162. /**
  97163. * @private
  97164. * Performs the submit of the form data.
  97165. */
  97166. doSubmit: function() {
  97167. var formEl,
  97168. ajaxOptions = Ext.apply(this.createCallback(), {
  97169. url: this.getUrl(),
  97170. method: this.getMethod(),
  97171. headers: this.headers
  97172. });
  97173. // For uploads we need to create an actual form that contains the file upload fields,
  97174. // and pass that to the ajax call so it can do its iframe-based submit method.
  97175. if (this.form.hasUpload()) {
  97176. formEl = ajaxOptions.form = this.buildForm();
  97177. ajaxOptions.isUpload = true;
  97178. } else {
  97179. ajaxOptions.params = this.getParams();
  97180. }
  97181. Ext.Ajax.request(ajaxOptions);
  97182. if (formEl) {
  97183. Ext.removeNode(formEl);
  97184. }
  97185. },
  97186. /**
  97187. * @private
  97188. * Builds the full set of parameters from the field values plus any additional configured params.
  97189. */
  97190. getParams: function() {
  97191. var nope = false,
  97192. configParams = this.callParent(),
  97193. fieldParams = this.form.getValues(nope, nope, this.submitEmptyText !== nope);
  97194. return Ext.apply({}, fieldParams, configParams);
  97195. },
  97196. /**
  97197. * @private
  97198. * Builds a form element containing fields corresponding to all the parameters to be
  97199. * submitted (everything returned by {@link #getParams}.
  97200. *
  97201. * NOTE: the form element is automatically added to the DOM, so any code that uses
  97202. * it must remove it from the DOM after finishing with it.
  97203. *
  97204. * @return {HTMLElement}
  97205. */
  97206. buildForm: function() {
  97207. var fieldsSpec = [],
  97208. formSpec,
  97209. formEl,
  97210. basicForm = this.form,
  97211. params = this.getParams(),
  97212. uploadFields = [],
  97213. fields = basicForm.getFields().items,
  97214. f,
  97215. fLen = fields.length,
  97216. field, key, value, v, vLen,
  97217. u, uLen;
  97218. for (f = 0; f < fLen; f++) {
  97219. field = fields[f];
  97220. if (field.isFileUpload()) {
  97221. uploadFields.push(field);
  97222. }
  97223. }
  97224. function addField(name, val) {
  97225. fieldsSpec.push({
  97226. tag: 'input',
  97227. type: 'hidden',
  97228. name: name,
  97229. value: Ext.String.htmlEncode(val)
  97230. });
  97231. }
  97232. for (key in params) {
  97233. if (params.hasOwnProperty(key)) {
  97234. value = params[key];
  97235. if (Ext.isArray(value)) {
  97236. vLen = value.length;
  97237. for (v = 0; v < vLen; v++) {
  97238. addField(key, value[v]);
  97239. }
  97240. } else {
  97241. addField(key, value);
  97242. }
  97243. }
  97244. }
  97245. formSpec = {
  97246. tag: 'form',
  97247. action: this.getUrl(),
  97248. method: this.getMethod(),
  97249. target: this.target || '_self',
  97250. style: 'display:none',
  97251. cn: fieldsSpec
  97252. };
  97253. // Set the proper encoding for file uploads
  97254. if (uploadFields.length) {
  97255. formSpec.encoding = formSpec.enctype = 'multipart/form-data';
  97256. }
  97257. // Create the form
  97258. formEl = Ext.DomHelper.append(Ext.getBody(), formSpec);
  97259. // Special handling for file upload fields: since browser security measures prevent setting
  97260. // their values programatically, and prevent carrying their selected values over when cloning,
  97261. // we have to move the actual field instances out of their components and into the form.
  97262. uLen = uploadFields.length;
  97263. for (u = 0; u < uLen; u++) {
  97264. field = uploadFields[u];
  97265. if (field.rendered) { // can only have a selected file value after being rendered
  97266. formEl.appendChild(field.extractFileInput());
  97267. }
  97268. }
  97269. return formEl;
  97270. },
  97271. /**
  97272. * @private
  97273. */
  97274. onSuccess: function(response) {
  97275. var form = this.form,
  97276. success = true,
  97277. result = this.processResponse(response);
  97278. if (result !== true && !result.success) {
  97279. if (result.errors) {
  97280. form.markInvalid(result.errors);
  97281. }
  97282. this.failureType = Ext.form.action.Action.SERVER_INVALID;
  97283. success = false;
  97284. }
  97285. form.afterAction(this, success);
  97286. },
  97287. /**
  97288. * @private
  97289. */
  97290. handleResponse: function(response) {
  97291. var form = this.form,
  97292. errorReader = form.errorReader,
  97293. rs, errors, i, len, records;
  97294. if (errorReader) {
  97295. rs = errorReader.read(response);
  97296. records = rs.records;
  97297. errors = [];
  97298. if (records) {
  97299. for(i = 0, len = records.length; i < len; i++) {
  97300. errors[i] = records[i].data;
  97301. }
  97302. }
  97303. if (errors.length < 1) {
  97304. errors = null;
  97305. }
  97306. return {
  97307. success : rs.success,
  97308. errors : errors
  97309. };
  97310. }
  97311. return Ext.decode(response.responseText);
  97312. }
  97313. });
  97314. /**
  97315. * A subclass of Ext.dd.DragTracker which handles dragging any Component.
  97316. *
  97317. * This is configured with a Component to be made draggable, and a config object for the {@link Ext.dd.DragTracker}
  97318. * class.
  97319. *
  97320. * A {@link #delegate} may be provided which may be either the element to use as the mousedown target or a {@link
  97321. * Ext.DomQuery} selector to activate multiple mousedown targets.
  97322. *
  97323. * When the Component begins to be dragged, its `beginDrag` method will be called if implemented.
  97324. *
  97325. * When the drag ends, its `endDrag` method will be called if implemented.
  97326. */
  97327. Ext.define('Ext.util.ComponentDragger', {
  97328. extend: 'Ext.dd.DragTracker',
  97329. /**
  97330. * @cfg {Boolean} constrain
  97331. * Specify as `true` to constrain the Component to within the bounds of the {@link #constrainTo} region.
  97332. */
  97333. /**
  97334. * @cfg {String/Ext.Element} delegate
  97335. * A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the Component's encapsulating
  97336. * Element which are the drag handles. This limits dragging to only begin when the matching elements are
  97337. * mousedowned.
  97338. *
  97339. * This may also be a specific child element within the Component's encapsulating element to use as the drag handle.
  97340. */
  97341. /**
  97342. * @cfg {Boolean} constrainDelegate
  97343. * Specify as `true` to constrain the drag handles within the {@link #constrainTo} region.
  97344. */
  97345. autoStart: 500,
  97346. /**
  97347. * Creates new ComponentDragger.
  97348. * @param {Object} comp The Component to provide dragging for.
  97349. * @param {Object} [config] Config object
  97350. */
  97351. constructor: function(comp, config) {
  97352. this.comp = comp;
  97353. this.initialConstrainTo = config.constrainTo;
  97354. this.callParent([ config ]);
  97355. },
  97356. onStart: function(e) {
  97357. var me = this,
  97358. comp = me.comp;
  97359. // Cache the start [X, Y] array
  97360. this.startPosition = comp.el.getXY();
  97361. // If client Component has a ghost method to show a lightweight version of itself
  97362. // then use that as a drag proxy unless configured to liveDrag.
  97363. if (comp.ghost && !comp.liveDrag) {
  97364. me.proxy = comp.ghost();
  97365. me.dragTarget = me.proxy.header.el;
  97366. }
  97367. // Set the constrainTo Region before we start dragging.
  97368. if (me.constrain || me.constrainDelegate) {
  97369. me.constrainTo = me.calculateConstrainRegion();
  97370. }
  97371. if (comp.beginDrag) {
  97372. comp.beginDrag();
  97373. }
  97374. },
  97375. calculateConstrainRegion: function() {
  97376. var me = this,
  97377. comp = me.comp,
  97378. c = me.initialConstrainTo,
  97379. delegateRegion,
  97380. elRegion,
  97381. dragEl = me.proxy ? me.proxy.el : comp.el,
  97382. shadowSize = (!me.constrainDelegate && dragEl.shadow && !dragEl.shadowDisabled) ? dragEl.shadow.getShadowSize() : 0;
  97383. // The configured constrainTo might be a Region or an element
  97384. if (!(c instanceof Ext.util.Region)) {
  97385. c = Ext.fly(c).getViewRegion();
  97386. }
  97387. // Reduce the constrain region to allow for shadow
  97388. if (shadowSize) {
  97389. c.adjust(shadowSize[0], -shadowSize[1], -shadowSize[2], shadowSize[3]);
  97390. }
  97391. // If they only want to constrain the *delegate* to within the constrain region,
  97392. // adjust the region to be larger based on the insets of the delegate from the outer
  97393. // edges of the Component.
  97394. if (!me.constrainDelegate) {
  97395. delegateRegion = Ext.fly(me.dragTarget).getRegion();
  97396. elRegion = dragEl.getRegion();
  97397. c.adjust(
  97398. delegateRegion.top - elRegion.top,
  97399. delegateRegion.right - elRegion.right,
  97400. delegateRegion.bottom - elRegion.bottom,
  97401. delegateRegion.left - elRegion.left
  97402. );
  97403. }
  97404. return c;
  97405. },
  97406. // Move either the ghost Component or the target Component to its new position on drag
  97407. onDrag: function(e) {
  97408. var me = this,
  97409. comp = (me.proxy && !me.comp.liveDrag) ? me.proxy : me.comp,
  97410. offset = me.getOffset(me.constrain || me.constrainDelegate ? 'dragTarget' : null);
  97411. comp.setPagePosition(me.startPosition[0] + offset[0], me.startPosition[1] + offset[1]);
  97412. },
  97413. onEnd: function(e) {
  97414. var comp = this.comp;
  97415. if (this.proxy && !comp.liveDrag) {
  97416. comp.unghost();
  97417. }
  97418. if (comp.endDrag) {
  97419. comp.endDrag();
  97420. }
  97421. }
  97422. });
  97423. /**
  97424. * A specialized panel intended for use as an application window. Windows are floated, {@link #resizable}, and
  97425. * {@link #cfg-draggable} by default. Windows can be {@link #maximizable maximized} to fill the viewport, restored to
  97426. * their prior size, and can be {@link #method-minimize}d.
  97427. *
  97428. * Windows can also be linked to a {@link Ext.ZIndexManager} or managed by the {@link Ext.WindowManager} to provide
  97429. * grouping, activation, to front, to back and other application-specific behavior.
  97430. *
  97431. * By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element specify
  97432. * {@link Ext.Component#renderTo renderTo}.
  97433. *
  97434. * **As with all {@link Ext.container.Container Container}s, it is important to consider how you want the Window to size
  97435. * and arrange any child Components. Choose an appropriate {@link #layout} configuration which lays out child Components
  97436. * in the required manner.**
  97437. *
  97438. * @example
  97439. * Ext.create('Ext.window.Window', {
  97440. * title: 'Hello',
  97441. * height: 200,
  97442. * width: 400,
  97443. * layout: 'fit',
  97444. * items: { // Let's put an empty grid in just to illustrate fit layout
  97445. * xtype: 'grid',
  97446. * border: false,
  97447. * columns: [{header: 'World'}], // One header just for show. There's no data,
  97448. * store: Ext.create('Ext.data.ArrayStore', {}) // A dummy empty data store
  97449. * }
  97450. * }).show();
  97451. */
  97452. Ext.define('Ext.window.Window', {
  97453. extend: 'Ext.panel.Panel',
  97454. alternateClassName: 'Ext.Window',
  97455. requires: ['Ext.util.ComponentDragger', 'Ext.util.Region', 'Ext.EventManager'],
  97456. alias: 'widget.window',
  97457. /**
  97458. * @cfg {Number} x
  97459. * The X position of the left edge of the window on initial showing. Defaults to centering the Window within the
  97460. * width of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
  97461. */
  97462. /**
  97463. * @cfg {Number} y
  97464. * The Y position of the top edge of the window on initial showing. Defaults to centering the Window within the
  97465. * height of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
  97466. */
  97467. /**
  97468. * @cfg {Boolean} [modal=false]
  97469. * True to make the window modal and mask everything behind it when displayed, false to display it without
  97470. * restricting access to other UI elements.
  97471. */
  97472. /**
  97473. * @cfg {String/Ext.Element} [animateTarget=null]
  97474. * Id or element from which the window should animate while opening.
  97475. */
  97476. /**
  97477. * @cfg {String/Number/Ext.Component} defaultFocus
  97478. * Specifies a Component to receive focus when this Window is focused.
  97479. *
  97480. * This may be one of:
  97481. *
  97482. * - The index of a footer Button.
  97483. * - The id or {@link Ext.AbstractComponent#itemId} of a descendant Component.
  97484. * - A Component.
  97485. */
  97486. /**
  97487. * @cfg {Function} onEsc
  97488. * Allows override of the built-in processing for the escape key. Default action is to close the Window (performing
  97489. * whatever action is specified in {@link #closeAction}. To prevent the Window closing when the escape key is
  97490. * pressed, specify this as {@link Ext#emptyFn Ext.emptyFn}.
  97491. */
  97492. /**
  97493. * @cfg {Boolean} [collapsed=false]
  97494. * True to render the window collapsed, false to render it expanded. Note that if {@link #expandOnShow}
  97495. * is true (the default) it will override the `collapsed` config and the window will always be
  97496. * expanded when shown.
  97497. */
  97498. /**
  97499. * @cfg {Boolean} [maximized=false]
  97500. * True to initially display the window in a maximized state.
  97501. */
  97502. /**
  97503. * @cfg {String} [baseCls='x-window']
  97504. * The base CSS class to apply to this panel's element.
  97505. */
  97506. baseCls: Ext.baseCSSPrefix + 'window',
  97507. /**
  97508. * @cfg {Boolean/Object} resizable
  97509. * Specify as `true` to allow user resizing at each edge and corner of the window, false to disable resizing.
  97510. *
  97511. * This may also be specified as a config object to Ext.resizer.Resizer
  97512. */
  97513. resizable: true,
  97514. /**
  97515. * @cfg {Boolean} draggable
  97516. * True to allow the window to be dragged by the header bar, false to disable dragging. Note that
  97517. * by default the window will be centered in the viewport, so if dragging is disabled the window may need to be
  97518. * positioned programmatically after render (e.g., myWindow.setPosition(100, 100);).
  97519. */
  97520. draggable: true,
  97521. /**
  97522. * @cfg {Boolean} constrain
  97523. * True to constrain the window within its containing element, false to allow it to fall outside of its containing
  97524. * element. By default the window will be rendered to document.body. To render and constrain the window within
  97525. * another element specify {@link #renderTo}. Optionally the header only can be constrained
  97526. * using {@link #constrainHeader}.
  97527. */
  97528. constrain: false,
  97529. /**
  97530. * @cfg {Boolean} constrainHeader
  97531. * True to constrain the window header within its containing element (allowing the window body to fall outside of
  97532. * its containing element) or false to allow the header to fall outside its containing element.
  97533. * Optionally the entire window can be constrained using {@link #constrain}.
  97534. */
  97535. constrainHeader: false,
  97536. /**
  97537. * @cfg {Ext.util.Region/Ext.Element} constrainTo
  97538. * A {@link Ext.util.Region Region} (or an element from which a Region measurement will be read) which is used
  97539. * to constrain the window.
  97540. */
  97541. /**
  97542. * @cfg {Boolean} plain
  97543. * True to render the window body with a transparent background so that it will blend into the framing elements,
  97544. * false to add a lighter background color to visually highlight the body element and separate it more distinctly
  97545. * from the surrounding frame.
  97546. */
  97547. plain: false,
  97548. /**
  97549. * @cfg {Boolean} minimizable
  97550. * True to display the 'minimize' tool button and allow the user to minimize the window, false to hide the button
  97551. * and disallow minimizing the window. Note that this button provides no implementation -- the
  97552. * behavior of minimizing a window is implementation-specific, so the minimize event must be handled and a custom
  97553. * minimize behavior implemented for this option to be useful.
  97554. */
  97555. minimizable: false,
  97556. /**
  97557. * @cfg {Boolean} maximizable
  97558. * True to display the 'maximize' tool button and allow the user to maximize the window, false to hide the button
  97559. * and disallow maximizing the window. Note that when a window is maximized, the tool button
  97560. * will automatically change to a 'restore' button with the appropriate behavior already built-in that will restore
  97561. * the window to its previous size.
  97562. */
  97563. maximizable: false,
  97564. // inherit docs
  97565. minHeight: 50,
  97566. // inherit docs
  97567. minWidth: 50,
  97568. /**
  97569. * @cfg {Boolean} expandOnShow
  97570. * True to always expand the window when it is displayed, false to keep it in its current state (which may be
  97571. * {@link #collapsed}) when displayed.
  97572. */
  97573. expandOnShow: true,
  97574. // inherited docs, same default
  97575. collapsible: false,
  97576. /**
  97577. * @cfg {Boolean} closable
  97578. * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
  97579. * disallow closing the window.
  97580. *
  97581. * By default, when close is requested by either clicking the close button in the header or pressing ESC when the
  97582. * Window has focus, the {@link #method-close} method will be called. This will _{@link Ext.Component#method-destroy destroy}_ the
  97583. * Window and its content meaning that it may not be reused.
  97584. *
  97585. * To make closing a Window _hide_ the Window so that it may be reused, set {@link #closeAction} to 'hide'.
  97586. */
  97587. closable: true,
  97588. /**
  97589. * @cfg {Boolean} hidden
  97590. * Render this Window hidden. If `true`, the {@link #method-hide} method will be called internally.
  97591. */
  97592. hidden: true,
  97593. /**
  97594. * @cfg
  97595. * @inheritdoc
  97596. * Windows render to the body on first show.
  97597. */
  97598. autoRender: true,
  97599. /**
  97600. * @cfg
  97601. * @inheritdoc
  97602. * Windows hide using offsets in order to preserve the scroll positions of their descendants.
  97603. */
  97604. hideMode: 'offsets',
  97605. /**
  97606. * @cfg
  97607. * @private
  97608. */
  97609. floating: true,
  97610. ariaRole: 'alertdialog',
  97611. itemCls: Ext.baseCSSPrefix + 'window-item',
  97612. initialAlphaNum: /^[a-z0-9]/,
  97613. overlapHeader: true,
  97614. ignoreHeaderBorderManagement: true,
  97615. // Flag to Renderable to always look up the framing styles for this Component
  97616. alwaysFramed: true,
  97617. /**
  97618. * @property {Boolean} isWindow
  97619. * `true` in this class to identify an object as an instantiated Window, or subclass thereof.
  97620. */
  97621. isWindow: true,
  97622. // private
  97623. initComponent: function() {
  97624. var me = this;
  97625. // Explicitly set frame to false, since alwaysFramed is
  97626. // true, we only want to lookup framing in a specific instance
  97627. me.frame = false;
  97628. me.callParent();
  97629. me.addEvents(
  97630. /**
  97631. * @event activate
  97632. * Fires after the window has been visually activated via {@link #setActive}.
  97633. * @param {Ext.window.Window} this
  97634. */
  97635. /**
  97636. * @event deactivate
  97637. * Fires after the window has been visually deactivated via {@link #setActive}.
  97638. * @param {Ext.window.Window} this
  97639. */
  97640. /**
  97641. * @event resize
  97642. * Fires after the window has been resized.
  97643. * @param {Ext.window.Window} this
  97644. * @param {Number} width The window's new width
  97645. * @param {Number} height The window's new height
  97646. */
  97647. 'resize',
  97648. /**
  97649. * @event maximize
  97650. * Fires after the window has been maximized.
  97651. * @param {Ext.window.Window} this
  97652. */
  97653. 'maximize',
  97654. /**
  97655. * @event minimize
  97656. * Fires after the window has been minimized.
  97657. * @param {Ext.window.Window} this
  97658. */
  97659. 'minimize',
  97660. /**
  97661. * @event restore
  97662. * Fires after the window has been restored to its original size after being maximized.
  97663. * @param {Ext.window.Window} this
  97664. */
  97665. 'restore'
  97666. );
  97667. if (me.plain) {
  97668. me.addClsWithUI('plain');
  97669. }
  97670. if (me.modal) {
  97671. me.ariaRole = 'dialog';
  97672. }
  97673. // clickToRaise
  97674. if (me.floating) {
  97675. me.on({
  97676. element: 'el',
  97677. mousedown: me.onMouseDown,
  97678. scope: me
  97679. });
  97680. }
  97681. me.addStateEvents(['maximize', 'restore', 'resize', 'dragend']);
  97682. },
  97683. getElConfig: function () {
  97684. var me = this,
  97685. elConfig;
  97686. elConfig = me.callParent();
  97687. elConfig.tabIndex = -1;
  97688. return elConfig;
  97689. },
  97690. // State Management
  97691. // private
  97692. getState: function() {
  97693. var me = this,
  97694. state = me.callParent() || {},
  97695. maximized = !!me.maximized;
  97696. state.maximized = maximized;
  97697. Ext.apply(state, {
  97698. size: maximized ? me.restoreSize : me.getSize(),
  97699. pos: maximized ? me.restorePos : me.getPosition()
  97700. });
  97701. return state;
  97702. },
  97703. applyState: function(state){
  97704. var me = this;
  97705. if (state) {
  97706. me.maximized = state.maximized;
  97707. if (me.maximized) {
  97708. me.hasSavedRestore = true;
  97709. me.restoreSize = state.size;
  97710. me.restorePos = state.pos;
  97711. } else {
  97712. Ext.apply(me, {
  97713. width: state.size.width,
  97714. height: state.size.height,
  97715. x: state.pos[0],
  97716. y: state.pos[1]
  97717. });
  97718. }
  97719. }
  97720. },
  97721. // private
  97722. onMouseDown: function (e) {
  97723. var preventFocus;
  97724. if (this.floating) {
  97725. if (Ext.fly(e.getTarget()).focusable()) {
  97726. preventFocus = true;
  97727. }
  97728. this.toFront(preventFocus);
  97729. }
  97730. },
  97731. // private
  97732. onRender: function(ct, position) {
  97733. var me = this;
  97734. me.callParent(arguments);
  97735. me.focusEl = me.el;
  97736. // Double clicking a header will toggleMaximize
  97737. if (me.maximizable) {
  97738. me.header.on({
  97739. scope: me,
  97740. dblclick: me.toggleMaximize
  97741. });
  97742. }
  97743. },
  97744. // private
  97745. afterRender: function() {
  97746. var me = this,
  97747. keyMap;
  97748. me.callParent();
  97749. // Initialize
  97750. if (me.maximized) {
  97751. me.maximized = false;
  97752. me.maximize();
  97753. }
  97754. if (me.closable) {
  97755. keyMap = me.getKeyMap();
  97756. keyMap.on(27, me.onEsc, me);
  97757. } else {
  97758. keyMap = me.keyMap;
  97759. }
  97760. if (keyMap && me.hidden) {
  97761. keyMap.disable();
  97762. }
  97763. },
  97764. /**
  97765. * @private
  97766. * Override Component.initDraggable.
  97767. * Window uses the header element as the delegate.
  97768. */
  97769. initDraggable: function() {
  97770. var me = this,
  97771. ddConfig;
  97772. if (!me.header) {
  97773. me.updateHeader(true);
  97774. }
  97775. /*
  97776. * Check the header here again. If for whatever reason it wasn't created in
  97777. * updateHeader (we were configured with header: false) then we'll just ignore the rest since the
  97778. * header acts as the drag handle.
  97779. */
  97780. if (me.header) {
  97781. ddConfig = Ext.applyIf({
  97782. el: me.el,
  97783. delegate: '#' + Ext.escapeId(me.header.id)
  97784. }, me.draggable);
  97785. // Add extra configs if Window is specified to be constrained
  97786. if (me.constrain || me.constrainHeader) {
  97787. ddConfig.constrain = me.constrain;
  97788. ddConfig.constrainDelegate = me.constrainHeader;
  97789. ddConfig.constrainTo = me.constrainTo || me.container;
  97790. }
  97791. /**
  97792. * @property {Ext.util.ComponentDragger} dd
  97793. * If this Window is configured {@link #cfg-draggable}, this property will contain an instance of
  97794. * {@link Ext.util.ComponentDragger} (A subclass of {@link Ext.dd.DragTracker DragTracker}) which handles dragging
  97795. * the Window's DOM Element, and constraining according to the {@link #constrain} and {@link #constrainHeader} .
  97796. *
  97797. * This has implementations of `onBeforeStart`, `onDrag` and `onEnd` which perform the dragging action. If
  97798. * extra logic is needed at these points, use {@link Ext.Function#createInterceptor createInterceptor} or
  97799. * {@link Ext.Function#createSequence createSequence} to augment the existing implementations.
  97800. */
  97801. me.dd = new Ext.util.ComponentDragger(this, ddConfig);
  97802. me.relayEvents(me.dd, ['dragstart', 'drag', 'dragend']);
  97803. }
  97804. },
  97805. // private
  97806. onEsc: function(k, e) {
  97807. // Only process ESC if the FocusManager is not doing it
  97808. if (!Ext.FocusManager || !Ext.FocusManager.enabled || Ext.FocusManager.focusedCmp === this) {
  97809. e.stopEvent();
  97810. this.close();
  97811. }
  97812. },
  97813. // private
  97814. beforeDestroy: function() {
  97815. var me = this;
  97816. if (me.rendered) {
  97817. delete this.animateTarget;
  97818. me.hide();
  97819. Ext.destroy(
  97820. me.keyMap
  97821. );
  97822. }
  97823. me.callParent();
  97824. },
  97825. /**
  97826. * @private
  97827. * Contribute class-specific tools to the header.
  97828. * Called by Panel's initTools.
  97829. */
  97830. addTools: function() {
  97831. var me = this;
  97832. // Call Panel's initTools
  97833. me.callParent();
  97834. if (me.minimizable) {
  97835. me.addTool({
  97836. type: 'minimize',
  97837. handler: Ext.Function.bind(me.minimize, me, [])
  97838. });
  97839. }
  97840. if (me.maximizable) {
  97841. me.addTool({
  97842. type: 'maximize',
  97843. handler: Ext.Function.bind(me.maximize, me, [])
  97844. });
  97845. me.addTool({
  97846. type: 'restore',
  97847. handler: Ext.Function.bind(me.restore, me, []),
  97848. hidden: true
  97849. });
  97850. }
  97851. },
  97852. /**
  97853. * @private
  97854. * Returns the focus holder element associated with this Window. By default, this is the Window's element.
  97855. * @returns {Ext.Element/Ext.Component} the focus holding element or Component.
  97856. */
  97857. getFocusEl: function() {
  97858. return this.getDefaultFocus();
  97859. },
  97860. /**
  97861. * Gets the configured default focus item. If a {@link #defaultFocus} is set, it will
  97862. * receive focus when the Window's <code>focus</code> method is called, otherwise the
  97863. * Window itself will receive focus.
  97864. */
  97865. getDefaultFocus: function() {
  97866. var me = this,
  97867. result,
  97868. defaultComp = me.defaultButton || me.defaultFocus,
  97869. selector;
  97870. if (defaultComp !== undefined) {
  97871. // Number is index of Button
  97872. if (Ext.isNumber(defaultComp)) {
  97873. result = me.query('button')[defaultComp];
  97874. }
  97875. // String is ID or CQ selector
  97876. else if (Ext.isString(defaultComp)) {
  97877. selector = defaultComp;
  97878. // Try id/itemId match if selector begins with alphanumeric
  97879. if (selector.match(me.initialAlphaNum)) {
  97880. result = me.down('#' + selector);
  97881. }
  97882. // If not found, use as selector
  97883. if (!result) {
  97884. result = me.down(selector);
  97885. }
  97886. }
  97887. // Otherwise, if it's got a focus method, use it
  97888. else if (defaultComp.focus) {
  97889. result = defaultComp;
  97890. }
  97891. }
  97892. return result || me.el;
  97893. },
  97894. /**
  97895. * @private
  97896. * Called when a Component's focusEl receives focus.
  97897. * If there is a valid default focus Component to jump to, focus that,
  97898. * otherwise continue as usual, focus this Component.
  97899. */
  97900. onFocus: function() {
  97901. var me = this,
  97902. focusDescendant;
  97903. // If the FocusManager is enabled, then we must noy jumpt to focus the default focus. We must focus the Window
  97904. if ((Ext.FocusManager && Ext.FocusManager.enabled) || ((focusDescendant = me.getDefaultFocus()) === me)) {
  97905. me.callParent(arguments);
  97906. } else {
  97907. focusDescendant.focus();
  97908. }
  97909. },
  97910. beforeLayout: function () {
  97911. var shadow = this.el.shadow;
  97912. this.callParent();
  97913. if (shadow) {
  97914. shadow.hide();
  97915. }
  97916. },
  97917. onShow: function() {
  97918. var me = this;
  97919. me.callParent(arguments);
  97920. if (me.expandOnShow) {
  97921. me.expand(false);
  97922. }
  97923. me.syncMonitorWindowResize();
  97924. if (me.keyMap) {
  97925. me.keyMap.enable();
  97926. }
  97927. },
  97928. // private
  97929. doClose: function() {
  97930. var me = this;
  97931. // Being called as callback after going through the hide call below
  97932. if (me.hidden) {
  97933. me.fireEvent('close', me);
  97934. if (me.closeAction == 'destroy') {
  97935. this.destroy();
  97936. }
  97937. } else {
  97938. // close after hiding
  97939. me.hide(me.animateTarget, me.doClose, me);
  97940. }
  97941. },
  97942. // private
  97943. afterHide: function() {
  97944. var me = this;
  97945. // No longer subscribe to resizing now that we're hidden
  97946. me.syncMonitorWindowResize();
  97947. // Turn off keyboard handling once window is hidden
  97948. if (me.keyMap) {
  97949. me.keyMap.disable();
  97950. }
  97951. // Perform superclass's afterHide tasks.
  97952. me.callParent(arguments);
  97953. },
  97954. // private
  97955. onWindowResize: function() {
  97956. var me = this,
  97957. sizeModel;
  97958. if (me.maximized) {
  97959. me.fitContainer();
  97960. } else {
  97961. sizeModel = me.getSizeModel();
  97962. if (sizeModel.width.natural || sizeModel.height.natural) {
  97963. me.updateLayout();
  97964. }
  97965. }
  97966. me.doConstrain();
  97967. },
  97968. /**
  97969. * Placeholder method for minimizing the window. By default, this method simply fires the {@link #event-minimize} event
  97970. * since the behavior of minimizing a window is application-specific. To implement custom minimize behavior, either
  97971. * the minimize event can be handled or this method can be overridden.
  97972. * @return {Ext.window.Window} this
  97973. */
  97974. minimize: function() {
  97975. this.fireEvent('minimize', this);
  97976. return this;
  97977. },
  97978. afterCollapse: function() {
  97979. var me = this;
  97980. if (me.maximizable) {
  97981. me.tools.maximize.hide();
  97982. me.tools.restore.hide();
  97983. }
  97984. if (me.resizer) {
  97985. me.resizer.disable();
  97986. }
  97987. me.callParent(arguments);
  97988. },
  97989. afterExpand: function() {
  97990. var me = this;
  97991. if (me.maximized) {
  97992. me.tools.restore.show();
  97993. } else if (me.maximizable) {
  97994. me.tools.maximize.show();
  97995. }
  97996. if (me.resizer) {
  97997. me.resizer.enable();
  97998. }
  97999. me.callParent(arguments);
  98000. },
  98001. /**
  98002. * Fits the window within its current container and automatically replaces the {@link #maximizable 'maximize' tool
  98003. * button} with the 'restore' tool button. Also see {@link #toggleMaximize}.
  98004. * @return {Ext.window.Window} this
  98005. */
  98006. maximize: function() {
  98007. var me = this;
  98008. if (!me.maximized) {
  98009. me.expand(false);
  98010. if (!me.hasSavedRestore) {
  98011. me.restoreSize = me.getSize();
  98012. me.restorePos = me.getPosition(true);
  98013. }
  98014. if (me.maximizable) {
  98015. me.tools.maximize.hide();
  98016. me.tools.restore.show();
  98017. }
  98018. me.maximized = true;
  98019. me.el.disableShadow();
  98020. if (me.dd) {
  98021. me.dd.disable();
  98022. }
  98023. if (me.resizer) {
  98024. me.resizer.disable();
  98025. }
  98026. if (me.collapseTool) {
  98027. me.collapseTool.hide();
  98028. }
  98029. me.el.addCls(Ext.baseCSSPrefix + 'window-maximized');
  98030. me.container.addCls(Ext.baseCSSPrefix + 'window-maximized-ct');
  98031. me.syncMonitorWindowResize();
  98032. me.fitContainer();
  98033. me.fireEvent('maximize', me);
  98034. }
  98035. return me;
  98036. },
  98037. /**
  98038. * Restores a {@link #maximizable maximized} window back to its original size and position prior to being maximized
  98039. * and also replaces the 'restore' tool button with the 'maximize' tool button. Also see {@link #toggleMaximize}.
  98040. * @return {Ext.window.Window} this
  98041. */
  98042. restore: function() {
  98043. var me = this,
  98044. tools = me.tools;
  98045. if (me.maximized) {
  98046. delete me.hasSavedRestore;
  98047. me.removeCls(Ext.baseCSSPrefix + 'window-maximized');
  98048. // Toggle tool visibility
  98049. if (tools.restore) {
  98050. tools.restore.hide();
  98051. }
  98052. if (tools.maximize) {
  98053. tools.maximize.show();
  98054. }
  98055. if (me.collapseTool) {
  98056. me.collapseTool.show();
  98057. }
  98058. me.maximized = false;
  98059. // Restore the position/sizing
  98060. me.setPosition(me.restorePos);
  98061. me.setSize(me.restoreSize);
  98062. // Unset old position/sizing
  98063. delete me.restorePos;
  98064. delete me.restoreSize;
  98065. me.el.enableShadow(true);
  98066. // Allow users to drag and drop again
  98067. if (me.dd) {
  98068. me.dd.enable();
  98069. }
  98070. if (me.resizer) {
  98071. me.resizer.enable();
  98072. }
  98073. me.container.removeCls(Ext.baseCSSPrefix + 'window-maximized-ct');
  98074. me.syncMonitorWindowResize();
  98075. me.doConstrain();
  98076. me.fireEvent('restore', me);
  98077. }
  98078. return me;
  98079. },
  98080. /**
  98081. * Synchronizes the presence of our listener for window resize events. This method
  98082. * should be called whenever this status might change.
  98083. * @private
  98084. */
  98085. syncMonitorWindowResize: function () {
  98086. var me = this,
  98087. currentlyMonitoring = me._monitoringResize,
  98088. // all the states where we should be listening to window resize:
  98089. yes = me.monitorResize || me.constrain || me.constrainHeader || me.maximized,
  98090. // all the states where we veto this:
  98091. veto = me.hidden || me.destroying || me.isDestroyed;
  98092. if (yes && !veto) {
  98093. // we should be listening...
  98094. if (!currentlyMonitoring) {
  98095. // but we aren't, so set it up
  98096. Ext.EventManager.onWindowResize(me.onWindowResize, me);
  98097. me._monitoringResize = true;
  98098. }
  98099. } else if (currentlyMonitoring) {
  98100. // we should not be listening, but we are, so tear it down
  98101. Ext.EventManager.removeResizeListener(me.onWindowResize, me);
  98102. me._monitoringResize = false;
  98103. }
  98104. },
  98105. /**
  98106. * A shortcut method for toggling between {@link #method-maximize} and {@link #method-restore} based on the current maximized
  98107. * state of the window.
  98108. * @return {Ext.window.Window} this
  98109. */
  98110. toggleMaximize: function() {
  98111. return this[this.maximized ? 'restore': 'maximize']();
  98112. }
  98113. });
  98114. /**
  98115. * Layout class for components with {@link Ext.form.Labelable field labeling}, handling the sizing and alignment of
  98116. * the form control, label, and error message treatment.
  98117. * @private
  98118. */
  98119. Ext.define('Ext.layout.component.field.Field', {
  98120. /* Begin Definitions */
  98121. extend: 'Ext.layout.component.Auto',
  98122. alias: 'layout.field',
  98123. uses: ['Ext.tip.QuickTip', 'Ext.util.TextMetrics', 'Ext.util.CSS'],
  98124. /* End Definitions */
  98125. type: 'field',
  98126. naturalSizingProp: 'size',
  98127. beginLayout: function(ownerContext) {
  98128. var me = this,
  98129. owner = me.owner,
  98130. widthModel = ownerContext.widthModel,
  98131. ownerNaturalSize = owner[me.naturalSizingProp],
  98132. width;
  98133. me.callParent(arguments);
  98134. ownerContext.labelStrategy = me.getLabelStrategy();
  98135. ownerContext.errorStrategy = me.getErrorStrategy();
  98136. ownerContext.labelContext = ownerContext.getEl('labelEl');
  98137. ownerContext.bodyCellContext = ownerContext.getEl('bodyEl');
  98138. ownerContext.inputContext = ownerContext.getEl('inputEl');
  98139. ownerContext.errorContext = ownerContext.getEl('errorEl');
  98140. // width:100% on an element inside a table in IE6/7 "strict" sizes the content box.
  98141. // store the input element's border and padding info so that subclasses can take it into consideration if needed
  98142. if ((Ext.isIE6 || Ext.isIE7) && Ext.isStrict && ownerContext.inputContext) {
  98143. me.ieInputWidthAdjustment = ownerContext.inputContext.getPaddingInfo().width + ownerContext.inputContext.getBorderInfo().width;
  98144. }
  98145. // perform preparation on the label and error (setting css classes, qtips, etc.)
  98146. ownerContext.labelStrategy.prepare(ownerContext, owner);
  98147. ownerContext.errorStrategy.prepare(ownerContext, owner);
  98148. // Body cell must stretch to use up available width unless the field is auto width
  98149. if (widthModel.shrinkWrap) {
  98150. // When the width needs to be auto, table-layout cannot be fixed
  98151. me.beginLayoutShrinkWrap(ownerContext);
  98152. } else if (widthModel.natural) {
  98153. // When a size specified, natural becomes fixed width unless the inpiutWidth is specified - we shrinkwrap that
  98154. if (typeof ownerNaturalSize == 'number' && !owner.inputWidth) {
  98155. me.beginLayoutFixed(ownerContext, (width = ownerNaturalSize * 6.5 + 20), 'px');
  98156. }
  98157. // Otherwise it is the same as shrinkWrap
  98158. else {
  98159. me.beginLayoutShrinkWrap(ownerContext);
  98160. }
  98161. ownerContext.setWidth(width, false);
  98162. } else {
  98163. me.beginLayoutFixed(ownerContext, '100', '%');
  98164. }
  98165. },
  98166. beginLayoutFixed: function (ownerContext, width, suffix) {
  98167. var owner = ownerContext.target,
  98168. inputEl = owner.inputEl,
  98169. inputWidth = owner.inputWidth;
  98170. owner.el.setStyle('table-layout', 'fixed');
  98171. owner.bodyEl.setStyle('width', width + suffix);
  98172. if (inputEl && inputWidth) {
  98173. inputEl.setStyle('width', inputWidth + 'px');
  98174. }
  98175. ownerContext.isFixed = true;
  98176. },
  98177. beginLayoutShrinkWrap: function (ownerContext) {
  98178. var owner = ownerContext.target,
  98179. inputEl = owner.inputEl,
  98180. inputWidth = owner.inputWidth;
  98181. if (inputEl && inputEl.dom) {
  98182. inputEl.dom.removeAttribute('size');
  98183. if (inputWidth) {
  98184. inputEl.setStyle('width', inputWidth + 'px');
  98185. }
  98186. }
  98187. owner.el.setStyle('table-layout', 'auto');
  98188. owner.bodyEl.setStyle('width', '');
  98189. },
  98190. finishedLayout: function(ownerContext){
  98191. var owner = this.owner;
  98192. this.callParent(arguments);
  98193. ownerContext.labelStrategy.finishedLayout(ownerContext, owner);
  98194. ownerContext.errorStrategy.finishedLayout(ownerContext, owner);
  98195. },
  98196. calculateOwnerHeightFromContentHeight: function(ownerContext, contentHeight) {
  98197. return contentHeight;
  98198. },
  98199. measureContentHeight: function (ownerContext) {
  98200. return ownerContext.el.getHeight();
  98201. },
  98202. measureContentWidth: function (ownerContext) {
  98203. return ownerContext.el.getWidth();
  98204. },
  98205. measureLabelErrorHeight: function (ownerContext) {
  98206. return ownerContext.labelStrategy.getHeight(ownerContext) +
  98207. ownerContext.errorStrategy.getHeight(ownerContext);
  98208. },
  98209. onFocus: function() {
  98210. this.getErrorStrategy().onFocus(this.owner);
  98211. },
  98212. /**
  98213. * Return the set of strategy functions from the {@link #labelStrategies labelStrategies collection}
  98214. * that is appropriate for the field's {@link Ext.form.Labelable#labelAlign labelAlign} config.
  98215. */
  98216. getLabelStrategy: function() {
  98217. var me = this,
  98218. strategies = me.labelStrategies,
  98219. labelAlign = me.owner.labelAlign;
  98220. return strategies[labelAlign] || strategies.base;
  98221. },
  98222. /**
  98223. * Return the set of strategy functions from the {@link #errorStrategies errorStrategies collection}
  98224. * that is appropriate for the field's {@link Ext.form.Labelable#msgTarget msgTarget} config.
  98225. */
  98226. getErrorStrategy: function() {
  98227. var me = this,
  98228. owner = me.owner,
  98229. strategies = me.errorStrategies,
  98230. msgTarget = owner.msgTarget;
  98231. return !owner.preventMark && Ext.isString(msgTarget) ?
  98232. (strategies[msgTarget] || strategies.elementId) :
  98233. strategies.none;
  98234. },
  98235. /**
  98236. * Collection of named strategies for laying out and adjusting labels to accommodate error messages.
  98237. * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#labelAlign} config.
  98238. */
  98239. labelStrategies: (function() {
  98240. var base = {
  98241. prepare: function(ownerContext, owner) {
  98242. var cls = owner.labelCls + '-' + owner.labelAlign,
  98243. labelEl = owner.labelEl;
  98244. if (labelEl) {
  98245. labelEl.addCls(cls);
  98246. }
  98247. },
  98248. getHeight: function () {
  98249. return 0;
  98250. },
  98251. finishedLayout: Ext.emptyFn
  98252. };
  98253. return {
  98254. base: base,
  98255. /**
  98256. * Label displayed above the bodyEl
  98257. */
  98258. top: Ext.applyIf({
  98259. getHeight: function (ownerContext) {
  98260. var labelContext = ownerContext.labelContext,
  98261. props = labelContext.props,
  98262. height = props.height;
  98263. if (height === undefined) {
  98264. props.height = height = labelContext.el.getHeight();
  98265. }
  98266. return height;
  98267. }
  98268. }, base),
  98269. /**
  98270. * Label displayed to the left of the bodyEl
  98271. */
  98272. left: base,
  98273. /**
  98274. * Same as left, only difference is text-align in CSS
  98275. */
  98276. right: base
  98277. };
  98278. }()),
  98279. /**
  98280. * Collection of named strategies for laying out and adjusting insets to accommodate error messages.
  98281. * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#msgTarget} config.
  98282. */
  98283. errorStrategies: (function() {
  98284. function showTip(owner) {
  98285. var tip = Ext.layout.component.field.Field.tip,
  98286. target;
  98287. if (tip && tip.isVisible()) {
  98288. target = tip.activeTarget;
  98289. if (target && target.el === owner.getActionEl().dom) {
  98290. tip.toFront(true);
  98291. }
  98292. }
  98293. }
  98294. var applyIf = Ext.applyIf,
  98295. emptyFn = Ext.emptyFn,
  98296. iconCls = Ext.baseCSSPrefix + 'form-invalid-icon',
  98297. iconWidth,
  98298. base = {
  98299. prepare: function(ownerContext, owner) {
  98300. var el = owner.errorEl;
  98301. if (el) {
  98302. el.setDisplayed(false);
  98303. }
  98304. },
  98305. getHeight: function () {
  98306. return 0;
  98307. },
  98308. onFocus: emptyFn,
  98309. finishedLayout: emptyFn
  98310. };
  98311. return {
  98312. none: base,
  98313. /**
  98314. * Error displayed as icon (with QuickTip on hover) to right of the bodyEl
  98315. */
  98316. side: applyIf({
  98317. prepare: function(ownerContext, owner) {
  98318. var errorEl = owner.errorEl,
  98319. sideErrorCell = owner.sideErrorCell,
  98320. displayError = owner.hasActiveError(),
  98321. tempEl;
  98322. // Capture error icon width once
  98323. if (!iconWidth) {
  98324. iconWidth = (tempEl = Ext.getBody().createChild({style: 'position:absolute', cls: iconCls})).getWidth();
  98325. tempEl.remove();
  98326. }
  98327. errorEl.addCls(iconCls);
  98328. errorEl.set({'data-errorqtip': owner.getActiveError() || ''});
  98329. if (owner.autoFitErrors) {
  98330. errorEl.setDisplayed(displayError);
  98331. }
  98332. // Not autofitting, the space must still be allocated.
  98333. else {
  98334. errorEl.setVisible(displayError);
  98335. }
  98336. // If we are auto fitting, then hide and show the entire cell
  98337. if (sideErrorCell && owner.autoFitErrors) {
  98338. sideErrorCell.setDisplayed(displayError);
  98339. }
  98340. owner.bodyEl.dom.colSpan = owner.getBodyColspan();
  98341. // TODO: defer the tip call until after the layout to avoid immediate DOM reads now
  98342. Ext.layout.component.field.Field.initTip();
  98343. },
  98344. onFocus: showTip
  98345. }, base),
  98346. /**
  98347. * Error message displayed underneath the bodyEl
  98348. */
  98349. under: applyIf({
  98350. prepare: function(ownerContext, owner) {
  98351. var errorEl = owner.errorEl,
  98352. cls = Ext.baseCSSPrefix + 'form-invalid-under';
  98353. errorEl.addCls(cls);
  98354. errorEl.setDisplayed(owner.hasActiveError());
  98355. },
  98356. getHeight: function (ownerContext) {
  98357. var height = 0,
  98358. errorContext, props;
  98359. if (ownerContext.target.hasActiveError()) {
  98360. errorContext = ownerContext.errorContext;
  98361. props = errorContext.props;
  98362. height = props.height;
  98363. if (height === undefined) {
  98364. props.height = height = errorContext.el.getHeight();
  98365. }
  98366. }
  98367. return height;
  98368. }
  98369. }, base),
  98370. /**
  98371. * Error displayed as QuickTip on hover of the field container
  98372. */
  98373. qtip: applyIf({
  98374. prepare: function(ownerContext, owner) {
  98375. Ext.layout.component.field.Field.initTip();
  98376. owner.getActionEl().set({'data-errorqtip': owner.getActiveError() || ''});
  98377. },
  98378. onFocus: showTip
  98379. }, base),
  98380. /**
  98381. * Error displayed as title tip on hover of the field container
  98382. */
  98383. title: applyIf({
  98384. prepare: function(ownerContext, owner) {
  98385. owner.el.set({'title': owner.getActiveError() || ''});
  98386. }
  98387. }, base),
  98388. /**
  98389. * Error message displayed as content of an element with a given id elsewhere in the app
  98390. */
  98391. elementId: applyIf({
  98392. prepare: function(ownerContext, owner) {
  98393. var targetEl = Ext.fly(owner.msgTarget);
  98394. if (targetEl) {
  98395. targetEl.dom.innerHTML = owner.getActiveError() || '';
  98396. targetEl.setDisplayed(owner.hasActiveError());
  98397. }
  98398. }
  98399. }, base)
  98400. };
  98401. }()),
  98402. statics: {
  98403. /**
  98404. * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we
  98405. * can give it a custom frame style. Responds to errorqtip rather than the qtip property.
  98406. * @static
  98407. */
  98408. initTip: function() {
  98409. var tip = this.tip;
  98410. if (!tip) {
  98411. tip = this.tip = Ext.create('Ext.tip.QuickTip', {
  98412. baseCls: Ext.baseCSSPrefix + 'form-invalid-tip'
  98413. });
  98414. tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig);
  98415. }
  98416. },
  98417. /**
  98418. * Destroy the error tip instance.
  98419. * @static
  98420. */
  98421. destroyTip: function() {
  98422. var tip = this.tip;
  98423. if (tip) {
  98424. tip.destroy();
  98425. delete this.tip;
  98426. }
  98427. }
  98428. }
  98429. });
  98430. /**
  98431. * Layout class for {@link Ext.form.field.Text} fields. Handles sizing the input field.
  98432. * @private
  98433. */
  98434. Ext.define('Ext.layout.component.field.Text', {
  98435. extend: 'Ext.layout.component.field.Field',
  98436. alias: 'layout.textfield',
  98437. requires: ['Ext.util.TextMetrics'],
  98438. type: 'textfield',
  98439. canGrowWidth: true,
  98440. beginLayoutCycle: function(ownerContext) {
  98441. var me = this;
  98442. me.callParent(arguments);
  98443. // Clear height, in case a previous layout cycle stretched it.
  98444. if (ownerContext.shrinkWrap) {
  98445. ownerContext.inputContext.el.setStyle('height', '');
  98446. }
  98447. },
  98448. measureContentWidth: function (ownerContext) {
  98449. var me = this,
  98450. owner = me.owner,
  98451. width = me.callParent(arguments),
  98452. inputContext = ownerContext.inputContext,
  98453. inputEl, value, calcWidth, max, min;
  98454. if (owner.grow && me.canGrowWidth && !ownerContext.state.growHandled) {
  98455. inputEl = owner.inputEl;
  98456. // Find the width that contains the whole text value
  98457. value = Ext.util.Format.htmlEncode(inputEl.dom.value || (owner.hasFocus ? '' : owner.emptyText) || '');
  98458. value += owner.growAppend;
  98459. calcWidth = inputEl.getTextWidth(value) + inputContext.getFrameInfo().width;
  98460. max = owner.growMax;
  98461. min = Math.min(max, width);
  98462. max = Math.max(owner.growMin, max, min);
  98463. // Constrain
  98464. calcWidth = Ext.Number.constrain(calcWidth, owner.growMin, max);
  98465. inputContext.setWidth(calcWidth);
  98466. ownerContext.state.growHandled = true;
  98467. // Now that we've set the inputContext, we need to recalculate the width
  98468. inputContext.domBlock(me, 'width');
  98469. width = NaN;
  98470. }
  98471. return width;
  98472. },
  98473. publishInnerHeight: function(ownerContext, height) {
  98474. ownerContext.inputContext.setHeight(height - this.measureLabelErrorHeight(ownerContext));
  98475. },
  98476. beginLayoutFixed: function(ownerContext, width, suffix) {
  98477. var me = this,
  98478. ieInputWidthAdjustment = me.ieInputWidthAdjustment;
  98479. if (ieInputWidthAdjustment) {
  98480. // adjust for IE 6/7 strict content-box model
  98481. // RTL: This might have to be padding-left unless the senses of the padding styles switch when in RTL mode.
  98482. me.owner.bodyEl.setStyle('padding-right', ieInputWidthAdjustment + 'px');
  98483. if(suffix === 'px') {
  98484. width -= ieInputWidthAdjustment;
  98485. }
  98486. }
  98487. me.callParent(arguments);
  98488. }
  98489. });
  98490. /**
  98491. * A mixin which allows a component to be configured and decorated with a label and/or error message as is
  98492. * common for form fields. This is used by e.g. Ext.form.field.Base and Ext.form.FieldContainer
  98493. * to let them be managed by the Field layout.
  98494. *
  98495. * NOTE: This mixin is mainly for internal library use and most users should not need to use it directly. It
  98496. * is more likely you will want to use one of the component classes that import this mixin, such as
  98497. * Ext.form.field.Base or Ext.form.FieldContainer.
  98498. *
  98499. * Use of this mixin does not make a component a field in the logical sense, meaning it does not provide any
  98500. * logic or state related to values or validation; that is handled by the related Ext.form.field.Field
  98501. * mixin. These two mixins may be used separately (for example Ext.form.FieldContainer is Labelable but not a
  98502. * Field), or in combination (for example Ext.form.field.Base implements both and has logic for connecting the
  98503. * two.)
  98504. *
  98505. * Component classes which use this mixin should use the Field layout
  98506. * or a derivation thereof to properly size and position the label and message according to the component config.
  98507. * They must also call the {@link #initLabelable} method during component initialization to ensure the mixin gets
  98508. * set up correctly.
  98509. *
  98510. * @docauthor Jason Johnston <jason@sencha.com>
  98511. */
  98512. Ext.define("Ext.form.Labelable", {
  98513. requires: ['Ext.XTemplate'],
  98514. autoEl: {
  98515. tag: 'table',
  98516. cellpadding: 0
  98517. },
  98518. childEls: [
  98519. /**
  98520. * @property {Ext.Element} labelCell
  98521. * The `<TD>` Element which contains the label Element for this component. Only available after the component has been rendered.
  98522. */
  98523. 'labelCell',
  98524. /**
  98525. * @property {Ext.Element} labelEl
  98526. * The label Element for this component. Only available after the component has been rendered.
  98527. */
  98528. 'labelEl',
  98529. /**
  98530. * @property {Ext.Element} bodyEl
  98531. * The div Element wrapping the component's contents. Only available after the component has been rendered.
  98532. */
  98533. 'bodyEl',
  98534. // private - the TD which contains the msgTarget: 'side' error icon
  98535. 'sideErrorCell',
  98536. /**
  98537. * @property {Ext.Element} errorEl
  98538. * The div Element that will contain the component's error message(s). Note that depending on the configured
  98539. * {@link #msgTarget}, this element may be hidden in favor of some other form of presentation, but will always
  98540. * be present in the DOM for use by assistive technologies.
  98541. */
  98542. 'errorEl',
  98543. 'inputRow',
  98544. 'bottomPlaceHolder'
  98545. ],
  98546. /**
  98547. * @cfg {String/String[]/Ext.XTemplate} labelableRenderTpl
  98548. * The rendering template for the field decorations. Component classes using this mixin
  98549. * should include logic to use this as their {@link Ext.AbstractComponent#renderTpl renderTpl},
  98550. * and implement the {@link #getSubTplMarkup} method to generate the field body content.
  98551. *
  98552. * The structure of a field is a table as follows:
  98553. *
  98554. * If `labelAlign: 'left', `msgTarget: 'side'`
  98555. *
  98556. * +----------------------+----------------------+-------------+
  98557. * | Label: | InputField | sideErrorEl |
  98558. * +----------------------+----------------------+-------------+
  98559. *
  98560. * If `labelAlign: 'left', `msgTarget: 'under'`
  98561. *
  98562. * +----------------------+------------------------------------+
  98563. * | Label: | InputField (colspan=2) |
  98564. * | | underErrorEl |
  98565. * +----------------------+------------------------------------+
  98566. *
  98567. * If `labelAlign: 'top', `msgTarget: 'side'`
  98568. *
  98569. * +---------------------------------------------+-------------+
  98570. * | label | |
  98571. * | InputField | sideErrorEl |
  98572. * +---------------------------------------------+-------------+
  98573. *
  98574. * If `labelAlign: 'top', `msgTarget: 'under'`
  98575. *
  98576. * +-----------------------------------------------------------+
  98577. * | label |
  98578. * | InputField (colspan=2) |
  98579. * | underErrorEl |
  98580. * +-----------------------------------------------------------+
  98581. *
  98582. * The total columns always the same for fields with each setting of {@link #labelAlign} because when
  98583. * rendered into a {@link Ext.layout.container.Form} layout, just the `TR` of the table
  98584. * will be placed into the form's main `TABLE`, and the columns of all the siblings
  98585. * must match so that they all line up. In a {@link Ext.layout.container.Form} layout, different
  98586. * settings of {@link #labelAlign} are not supported because of the incompatible column structure.
  98587. *
  98588. * When the triggerCell or side error cell are hidden or shown, the input cell's colspan
  98589. * is recalculated to maintain the correct 3 visible column count.
  98590. * @private
  98591. */
  98592. labelableRenderTpl: [
  98593. // body row. If a heighted Field (eg TextArea, HtmlEditor, this must greedily consume height.
  98594. '<tr id="{id}-inputRow" <tpl if="inFormLayout">id="{id}"</tpl>>',
  98595. // Label cell
  98596. '<tpl if="labelOnLeft">',
  98597. '<td id="{id}-labelCell" style="{labelCellStyle}" {labelCellAttrs}>',
  98598. '{beforeLabelTpl}',
  98599. '<label id="{id}-labelEl" {labelAttrTpl}<tpl if="inputId"> for="{inputId}"</tpl> class="{labelCls}"',
  98600. '<tpl if="labelStyle"> style="{labelStyle}"</tpl>>',
  98601. '{beforeLabelTextTpl}',
  98602. '<tpl if="fieldLabel">{fieldLabel}{labelSeparator}</tpl>',
  98603. '{afterLabelTextTpl}',
  98604. '</label>',
  98605. '{afterLabelTpl}',
  98606. '</td>',
  98607. '</tpl>',
  98608. // Body of the input. That will be an input element, or, from a TriggerField, a table containing an input cell and trigger cell(s)
  98609. '<td class="{baseBodyCls} {fieldBodyCls}" id="{id}-bodyEl" colspan="{bodyColspan}" role="presentation">',
  98610. '{beforeBodyEl}',
  98611. // Label just sits on top of the input field if labelAlign === 'top'
  98612. '<tpl if="labelAlign==\'top\'">',
  98613. '{beforeLabelTpl}',
  98614. '<div id="{id}-labelCell" style="{labelCellStyle}">',
  98615. '<label id="{id}-labelEl" {labelAttrTpl}<tpl if="inputId"> for="{inputId}"</tpl> class="{labelCls}"',
  98616. '<tpl if="labelStyle"> style="{labelStyle}"</tpl>>',
  98617. '{beforeLabelTextTpl}',
  98618. '<tpl if="fieldLabel">{fieldLabel}{labelSeparator}</tpl>',
  98619. '{afterLabelTextTpl}',
  98620. '</label>',
  98621. '</div>',
  98622. '{afterLabelTpl}',
  98623. '</tpl>',
  98624. '{beforeSubTpl}',
  98625. '{[values.$comp.getSubTplMarkup()]}',
  98626. '{afterSubTpl}',
  98627. // Final TD. It's a side error element unless there's a floating external one
  98628. '<tpl if="msgTarget===\'side\'">',
  98629. '{afterBodyEl}',
  98630. '</td>',
  98631. '<td id="{id}-sideErrorCell" vAlign="{[values.labelAlign===\'top\' && !values.hideLabel ? \'bottom\' : \'middle\']}" style="{[values.autoFitErrors ? \'display:none\' : \'\']}" width="{errorIconWidth}">',
  98632. '<div id="{id}-errorEl" class="{errorMsgCls}" style="display:none;width:{errorIconWidth}px"></div>',
  98633. '</td>',
  98634. '<tpl elseif="msgTarget==\'under\'">',
  98635. '<div id="{id}-errorEl" class="{errorMsgClass}" colspan="2" style="display:none"></div>',
  98636. '{afterBodyEl}',
  98637. '</td>',
  98638. '</tpl>',
  98639. '</tr>',
  98640. {
  98641. disableFormats: true
  98642. }
  98643. ],
  98644. /**
  98645. * @cfg {String/String[]/Ext.XTemplate} activeErrorsTpl
  98646. * The template used to format the Array of error messages passed to {@link #setActiveErrors} into a single HTML
  98647. * string. By default this renders each message as an item in an unordered list.
  98648. */
  98649. activeErrorsTpl: [
  98650. '<tpl if="errors && errors.length">',
  98651. '<ul><tpl for="errors"><li>{.}</li></tpl></ul>',
  98652. '</tpl>'
  98653. ],
  98654. /**
  98655. * @property {Boolean} isFieldLabelable
  98656. * Flag denoting that this object is labelable as a field. Always true.
  98657. */
  98658. isFieldLabelable: true,
  98659. /**
  98660. * @cfg {String} formItemCls
  98661. * A CSS class to be applied to the outermost element to denote that it is participating in the form field layout.
  98662. */
  98663. formItemCls: Ext.baseCSSPrefix + 'form-item',
  98664. /**
  98665. * @cfg {String} labelCls
  98666. * The CSS class to be applied to the label element. This (single) CSS class is used to formulate the renderSelector
  98667. * and drives the field layout where it is concatenated with a hyphen ('-') and {@link #labelAlign}. To add
  98668. * additional classes, use {@link #labelClsExtra}.
  98669. */
  98670. labelCls: Ext.baseCSSPrefix + 'form-item-label',
  98671. /**
  98672. * @cfg {String} labelClsExtra
  98673. * An optional string of one or more additional CSS classes to add to the label element. Defaults to empty.
  98674. */
  98675. /**
  98676. * @cfg {String} errorMsgCls
  98677. * The CSS class to be applied to the error message element.
  98678. */
  98679. errorMsgCls: Ext.baseCSSPrefix + 'form-error-msg',
  98680. /**
  98681. * @cfg {String} baseBodyCls
  98682. * The CSS class to be applied to the body content element.
  98683. */
  98684. baseBodyCls: Ext.baseCSSPrefix + 'form-item-body',
  98685. /**
  98686. * @cfg {String} fieldBodyCls
  98687. * An extra CSS class to be applied to the body content element in addition to {@link #baseBodyCls}.
  98688. */
  98689. fieldBodyCls: '',
  98690. /**
  98691. * @cfg {String} clearCls
  98692. * The CSS class to be applied to the special clearing div rendered directly after the field contents wrapper to
  98693. * provide field clearing.
  98694. */
  98695. clearCls: Ext.baseCSSPrefix + 'clear',
  98696. /**
  98697. * @cfg {String} invalidCls
  98698. * The CSS class to use when marking the component invalid.
  98699. */
  98700. invalidCls : Ext.baseCSSPrefix + 'form-invalid',
  98701. /**
  98702. * @cfg {String} fieldLabel
  98703. * The label for the field. It gets appended with the {@link #labelSeparator}, and its position and sizing is
  98704. * determined by the {@link #labelAlign}, {@link #labelWidth}, and {@link #labelPad} configs.
  98705. */
  98706. fieldLabel: undefined,
  98707. /**
  98708. * @cfg {String} labelAlign
  98709. * Controls the position and alignment of the {@link #fieldLabel}. Valid values are:
  98710. *
  98711. * - "left" (the default) - The label is positioned to the left of the field, with its text aligned to the left.
  98712. * Its width is determined by the {@link #labelWidth} config.
  98713. * - "top" - The label is positioned above the field.
  98714. * - "right" - The label is positioned to the left of the field, with its text aligned to the right.
  98715. * Its width is determined by the {@link #labelWidth} config.
  98716. */
  98717. labelAlign : 'left',
  98718. /**
  98719. * @cfg {Number} labelWidth
  98720. * The width of the {@link #fieldLabel} in pixels. Only applicable if the {@link #labelAlign} is set to "left" or
  98721. * "right".
  98722. */
  98723. labelWidth: 100,
  98724. /**
  98725. * @cfg {Number} labelPad
  98726. * The amount of space in pixels between the {@link #fieldLabel} and the input field.
  98727. */
  98728. labelPad : 5,
  98729. //<locale>
  98730. /**
  98731. * @cfg {String} labelSeparator
  98732. * Character(s) to be inserted at the end of the {@link #fieldLabel label text}.
  98733. *
  98734. * Set to empty string to hide the separator completely.
  98735. */
  98736. labelSeparator : ':',
  98737. //</locale>
  98738. /**
  98739. * @cfg {String} labelStyle
  98740. * A CSS style specification string to apply directly to this field's label.
  98741. */
  98742. /**
  98743. * @cfg {Boolean} hideLabel
  98744. * Set to true to completely hide the label element ({@link #fieldLabel} and {@link #labelSeparator}). Also see
  98745. * {@link #hideEmptyLabel}, which controls whether space will be reserved for an empty fieldLabel.
  98746. */
  98747. hideLabel: false,
  98748. /**
  98749. * @cfg {Boolean} hideEmptyLabel
  98750. * When set to true, the label element ({@link #fieldLabel} and {@link #labelSeparator}) will be automatically
  98751. * hidden if the {@link #fieldLabel} is empty. Setting this to false will cause the empty label element to be
  98752. * rendered and space to be reserved for it; this is useful if you want a field without a label to line up with
  98753. * other labeled fields in the same form.
  98754. *
  98755. * If you wish to unconditionall hide the label even if a non-empty fieldLabel is configured, then set the
  98756. * {@link #hideLabel} config to true.
  98757. */
  98758. hideEmptyLabel: true,
  98759. /**
  98760. * @cfg {Boolean} preventMark
  98761. * true to disable displaying any {@link #setActiveError error message} set on this object.
  98762. */
  98763. preventMark: false,
  98764. /**
  98765. * @cfg {Boolean} autoFitErrors
  98766. * Whether to adjust the component's body area to make room for 'side' or 'under' {@link #msgTarget error messages}.
  98767. */
  98768. autoFitErrors: true,
  98769. /**
  98770. * @cfg {String} msgTarget
  98771. * The location where the error message text should display. Must be one of the following values:
  98772. *
  98773. * - `qtip` Display a quick tip containing the message when the user hovers over the field.
  98774. * This is the default.
  98775. *
  98776. * **{@link Ext.tip.QuickTipManager#init} must have been called for this setting to work.**
  98777. *
  98778. * - `title` Display the message in a default browser title attribute popup.
  98779. * - `under` Add a block div beneath the field containing the error message.
  98780. * - `side` Add an error icon to the right of the field, displaying the message in a popup on hover.
  98781. * - `none` Don't display any error message. This might be useful if you are implementing custom error display.
  98782. * - `[element id]` Add the error message directly to the innerHTML of the specified element.
  98783. */
  98784. msgTarget: 'qtip',
  98785. /**
  98786. * @cfg {String} activeError
  98787. * If specified, then the component will be displayed with this value as its active error when first rendered. Use
  98788. * {@link #setActiveError} or {@link #unsetActiveError} to change it after component creation.
  98789. */
  98790. /**
  98791. * @private
  98792. * Tells the layout system that the height can be measured immediately because the width does not need setting.
  98793. */
  98794. noWrap: true,
  98795. labelableInsertions: [
  98796. /**
  98797. * @cfg {String/Array/Ext.XTemplate} beforeBodyEl
  98798. * An optional string or `XTemplate` configuration to insert in the field markup
  98799. * at the beginning of the input containing element. If an `XTemplate` is used, the component's {@link Ext.AbstractComponent#renderData render data}
  98800. * serves as the context.
  98801. */
  98802. 'beforeBodyEl',
  98803. /**
  98804. * @cfg {String/Array/Ext.XTemplate} afterBodyEl
  98805. * An optional string or `XTemplate` configuration to insert in the field markup
  98806. * at the end of the input containing element. If an `XTemplate` is used, the component's {@link Ext.AbstractComponent#renderData render data}
  98807. * serves as the context.
  98808. */
  98809. 'afterBodyEl',
  98810. /**
  98811. * @cfg {String/Array/Ext.XTemplate} beforeLabelTpl
  98812. * An optional string or `XTemplate` configuration to insert in the field markup
  98813. * before the label element. If an `XTemplate` is used, the component's {@link Ext.AbstractComponent#renderData render data}
  98814. * serves as the context.
  98815. */
  98816. 'beforeLabelTpl',
  98817. /**
  98818. * @cfg {String/Array/Ext.XTemplate} afterLabelTpl
  98819. * An optional string or `XTemplate` configuration to insert in the field markup
  98820. * after the label element. If an `XTemplate` is used, the component's {@link Ext.AbstractComponent#renderData render data}
  98821. * serves as the context.
  98822. */
  98823. 'afterLabelTpl',
  98824. /**
  98825. * @cfg {String/Array/Ext.XTemplate} beforeSubTpl
  98826. * An optional string or `XTemplate` configuration to insert in the field markup
  98827. * before the {@link #getSubTplMarkup subTpl markup}. If an `XTemplate` is used, the
  98828. * component's {@link Ext.AbstractComponent#renderData render data} serves as the context.
  98829. */
  98830. 'beforeSubTpl',
  98831. /**
  98832. * @cfg {String/Array/Ext.XTemplate} afterSubTpl
  98833. * An optional string or `XTemplate` configuration to insert in the field markup
  98834. * after the {@link #getSubTplMarkup subTpl markup}. If an `XTemplate` is used, the
  98835. * component's {@link Ext.AbstractComponent#renderData render data} serves as the context.
  98836. */
  98837. 'afterSubTpl',
  98838. /**
  98839. * @cfg {String/Array/Ext.XTemplate} beforeLabelTextTpl
  98840. * An optional string or `XTemplate` configuration to insert in the field markup
  98841. * before the label text. If an `XTemplate` is used, the component's {@link Ext.AbstractComponent#renderData render data}
  98842. * serves as the context.
  98843. */
  98844. 'beforeLabelTextTpl',
  98845. /**
  98846. * @cfg {String/Array/Ext.XTemplate} afterLabelTextTpl
  98847. * An optional string or `XTemplate` configuration to insert in the field markup
  98848. * after the label text. If an `XTemplate` is used, the component's {@link Ext.AbstractComponent#renderData render data}
  98849. * serves as the context.
  98850. */
  98851. 'afterLabelTextTpl',
  98852. /**
  98853. * @cfg {String/Array/Ext.XTemplate} labelAttrTpl
  98854. * An optional string or `XTemplate` configuration to insert in the field markup
  98855. * inside the label element (as attributes). If an `XTemplate` is used, the component's
  98856. * {@link Ext.AbstractComponent#renderData render data} serves as the context.
  98857. */
  98858. 'labelAttrTpl'
  98859. ],
  98860. // This is an array to avoid a split on every call to Ext.copyTo
  98861. labelableRenderProps: [ 'allowBlank', 'id', 'labelAlign', 'fieldBodyCls', 'baseBodyCls',
  98862. 'clearCls', 'labelSeparator', 'msgTarget' ],
  98863. /**
  98864. * Performs initialization of this mixin. Component classes using this mixin should call this method during their
  98865. * own initialization.
  98866. */
  98867. initLabelable: function() {
  98868. var me = this,
  98869. padding = me.padding;
  98870. // This Component is rendered as a table. Padding doesn't work on tables
  98871. // Before padding can be applied to the encapsulating table element, copy the padding into
  98872. // an extraMargins property which is to be added to all computed margins post render :(
  98873. if (padding) {
  98874. me.padding = undefined;
  98875. me.extraMargins = Ext.Element.parseBox(padding);
  98876. }
  98877. me.addCls(me.formItemCls);
  98878. // Prevent first render of active error, at Field render time from signalling a change from undefined to "
  98879. me.lastActiveError = '';
  98880. me.addEvents(
  98881. /**
  98882. * @event errorchange
  98883. * Fires when the active error message is changed via {@link #setActiveError}.
  98884. * @param {Ext.form.Labelable} this
  98885. * @param {String} error The active error message
  98886. */
  98887. 'errorchange'
  98888. );
  98889. },
  98890. /**
  98891. * Returns the trimmed label by slicing off the label separator character. Can be overridden.
  98892. * @return {String} The trimmed field label, or empty string if not defined
  98893. */
  98894. trimLabelSeparator: function() {
  98895. var me = this,
  98896. separator = me.labelSeparator,
  98897. label = me.fieldLabel || '',
  98898. lastChar = label.substr(label.length - 1);
  98899. // if the last char is the same as the label separator then slice it off otherwise just return label value
  98900. return lastChar === separator ? label.slice(0, -1) : label;
  98901. },
  98902. /**
  98903. * Returns the label for the field. Defaults to simply returning the {@link #fieldLabel} config. Can be overridden
  98904. * to provide a custom generated label.
  98905. * @template
  98906. * @return {String} The configured field label, or empty string if not defined
  98907. */
  98908. getFieldLabel: function() {
  98909. return this.trimLabelSeparator();
  98910. },
  98911. /**
  98912. * Set the label of this field.
  98913. * @param {String} label The new label. The {@link #labelSeparator} will be automatically appended to the label
  98914. * string.
  98915. */
  98916. setFieldLabel: function(label){
  98917. label = label || '';
  98918. var me = this,
  98919. separator = me.labelSeparator,
  98920. labelEl = me.labelEl;
  98921. me.fieldLabel = label;
  98922. if (me.rendered) {
  98923. if (Ext.isEmpty(label) && me.hideEmptyLabel) {
  98924. labelEl.parent().setDisplayed('none');
  98925. } else {
  98926. if (separator) {
  98927. label = me.trimLabelSeparator() + separator;
  98928. }
  98929. labelEl.update(label);
  98930. labelEl.parent().setDisplayed('');
  98931. }
  98932. me.updateLayout();
  98933. }
  98934. },
  98935. getInsertionRenderData: function (data, names) {
  98936. var i = names.length,
  98937. name, value;
  98938. while (i--) {
  98939. name = names[i];
  98940. value = this[name];
  98941. if (value) {
  98942. if (typeof value != 'string') {
  98943. if (!value.isTemplate) {
  98944. value = Ext.XTemplate.getTpl(this, name);
  98945. }
  98946. value = value.apply(data);
  98947. }
  98948. }
  98949. data[name] = value || '';
  98950. }
  98951. return data;
  98952. },
  98953. /**
  98954. * Generates the arguments for the field decorations {@link #labelableRenderTpl rendering template}.
  98955. * @return {Object} The template arguments
  98956. * @protected
  98957. */
  98958. getLabelableRenderData: function() {
  98959. var me = this,
  98960. data,
  98961. tempEl,
  98962. topLabel = me.labelAlign === 'top';
  98963. if (!Ext.form.Labelable.errorIconWidth) {
  98964. Ext.form.Labelable.errorIconWidth = (tempEl = Ext.resetElement.createChild({style: 'position:absolute', cls: Ext.baseCSSPrefix + 'form-invalid-icon'})).getWidth();
  98965. tempEl.remove();
  98966. }
  98967. data = Ext.copyTo({
  98968. inFormLayout : me.ownerLayout && me.ownerLayout.type === 'form',
  98969. inputId : me.getInputId(),
  98970. labelOnLeft : !topLabel,
  98971. hideLabel : !me.hasVisibleLabel(),
  98972. fieldLabel : me.getFieldLabel(),
  98973. labelCellStyle : me.getLabelCellStyle(),
  98974. labelCellAttrs : me.getLabelCellAttrs(),
  98975. labelCls : me.getLabelCls(),
  98976. labelStyle : me.getLabelStyle(),
  98977. bodyColspan : me.getBodyColspan(),
  98978. externalError : !me.autoFitErrors,
  98979. errorMsgCls : me.getErrorMsgCls(),
  98980. errorIconWidth : Ext.form.Labelable.errorIconWidth
  98981. },
  98982. me, me.labelableRenderProps, true);
  98983. me.getInsertionRenderData(data, me.labelableInsertions);
  98984. return data;
  98985. },
  98986. beforeLabelableRender: function() {
  98987. var me = this;
  98988. if (me.ownerLayout) {
  98989. me.addCls(Ext.baseCSSPrefix + me.ownerLayout.type + '-form-item');
  98990. }
  98991. },
  98992. onLabelableRender: function() {
  98993. var me = this,
  98994. margins,
  98995. side,
  98996. style = {};
  98997. if (me.extraMargins) {
  98998. margins = me.el.getMargin();
  98999. for (side in margins) {
  99000. if (margins.hasOwnProperty(side)) {
  99001. style['margin-' + side] = (margins[side] + me.extraMargins[side]) + 'px';
  99002. }
  99003. }
  99004. me.el.setStyle(style);
  99005. }
  99006. },
  99007. /**
  99008. * Checks if the field has a visible label
  99009. * @return {Boolean} True if the field has a visible label
  99010. */
  99011. hasVisibleLabel: function(){
  99012. if (this.hideLabel) {
  99013. return false;
  99014. }
  99015. return !(this.hideEmptyLabel && !this.getFieldLabel());
  99016. },
  99017. /**
  99018. * @private
  99019. * Calculates the colspan value for the body cell - the cell which contains the input field.
  99020. *
  99021. * The field table structure contains 4 columns:
  99022. */
  99023. getBodyColspan: function() {
  99024. var me = this,
  99025. result;
  99026. if (me.msgTarget === 'side' && (!me.autoFitErrors || me.hasActiveError())) {
  99027. result = 1;
  99028. } else {
  99029. result = 2;
  99030. }
  99031. if (me.labelAlign !== 'top' && !me.hasVisibleLabel()) {
  99032. result++;
  99033. }
  99034. return result;
  99035. },
  99036. getLabelCls: function() {
  99037. var labelCls = this.labelCls,
  99038. labelClsExtra = this.labelClsExtra;
  99039. if (this.labelAlign === 'top') {
  99040. labelCls += '-top';
  99041. }
  99042. return labelClsExtra ? labelCls + ' ' + labelClsExtra : labelCls;
  99043. },
  99044. getLabelCellStyle: function() {
  99045. var me = this,
  99046. hideLabelCell = me.hideLabel || (!me.fieldLabel && me.hideEmptyLabel);
  99047. return hideLabelCell ? 'display:none;' : '';
  99048. },
  99049. getErrorMsgCls: function() {
  99050. var me = this,
  99051. hideLabelCell = (me.hideLabel || (!me.fieldLabel && me.hideEmptyLabel));
  99052. return me.errorMsgCls + (!hideLabelCell && me.labelAlign === 'top' ? ' ' + Ext.baseCSSPrefix + 'lbl-top-err-icon' : '');
  99053. },
  99054. getLabelCellAttrs: function() {
  99055. var me = this,
  99056. labelAlign = me.labelAlign,
  99057. result = '';
  99058. if (labelAlign !== 'top') {
  99059. result = 'valign="top" halign="' + labelAlign + '" width="' + (me.labelWidth + me.labelPad) + '"';
  99060. }
  99061. return result + ' class="' + Ext.baseCSSPrefix + 'field-label-cell"';
  99062. },
  99063. /**
  99064. * Gets any label styling for the labelEl
  99065. * @private
  99066. * @return {String} The label styling
  99067. */
  99068. getLabelStyle: function(){
  99069. var me = this,
  99070. labelPad = me.labelPad,
  99071. labelStyle = '';
  99072. // Calculate label styles up front rather than in the Field layout for speed; this
  99073. // is safe because label alignment/width/pad are not expected to change.
  99074. if (me.labelAlign !== 'top') {
  99075. if (me.labelWidth) {
  99076. labelStyle = 'width:' + me.labelWidth + 'px;';
  99077. }
  99078. labelStyle += 'margin-right:' + labelPad + 'px;';
  99079. }
  99080. return labelStyle + (me.labelStyle || '');
  99081. },
  99082. /**
  99083. * Gets the markup to be inserted into the outer template's bodyEl. Defaults to empty string, should be implemented
  99084. * by classes including this mixin as needed.
  99085. * @return {String} The markup to be inserted
  99086. * @protected
  99087. */
  99088. getSubTplMarkup: function() {
  99089. return '';
  99090. },
  99091. /**
  99092. * Get the input id, if any, for this component. This is used as the "for" attribute on the label element.
  99093. * Implementing subclasses may also use this as e.g. the id for their own input element.
  99094. * @return {String} The input id
  99095. */
  99096. getInputId: function() {
  99097. return '';
  99098. },
  99099. /**
  99100. * Gets the active error message for this component, if any. This does not trigger validation on its own, it merely
  99101. * returns any message that the component may already hold.
  99102. * @return {String} The active error message on the component; if there is no error, an empty string is returned.
  99103. */
  99104. getActiveError : function() {
  99105. return this.activeError || '';
  99106. },
  99107. /**
  99108. * Tells whether the field currently has an active error message. This does not trigger validation on its own, it
  99109. * merely looks for any message that the component may already hold.
  99110. * @return {Boolean}
  99111. */
  99112. hasActiveError: function() {
  99113. return !!this.getActiveError();
  99114. },
  99115. /**
  99116. * Sets the active error message to the given string. This replaces the entire error message contents with the given
  99117. * string. Also see {@link #setActiveErrors} which accepts an Array of messages and formats them according to the
  99118. * {@link #activeErrorsTpl}. Note that this only updates the error message element's text and attributes, you'll
  99119. * have to call doComponentLayout to actually update the field's layout to match. If the field extends {@link
  99120. * Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
  99121. * @param {String} msg The error message
  99122. */
  99123. setActiveError: function(msg) {
  99124. this.setActiveErrors(msg);
  99125. },
  99126. /**
  99127. * Gets an Array of any active error messages currently applied to the field. This does not trigger validation on
  99128. * its own, it merely returns any messages that the component may already hold.
  99129. * @return {String[]} The active error messages on the component; if there are no errors, an empty Array is
  99130. * returned.
  99131. */
  99132. getActiveErrors: function() {
  99133. return this.activeErrors || [];
  99134. },
  99135. /**
  99136. * Set the active error message to an Array of error messages. The messages are formatted into a single message
  99137. * string using the {@link #activeErrorsTpl}. Also see {@link #setActiveError} which allows setting the entire error
  99138. * contents with a single string. Note that this only updates the error message element's text and attributes,
  99139. * you'll have to call doComponentLayout to actually update the field's layout to match. If the field extends
  99140. * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
  99141. * @param {String[]} errors The error messages
  99142. */
  99143. setActiveErrors: function(errors) {
  99144. errors = Ext.Array.from(errors);
  99145. this.activeError = errors[0];
  99146. this.activeErrors = errors;
  99147. this.activeError = this.getTpl('activeErrorsTpl').apply({errors: errors});
  99148. this.renderActiveError();
  99149. },
  99150. /**
  99151. * Clears the active error message(s). Note that this only clears the error message element's text and attributes,
  99152. * you'll have to call doComponentLayout to actually update the field's layout to match. If the field extends {@link
  99153. * Ext.form.field.Base} you should call {@link Ext.form.field.Base#clearInvalid clearInvalid} instead.
  99154. */
  99155. unsetActiveError: function() {
  99156. delete this.activeError;
  99157. delete this.activeErrors;
  99158. this.renderActiveError();
  99159. },
  99160. /**
  99161. * @private
  99162. * Updates the rendered DOM to match the current activeError. This only updates the content and
  99163. * attributes, you'll have to call doComponentLayout to actually update the display.
  99164. */
  99165. renderActiveError: function() {
  99166. var me = this,
  99167. activeError = me.getActiveError(),
  99168. hasError = !!activeError;
  99169. if (activeError !== me.lastActiveError) {
  99170. me.fireEvent('errorchange', me, activeError);
  99171. me.lastActiveError = activeError;
  99172. }
  99173. if (me.rendered && !me.isDestroyed && !me.preventMark) {
  99174. // Add/remove invalid class
  99175. me.el[hasError ? 'addCls' : 'removeCls'](me.invalidCls);
  99176. // Update the aria-invalid attribute
  99177. me.getActionEl().dom.setAttribute('aria-invalid', hasError);
  99178. // Update the errorEl (There will only be one if msgTarget is 'side' or 'under') with the error message text
  99179. if (me.errorEl) {
  99180. me.errorEl.dom.innerHTML = activeError;
  99181. }
  99182. }
  99183. },
  99184. /**
  99185. * Applies a set of default configuration values to this Labelable instance. For each of the properties in the given
  99186. * object, check if this component hasOwnProperty that config; if not then it's inheriting a default value from its
  99187. * prototype and we should apply the default value.
  99188. * @param {Object} defaults The defaults to apply to the object.
  99189. */
  99190. setFieldDefaults: function(defaults) {
  99191. var me = this,
  99192. val, key;
  99193. for (key in defaults) {
  99194. if (defaults.hasOwnProperty(key)) {
  99195. val = defaults[key];
  99196. if (!me.hasOwnProperty(key)) {
  99197. me[key] = val;
  99198. }
  99199. }
  99200. }
  99201. }
  99202. });
  99203. /**
  99204. * @docauthor Jason Johnston <jason@sencha.com>
  99205. *
  99206. * This mixin provides a common interface for the logical behavior and state of form fields, including:
  99207. *
  99208. * - Getter and setter methods for field values
  99209. * - Events and methods for tracking value and validity changes
  99210. * - Methods for triggering validation
  99211. *
  99212. * **NOTE**: When implementing custom fields, it is most likely that you will want to extend the {@link Ext.form.field.Base}
  99213. * component class rather than using this mixin directly, as BaseField contains additional logic for generating an
  99214. * actual DOM complete with {@link Ext.form.Labelable label and error message} display and a form input field,
  99215. * plus methods that bind the Field value getters and setters to the input field's value.
  99216. *
  99217. * If you do want to implement this mixin directly and don't want to extend {@link Ext.form.field.Base}, then
  99218. * you will most likely want to override the following methods with custom implementations: {@link #getValue},
  99219. * {@link #setValue}, and {@link #getErrors}. Other methods may be overridden as needed but their base
  99220. * implementations should be sufficient for common cases. You will also need to make sure that {@link #initField}
  99221. * is called during the component's initialization.
  99222. */
  99223. Ext.define('Ext.form.field.Field', {
  99224. /**
  99225. * @property {Boolean} isFormField
  99226. * Flag denoting that this component is a Field. Always true.
  99227. */
  99228. isFormField : true,
  99229. /**
  99230. * @cfg {Object} value
  99231. * A value to initialize this field with.
  99232. */
  99233. /**
  99234. * @cfg {String} name
  99235. * The name of the field. By default this is used as the parameter name when including the
  99236. * {@link #getSubmitData field value} in a {@link Ext.form.Basic#submit form submit()}. To prevent the field from
  99237. * being included in the form submit, set {@link #submitValue} to false.
  99238. */
  99239. /**
  99240. * @cfg {Boolean} disabled
  99241. * True to disable the field. Disabled Fields will not be {@link Ext.form.Basic#submit submitted}.
  99242. */
  99243. disabled : false,
  99244. /**
  99245. * @cfg {Boolean} submitValue
  99246. * Setting this to false will prevent the field from being {@link Ext.form.Basic#submit submitted} even when it is
  99247. * not disabled.
  99248. */
  99249. submitValue: true,
  99250. /**
  99251. * @cfg {Boolean} validateOnChange
  99252. * Specifies whether this field should be validated immediately whenever a change in its value is detected.
  99253. * If the validation results in a change in the field's validity, a {@link #validitychange} event will be
  99254. * fired. This allows the field to show feedback about the validity of its contents immediately as the user is
  99255. * typing.
  99256. *
  99257. * When set to false, feedback will not be immediate. However the form will still be validated before submitting if
  99258. * the clientValidation option to {@link Ext.form.Basic#doAction} is enabled, or if the field or form are validated
  99259. * manually.
  99260. *
  99261. * See also {@link Ext.form.field.Base#checkChangeEvents} for controlling how changes to the field's value are
  99262. * detected.
  99263. */
  99264. validateOnChange: true,
  99265. /**
  99266. * @private
  99267. */
  99268. suspendCheckChange: 0,
  99269. /**
  99270. * Initializes this Field mixin on the current instance. Components using this mixin should call this method during
  99271. * their own initialization process.
  99272. */
  99273. initField: function() {
  99274. this.addEvents(
  99275. /**
  99276. * @event change
  99277. * Fires when the value of a field is changed via the {@link #setValue} method.
  99278. * @param {Ext.form.field.Field} this
  99279. * @param {Object} newValue The new value
  99280. * @param {Object} oldValue The original value
  99281. */
  99282. 'change',
  99283. /**
  99284. * @event validitychange
  99285. * Fires when a change in the field's validity is detected.
  99286. * @param {Ext.form.field.Field} this
  99287. * @param {Boolean} isValid Whether or not the field is now valid
  99288. */
  99289. 'validitychange',
  99290. /**
  99291. * @event dirtychange
  99292. * Fires when a change in the field's {@link #isDirty} state is detected.
  99293. * @param {Ext.form.field.Field} this
  99294. * @param {Boolean} isDirty Whether or not the field is now dirty
  99295. */
  99296. 'dirtychange'
  99297. );
  99298. this.initValue();
  99299. },
  99300. /**
  99301. * Initializes the field's value based on the initial config.
  99302. */
  99303. initValue: function() {
  99304. var me = this;
  99305. me.value = me.transformOriginalValue(me.value);
  99306. /**
  99307. * @property {Object} originalValue
  99308. * The original value of the field as configured in the {@link #value} configuration, or as loaded by the last
  99309. * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
  99310. */
  99311. me.originalValue = me.lastValue = me.value;
  99312. // Set the initial value - prevent validation on initial set
  99313. me.suspendCheckChange++;
  99314. me.setValue(me.value);
  99315. me.suspendCheckChange--;
  99316. },
  99317. /**
  99318. * Allows for any necessary modifications before the original
  99319. * value is set
  99320. * @protected
  99321. * @param {Object} value The initial value
  99322. * @return {Object} The modified initial value
  99323. */
  99324. transformOriginalValue: function(value){
  99325. return value;
  99326. },
  99327. /**
  99328. * Returns the {@link Ext.form.field.Field#name name} attribute of the field. This is used as the parameter name
  99329. * when including the field value in a {@link Ext.form.Basic#submit form submit()}.
  99330. * @return {String} name The field {@link Ext.form.field.Field#name name}
  99331. */
  99332. getName: function() {
  99333. return this.name;
  99334. },
  99335. /**
  99336. * Returns the current data value of the field. The type of value returned is particular to the type of the
  99337. * particular field (e.g. a Date object for {@link Ext.form.field.Date}).
  99338. * @return {Object} value The field value
  99339. */
  99340. getValue: function() {
  99341. return this.value;
  99342. },
  99343. /**
  99344. * Sets a data value into the field and runs the change detection and validation.
  99345. * @param {Object} value The value to set
  99346. * @return {Ext.form.field.Field} this
  99347. */
  99348. setValue: function(value) {
  99349. var me = this;
  99350. me.value = value;
  99351. me.checkChange();
  99352. return me;
  99353. },
  99354. /**
  99355. * Returns whether two field {@link #getValue values} are logically equal. Field implementations may override this
  99356. * to provide custom comparison logic appropriate for the particular field's data type.
  99357. * @param {Object} value1 The first value to compare
  99358. * @param {Object} value2 The second value to compare
  99359. * @return {Boolean} True if the values are equal, false if inequal.
  99360. */
  99361. isEqual: function(value1, value2) {
  99362. return String(value1) === String(value2);
  99363. },
  99364. /**
  99365. * Returns whether two values are logically equal.
  99366. * Similar to {@link #isEqual}, however null or undefined values will be treated as empty strings.
  99367. * @private
  99368. * @param {Object} value1 The first value to compare
  99369. * @param {Object} value2 The second value to compare
  99370. * @return {Boolean} True if the values are equal, false if inequal.
  99371. */
  99372. isEqualAsString: function(value1, value2){
  99373. return String(Ext.value(value1, '')) === String(Ext.value(value2, ''));
  99374. },
  99375. /**
  99376. * Returns the parameter(s) that would be included in a standard form submit for this field. Typically this will be
  99377. * an object with a single name-value pair, the name being this field's {@link #getName name} and the value being
  99378. * its current stringified value. More advanced field implementations may return more than one name-value pair.
  99379. *
  99380. * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
  99381. * validated}.
  99382. *
  99383. * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
  99384. * strings if that particular name has multiple values. It can also return null if there are no parameters to be
  99385. * submitted.
  99386. */
  99387. getSubmitData: function() {
  99388. var me = this,
  99389. data = null;
  99390. if (!me.disabled && me.submitValue && !me.isFileUpload()) {
  99391. data = {};
  99392. data[me.getName()] = '' + me.getValue();
  99393. }
  99394. return data;
  99395. },
  99396. /**
  99397. * Returns the value(s) that should be saved to the {@link Ext.data.Model} instance for this field, when {@link
  99398. * Ext.form.Basic#updateRecord} is called. Typically this will be an object with a single name-value pair, the name
  99399. * being this field's {@link #getName name} and the value being its current data value. More advanced field
  99400. * implementations may return more than one name-value pair. The returned values will be saved to the corresponding
  99401. * field names in the Model.
  99402. *
  99403. * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
  99404. * validated}.
  99405. *
  99406. * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
  99407. * strings if that particular name has multiple values. It can also return null if there are no parameters to be
  99408. * submitted.
  99409. */
  99410. getModelData: function() {
  99411. var me = this,
  99412. data = null;
  99413. if (!me.disabled && !me.isFileUpload()) {
  99414. data = {};
  99415. data[me.getName()] = me.getValue();
  99416. }
  99417. return data;
  99418. },
  99419. /**
  99420. * Resets the current field value to the originally loaded value and clears any validation messages. See {@link
  99421. * Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
  99422. */
  99423. reset : function(){
  99424. var me = this;
  99425. me.beforeReset();
  99426. me.setValue(me.originalValue);
  99427. me.clearInvalid();
  99428. // delete here so we reset back to the original state
  99429. delete me.wasValid;
  99430. },
  99431. /**
  99432. * Template method before a field is reset.
  99433. * @protected
  99434. */
  99435. beforeReset: Ext.emptyFn,
  99436. /**
  99437. * Resets the field's {@link #originalValue} property so it matches the current {@link #getValue value}. This is
  99438. * called by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues} if the form's
  99439. * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} property is set to true.
  99440. */
  99441. resetOriginalValue: function() {
  99442. this.originalValue = this.getValue();
  99443. this.checkDirty();
  99444. },
  99445. /**
  99446. * Checks whether the value of the field has changed since the last time it was checked.
  99447. * If the value has changed, it:
  99448. *
  99449. * 1. Fires the {@link #change change event},
  99450. * 2. Performs validation if the {@link #validateOnChange} config is enabled, firing the
  99451. * {@link #validitychange validitychange event} if the validity has changed, and
  99452. * 3. Checks the {@link #isDirty dirty state} of the field and fires the {@link #dirtychange dirtychange event}
  99453. * if it has changed.
  99454. */
  99455. checkChange: function() {
  99456. if (!this.suspendCheckChange) {
  99457. var me = this,
  99458. newVal = me.getValue(),
  99459. oldVal = me.lastValue;
  99460. if (!me.isEqual(newVal, oldVal) && !me.isDestroyed) {
  99461. me.lastValue = newVal;
  99462. me.fireEvent('change', me, newVal, oldVal);
  99463. me.onChange(newVal, oldVal);
  99464. }
  99465. }
  99466. },
  99467. /**
  99468. * @private
  99469. * Called when the field's value changes. Performs validation if the {@link #validateOnChange}
  99470. * config is enabled, and invokes the dirty check.
  99471. */
  99472. onChange: function(newVal, oldVal) {
  99473. if (this.validateOnChange) {
  99474. this.validate();
  99475. }
  99476. this.checkDirty();
  99477. },
  99478. /**
  99479. * Returns true if the value of this Field has been changed from its {@link #originalValue}.
  99480. * Will always return false if the field is disabled.
  99481. *
  99482. * Note that if the owning {@link Ext.form.Basic form} was configured with
  99483. * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} then the {@link #originalValue} is updated when
  99484. * the values are loaded by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues}.
  99485. * @return {Boolean} True if this field has been changed from its original value (and is not disabled),
  99486. * false otherwise.
  99487. */
  99488. isDirty : function() {
  99489. var me = this;
  99490. return !me.disabled && !me.isEqual(me.getValue(), me.originalValue);
  99491. },
  99492. /**
  99493. * Checks the {@link #isDirty} state of the field and if it has changed since the last time it was checked,
  99494. * fires the {@link #dirtychange} event.
  99495. */
  99496. checkDirty: function() {
  99497. var me = this,
  99498. isDirty = me.isDirty();
  99499. if (isDirty !== me.wasDirty) {
  99500. me.fireEvent('dirtychange', me, isDirty);
  99501. me.onDirtyChange(isDirty);
  99502. me.wasDirty = isDirty;
  99503. }
  99504. },
  99505. /**
  99506. * @private Called when the field's dirty state changes.
  99507. * @param {Boolean} isDirty
  99508. */
  99509. onDirtyChange: Ext.emptyFn,
  99510. /**
  99511. * Runs this field's validators and returns an array of error messages for any validation failures. This is called
  99512. * internally during validation and would not usually need to be used manually.
  99513. *
  99514. * Each subclass should override or augment the return value to provide their own errors.
  99515. *
  99516. * @param {Object} value The value to get errors for (defaults to the current field value)
  99517. * @return {String[]} All error messages for this field; an empty Array if none.
  99518. */
  99519. getErrors: function(value) {
  99520. return [];
  99521. },
  99522. /**
  99523. * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
  99524. * value. The {@link #validitychange} event will not be fired; use {@link #validate} instead if you want the event
  99525. * to fire. **Note**: {@link #disabled} fields are always treated as valid.
  99526. *
  99527. * Implementations are encouraged to ensure that this method does not have side-effects such as triggering error
  99528. * message display.
  99529. *
  99530. * @return {Boolean} True if the value is valid, else false
  99531. */
  99532. isValid : function() {
  99533. var me = this;
  99534. return me.disabled || Ext.isEmpty(me.getErrors());
  99535. },
  99536. /**
  99537. * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
  99538. * value, and fires the {@link #validitychange} event if the field's validity has changed since the last validation.
  99539. * **Note**: {@link #disabled} fields are always treated as valid.
  99540. *
  99541. * Custom implementations of this method are allowed to have side-effects such as triggering error message display.
  99542. * To validate without side-effects, use {@link #isValid}.
  99543. *
  99544. * @return {Boolean} True if the value is valid, else false
  99545. */
  99546. validate : function() {
  99547. var me = this,
  99548. isValid = me.isValid();
  99549. if (isValid !== me.wasValid) {
  99550. me.wasValid = isValid;
  99551. me.fireEvent('validitychange', me, isValid);
  99552. }
  99553. return isValid;
  99554. },
  99555. /**
  99556. * A utility for grouping a set of modifications which may trigger value changes into a single transaction, to
  99557. * prevent excessive firing of {@link #change} events. This is useful for instance if the field has sub-fields which
  99558. * are being updated as a group; you don't want the container field to check its own changed state for each subfield
  99559. * change.
  99560. * @param {Object} fn A function containing the transaction code
  99561. */
  99562. batchChanges: function(fn) {
  99563. try {
  99564. this.suspendCheckChange++;
  99565. fn();
  99566. } catch(e){
  99567. throw e;
  99568. } finally {
  99569. this.suspendCheckChange--;
  99570. }
  99571. this.checkChange();
  99572. },
  99573. /**
  99574. * Returns whether this Field is a file upload field; if it returns true, forms will use special techniques for
  99575. * {@link Ext.form.Basic#submit submitting the form} via AJAX. See {@link Ext.form.Basic#hasUpload} for details. If
  99576. * this returns true, the {@link #extractFileInput} method must also be implemented to return the corresponding file
  99577. * input element.
  99578. * @return {Boolean}
  99579. */
  99580. isFileUpload: function() {
  99581. return false;
  99582. },
  99583. /**
  99584. * Only relevant if the instance's {@link #isFileUpload} method returns true. Returns a reference to the file input
  99585. * DOM element holding the user's selected file. The input will be appended into the submission form and will not be
  99586. * returned, so this method should also create a replacement.
  99587. * @return {HTMLElement}
  99588. */
  99589. extractFileInput: function() {
  99590. return null;
  99591. },
  99592. /**
  99593. * @method markInvalid
  99594. * Associate one or more error messages with this field. Components using this mixin should implement this method to
  99595. * update the component's rendering to display the messages.
  99596. *
  99597. * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
  99598. * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
  99599. * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
  99600. *
  99601. * @param {String/String[]} errors The error message(s) for the field.
  99602. */
  99603. markInvalid: Ext.emptyFn,
  99604. /**
  99605. * @method clearInvalid
  99606. * Clear any invalid styles/messages for this field. Components using this mixin should implement this method to
  99607. * update the components rendering to clear any existing messages.
  99608. *
  99609. * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
  99610. * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
  99611. * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
  99612. */
  99613. clearInvalid: Ext.emptyFn
  99614. });
  99615. /**
  99616. * @docauthor Jason Johnston <jason@sencha.com>
  99617. *
  99618. * Base class for form fields that provides default event handling, rendering, and other common functionality
  99619. * needed by all form field types. Utilizes the {@link Ext.form.field.Field} mixin for value handling and validation,
  99620. * and the {@link Ext.form.Labelable} mixin to provide label and error message display.
  99621. *
  99622. * In most cases you will want to use a subclass, such as {@link Ext.form.field.Text} or {@link Ext.form.field.Checkbox},
  99623. * rather than creating instances of this class directly. However if you are implementing a custom form field,
  99624. * using this as the parent class is recommended.
  99625. *
  99626. * # Values and Conversions
  99627. *
  99628. * Because Base implements the Field mixin, it has a main value that can be initialized with the
  99629. * {@link #value} config and manipulated via the {@link #getValue} and {@link #setValue} methods. This main
  99630. * value can be one of many data types appropriate to the current field, for instance a {@link Ext.form.field.Date Date}
  99631. * field would use a JavaScript Date object as its value type. However, because the field is rendered as a HTML
  99632. * input, this value data type can not always be directly used in the rendered field.
  99633. *
  99634. * Therefore Base introduces the concept of a "raw value". This is the value of the rendered HTML input field,
  99635. * and is normally a String. The {@link #getRawValue} and {@link #setRawValue} methods can be used to directly
  99636. * work with the raw value, though it is recommended to use getValue and setValue in most cases.
  99637. *
  99638. * Conversion back and forth between the main value and the raw value is handled by the {@link #valueToRaw} and
  99639. * {@link #rawToValue} methods. If you are implementing a subclass that uses a non-String value data type, you
  99640. * should override these methods to handle the conversion.
  99641. *
  99642. * # Rendering
  99643. *
  99644. * The content of the field body is defined by the {@link #fieldSubTpl} XTemplate, with its argument data
  99645. * created by the {@link #getSubTplData} method. Override this template and/or method to create custom
  99646. * field renderings.
  99647. *
  99648. * # Example usage:
  99649. *
  99650. * @example
  99651. * // A simple subclass of Base that creates a HTML5 search field. Redirects to the
  99652. * // searchUrl when the Enter key is pressed.222
  99653. * Ext.define('Ext.form.SearchField', {
  99654. * extend: 'Ext.form.field.Base',
  99655. * alias: 'widget.searchfield',
  99656. *
  99657. * inputType: 'search',
  99658. *
  99659. * // Config defining the search URL
  99660. * searchUrl: 'http://www.google.com/search?q={0}',
  99661. *
  99662. * // Add specialkey listener
  99663. * initComponent: function() {
  99664. * this.callParent();
  99665. * this.on('specialkey', this.checkEnterKey, this);
  99666. * },
  99667. *
  99668. * // Handle enter key presses, execute the search if the field has a value
  99669. * checkEnterKey: function(field, e) {
  99670. * var value = this.getValue();
  99671. * if (e.getKey() === e.ENTER && !Ext.isEmpty(value)) {
  99672. * location.href = Ext.String.format(this.searchUrl, value);
  99673. * }
  99674. * }
  99675. * });
  99676. *
  99677. * Ext.create('Ext.form.Panel', {
  99678. * title: 'Base Example',
  99679. * bodyPadding: 5,
  99680. * width: 250,
  99681. *
  99682. * // Fields will be arranged vertically, stretched to full width
  99683. * layout: 'anchor',
  99684. * defaults: {
  99685. * anchor: '100%'
  99686. * },
  99687. * items: [{
  99688. * xtype: 'searchfield',
  99689. * fieldLabel: 'Search',
  99690. * name: 'query'
  99691. * }],
  99692. * renderTo: Ext.getBody()
  99693. * });
  99694. */
  99695. Ext.define('Ext.form.field.Base', {
  99696. extend: 'Ext.Component',
  99697. mixins: {
  99698. labelable: 'Ext.form.Labelable',
  99699. field: 'Ext.form.field.Field'
  99700. },
  99701. alias: 'widget.field',
  99702. alternateClassName: ['Ext.form.Field', 'Ext.form.BaseField'],
  99703. requires: ['Ext.util.DelayedTask', 'Ext.XTemplate', 'Ext.layout.component.field.Field'],
  99704. /**
  99705. * @cfg {Ext.XTemplate} fieldSubTpl
  99706. * The content of the field body is defined by this config option.
  99707. * @private
  99708. */
  99709. fieldSubTpl: [ // note: {id} here is really {inputId}, but {cmpId} is available
  99710. '<input id="{id}" type="{type}" {inputAttrTpl}',
  99711. ' size="1"', // allows inputs to fully respect CSS widths across all browsers
  99712. '<tpl if="name"> name="{name}"</tpl>',
  99713. '<tpl if="value"> value="{[Ext.util.Format.htmlEncode(values.value)]}"</tpl>',
  99714. '<tpl if="placeholder"> placeholder="{placeholder}"</tpl>',
  99715. '{%if (values.maxLength !== undefined){%} maxlength="{maxLength}"{%}%}',
  99716. '<tpl if="readOnly"> readonly="readonly"</tpl>',
  99717. '<tpl if="disabled"> disabled="disabled"</tpl>',
  99718. '<tpl if="tabIdx"> tabIndex="{tabIdx}"</tpl>',
  99719. '<tpl if="fieldStyle"> style="{fieldStyle}"</tpl>',
  99720. ' class="{fieldCls} {typeCls} {editableCls}" autocomplete="off"/>',
  99721. {
  99722. disableFormats: true
  99723. }
  99724. ],
  99725. subTplInsertions: [
  99726. /**
  99727. * @cfg {String/Array/Ext.XTemplate} inputAttrTpl
  99728. * An optional string or `XTemplate` configuration to insert in the field markup
  99729. * inside the input element (as attributes). If an `XTemplate` is used, the component's
  99730. * {@link #getSubTplData subTpl data} serves as the context.
  99731. */
  99732. 'inputAttrTpl'
  99733. ],
  99734. /**
  99735. * @cfg {String} name
  99736. * The name of the field. This is used as the parameter name when including the field value
  99737. * in a {@link Ext.form.Basic#submit form submit()}. If no name is configured, it falls back to the {@link #inputId}.
  99738. * To prevent the field from being included in the form submit, set {@link #submitValue} to false.
  99739. */
  99740. /**
  99741. * @cfg {String} inputType
  99742. * The type attribute for input fields -- e.g. radio, text, password, file. The extended types
  99743. * supported by HTML5 inputs (url, email, etc.) may also be used, though using them will cause older browsers to
  99744. * fall back to 'text'.
  99745. *
  99746. * The type 'password' must be used to render that field type currently -- there is no separate Ext component for
  99747. * that. You can use {@link Ext.form.field.File} which creates a custom-rendered file upload field, but if you want
  99748. * a plain unstyled file input you can use a Base with inputType:'file'.
  99749. */
  99750. inputType: 'text',
  99751. /**
  99752. * @cfg {Number} tabIndex
  99753. * The tabIndex for this field. Note this only applies to fields that are rendered, not those which are built via
  99754. * applyTo
  99755. */
  99756. //<locale>
  99757. /**
  99758. * @cfg {String} invalidText
  99759. * The error text to use when marking a field invalid and no message is provided
  99760. */
  99761. invalidText : 'The value in this field is invalid',
  99762. //</locale>
  99763. /**
  99764. * @cfg {String} [fieldCls='x-form-field']
  99765. * The default CSS class for the field input
  99766. */
  99767. fieldCls : Ext.baseCSSPrefix + 'form-field',
  99768. /**
  99769. * @cfg {String} fieldStyle
  99770. * Optional CSS style(s) to be applied to the {@link #inputEl field input element}. Should be a valid argument to
  99771. * {@link Ext.Element#applyStyles}. Defaults to undefined. See also the {@link #setFieldStyle} method for changing
  99772. * the style after initialization.
  99773. */
  99774. /**
  99775. * @cfg {String} [focusCls='x-form-focus']
  99776. * The CSS class to use when the field receives focus
  99777. */
  99778. focusCls : 'form-focus',
  99779. /**
  99780. * @cfg {String} dirtyCls
  99781. * The CSS class to use when the field value {@link #isDirty is dirty}.
  99782. */
  99783. dirtyCls : Ext.baseCSSPrefix + 'form-dirty',
  99784. /**
  99785. * @cfg {String[]} checkChangeEvents
  99786. * A list of event names that will be listened for on the field's {@link #inputEl input element}, which will cause
  99787. * the field's value to be checked for changes. If a change is detected, the {@link #change change event} will be
  99788. * fired, followed by validation if the {@link #validateOnChange} option is enabled.
  99789. *
  99790. * Defaults to ['change', 'propertychange'] in Internet Explorer, and ['change', 'input', 'textInput', 'keyup',
  99791. * 'dragdrop'] in other browsers. This catches all the ways that field values can be changed in most supported
  99792. * browsers; the only known exceptions at the time of writing are:
  99793. *
  99794. * - Safari 3.2 and older: cut/paste in textareas via the context menu, and dragging text into textareas
  99795. * - Opera 10 and 11: dragging text into text fields and textareas, and cut via the context menu in text fields
  99796. * and textareas
  99797. * - Opera 9: Same as Opera 10 and 11, plus paste from context menu in text fields and textareas
  99798. *
  99799. * If you need to guarantee on-the-fly change notifications including these edge cases, you can call the
  99800. * {@link #checkChange} method on a repeating interval, e.g. using {@link Ext.TaskManager}, or if the field is within
  99801. * a {@link Ext.form.Panel}, you can use the FormPanel's {@link Ext.form.Panel#pollForChanges} configuration to set up
  99802. * such a task automatically.
  99803. */
  99804. checkChangeEvents: Ext.isIE && (!document.documentMode || document.documentMode < 9) ?
  99805. ['change', 'propertychange'] :
  99806. ['change', 'input', 'textInput', 'keyup', 'dragdrop'],
  99807. /**
  99808. * @cfg {Number} checkChangeBuffer
  99809. * Defines a timeout in milliseconds for buffering {@link #checkChangeEvents} that fire in rapid succession.
  99810. * Defaults to 50 milliseconds.
  99811. */
  99812. checkChangeBuffer: 50,
  99813. componentLayout: 'field',
  99814. /**
  99815. * @cfg {Boolean} readOnly
  99816. * true to mark the field as readOnly in HTML.
  99817. *
  99818. * **Note**: this only sets the element's readOnly DOM attribute. Setting `readOnly=true`, for example, will not
  99819. * disable triggering a ComboBox or Date; it gives you the option of forcing the user to choose via the trigger
  99820. * without typing in the text box. To hide the trigger use `{@link Ext.form.field.Trigger#hideTrigger hideTrigger}`.
  99821. */
  99822. readOnly : false,
  99823. /**
  99824. * @cfg {String} readOnlyCls
  99825. * The CSS class applied to the component's main element when it is {@link #readOnly}.
  99826. */
  99827. readOnlyCls: Ext.baseCSSPrefix + 'form-readonly',
  99828. /**
  99829. * @cfg {String} inputId
  99830. * The id that will be given to the generated input DOM element. Defaults to an automatically generated id. If you
  99831. * configure this manually, you must make sure it is unique in the document.
  99832. */
  99833. /**
  99834. * @cfg {Boolean} validateOnBlur
  99835. * Whether the field should validate when it loses focus. This will cause fields to be validated
  99836. * as the user steps through the fields in the form regardless of whether they are making changes to those fields
  99837. * along the way. See also {@link #validateOnChange}.
  99838. */
  99839. validateOnBlur: true,
  99840. // private
  99841. hasFocus : false,
  99842. baseCls: Ext.baseCSSPrefix + 'field',
  99843. maskOnDisable: false,
  99844. // private
  99845. initComponent : function() {
  99846. var me = this;
  99847. me.callParent();
  99848. me.subTplData = me.subTplData || {};
  99849. me.addEvents(
  99850. /**
  99851. * @event specialkey
  99852. * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. To handle other keys
  99853. * see {@link Ext.util.KeyMap}. You can check {@link Ext.EventObject#getKey} to determine which key was
  99854. * pressed. For example:
  99855. *
  99856. * var form = new Ext.form.Panel({
  99857. * ...
  99858. * items: [{
  99859. * fieldLabel: 'Field 1',
  99860. * name: 'field1',
  99861. * allowBlank: false
  99862. * },{
  99863. * fieldLabel: 'Field 2',
  99864. * name: 'field2',
  99865. * listeners: {
  99866. * specialkey: function(field, e){
  99867. * // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
  99868. * // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
  99869. * if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
  99870. * var form = field.up('form').getForm();
  99871. * form.submit();
  99872. * }
  99873. * }
  99874. * }
  99875. * }
  99876. * ],
  99877. * ...
  99878. * });
  99879. *
  99880. * @param {Ext.form.field.Base} this
  99881. * @param {Ext.EventObject} e The event object
  99882. */
  99883. 'specialkey',
  99884. /**
  99885. * @event writeablechange
  99886. * Fires when this field changes its read-only status.
  99887. * @param {Ext.form.field.Base} this
  99888. * @param {Boolean} Read only flag
  99889. */
  99890. 'writeablechange'
  99891. );
  99892. // Init mixins
  99893. me.initLabelable();
  99894. me.initField();
  99895. // Default name to inputId
  99896. if (!me.name) {
  99897. me.name = me.getInputId();
  99898. }
  99899. },
  99900. beforeRender: function(){
  99901. var me = this;
  99902. me.callParent(arguments);
  99903. me.beforeLabelableRender(arguments);
  99904. if (me.readOnly) {
  99905. me.addCls(me.readOnlyCls);
  99906. }
  99907. },
  99908. /**
  99909. * Returns the input id for this field. If none was specified via the {@link #inputId} config, then an id will be
  99910. * automatically generated.
  99911. */
  99912. getInputId: function() {
  99913. return this.inputId || (this.inputId = this.id + '-inputEl');
  99914. },
  99915. /**
  99916. * Creates and returns the data object to be used when rendering the {@link #fieldSubTpl}.
  99917. * @return {Object} The template data
  99918. * @template
  99919. */
  99920. getSubTplData: function() {
  99921. var me = this,
  99922. type = me.inputType,
  99923. inputId = me.getInputId(),
  99924. data;
  99925. data = Ext.apply({
  99926. id : inputId,
  99927. cmpId : me.id,
  99928. name : me.name || inputId,
  99929. disabled : me.disabled,
  99930. readOnly : me.readOnly,
  99931. value : me.getRawValue(),
  99932. type : type,
  99933. fieldCls : me.fieldCls,
  99934. fieldStyle : me.getFieldStyle(),
  99935. tabIdx : me.tabIndex,
  99936. typeCls : Ext.baseCSSPrefix + 'form-' + (type === 'password' ? 'text' : type)
  99937. }, me.subTplData);
  99938. me.getInsertionRenderData(data, me.subTplInsertions);
  99939. return data;
  99940. },
  99941. afterFirstLayout: function() {
  99942. this.callParent();
  99943. var el = this.inputEl;
  99944. if (el) {
  99945. el.selectable();
  99946. }
  99947. },
  99948. applyRenderSelectors: function() {
  99949. var me = this;
  99950. me.callParent();
  99951. /**
  99952. * @property {Ext.Element} inputEl
  99953. * The input Element for this Field. Only available after the field has been rendered.
  99954. */
  99955. me.inputEl = me.el.getById(me.getInputId());
  99956. },
  99957. /**
  99958. * Gets the markup to be inserted into the outer template's bodyEl. For fields this is the actual input element.
  99959. */
  99960. getSubTplMarkup: function() {
  99961. return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
  99962. },
  99963. initRenderTpl: function() {
  99964. var me = this;
  99965. if (!me.hasOwnProperty('renderTpl')) {
  99966. me.renderTpl = me.getTpl('labelableRenderTpl');
  99967. }
  99968. return me.callParent();
  99969. },
  99970. initRenderData: function() {
  99971. return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
  99972. },
  99973. /**
  99974. * Set the {@link #fieldStyle CSS style} of the {@link #inputEl field input element}.
  99975. * @param {String/Object/Function} style The style(s) to apply. Should be a valid argument to {@link
  99976. * Ext.Element#applyStyles}.
  99977. */
  99978. setFieldStyle: function(style) {
  99979. var me = this,
  99980. inputEl = me.inputEl;
  99981. if (inputEl) {
  99982. inputEl.applyStyles(style);
  99983. }
  99984. me.fieldStyle = style;
  99985. },
  99986. getFieldStyle: function() {
  99987. return 'width:100%;' + (Ext.isObject(this.fieldStyle) ? Ext.DomHelper.generateStyles(this.fieldStyle) : this.fieldStyle ||'');
  99988. },
  99989. // private
  99990. onRender : function() {
  99991. var me = this;
  99992. me.callParent(arguments);
  99993. me.onLabelableRender();
  99994. me.renderActiveError();
  99995. },
  99996. getFocusEl: function() {
  99997. return this.inputEl;
  99998. },
  99999. isFileUpload: function() {
  100000. return this.inputType === 'file';
  100001. },
  100002. extractFileInput: function() {
  100003. var me = this,
  100004. fileInput = me.isFileUpload() ? me.inputEl.dom : null,
  100005. clone;
  100006. if (fileInput) {
  100007. clone = fileInput.cloneNode(true);
  100008. fileInput.parentNode.replaceChild(clone, fileInput);
  100009. me.inputEl = Ext.get(clone);
  100010. }
  100011. return fileInput;
  100012. },
  100013. // private override to use getSubmitValue() as a convenience
  100014. getSubmitData: function() {
  100015. var me = this,
  100016. data = null,
  100017. val;
  100018. if (!me.disabled && me.submitValue && !me.isFileUpload()) {
  100019. val = me.getSubmitValue();
  100020. if (val !== null) {
  100021. data = {};
  100022. data[me.getName()] = val;
  100023. }
  100024. }
  100025. return data;
  100026. },
  100027. /**
  100028. * Returns the value that would be included in a standard form submit for this field. This will be combined with the
  100029. * field's name to form a name=value pair in the {@link #getSubmitData submitted parameters}. If an empty string is
  100030. * returned then just the name= will be submitted; if null is returned then nothing will be submitted.
  100031. *
  100032. * Note that the value returned will have been {@link #processRawValue processed} but may or may not have been
  100033. * successfully {@link #validate validated}.
  100034. *
  100035. * @return {String} The value to be submitted, or null.
  100036. */
  100037. getSubmitValue: function() {
  100038. return this.processRawValue(this.getRawValue());
  100039. },
  100040. /**
  100041. * Returns the raw value of the field, without performing any normalization, conversion, or validation. To get a
  100042. * normalized and converted value see {@link #getValue}.
  100043. * @return {String} value The raw String value of the field
  100044. */
  100045. getRawValue: function() {
  100046. var me = this,
  100047. v = (me.inputEl ? me.inputEl.getValue() : Ext.value(me.rawValue, ''));
  100048. me.rawValue = v;
  100049. return v;
  100050. },
  100051. /**
  100052. * Sets the field's raw value directly, bypassing {@link #valueToRaw value conversion}, change detection, and
  100053. * validation. To set the value with these additional inspections see {@link #setValue}.
  100054. * @param {Object} value The value to set
  100055. * @return {Object} value The field value that is set
  100056. */
  100057. setRawValue: function(value) {
  100058. var me = this;
  100059. value = Ext.value(me.transformRawValue(value), '');
  100060. me.rawValue = value;
  100061. // Some Field subclasses may not render an inputEl
  100062. if (me.inputEl) {
  100063. me.inputEl.dom.value = value;
  100064. }
  100065. return value;
  100066. },
  100067. /**
  100068. * Transform the raw value before it is set
  100069. * @protected
  100070. * @param {Object} value The value
  100071. * @return {Object} The value to set
  100072. */
  100073. transformRawValue: function(value) {
  100074. return value;
  100075. },
  100076. /**
  100077. * Converts a mixed-type value to a raw representation suitable for displaying in the field. This allows controlling
  100078. * how value objects passed to {@link #setValue} are shown to the user, including localization. For instance, for a
  100079. * {@link Ext.form.field.Date}, this would control how a Date object passed to {@link #setValue} would be converted
  100080. * to a String for display in the field.
  100081. *
  100082. * See {@link #rawToValue} for the opposite conversion.
  100083. *
  100084. * The base implementation simply does a standard toString conversion, and converts {@link Ext#isEmpty empty values}
  100085. * to an empty string.
  100086. *
  100087. * @param {Object} value The mixed-type value to convert to the raw representation.
  100088. * @return {Object} The converted raw value.
  100089. */
  100090. valueToRaw: function(value) {
  100091. return '' + Ext.value(value, '');
  100092. },
  100093. /**
  100094. * Converts a raw input field value into a mixed-type value that is suitable for this particular field type. This
  100095. * allows controlling the normalization and conversion of user-entered values into field-type-appropriate values,
  100096. * e.g. a Date object for {@link Ext.form.field.Date}, and is invoked by {@link #getValue}.
  100097. *
  100098. * It is up to individual implementations to decide how to handle raw values that cannot be successfully converted
  100099. * to the desired object type.
  100100. *
  100101. * See {@link #valueToRaw} for the opposite conversion.
  100102. *
  100103. * The base implementation does no conversion, returning the raw value untouched.
  100104. *
  100105. * @param {Object} rawValue
  100106. * @return {Object} The converted value.
  100107. */
  100108. rawToValue: function(rawValue) {
  100109. return rawValue;
  100110. },
  100111. /**
  100112. * Performs any necessary manipulation of a raw field value to prepare it for {@link #rawToValue conversion} and/or
  100113. * {@link #validate validation}, for instance stripping out ignored characters. In the base implementation it does
  100114. * nothing; individual subclasses may override this as needed.
  100115. *
  100116. * @param {Object} value The unprocessed string value
  100117. * @return {Object} The processed string value
  100118. */
  100119. processRawValue: function(value) {
  100120. return value;
  100121. },
  100122. /**
  100123. * Returns the current data value of the field. The type of value returned is particular to the type of the
  100124. * particular field (e.g. a Date object for {@link Ext.form.field.Date}), as the result of calling {@link #rawToValue} on
  100125. * the field's {@link #processRawValue processed} String value. To return the raw String value, see {@link #getRawValue}.
  100126. * @return {Object} value The field value
  100127. */
  100128. getValue: function() {
  100129. var me = this,
  100130. val = me.rawToValue(me.processRawValue(me.getRawValue()));
  100131. me.value = val;
  100132. return val;
  100133. },
  100134. /**
  100135. * Sets a data value into the field and runs the change detection and validation. To set the value directly
  100136. * without these inspections see {@link #setRawValue}.
  100137. * @param {Object} value The value to set
  100138. * @return {Ext.form.field.Field} this
  100139. */
  100140. setValue: function(value) {
  100141. var me = this;
  100142. me.setRawValue(me.valueToRaw(value));
  100143. return me.mixins.field.setValue.call(me, value);
  100144. },
  100145. onBoxReady: function() {
  100146. var me = this;
  100147. me.callParent();
  100148. if (me.setReadOnlyOnBoxReady) {
  100149. me.setReadOnly(me.readOnly);
  100150. }
  100151. },
  100152. //private
  100153. onDisable: function() {
  100154. var me = this,
  100155. inputEl = me.inputEl;
  100156. me.callParent();
  100157. if (inputEl) {
  100158. inputEl.dom.disabled = true;
  100159. if (me.hasActiveError()) {
  100160. // clear invalid state since the field is now disabled
  100161. me.clearInvalid();
  100162. me.needsValidateOnEnable = true;
  100163. }
  100164. }
  100165. },
  100166. //private
  100167. onEnable: function() {
  100168. var me = this,
  100169. inputEl = me.inputEl;
  100170. me.callParent();
  100171. if (inputEl) {
  100172. inputEl.dom.disabled = false;
  100173. if (me.needsValidateOnEnable) {
  100174. delete me.needsValidateOnEnable;
  100175. // will trigger errors to be shown
  100176. me.forceValidation = true;
  100177. me.isValid();
  100178. delete me.forceValidation;
  100179. }
  100180. }
  100181. },
  100182. /**
  100183. * Sets the read only state of this field.
  100184. * @param {Boolean} readOnly Whether the field should be read only.
  100185. */
  100186. setReadOnly: function(readOnly) {
  100187. var me = this,
  100188. inputEl = me.inputEl;
  100189. readOnly = !!readOnly;
  100190. me[readOnly ? 'addCls' : 'removeCls'](me.readOnlyCls);
  100191. me.readOnly = readOnly;
  100192. if (inputEl) {
  100193. inputEl.dom.readOnly = readOnly;
  100194. } else if (me.rendering) {
  100195. me.setReadOnlyOnBoxReady = true;
  100196. }
  100197. me.fireEvent('writeablechange', me, readOnly);
  100198. },
  100199. // private
  100200. fireKey: function(e){
  100201. if(e.isSpecialKey()){
  100202. this.fireEvent('specialkey', this, new Ext.EventObjectImpl(e));
  100203. }
  100204. },
  100205. // private
  100206. initEvents : function(){
  100207. var me = this,
  100208. inputEl = me.inputEl,
  100209. onChangeTask,
  100210. onChangeEvent,
  100211. events = me.checkChangeEvents,
  100212. e,
  100213. eLen = events.length,
  100214. event;
  100215. // standardise buffer across all browsers + OS-es for consistent event order.
  100216. // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus)
  100217. if (me.inEditor) {
  100218. me.onBlur = Ext.Function.createBuffered(me.onBlur, 10);
  100219. }
  100220. if (inputEl) {
  100221. me.mon(inputEl, Ext.EventManager.getKeyEvent(), me.fireKey, me);
  100222. // listen for immediate value changes
  100223. onChangeTask = new Ext.util.DelayedTask(me.checkChange, me);
  100224. me.onChangeEvent = onChangeEvent = function() {
  100225. onChangeTask.delay(me.checkChangeBuffer);
  100226. };
  100227. for (e = 0; e < eLen; e++) {
  100228. event = events[e];
  100229. if (event === 'propertychange') {
  100230. me.usesPropertychange = true;
  100231. }
  100232. me.mon(inputEl, event, onChangeEvent);
  100233. }
  100234. }
  100235. me.callParent();
  100236. },
  100237. doComponentLayout: function() {
  100238. var me = this,
  100239. inputEl = me.inputEl,
  100240. usesPropertychange = me.usesPropertychange,
  100241. ename = 'propertychange',
  100242. onChangeEvent = me.onChangeEvent;
  100243. // In IE if propertychange is one of the checkChangeEvents, we need to remove
  100244. // the listener prior to layout and re-add it after, to prevent it from firing
  100245. // needlessly for attribute and style changes applied to the inputEl.
  100246. if (usesPropertychange) {
  100247. me.mun(inputEl, ename, onChangeEvent);
  100248. }
  100249. me.callParent(arguments);
  100250. if (usesPropertychange) {
  100251. me.mon(inputEl, ename, onChangeEvent);
  100252. }
  100253. },
  100254. /**
  100255. * @private Called when the field's dirty state changes. Adds/removes the {@link #dirtyCls} on the main element.
  100256. * @param {Boolean} isDirty
  100257. */
  100258. onDirtyChange: function(isDirty) {
  100259. this[isDirty ? 'addCls' : 'removeCls'](this.dirtyCls);
  100260. },
  100261. /**
  100262. * Returns whether or not the field value is currently valid by {@link #getErrors validating} the
  100263. * {@link #processRawValue processed raw value} of the field. **Note**: {@link #disabled} fields are
  100264. * always treated as valid.
  100265. *
  100266. * @return {Boolean} True if the value is valid, else false
  100267. */
  100268. isValid : function() {
  100269. var me = this,
  100270. disabled = me.disabled,
  100271. validate = me.forceValidation || !disabled;
  100272. return validate ? me.validateValue(me.processRawValue(me.getRawValue())) : disabled;
  100273. },
  100274. /**
  100275. * Uses {@link #getErrors} to build an array of validation errors. If any errors are found, they are passed to
  100276. * {@link #markInvalid} and false is returned, otherwise true is returned.
  100277. *
  100278. * Previously, subclasses were invited to provide an implementation of this to process validations - from 3.2
  100279. * onwards {@link #getErrors} should be overridden instead.
  100280. *
  100281. * @param {Object} value The value to validate
  100282. * @return {Boolean} True if all validations passed, false if one or more failed
  100283. */
  100284. validateValue: function(value) {
  100285. var me = this,
  100286. errors = me.getErrors(value),
  100287. isValid = Ext.isEmpty(errors);
  100288. if (!me.preventMark) {
  100289. if (isValid) {
  100290. me.clearInvalid();
  100291. } else {
  100292. me.markInvalid(errors);
  100293. }
  100294. }
  100295. return isValid;
  100296. },
  100297. /**
  100298. * Display one or more error messages associated with this field, using {@link #msgTarget} to determine how to
  100299. * display the messages and applying {@link #invalidCls} to the field's UI element.
  100300. *
  100301. * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
  100302. * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
  100303. * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
  100304. *
  100305. * @param {String/String[]} errors The validation message(s) to display.
  100306. */
  100307. markInvalid : function(errors) {
  100308. // Save the message and fire the 'invalid' event
  100309. var me = this,
  100310. oldMsg = me.getActiveError();
  100311. me.setActiveErrors(Ext.Array.from(errors));
  100312. if (oldMsg !== me.getActiveError()) {
  100313. me.updateLayout();
  100314. }
  100315. },
  100316. /**
  100317. * Clear any invalid styles/messages for this field.
  100318. *
  100319. * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
  100320. * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
  100321. * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
  100322. */
  100323. clearInvalid : function() {
  100324. // Clear the message and fire the 'valid' event
  100325. var me = this,
  100326. hadError = me.hasActiveError();
  100327. me.unsetActiveError();
  100328. if (hadError) {
  100329. me.updateLayout();
  100330. }
  100331. },
  100332. /**
  100333. * @private Overrides the method from the Ext.form.Labelable mixin to also add the invalidCls to the inputEl,
  100334. * as that is required for proper styling in IE with nested fields (due to lack of child selector)
  100335. */
  100336. renderActiveError: function() {
  100337. var me = this,
  100338. hasError = me.hasActiveError();
  100339. if (me.inputEl) {
  100340. // Add/remove invalid class
  100341. me.inputEl[hasError ? 'addCls' : 'removeCls'](me.invalidCls + '-field');
  100342. }
  100343. me.mixins.labelable.renderActiveError.call(me);
  100344. },
  100345. getActionEl: function() {
  100346. return this.inputEl || this.el;
  100347. }
  100348. });
  100349. /**
  100350. * @singleton
  100351. * @alternateClassName Ext.form.VTypes
  100352. *
  100353. * This is a singleton object which contains a set of commonly used field validation functions
  100354. * and provides a mechanism for creating reusable custom field validations.
  100355. * The following field validation functions are provided out of the box:
  100356. *
  100357. * - {@link #alpha}
  100358. * - {@link #alphanum}
  100359. * - {@link #email}
  100360. * - {@link #url}
  100361. *
  100362. * VTypes can be applied to a {@link Ext.form.field.Text Text Field} using the `{@link Ext.form.field.Text#vtype vtype}` configuration:
  100363. *
  100364. * Ext.create('Ext.form.field.Text', {
  100365. * fieldLabel: 'Email Address',
  100366. * name: 'email',
  100367. * vtype: 'email' // applies email validation rules to this field
  100368. * });
  100369. *
  100370. * To create custom VTypes:
  100371. *
  100372. * // custom Vtype for vtype:'time'
  100373. * var timeTest = /^([1-9]|1[0-9]):([0-5][0-9])(\s[a|p]m)$/i;
  100374. * Ext.apply(Ext.form.field.VTypes, {
  100375. * // vtype validation function
  100376. * time: function(val, field) {
  100377. * return timeTest.test(val);
  100378. * },
  100379. * // vtype Text property: The error text to display when the validation function returns false
  100380. * timeText: 'Not a valid time. Must be in the format "12:34 PM".',
  100381. * // vtype Mask property: The keystroke filter mask
  100382. * timeMask: /[\d\s:amp]/i
  100383. * });
  100384. *
  100385. * In the above example the `time` function is the validator that will run when field validation occurs,
  100386. * `timeText` is the error message, and `timeMask` limits what characters can be typed into the field.
  100387. * Note that the `Text` and `Mask` functions must begin with the same name as the validator function.
  100388. *
  100389. * Using a custom validator is the same as using one of the build-in validators - just use the name of the validator function
  100390. * as the `{@link Ext.form.field.Text#vtype vtype}` configuration on a {@link Ext.form.field.Text Text Field}:
  100391. *
  100392. * Ext.create('Ext.form.field.Text', {
  100393. * fieldLabel: 'Departure Time',
  100394. * name: 'departureTime',
  100395. * vtype: 'time' // applies custom time validation rules to this field
  100396. * });
  100397. *
  100398. * Another example of a custom validator:
  100399. *
  100400. * // custom Vtype for vtype:'IPAddress'
  100401. * Ext.apply(Ext.form.field.VTypes, {
  100402. * IPAddress: function(v) {
  100403. * return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
  100404. * },
  100405. * IPAddressText: 'Must be a numeric IP address',
  100406. * IPAddressMask: /[\d\.]/i
  100407. * });
  100408. *
  100409. * It's important to note that using {@link Ext#apply Ext.apply()} means that the custom validator function
  100410. * as well as `Text` and `Mask` fields are added as properties of the `Ext.form.field.VTypes` singleton.
  100411. */
  100412. Ext.define('Ext.form.field.VTypes', (function(){
  100413. // closure these in so they are only created once.
  100414. var alpha = /^[a-zA-Z_]+$/,
  100415. alphanum = /^[a-zA-Z0-9_]+$/,
  100416. email = /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,
  100417. url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
  100418. // All these messages and functions are configurable
  100419. return {
  100420. singleton: true,
  100421. alternateClassName: 'Ext.form.VTypes',
  100422. /**
  100423. * The function used to validate email addresses. Note that this is a very basic validation - complete
  100424. * validation per the email RFC specifications is very complex and beyond the scope of this class, although this
  100425. * function can be overridden if a more comprehensive validation scheme is desired. See the validation section
  100426. * of the [Wikipedia article on email addresses][1] for additional information. This implementation is intended
  100427. * to validate the following emails:
  100428. *
  100429. * - `barney@example.de`
  100430. * - `barney.rubble@example.com`
  100431. * - `barney-rubble@example.coop`
  100432. * - `barney+rubble@example.com`
  100433. *
  100434. * [1]: http://en.wikipedia.org/wiki/E-mail_address
  100435. *
  100436. * @param {String} value The email address
  100437. * @return {Boolean} true if the RegExp test passed, and false if not.
  100438. */
  100439. 'email' : function(v){
  100440. return email.test(v);
  100441. },
  100442. //<locale>
  100443. /**
  100444. * @property {String} emailText
  100445. * The error text to display when the email validation function returns false.
  100446. * Defaults to: 'This field should be an e-mail address in the format "user@example.com"'
  100447. */
  100448. 'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
  100449. //</locale>
  100450. /**
  100451. * @property {RegExp} emailMask
  100452. * The keystroke filter mask to be applied on email input. See the {@link #email} method for information about
  100453. * more complex email validation. Defaults to: /[a-z0-9_\.\-@]/i
  100454. */
  100455. 'emailMask' : /[a-z0-9_\.\-@\+]/i,
  100456. /**
  100457. * The function used to validate URLs
  100458. * @param {String} value The URL
  100459. * @return {Boolean} true if the RegExp test passed, and false if not.
  100460. */
  100461. 'url' : function(v){
  100462. return url.test(v);
  100463. },
  100464. //<locale>
  100465. /**
  100466. * @property {String} urlText
  100467. * The error text to display when the url validation function returns false.
  100468. * Defaults to: 'This field should be a URL in the format "http:/'+'/www.example.com"'
  100469. */
  100470. 'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
  100471. //</locale>
  100472. /**
  100473. * The function used to validate alpha values
  100474. * @param {String} value The value
  100475. * @return {Boolean} true if the RegExp test passed, and false if not.
  100476. */
  100477. 'alpha' : function(v){
  100478. return alpha.test(v);
  100479. },
  100480. //<locale>
  100481. /**
  100482. * @property {String} alphaText
  100483. * The error text to display when the alpha validation function returns false.
  100484. * Defaults to: 'This field should only contain letters and _'
  100485. */
  100486. 'alphaText' : 'This field should only contain letters and _',
  100487. //</locale>
  100488. /**
  100489. * @property {RegExp} alphaMask
  100490. * The keystroke filter mask to be applied on alpha input. Defaults to: /[a-z_]/i
  100491. */
  100492. 'alphaMask' : /[a-z_]/i,
  100493. /**
  100494. * The function used to validate alphanumeric values
  100495. * @param {String} value The value
  100496. * @return {Boolean} true if the RegExp test passed, and false if not.
  100497. */
  100498. 'alphanum' : function(v){
  100499. return alphanum.test(v);
  100500. },
  100501. //<locale>
  100502. /**
  100503. * @property {String} alphanumText
  100504. * The error text to display when the alphanumeric validation function returns false.
  100505. * Defaults to: 'This field should only contain letters, numbers and _'
  100506. */
  100507. 'alphanumText' : 'This field should only contain letters, numbers and _',
  100508. //</locale>
  100509. /**
  100510. * @property {RegExp} alphanumMask
  100511. * The keystroke filter mask to be applied on alphanumeric input. Defaults to: /[a-z0-9_]/i
  100512. */
  100513. 'alphanumMask' : /[a-z0-9_]/i
  100514. };
  100515. }()));
  100516. /**
  100517. * @docauthor Jason Johnston <jason@sencha.com>
  100518. *
  100519. * A basic text field. Can be used as a direct replacement for traditional text inputs,
  100520. * or as the base class for more sophisticated input controls (like {@link Ext.form.field.TextArea}
  100521. * and {@link Ext.form.field.ComboBox}). Has support for empty-field placeholder values (see {@link #emptyText}).
  100522. *
  100523. * # Validation
  100524. *
  100525. * The Text field has a useful set of validations built in:
  100526. *
  100527. * - {@link #allowBlank} for making the field required
  100528. * - {@link #minLength} for requiring a minimum value length
  100529. * - {@link #maxLength} for setting a maximum value length (with {@link #enforceMaxLength} to add it
  100530. * as the `maxlength` attribute on the input element)
  100531. * - {@link #regex} to specify a custom regular expression for validation
  100532. *
  100533. * In addition, custom validations may be added:
  100534. *
  100535. * - {@link #vtype} specifies a virtual type implementation from {@link Ext.form.field.VTypes} which can contain
  100536. * custom validation logic
  100537. * - {@link #validator} allows a custom arbitrary function to be called during validation
  100538. *
  100539. * The details around how and when each of these validation options get used are described in the
  100540. * documentation for {@link #getErrors}.
  100541. *
  100542. * By default, the field value is checked for validity immediately while the user is typing in the
  100543. * field. This can be controlled with the {@link #validateOnChange}, {@link #checkChangeEvents}, and
  100544. * {@link #checkChangeBuffer} configurations. Also see the details on Form Validation in the
  100545. * {@link Ext.form.Panel} class documentation.
  100546. *
  100547. * # Masking and Character Stripping
  100548. *
  100549. * Text fields can be configured with custom regular expressions to be applied to entered values before
  100550. * validation: see {@link #maskRe} and {@link #stripCharsRe} for details.
  100551. *
  100552. * # Example usage
  100553. *
  100554. * @example
  100555. * Ext.create('Ext.form.Panel', {
  100556. * title: 'Contact Info',
  100557. * width: 300,
  100558. * bodyPadding: 10,
  100559. * renderTo: Ext.getBody(),
  100560. * items: [{
  100561. * xtype: 'textfield',
  100562. * name: 'name',
  100563. * fieldLabel: 'Name',
  100564. * allowBlank: false // requires a non-empty value
  100565. * }, {
  100566. * xtype: 'textfield',
  100567. * name: 'email',
  100568. * fieldLabel: 'Email Address',
  100569. * vtype: 'email' // requires value to be a valid email address format
  100570. * }]
  100571. * });
  100572. */
  100573. Ext.define('Ext.form.field.Text', {
  100574. extend:'Ext.form.field.Base',
  100575. alias: 'widget.textfield',
  100576. requires: ['Ext.form.field.VTypes', 'Ext.layout.component.field.Text'],
  100577. alternateClassName: ['Ext.form.TextField', 'Ext.form.Text'],
  100578. /**
  100579. * @cfg {String} vtypeText
  100580. * A custom error message to display in place of the default message provided for the **`{@link #vtype}`** currently
  100581. * set for this field. **Note**: only applies if **`{@link #vtype}`** is set, else ignored.
  100582. */
  100583. /**
  100584. * @cfg {RegExp} stripCharsRe
  100585. * A JavaScript RegExp object used to strip unwanted content from the value
  100586. * during input. If `stripCharsRe` is specified,
  100587. * every *character sequence* matching `stripCharsRe` will be removed.
  100588. */
  100589. /**
  100590. * @cfg {Number} size
  100591. * An initial value for the 'size' attribute on the text input element. This is only used if the field has no
  100592. * configured {@link #width} and is not given a width by its container's layout. Defaults to 20.
  100593. */
  100594. size: 20,
  100595. /**
  100596. * @cfg {Boolean} [grow=false]
  100597. * true if this field should automatically grow and shrink to its content
  100598. */
  100599. /**
  100600. * @cfg {Number} growMin
  100601. * The minimum width to allow when `{@link #grow} = true`
  100602. */
  100603. growMin : 30,
  100604. /**
  100605. * @cfg {Number} growMax
  100606. * The maximum width to allow when `{@link #grow} = true`
  100607. */
  100608. growMax : 800,
  100609. //<locale>
  100610. /**
  100611. * @cfg {String} growAppend
  100612. * A string that will be appended to the field's current value for the purposes of calculating the target field
  100613. * size. Only used when the {@link #grow} config is true. Defaults to a single capital "W" (the widest character in
  100614. * common fonts) to leave enough space for the next typed character and avoid the field value shifting before the
  100615. * width is adjusted.
  100616. */
  100617. growAppend: 'W',
  100618. //</locale>
  100619. /**
  100620. * @cfg {String} vtype
  100621. * A validation type name as defined in {@link Ext.form.field.VTypes}
  100622. */
  100623. /**
  100624. * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes (character being
  100625. * typed) that do not match.
  100626. * Note: It does not filter characters already in the input.
  100627. */
  100628. /**
  100629. * @cfg {Boolean} [disableKeyFilter=false]
  100630. * Specify true to disable input keystroke filtering
  100631. */
  100632. /**
  100633. * @cfg {Boolean} allowBlank
  100634. * Specify false to validate that the value's length is > 0
  100635. */
  100636. allowBlank : true,
  100637. /**
  100638. * @cfg {Number} minLength
  100639. * Minimum input field length required
  100640. */
  100641. minLength : 0,
  100642. /**
  100643. * @cfg {Number} maxLength
  100644. * Maximum input field length allowed by validation. This behavior is intended to
  100645. * provide instant feedback to the user by improving usability to allow pasting and editing or overtyping and back
  100646. * tracking. To restrict the maximum number of characters that can be entered into the field use the
  100647. * **{@link Ext.form.field.Text#enforceMaxLength enforceMaxLength}** option.
  100648. *
  100649. * Defaults to Number.MAX_VALUE.
  100650. */
  100651. maxLength : Number.MAX_VALUE,
  100652. /**
  100653. * @cfg {Boolean} enforceMaxLength
  100654. * True to set the maxLength property on the underlying input field. Defaults to false
  100655. */
  100656. /**
  100657. * @cfg {String} minLengthText
  100658. * Error text to display if the **{@link #minLength minimum length}** validation fails.
  100659. */
  100660. //<locale>
  100661. minLengthText : 'The minimum length for this field is {0}',
  100662. //</locale>
  100663. //<locale>
  100664. /**
  100665. * @cfg {String} maxLengthText
  100666. * Error text to display if the **{@link #maxLength maximum length}** validation fails
  100667. */
  100668. maxLengthText : 'The maximum length for this field is {0}',
  100669. //</locale>
  100670. /**
  100671. * @cfg {Boolean} [selectOnFocus=false]
  100672. * true to automatically select any existing field text when the field receives input focus
  100673. */
  100674. //<locale>
  100675. /**
  100676. * @cfg {String} blankText
  100677. * The error text to display if the **{@link #allowBlank}** validation fails
  100678. */
  100679. blankText : 'This field is required',
  100680. //</locale>
  100681. /**
  100682. * @cfg {Function} validator
  100683. * A custom validation function to be called during field validation ({@link #getErrors}).
  100684. * If specified, this function will be called first, allowing the developer to override the default validation
  100685. * process.
  100686. *
  100687. * This function will be passed the following parameters:
  100688. *
  100689. * @cfg {Object} validator.value The current field value
  100690. * @cfg {Boolean/String} validator.return
  100691. *
  100692. * - True if the value is valid
  100693. * - An error message if the value is invalid
  100694. */
  100695. /**
  100696. * @cfg {RegExp} regex
  100697. * A JavaScript RegExp object to be tested against the field value during validation.
  100698. * If the test fails, the field will be marked invalid using
  100699. * either **{@link #regexText}** or **{@link #invalidText}**.
  100700. */
  100701. /**
  100702. * @cfg {String} regexText
  100703. * The error text to display if **{@link #regex}** is used and the test fails during validation
  100704. */
  100705. regexText : '',
  100706. /**
  100707. * @cfg {String} emptyText
  100708. * The default text to place into an empty field.
  100709. *
  100710. * Note that normally this value will be submitted to the server if this field is enabled; to prevent this you can
  100711. * set the {@link Ext.form.action.Action#submitEmptyText submitEmptyText} option of {@link Ext.form.Basic#submit} to
  100712. * false.
  100713. *
  100714. * Also note that if you use {@link #inputType inputType}:'file', {@link #emptyText} is not supported and should be
  100715. * avoided.
  100716. *
  100717. * Note that for browsers that support it, setting this property will use the HTML 5 placeholder attribute, and for
  100718. * older browsers that don't support the HTML 5 placeholder attribute the value will be placed directly into the input
  100719. * element itself as the raw value. This means that older browsers will obfuscate the {@link #emptyText} value for
  100720. * password input fields.
  100721. */
  100722. /**
  100723. * @cfg {String} [emptyCls='x-form-empty-field']
  100724. * The CSS class to apply to an empty field to style the **{@link #emptyText}**.
  100725. * This class is automatically added and removed as needed depending on the current field value.
  100726. */
  100727. emptyCls : Ext.baseCSSPrefix + 'form-empty-field',
  100728. /**
  100729. * @cfg {String} [requiredCls='x-form-required-field']
  100730. * The CSS class to apply to a required field, i.e. a field where **{@link #allowBlank}** is false.
  100731. */
  100732. requiredCls : Ext.baseCSSPrefix + 'form-required-field',
  100733. /**
  100734. * @cfg {Boolean} [enableKeyEvents=false]
  100735. * true to enable the proxying of key events for the HTML input field
  100736. */
  100737. componentLayout: 'textfield',
  100738. // private
  100739. valueContainsPlaceholder : false,
  100740. initComponent: function () {
  100741. var me = this;
  100742. me.callParent();
  100743. me.addEvents(
  100744. /**
  100745. * @event autosize
  100746. * Fires when the **{@link #autoSize}** function is triggered and the field is resized according to the
  100747. * {@link #grow}/{@link #growMin}/{@link #growMax} configs as a result. This event provides a hook for the
  100748. * developer to apply additional logic at runtime to resize the field if needed.
  100749. * @param {Ext.form.field.Text} this This text field
  100750. * @param {Number} width The new field width
  100751. */
  100752. 'autosize',
  100753. /**
  100754. * @event keydown
  100755. * Keydown input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
  100756. * @param {Ext.form.field.Text} this This text field
  100757. * @param {Ext.EventObject} e
  100758. */
  100759. 'keydown',
  100760. /**
  100761. * @event keyup
  100762. * Keyup input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
  100763. * @param {Ext.form.field.Text} this This text field
  100764. * @param {Ext.EventObject} e
  100765. */
  100766. 'keyup',
  100767. /**
  100768. * @event keypress
  100769. * Keypress input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
  100770. * @param {Ext.form.field.Text} this This text field
  100771. * @param {Ext.EventObject} e
  100772. */
  100773. 'keypress'
  100774. );
  100775. me.addStateEvents('change');
  100776. me.setGrowSizePolicy();
  100777. },
  100778. // private
  100779. setGrowSizePolicy: function(){
  100780. if (this.grow) {
  100781. this.shrinkWrap |= 1; // width must shrinkWrap
  100782. }
  100783. },
  100784. // private
  100785. initEvents : function(){
  100786. var me = this,
  100787. el = me.inputEl;
  100788. me.callParent();
  100789. if(me.selectOnFocus || me.emptyText){
  100790. me.mon(el, 'mousedown', me.onMouseDown, me);
  100791. }
  100792. if(me.maskRe || (me.vtype && me.disableKeyFilter !== true && (me.maskRe = Ext.form.field.VTypes[me.vtype+'Mask']))){
  100793. me.mon(el, 'keypress', me.filterKeys, me);
  100794. }
  100795. if (me.enableKeyEvents) {
  100796. me.mon(el, {
  100797. scope: me,
  100798. keyup: me.onKeyUp,
  100799. keydown: me.onKeyDown,
  100800. keypress: me.onKeyPress
  100801. });
  100802. }
  100803. },
  100804. /**
  100805. * @private
  100806. * Override. Treat undefined and null values as equal to an empty string value.
  100807. */
  100808. isEqual: function(value1, value2) {
  100809. return this.isEqualAsString(value1, value2);
  100810. },
  100811. /**
  100812. * @private
  100813. * If grow=true, invoke the autoSize method when the field's value is changed.
  100814. */
  100815. onChange: function() {
  100816. this.callParent();
  100817. this.autoSize();
  100818. },
  100819. getSubTplData: function() {
  100820. var me = this,
  100821. value = me.getRawValue(),
  100822. isEmpty = me.emptyText && value.length < 1,
  100823. maxLength = me.maxLength,
  100824. placeholder;
  100825. // We can't just dump the value here, since MAX_VALUE ends up
  100826. // being something like 1.xxxxe+300, which gets interpreted as 1
  100827. // in the markup
  100828. if (me.enforceMaxLength) {
  100829. if (maxLength === Number.MAX_VALUE) {
  100830. maxLength = undefined;
  100831. }
  100832. } else {
  100833. maxLength = undefined;
  100834. }
  100835. if (isEmpty) {
  100836. if (Ext.supports.Placeholder) {
  100837. placeholder = me.emptyText;
  100838. } else {
  100839. value = me.emptyText;
  100840. me.valueContainsPlaceholder = true;
  100841. }
  100842. }
  100843. return Ext.apply(me.callParent(), {
  100844. maxLength : maxLength,
  100845. readOnly : me.readOnly,
  100846. placeholder : placeholder,
  100847. value : value,
  100848. fieldCls : me.fieldCls + ((isEmpty && (placeholder || value)) ? ' ' + me.emptyCls : '') + (me.allowBlank ? '' : ' ' + me.requiredCls)
  100849. });
  100850. },
  100851. afterRender: function(){
  100852. this.autoSize();
  100853. this.callParent();
  100854. },
  100855. onMouseDown: function(e){
  100856. var me = this;
  100857. if(!me.hasFocus){
  100858. me.mon(me.inputEl, 'mouseup', Ext.emptyFn, me, { single: true, preventDefault: true });
  100859. }
  100860. },
  100861. /**
  100862. * Performs any necessary manipulation of a raw String value to prepare it for conversion and/or
  100863. * {@link #validate validation}. For text fields this applies the configured {@link #stripCharsRe}
  100864. * to the raw value.
  100865. * @param {String} value The unprocessed string value
  100866. * @return {String} The processed string value
  100867. */
  100868. processRawValue: function(value) {
  100869. var me = this,
  100870. stripRe = me.stripCharsRe,
  100871. newValue;
  100872. if (stripRe) {
  100873. newValue = value.replace(stripRe, '');
  100874. if (newValue !== value) {
  100875. me.setRawValue(newValue);
  100876. value = newValue;
  100877. }
  100878. }
  100879. return value;
  100880. },
  100881. //private
  100882. onDisable: function(){
  100883. this.callParent();
  100884. if (Ext.isIE) {
  100885. this.inputEl.dom.unselectable = 'on';
  100886. }
  100887. },
  100888. //private
  100889. onEnable: function(){
  100890. this.callParent();
  100891. if (Ext.isIE) {
  100892. this.inputEl.dom.unselectable = '';
  100893. }
  100894. },
  100895. onKeyDown: function(e) {
  100896. this.fireEvent('keydown', this, e);
  100897. },
  100898. onKeyUp: function(e) {
  100899. this.fireEvent('keyup', this, e);
  100900. },
  100901. onKeyPress: function(e) {
  100902. this.fireEvent('keypress', this, e);
  100903. },
  100904. /**
  100905. * Resets the current field value to the originally-loaded value and clears any validation messages.
  100906. * Also adds **{@link #emptyText}** and **{@link #emptyCls}** if the original value was blank.
  100907. */
  100908. reset : function(){
  100909. this.callParent();
  100910. this.applyEmptyText();
  100911. },
  100912. applyEmptyText : function(){
  100913. var me = this,
  100914. emptyText = me.emptyText,
  100915. isEmpty;
  100916. if (me.rendered && emptyText) {
  100917. isEmpty = me.getRawValue().length < 1 && !me.hasFocus;
  100918. if (Ext.supports.Placeholder) {
  100919. me.inputEl.dom.placeholder = emptyText;
  100920. } else if (isEmpty) {
  100921. me.setRawValue(emptyText);
  100922. me.valueContainsPlaceholder = true;
  100923. }
  100924. //all browsers need this because of a styling issue with chrome + placeholders.
  100925. //the text isnt vertically aligned when empty (and using the placeholder)
  100926. if (isEmpty) {
  100927. me.inputEl.addCls(me.emptyCls);
  100928. }
  100929. me.autoSize();
  100930. }
  100931. },
  100932. afterFirstLayout: function() {
  100933. this.callParent();
  100934. if (Ext.isIE && this.disabled) {
  100935. var el = this.inputEl;
  100936. if (el) {
  100937. el.dom.unselectable = 'on';
  100938. }
  100939. }
  100940. },
  100941. // private
  100942. preFocus : function(){
  100943. var me = this,
  100944. inputEl = me.inputEl,
  100945. emptyText = me.emptyText,
  100946. isEmpty;
  100947. me.callParent(arguments);
  100948. if ((emptyText && !Ext.supports.Placeholder) && (inputEl.dom.value === me.emptyText && me.valueContainsPlaceholder)) {
  100949. me.setRawValue('');
  100950. isEmpty = true;
  100951. inputEl.removeCls(me.emptyCls);
  100952. me.valueContainsPlaceholder = false;
  100953. } else if (Ext.supports.Placeholder) {
  100954. me.inputEl.removeCls(me.emptyCls);
  100955. }
  100956. if (me.selectOnFocus || isEmpty) {
  100957. inputEl.dom.select();
  100958. }
  100959. },
  100960. onFocus: function() {
  100961. var me = this;
  100962. me.callParent(arguments);
  100963. if (me.emptyText) {
  100964. me.autoSize();
  100965. }
  100966. },
  100967. // private
  100968. postBlur : function(){
  100969. this.callParent(arguments);
  100970. this.applyEmptyText();
  100971. },
  100972. // private
  100973. filterKeys : function(e){
  100974. /*
  100975. * On European keyboards, the right alt key, Alt Gr, is used to type certain special characters.
  100976. * JS detects a keypress of this as ctrlKey & altKey. As such, we check that alt isn't pressed
  100977. * so we can still process these special characters.
  100978. */
  100979. if (e.ctrlKey && !e.altKey) {
  100980. return;
  100981. }
  100982. var key = e.getKey(),
  100983. charCode = String.fromCharCode(e.getCharCode());
  100984. if((Ext.isGecko || Ext.isOpera) && (e.isNavKeyPress() || key === e.BACKSPACE || (key === e.DELETE && e.button === -1))){
  100985. return;
  100986. }
  100987. if((!Ext.isGecko && !Ext.isOpera) && e.isSpecialKey() && !charCode){
  100988. return;
  100989. }
  100990. if(!this.maskRe.test(charCode)){
  100991. e.stopEvent();
  100992. }
  100993. },
  100994. getState: function() {
  100995. return this.addPropertyToState(this.callParent(), 'value');
  100996. },
  100997. applyState: function(state) {
  100998. this.callParent(arguments);
  100999. if(state.hasOwnProperty('value')) {
  101000. this.setValue(state.value);
  101001. }
  101002. },
  101003. /**
  101004. * Returns the raw String value of the field, without performing any normalization, conversion, or validation. Gets
  101005. * the current value of the input element if the field has been rendered, ignoring the value if it is the
  101006. * {@link #emptyText}. To get a normalized and converted value see {@link #getValue}.
  101007. * @return {String} The raw String value of the field
  101008. */
  101009. getRawValue: function() {
  101010. var me = this,
  101011. v = me.callParent();
  101012. if (v === me.emptyText && me.valueContainsPlaceholder) {
  101013. v = '';
  101014. }
  101015. return v;
  101016. },
  101017. /**
  101018. * Sets a data value into the field and runs the change detection and validation. Also applies any configured
  101019. * {@link #emptyText} for text fields. To set the value directly without these inspections see {@link #setRawValue}.
  101020. * @param {Object} value The value to set
  101021. * @return {Ext.form.field.Text} this
  101022. */
  101023. setValue: function(value) {
  101024. var me = this,
  101025. inputEl = me.inputEl;
  101026. if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
  101027. inputEl.removeCls(me.emptyCls);
  101028. me.valueContainsPlaceholder = false;
  101029. }
  101030. me.callParent(arguments);
  101031. me.applyEmptyText();
  101032. return me;
  101033. },
  101034. /**
  101035. * Validates a value according to the field's validation rules and returns an array of errors
  101036. * for any failing validations. Validation rules are processed in the following order:
  101037. *
  101038. * 1. **Field specific validator**
  101039. *
  101040. * A validator offers a way to customize and reuse a validation specification.
  101041. * If a field is configured with a `{@link #validator}`
  101042. * function, it will be passed the current field value. The `{@link #validator}`
  101043. * function is expected to return either:
  101044. *
  101045. * - Boolean `true` if the value is valid (validation continues).
  101046. * - a String to represent the invalid message if invalid (validation halts).
  101047. *
  101048. * 2. **Basic Validation**
  101049. *
  101050. * If the `{@link #validator}` has not halted validation,
  101051. * basic validation proceeds as follows:
  101052. *
  101053. * - `{@link #allowBlank}` : (Invalid message = `{@link #blankText}`)
  101054. *
  101055. * Depending on the configuration of `{@link #allowBlank}`, a
  101056. * blank field will cause validation to halt at this step and return
  101057. * Boolean true or false accordingly.
  101058. *
  101059. * - `{@link #minLength}` : (Invalid message = `{@link #minLengthText}`)
  101060. *
  101061. * If the passed value does not satisfy the `{@link #minLength}`
  101062. * specified, validation halts.
  101063. *
  101064. * - `{@link #maxLength}` : (Invalid message = `{@link #maxLengthText}`)
  101065. *
  101066. * If the passed value does not satisfy the `{@link #maxLength}`
  101067. * specified, validation halts.
  101068. *
  101069. * 3. **Preconfigured Validation Types (VTypes)**
  101070. *
  101071. * If none of the prior validation steps halts validation, a field
  101072. * configured with a `{@link #vtype}` will utilize the
  101073. * corresponding {@link Ext.form.field.VTypes VTypes} validation function.
  101074. * If invalid, either the field's `{@link #vtypeText}` or
  101075. * the VTypes vtype Text property will be used for the invalid message.
  101076. * Keystrokes on the field will be filtered according to the VTypes
  101077. * vtype Mask property.
  101078. *
  101079. * 4. **Field specific regex test**
  101080. *
  101081. * If none of the prior validation steps halts validation, a field's
  101082. * configured `{@link #regex}` test will be processed.
  101083. * The invalid message for this test is configured with `{@link #regexText}`
  101084. *
  101085. * @param {Object} value The value to validate. The processed raw value will be used if nothing is passed.
  101086. * @return {String[]} Array of any validation errors
  101087. */
  101088. getErrors: function(value) {
  101089. var me = this,
  101090. errors = me.callParent(arguments),
  101091. validator = me.validator,
  101092. emptyText = me.emptyText,
  101093. allowBlank = me.allowBlank,
  101094. vtype = me.vtype,
  101095. vtypes = Ext.form.field.VTypes,
  101096. regex = me.regex,
  101097. format = Ext.String.format,
  101098. msg;
  101099. value = value || me.processRawValue(me.getRawValue());
  101100. if (Ext.isFunction(validator)) {
  101101. msg = validator.call(me, value);
  101102. if (msg !== true) {
  101103. errors.push(msg);
  101104. }
  101105. }
  101106. if (value.length < 1 || (value === me.emptyText && me.valueContainsPlaceholder)) {
  101107. if (!allowBlank) {
  101108. errors.push(me.blankText);
  101109. }
  101110. //if value is blank, there cannot be any additional errors
  101111. return errors;
  101112. }
  101113. if (value.length < me.minLength) {
  101114. errors.push(format(me.minLengthText, me.minLength));
  101115. }
  101116. if (value.length > me.maxLength) {
  101117. errors.push(format(me.maxLengthText, me.maxLength));
  101118. }
  101119. if (vtype) {
  101120. if(!vtypes[vtype](value, me)){
  101121. errors.push(me.vtypeText || vtypes[vtype +'Text']);
  101122. }
  101123. }
  101124. if (regex && !regex.test(value)) {
  101125. errors.push(me.regexText || me.invalidText);
  101126. }
  101127. return errors;
  101128. },
  101129. /**
  101130. * Selects text in this field
  101131. * @param {Number} [start=0] The index where the selection should start
  101132. * @param {Number} [end] The index where the selection should end (defaults to the text length)
  101133. */
  101134. selectText : function(start, end){
  101135. var me = this,
  101136. v = me.getRawValue(),
  101137. doFocus = true,
  101138. el = me.inputEl.dom,
  101139. undef,
  101140. range;
  101141. if (v.length > 0) {
  101142. start = start === undef ? 0 : start;
  101143. end = end === undef ? v.length : end;
  101144. if (el.setSelectionRange) {
  101145. el.setSelectionRange(start, end);
  101146. }
  101147. else if(el.createTextRange) {
  101148. range = el.createTextRange();
  101149. range.moveStart('character', start);
  101150. range.moveEnd('character', end - v.length);
  101151. range.select();
  101152. }
  101153. doFocus = Ext.isGecko || Ext.isOpera;
  101154. }
  101155. if (doFocus) {
  101156. me.focus();
  101157. }
  101158. },
  101159. /**
  101160. * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed. This
  101161. * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the width changes.
  101162. */
  101163. autoSize: function() {
  101164. var me = this;
  101165. if (me.grow && me.rendered) {
  101166. me.autoSizing = true;
  101167. me.updateLayout();
  101168. }
  101169. },
  101170. afterComponentLayout: function() {
  101171. var me = this,
  101172. width;
  101173. me.callParent(arguments);
  101174. if (me.autoSizing) {
  101175. width = me.inputEl.getWidth();
  101176. if (width !== me.lastInputWidth) {
  101177. me.fireEvent('autosize', me, width);
  101178. me.lastInputWidth = width;
  101179. delete me.autoSizing;
  101180. }
  101181. }
  101182. }
  101183. });
  101184. /**
  101185. * Layout class for {@link Ext.form.field.TextArea} fields. Handles sizing the textarea field.
  101186. * @private
  101187. */
  101188. Ext.define('Ext.layout.component.field.TextArea', {
  101189. extend: 'Ext.layout.component.field.Text',
  101190. alias: 'layout.textareafield',
  101191. type: 'textareafield',
  101192. canGrowWidth: false,
  101193. naturalSizingProp: 'cols',
  101194. beginLayout: function(ownerContext){
  101195. this.callParent(arguments);
  101196. ownerContext.target.inputEl.setStyle('height', '');
  101197. },
  101198. measureContentHeight: function (ownerContext) {
  101199. var me = this,
  101200. owner = me.owner,
  101201. height = me.callParent(arguments),
  101202. inputContext, inputEl, value, max, curWidth, calcHeight;
  101203. if (owner.grow && !ownerContext.state.growHandled) {
  101204. inputContext = ownerContext.inputContext;
  101205. inputEl = owner.inputEl;
  101206. curWidth = inputEl.getWidth(true); //subtract border/padding to get the available width for the text
  101207. // Get and normalize the field value for measurement
  101208. value = Ext.util.Format.htmlEncode(inputEl.dom.value) || '&#160;';
  101209. value += owner.growAppend;
  101210. // Translate newlines to <br> tags
  101211. value = value.replace(/\n/g, '<br/>');
  101212. // Find the height that contains the whole text value
  101213. calcHeight = Ext.util.TextMetrics.measure(inputEl, value, curWidth).height +
  101214. inputContext.getBorderInfo().height + inputContext.getPaddingInfo().height;
  101215. // Constrain
  101216. calcHeight = Ext.Number.constrain(calcHeight, owner.growMin, owner.growMax);
  101217. inputContext.setHeight(calcHeight);
  101218. ownerContext.state.growHandled = true;
  101219. // Now that we've set the inputContext, we need to recalculate the width
  101220. inputContext.domBlock(me, 'height');
  101221. height = NaN;
  101222. }
  101223. return height;
  101224. }
  101225. });
  101226. /**
  101227. * @docauthor Robert Dougan <rob@sencha.com>
  101228. *
  101229. * This class creates a multiline text field, which can be used as a direct replacement for traditional
  101230. * textarea fields. In addition, it supports automatically {@link #grow growing} the height of the textarea to
  101231. * fit its content.
  101232. *
  101233. * All of the configuration options from {@link Ext.form.field.Text} can be used on TextArea.
  101234. *
  101235. * Example usage:
  101236. *
  101237. * @example
  101238. * Ext.create('Ext.form.FormPanel', {
  101239. * title : 'Sample TextArea',
  101240. * width : 400,
  101241. * bodyPadding: 10,
  101242. * renderTo : Ext.getBody(),
  101243. * items: [{
  101244. * xtype : 'textareafield',
  101245. * grow : true,
  101246. * name : 'message',
  101247. * fieldLabel: 'Message',
  101248. * anchor : '100%'
  101249. * }]
  101250. * });
  101251. *
  101252. * Some other useful configuration options when using {@link #grow} are {@link #growMin} and {@link #growMax}.
  101253. * These allow you to set the minimum and maximum grow heights for the textarea.
  101254. *
  101255. * **NOTE:** In some browsers, carriage returns ('\r', not to be confused with new lines)
  101256. * will be automatically stripped out the value is set to the textarea. Since we cannot
  101257. * use any reasonable method to attempt to re-insert these, they will automatically be
  101258. * stripped out to ensure the behaviour is consistent across browser.
  101259. */
  101260. Ext.define('Ext.form.field.TextArea', {
  101261. extend:'Ext.form.field.Text',
  101262. alias: ['widget.textareafield', 'widget.textarea'],
  101263. alternateClassName: 'Ext.form.TextArea',
  101264. requires: [
  101265. 'Ext.XTemplate',
  101266. 'Ext.layout.component.field.TextArea',
  101267. 'Ext.util.DelayedTask'
  101268. ],
  101269. // This template includes a \n after <textarea> opening tag so that an initial value starting
  101270. // with \n does not lose its first character when the markup is parsed.
  101271. // Both textareas below have the same value:
  101272. // <textarea>initial value</textarea>
  101273. // <textarea>
  101274. // initial value
  101275. // </textarea>
  101276. fieldSubTpl: [
  101277. '<textarea id="{id}" {inputAttrTpl}',
  101278. '<tpl if="name"> name="{name}"</tpl>',
  101279. '<tpl if="rows"> rows="{rows}" </tpl>',
  101280. '<tpl if="cols"> cols="{cols}" </tpl>',
  101281. '<tpl if="placeholder"> placeholder="{placeholder}"</tpl>',
  101282. '<tpl if="size"> size="{size}"</tpl>',
  101283. '<tpl if="maxLength !== undefined"> maxlength="{maxLength}"</tpl>',
  101284. '<tpl if="readOnly"> readonly="readonly"</tpl>',
  101285. '<tpl if="disabled"> disabled="disabled"</tpl>',
  101286. '<tpl if="tabIdx"> tabIndex="{tabIdx}"</tpl>',
  101287. ' class="{fieldCls} {typeCls}" ',
  101288. '<tpl if="fieldStyle"> style="{fieldStyle}"</tpl>',
  101289. ' autocomplete="off">\n',
  101290. '<tpl if="value">{[Ext.util.Format.htmlEncode(values.value)]}</tpl>',
  101291. '</textarea>',
  101292. {
  101293. disableFormats: true
  101294. }
  101295. ],
  101296. /**
  101297. * @cfg {Number} growMin
  101298. * The minimum height to allow when {@link #grow}=true
  101299. */
  101300. growMin: 60,
  101301. /**
  101302. * @cfg {Number} growMax
  101303. * The maximum height to allow when {@link #grow}=true
  101304. */
  101305. growMax: 1000,
  101306. /**
  101307. * @cfg {String} growAppend
  101308. * A string that will be appended to the field's current value for the purposes of calculating the target field
  101309. * size. Only used when the {@link #grow} config is true. Defaults to a newline for TextArea to ensure there is
  101310. * always a space below the current line.
  101311. */
  101312. growAppend: '\n-',
  101313. /**
  101314. * @cfg {Number} cols
  101315. * An initial value for the 'cols' attribute on the textarea element. This is only used if the component has no
  101316. * configured {@link #width} and is not given a width by its container's layout.
  101317. */
  101318. cols: 20,
  101319. /**
  101320. * @cfg {Number} rows
  101321. * An initial value for the 'rows' attribute on the textarea element. This is only used if the component has no
  101322. * configured {@link #height} and is not given a height by its container's layout. Defaults to 4.
  101323. */
  101324. rows: 4,
  101325. /**
  101326. * @cfg {Boolean} enterIsSpecial
  101327. * True if you want the ENTER key to be classed as a special key and the {@link #specialkey} event to be fired
  101328. * when ENTER is pressed.
  101329. */
  101330. enterIsSpecial: false,
  101331. /**
  101332. * @cfg {Boolean} preventScrollbars
  101333. * true to prevent scrollbars from appearing regardless of how much text is in the field. This option is only
  101334. * relevant when {@link #grow} is true. Equivalent to setting overflow: hidden.
  101335. */
  101336. preventScrollbars: false,
  101337. // private
  101338. componentLayout: 'textareafield',
  101339. setGrowSizePolicy: Ext.emptyFn,
  101340. returnRe: /\r/g,
  101341. // private
  101342. getSubTplData: function() {
  101343. var me = this,
  101344. fieldStyle = me.getFieldStyle(),
  101345. ret = me.callParent();
  101346. if (me.grow) {
  101347. if (me.preventScrollbars) {
  101348. ret.fieldStyle = (fieldStyle||'') + ';overflow:hidden;height:' + me.growMin + 'px';
  101349. }
  101350. }
  101351. Ext.applyIf(ret, {
  101352. cols: me.cols,
  101353. rows: me.rows
  101354. });
  101355. return ret;
  101356. },
  101357. afterRender: function () {
  101358. var me = this;
  101359. me.callParent(arguments);
  101360. me.needsMaxCheck = me.enforceMaxLength && me.maxLength !== Number.MAX_VALUE && !Ext.supports.TextAreaMaxLength;
  101361. if (me.needsMaxCheck) {
  101362. me.inputEl.on('paste', me.onPaste, me);
  101363. }
  101364. },
  101365. // The following overrides deal with an issue whereby some browsers
  101366. // will strip carriage returns from the textarea input, while others
  101367. // will not. Since there's no way to be sure where to insert returns,
  101368. // the best solution is to strip them out in all cases to ensure that
  101369. // the behaviour is consistent in a cross browser fashion. As such,
  101370. // we override in all cases when setting the value to control this.
  101371. transformRawValue: function(value){
  101372. return this.stripReturns(value);
  101373. },
  101374. transformOriginalValue: function(value){
  101375. return this.stripReturns(value);
  101376. },
  101377. valueToRaw: function(value){
  101378. value = this.stripReturns(value);
  101379. return this.callParent([value]);
  101380. },
  101381. stripReturns: function(value){
  101382. if (value) {
  101383. value = value.replace(this.returnRe, '');
  101384. }
  101385. return value;
  101386. },
  101387. onPaste: function(e){
  101388. var me = this;
  101389. if (!me.pasteTask) {
  101390. me.pasteTask = new Ext.util.DelayedTask(me.pasteCheck, me);
  101391. }
  101392. // since we can't get the paste data, we'll give the area a chance to populate
  101393. me.pasteTask.delay(1);
  101394. },
  101395. pasteCheck: function(){
  101396. var me = this,
  101397. value = me.getValue(),
  101398. max = me.maxLength;
  101399. if (value.length > max) {
  101400. value = value.substr(0, max);
  101401. me.setValue(value);
  101402. }
  101403. },
  101404. // private
  101405. fireKey: function(e) {
  101406. var me = this,
  101407. key = e.getKey(),
  101408. value;
  101409. if (e.isSpecialKey() && (me.enterIsSpecial || (key !== e.ENTER || e.hasModifier()))) {
  101410. me.fireEvent('specialkey', me, e);
  101411. }
  101412. if (me.needsMaxCheck && key !== e.BACKSPACE && key !== e.DELETE && !e.isNavKeyPress() && !me.isCutCopyPasteSelectAll(e, key)) {
  101413. value = me.getValue();
  101414. if (value.length >= me.maxLength) {
  101415. e.stopEvent();
  101416. }
  101417. }
  101418. },
  101419. isCutCopyPasteSelectAll: function(e, key) {
  101420. if (e.CTRL) {
  101421. return key === e.A || key === e.C || key === e.V || key === e.X;
  101422. }
  101423. return false;
  101424. },
  101425. /**
  101426. * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed. This
  101427. * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the height changes.
  101428. */
  101429. autoSize: function() {
  101430. var me = this,
  101431. height;
  101432. if (me.grow && me.rendered) {
  101433. me.updateLayout();
  101434. height = me.inputEl.getHeight();
  101435. if (height !== me.lastInputHeight) {
  101436. /**
  101437. * @event autosize
  101438. * Fires when the {@link #autoSize} function is triggered and the field is resized according to
  101439. * the grow/growMin/growMax configs as a result. This event provides a hook for the developer
  101440. * to apply additional logic at runtime to resize the field if needed.
  101441. * @param {Ext.form.field.Text} this
  101442. * @param {Number} height
  101443. */
  101444. me.fireEvent('autosize', me, height);
  101445. me.lastInputHeight = height;
  101446. }
  101447. }
  101448. },
  101449. // private
  101450. initAria: function() {
  101451. this.callParent(arguments);
  101452. this.getActionEl().dom.setAttribute('aria-multiline', true);
  101453. },
  101454. beforeDestroy: function(){
  101455. var task = this.pasteTask;
  101456. if (task) {
  101457. task.delay();
  101458. }
  101459. this.callParent();
  101460. }
  101461. });
  101462. /**
  101463. * A display-only text field which is not validated and not submitted. This is useful for when you want to display a
  101464. * value from a form's {@link Ext.form.Basic#load loaded data} but do not want to allow the user to edit or submit that
  101465. * value. The value can be optionally {@link #htmlEncode HTML encoded} if it contains HTML markup that you do not want
  101466. * to be rendered.
  101467. *
  101468. * If you have more complex content, or need to include components within the displayed content, also consider using a
  101469. * {@link Ext.form.FieldContainer} instead.
  101470. *
  101471. * Example:
  101472. *
  101473. * @example
  101474. * Ext.create('Ext.form.Panel', {
  101475. * renderTo: Ext.getBody(),
  101476. * width: 175,
  101477. * height: 120,
  101478. * bodyPadding: 10,
  101479. * title: 'Final Score',
  101480. * items: [{
  101481. * xtype: 'displayfield',
  101482. * fieldLabel: 'Home',
  101483. * name: 'home_score',
  101484. * value: '10'
  101485. * }, {
  101486. * xtype: 'displayfield',
  101487. * fieldLabel: 'Visitor',
  101488. * name: 'visitor_score',
  101489. * value: '11'
  101490. * }],
  101491. * buttons: [{
  101492. * text: 'Update'
  101493. * }]
  101494. * });
  101495. */
  101496. Ext.define('Ext.form.field.Display', {
  101497. extend:'Ext.form.field.Base',
  101498. alias: 'widget.displayfield',
  101499. requires: ['Ext.util.Format', 'Ext.XTemplate'],
  101500. alternateClassName: ['Ext.form.DisplayField', 'Ext.form.Display'],
  101501. fieldSubTpl: [
  101502. '<div id="{id}"',
  101503. '<tpl if="fieldStyle"> style="{fieldStyle}"</tpl>',
  101504. ' class="{fieldCls}">{value}</div>',
  101505. {
  101506. compiled: true,
  101507. disableFormats: true
  101508. }
  101509. ],
  101510. /**
  101511. * @cfg {String} [fieldCls="x-form-display-field"]
  101512. * The default CSS class for the field.
  101513. */
  101514. fieldCls: Ext.baseCSSPrefix + 'form-display-field',
  101515. /**
  101516. * @cfg {Boolean} htmlEncode
  101517. * True to escape HTML in text when rendering it.
  101518. */
  101519. htmlEncode: false,
  101520. /**
  101521. * @cfg {Function} renderer
  101522. * A function to transform the raw value for display in the field. The function will receive 2 arguments, the raw value
  101523. * and the {@link Ext.form.field.Display} object.
  101524. */
  101525. /**
  101526. * @cfg {Object} scope
  101527. * The scope to execute the {@link #renderer} function. Defaults to this.
  101528. */
  101529. validateOnChange: false,
  101530. initEvents: Ext.emptyFn,
  101531. submitValue: false,
  101532. isDirty: function(){
  101533. return false;
  101534. },
  101535. isValid: function() {
  101536. return true;
  101537. },
  101538. validate: function() {
  101539. return true;
  101540. },
  101541. getRawValue: function() {
  101542. return this.rawValue;
  101543. },
  101544. setRawValue: function(value) {
  101545. var me = this,
  101546. display;
  101547. value = Ext.value(value, '');
  101548. me.rawValue = value;
  101549. if (me.rendered) {
  101550. me.inputEl.dom.innerHTML = me.getDisplayValue();
  101551. me.updateLayout();
  101552. }
  101553. return value;
  101554. },
  101555. /**
  101556. * @private
  101557. * Format the value to display.
  101558. */
  101559. getDisplayValue: function() {
  101560. var me = this,
  101561. value = this.getRawValue(),
  101562. display;
  101563. if (me.renderer) {
  101564. display = me.renderer.call(me.scope || me, value, me);
  101565. } else {
  101566. display = me.htmlEncode ? Ext.util.Format.htmlEncode(value) : value;
  101567. }
  101568. return display;
  101569. },
  101570. getSubTplData: function() {
  101571. var ret = this.callParent(arguments);
  101572. ret.value = this.getDisplayValue();
  101573. return ret;
  101574. }
  101575. /**
  101576. * @cfg {String} inputType
  101577. * @private
  101578. */
  101579. /**
  101580. * @cfg {Boolean} disabled
  101581. * @private
  101582. */
  101583. /**
  101584. * @cfg {Boolean} readOnly
  101585. * @private
  101586. */
  101587. /**
  101588. * @cfg {Boolean} validateOnChange
  101589. * @private
  101590. */
  101591. /**
  101592. * @cfg {Number} checkChangeEvents
  101593. * @private
  101594. */
  101595. /**
  101596. * @cfg {Number} checkChangeBuffer
  101597. * @private
  101598. */
  101599. });
  101600. /**
  101601. * This is a layout that enables anchoring of contained elements relative to the container's dimensions.
  101602. * If the container is resized, all anchored items are automatically rerendered according to their
  101603. * `{@link #anchor}` rules.
  101604. *
  101605. * This class is intended to be extended or created via the {@link Ext.container.AbstractContainer#layout layout}: 'anchor'
  101606. * config, and should generally not need to be created directly via the new keyword.
  101607. *
  101608. * AnchorLayout does not have any direct config options (other than inherited ones). By default,
  101609. * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
  101610. * container using the AnchorLayout can supply an anchoring-specific config property of `anchorSize`.
  101611. *
  101612. * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
  101613. * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
  101614. * logic if necessary.
  101615. *
  101616. * @example
  101617. * Ext.create('Ext.Panel', {
  101618. * width: 500,
  101619. * height: 400,
  101620. * title: "AnchorLayout Panel",
  101621. * layout: 'anchor',
  101622. * renderTo: Ext.getBody(),
  101623. * items: [
  101624. * {
  101625. * xtype: 'panel',
  101626. * title: '75% Width and 20% Height',
  101627. * anchor: '75% 20%'
  101628. * },
  101629. * {
  101630. * xtype: 'panel',
  101631. * title: 'Offset -300 Width & -200 Height',
  101632. * anchor: '-300 -200'
  101633. * },
  101634. * {
  101635. * xtype: 'panel',
  101636. * title: 'Mixed Offset and Percent',
  101637. * anchor: '-250 20%'
  101638. * }
  101639. * ]
  101640. * });
  101641. */
  101642. Ext.define('Ext.layout.container.Anchor', {
  101643. /* Begin Definitions */
  101644. alias: 'layout.anchor',
  101645. extend: 'Ext.layout.container.Container',
  101646. alternateClassName: 'Ext.layout.AnchorLayout',
  101647. /* End Definitions */
  101648. type: 'anchor',
  101649. manageOverflow: 2,
  101650. renderTpl: [
  101651. '{%this.renderBody(out,values);this.renderPadder(out,values)%}'
  101652. ],
  101653. /**
  101654. * @cfg {String} anchor
  101655. *
  101656. * This configuation option is to be applied to **child `items`** of a container managed by
  101657. * this layout (ie. configured with `layout:'anchor'`).
  101658. *
  101659. * This value is what tells the layout how an item should be anchored to the container. `items`
  101660. * added to an AnchorLayout accept an anchoring-specific config property of **anchor** which is a string
  101661. * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
  101662. * The following types of anchor values are supported:
  101663. *
  101664. * - **Percentage** : Any value between 1 and 100, expressed as a percentage.
  101665. *
  101666. * The first anchor is the percentage width that the item should take up within the container, and the
  101667. * second is the percentage height. For example:
  101668. *
  101669. * // two values specified
  101670. * anchor: '100% 50%' // render item complete width of the container and
  101671. * // 1/2 height of the container
  101672. * // one value specified
  101673. * anchor: '100%' // the width value; the height will default to auto
  101674. *
  101675. * - **Offsets** : Any positive or negative integer value.
  101676. *
  101677. * This is a raw adjustment where the first anchor is the offset from the right edge of the container,
  101678. * and the second is the offset from the bottom edge. For example:
  101679. *
  101680. * // two values specified
  101681. * anchor: '-50 -100' // render item the complete width of the container
  101682. * // minus 50 pixels and
  101683. * // the complete height minus 100 pixels.
  101684. * // one value specified
  101685. * anchor: '-50' // anchor value is assumed to be the right offset value
  101686. * // bottom offset will default to 0
  101687. *
  101688. * - **Sides** : Valid values are `right` (or `r`) and `bottom` (or `b`).
  101689. *
  101690. * Either the container must have a fixed size or an anchorSize config value defined at render time in
  101691. * order for these to have any effect.
  101692. *
  101693. * - **Mixed** :
  101694. *
  101695. * Anchor values can also be mixed as needed. For example, to render the width offset from the container
  101696. * right edge by 50 pixels and 75% of the container's height use:
  101697. *
  101698. * anchor: '-50 75%'
  101699. */
  101700. /**
  101701. * @cfg {String} defaultAnchor
  101702. * Default anchor for all child **container** items applied if no anchor or specific width is set on the child item.
  101703. */
  101704. defaultAnchor: '100%',
  101705. parseAnchorRE: /^(r|right|b|bottom)$/i,
  101706. beginLayout: function (ownerContext) {
  101707. var me = this,
  101708. dimensions = 0,
  101709. anchorSpec, childContext, childItems, i, length, target;
  101710. me.callParent(arguments);
  101711. childItems = ownerContext.childItems; // populated by callParent
  101712. length = childItems.length;
  101713. for (i = 0; i < length; ++i) {
  101714. childContext = childItems[i];
  101715. anchorSpec = childContext.target.anchorSpec;
  101716. if (anchorSpec) {
  101717. if (childContext.widthModel.calculated && anchorSpec.right) {
  101718. dimensions |= 1;
  101719. }
  101720. if (childContext.heightModel.calculated && anchorSpec.bottom) {
  101721. dimensions |= 2;
  101722. }
  101723. if (dimensions == 3) { // if (both dimensions in play)
  101724. break;
  101725. }
  101726. }
  101727. }
  101728. ownerContext.anchorDimensions = dimensions;
  101729. // Work around WebKit RightMargin bug. We're going to inline-block all the children
  101730. // only ONCE and remove it when we're done
  101731. if (!Ext.supports.RightMargin && !me.rightMarginCleanerFn) {
  101732. target = ownerContext.targetContext.el; // targetContext is added by superclass
  101733. me.rightMarginCleanerFn = Ext.Element.getRightMarginFixCleaner(target);
  101734. target.addCls(Ext.baseCSSPrefix + 'inline-children');
  101735. }
  101736. me.sanityCheck(ownerContext);
  101737. },
  101738. calculate: function (ownerContext) {
  101739. var me = this,
  101740. containerSize = me.getContainerSize(ownerContext);
  101741. if (ownerContext.anchorDimensions !== ownerContext.state.calculatedAnchors) {
  101742. me.calculateAnchors(ownerContext, containerSize);
  101743. }
  101744. if (ownerContext.hasDomProp('containerChildrenDone')) {
  101745. // Once the child layouts are done we can determine the content sizes...
  101746. if (!containerSize.gotAll) {
  101747. me.done = false;
  101748. }
  101749. me.calculateContentSize(ownerContext, ownerContext.anchorDimensions);
  101750. if (me.done) {
  101751. me.calculateOverflow(ownerContext, containerSize, ownerContext.anchorDimensions);
  101752. return;
  101753. }
  101754. }
  101755. me.done = false;
  101756. },
  101757. calculateAnchors: function (ownerContext, containerSize) {
  101758. var me = this,
  101759. childItems = ownerContext.childItems,
  101760. length = childItems.length,
  101761. gotHeight = containerSize.gotHeight,
  101762. gotWidth = containerSize.gotWidth,
  101763. ownerHeight = containerSize.height,
  101764. ownerWidth = containerSize.width,
  101765. state = ownerContext.state,
  101766. calculatedAnchors = (gotWidth ? 1 : 0) | (gotHeight ? 2 : 0),
  101767. anchorSpec, childContext, childMargins, height, i, width;
  101768. state.calculatedAnchors = (state.calculatedAnchors || 0) | calculatedAnchors;
  101769. for (i = 0; i < length; i++) {
  101770. childContext = childItems[i];
  101771. childMargins = childContext.getMarginInfo();
  101772. anchorSpec = childContext.target.anchorSpec;
  101773. // Check widthModel in case "defaults" has applied an anchor to a component
  101774. // that also has width (which must win). If we did not make this check in this
  101775. // way, we would attempt to calculate a width where it had been configured.
  101776. //
  101777. if (gotWidth && childContext.widthModel.calculated) {
  101778. width = anchorSpec.right(ownerWidth) - childMargins.width;
  101779. width = me.adjustWidthAnchor(width, childContext);
  101780. childContext.setWidth(width);
  101781. }
  101782. // Repeat for height
  101783. if (gotHeight && childContext.heightModel.calculated) {
  101784. height = anchorSpec.bottom(ownerHeight) - childMargins.height;
  101785. height = me.adjustHeightAnchor(height, childContext);
  101786. childContext.setHeight(height);
  101787. }
  101788. }
  101789. },
  101790. finishedLayout: function (ownerContext) {
  101791. var cleanerFn = this.rightMarginCleanerFn;
  101792. if (cleanerFn) {
  101793. delete this.rightMarginCleanerFn;
  101794. ownerContext.targetContext.el.removeCls(Ext.baseCSSPrefix + 'inline-children');
  101795. cleanerFn();
  101796. }
  101797. },
  101798. sanityCheck: function (ownerContext) {
  101799. var shrinkWrapWidth = ownerContext.widthModel.shrinkWrap,
  101800. shrinkWrapHeight = ownerContext.heightModel.shrinkWrap,
  101801. children = ownerContext.childItems,
  101802. anchorSpec, comp, childContext,
  101803. i, length;
  101804. for (i = 0, length = children.length; i < length; ++i) {
  101805. childContext = children[i];
  101806. comp = childContext.target;
  101807. anchorSpec = comp.anchorSpec;
  101808. if (anchorSpec) {
  101809. if (childContext.widthModel.calculated && anchorSpec.right) {
  101810. if (shrinkWrapWidth) {
  101811. Ext.log({
  101812. level: 'warn',
  101813. msg: 'Right anchor on '+comp.id+' in shrinkWrap width container'
  101814. });
  101815. }
  101816. }
  101817. if (childContext.heightModel.calculated && anchorSpec.bottom) {
  101818. if (shrinkWrapHeight) {
  101819. Ext.log({
  101820. level: 'warn',
  101821. msg: 'Bottom anchor on '+comp.id+' in shrinkWrap height container'
  101822. });
  101823. }
  101824. }
  101825. }
  101826. }
  101827. },
  101828. // private
  101829. anchorFactory: {
  101830. offset: function (delta) {
  101831. return function(v) {
  101832. return v + delta;
  101833. };
  101834. },
  101835. ratio: function (ratio) {
  101836. return function(v) {
  101837. return Math.floor(v * ratio);
  101838. };
  101839. },
  101840. standard: function (diff) {
  101841. return function(v) {
  101842. return v - diff;
  101843. };
  101844. }
  101845. },
  101846. parseAnchor: function(a, start, cstart) {
  101847. if (a && a != 'none') {
  101848. var factory = this.anchorFactory,
  101849. delta;
  101850. if (this.parseAnchorRE.test(a)) {
  101851. return factory.standard(cstart - start);
  101852. }
  101853. if (a.indexOf('%') != -1) {
  101854. return factory.ratio(parseFloat(a.replace('%', '')) * 0.01);
  101855. }
  101856. delta = parseInt(a, 10);
  101857. if (!isNaN(delta)) {
  101858. return factory.offset(delta);
  101859. }
  101860. }
  101861. return null;
  101862. },
  101863. // private
  101864. adjustWidthAnchor: function(value, childContext) {
  101865. return value;
  101866. },
  101867. // private
  101868. adjustHeightAnchor: function(value, childContext) {
  101869. return value;
  101870. },
  101871. configureItem: function(item) {
  101872. var me = this,
  101873. owner = me.owner,
  101874. anchor= item.anchor,
  101875. anchorsArray,
  101876. anchorWidth,
  101877. anchorHeight;
  101878. me.callParent(arguments);
  101879. if (!item.anchor && item.items && !Ext.isNumber(item.width) && !(Ext.isIE6 && Ext.isStrict)) {
  101880. item.anchor = anchor = me.defaultAnchor;
  101881. }
  101882. /**
  101883. * @cfg {Number/Object} anchorSize
  101884. * Defines the anchoring size of container.
  101885. * Either a number to define the width of the container or an object with `width` and `height` fields.
  101886. * @member Ext.container.Container
  101887. */
  101888. if (owner.anchorSize) {
  101889. if (typeof owner.anchorSize == 'number') {
  101890. anchorWidth = owner.anchorSize;
  101891. } else {
  101892. anchorWidth = owner.anchorSize.width;
  101893. anchorHeight = owner.anchorSize.height;
  101894. }
  101895. } else {
  101896. anchorWidth = owner.initialConfig.width;
  101897. anchorHeight = owner.initialConfig.height;
  101898. }
  101899. if (anchor) {
  101900. // cache all anchor values
  101901. anchorsArray = anchor.split(' ');
  101902. item.anchorSpec = {
  101903. right: me.parseAnchor(anchorsArray[0], item.initialConfig.width, anchorWidth),
  101904. bottom: me.parseAnchor(anchorsArray[1], item.initialConfig.height, anchorHeight)
  101905. };
  101906. }
  101907. },
  101908. sizePolicy: {
  101909. '': {
  101910. setsWidth: 0,
  101911. setsHeight: 0
  101912. },
  101913. b: {
  101914. setsWidth: 0,
  101915. setsHeight: 1
  101916. },
  101917. r: {
  101918. '': {
  101919. setsWidth: 1,
  101920. setsHeight: 0
  101921. },
  101922. b: {
  101923. setsWidth: 1,
  101924. setsHeight: 1
  101925. }
  101926. }
  101927. },
  101928. getItemSizePolicy: function (item) {
  101929. var anchorSpec = item.anchorSpec,
  101930. key = '',
  101931. policy = this.sizePolicy,
  101932. sizeModel;
  101933. if (anchorSpec) {
  101934. sizeModel = this.owner.getSizeModel();
  101935. if (anchorSpec.right && !sizeModel.width.shrinkWrap) {
  101936. policy = policy.r;
  101937. }
  101938. if (anchorSpec.bottom && !sizeModel.height.shrinkWrap) {
  101939. key = 'b';
  101940. }
  101941. }
  101942. return policy[key];
  101943. }
  101944. });
  101945. /**
  101946. * Utility class for generating different styles of message boxes. The singleton instance, Ext.MessageBox
  101947. * alias `Ext.Msg` can also be used.
  101948. *
  101949. * Note that a MessageBox is asynchronous. Unlike a regular JavaScript `alert` (which will halt
  101950. * browser execution), showing a MessageBox will not cause the code to stop. For this reason, if you have code
  101951. * that should only run *after* some user feedback from the MessageBox, you must use a callback function
  101952. * (see the `function` parameter for {@link #method-show} for more details).
  101953. *
  101954. * Basic alert
  101955. *
  101956. * @example
  101957. * Ext.Msg.alert('Status', 'Changes saved successfully.');
  101958. *
  101959. * Prompt for user data and process the result using a callback
  101960. *
  101961. * @example
  101962. * Ext.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
  101963. * if (btn == 'ok'){
  101964. * // process text value and close...
  101965. * }
  101966. * });
  101967. *
  101968. * Show a dialog using config options
  101969. *
  101970. * @example
  101971. * Ext.Msg.show({
  101972. * title:'Save Changes?',
  101973. * msg: 'You are closing a tab that has unsaved changes. Would you like to save your changes?',
  101974. * buttons: Ext.Msg.YESNOCANCEL,
  101975. * icon: Ext.Msg.QUESTION
  101976. * });
  101977. */
  101978. Ext.define('Ext.window.MessageBox', {
  101979. extend: 'Ext.window.Window',
  101980. requires: [
  101981. 'Ext.toolbar.Toolbar',
  101982. 'Ext.form.field.Text',
  101983. 'Ext.form.field.TextArea',
  101984. 'Ext.form.field.Display',
  101985. 'Ext.button.Button',
  101986. 'Ext.layout.container.Anchor',
  101987. 'Ext.layout.container.HBox',
  101988. 'Ext.ProgressBar'
  101989. ],
  101990. alias: 'widget.messagebox',
  101991. /**
  101992. * @property
  101993. * Button config that displays a single OK button
  101994. */
  101995. OK : 1,
  101996. /**
  101997. * @property
  101998. * Button config that displays a single Yes button
  101999. */
  102000. YES : 2,
  102001. /**
  102002. * @property
  102003. * Button config that displays a single No button
  102004. */
  102005. NO : 4,
  102006. /**
  102007. * @property
  102008. * Button config that displays a single Cancel button
  102009. */
  102010. CANCEL : 8,
  102011. /**
  102012. * @property
  102013. * Button config that displays OK and Cancel buttons
  102014. */
  102015. OKCANCEL : 9,
  102016. /**
  102017. * @property
  102018. * Button config that displays Yes and No buttons
  102019. */
  102020. YESNO : 6,
  102021. /**
  102022. * @property
  102023. * Button config that displays Yes, No and Cancel buttons
  102024. */
  102025. YESNOCANCEL : 14,
  102026. /**
  102027. * @property
  102028. * The CSS class that provides the INFO icon image
  102029. */
  102030. INFO : Ext.baseCSSPrefix + 'message-box-info',
  102031. /**
  102032. * @property
  102033. * The CSS class that provides the WARNING icon image
  102034. */
  102035. WARNING : Ext.baseCSSPrefix + 'message-box-warning',
  102036. /**
  102037. * @property
  102038. * The CSS class that provides the QUESTION icon image
  102039. */
  102040. QUESTION : Ext.baseCSSPrefix + 'message-box-question',
  102041. /**
  102042. * @property
  102043. * The CSS class that provides the ERROR icon image
  102044. */
  102045. ERROR : Ext.baseCSSPrefix + 'message-box-error',
  102046. // hide it by offsets. Windows are hidden on render by default.
  102047. hideMode: 'offsets',
  102048. closeAction: 'hide',
  102049. resizable: false,
  102050. title: '&#160;',
  102051. width: 600,
  102052. height: 500,
  102053. minWidth: 250,
  102054. maxWidth: 600,
  102055. minHeight: 110,
  102056. maxHeight: 500,
  102057. constrain: true,
  102058. cls: Ext.baseCSSPrefix + 'message-box',
  102059. layout: {
  102060. type: 'vbox',
  102061. align: 'stretch'
  102062. },
  102063. /**
  102064. * @property
  102065. * The default height in pixels of the message box's multiline textarea if displayed.
  102066. */
  102067. defaultTextHeight : 75,
  102068. /**
  102069. * @property
  102070. * The minimum width in pixels of the message box if it is a progress-style dialog. This is useful
  102071. * for setting a different minimum width than text-only dialogs may need.
  102072. */
  102073. minProgressWidth : 250,
  102074. /**
  102075. * @property
  102076. * The minimum width in pixels of the message box if it is a prompt dialog. This is useful
  102077. * for setting a different minimum width than text-only dialogs may need.
  102078. */
  102079. minPromptWidth: 250,
  102080. //<locale type="object">
  102081. /**
  102082. * @property
  102083. * An object containing the default button text strings that can be overriden for localized language support.
  102084. * Supported properties are: ok, cancel, yes and no. Generally you should include a locale-specific
  102085. * resource file for handling language support across the framework.
  102086. * Customize the default text like so:
  102087. *
  102088. * Ext.window.MessageBox.buttonText.yes = "oui"; //french
  102089. */
  102090. buttonText: {
  102091. ok: 'OK',
  102092. yes: 'Yes',
  102093. no: 'No',
  102094. cancel: 'Cancel'
  102095. },
  102096. //</locale>
  102097. buttonIds: [
  102098. 'ok', 'yes', 'no', 'cancel'
  102099. ],
  102100. //<locale type="object">
  102101. titleText: {
  102102. confirm: 'Confirm',
  102103. prompt: 'Prompt',
  102104. wait: 'Loading...',
  102105. alert: 'Attention'
  102106. },
  102107. //</locale>
  102108. iconHeight: 35,
  102109. makeButton: function(btnIdx) {
  102110. var btnId = this.buttonIds[btnIdx];
  102111. return new Ext.button.Button({
  102112. handler: this.btnCallback,
  102113. itemId: btnId,
  102114. scope: this,
  102115. text: this.buttonText[btnId],
  102116. minWidth: 75
  102117. });
  102118. },
  102119. btnCallback: function(btn) {
  102120. var me = this,
  102121. value,
  102122. field;
  102123. if (me.cfg.prompt || me.cfg.multiline) {
  102124. if (me.cfg.multiline) {
  102125. field = me.textArea;
  102126. } else {
  102127. field = me.textField;
  102128. }
  102129. value = field.getValue();
  102130. field.reset();
  102131. }
  102132. // Important not to have focus remain in the hidden Window; Interferes with DnD.
  102133. btn.blur();
  102134. me.hide();
  102135. me.userCallback(btn.itemId, value, me.cfg);
  102136. },
  102137. hide: function() {
  102138. var me = this;
  102139. me.dd.endDrag();
  102140. me.progressBar.reset();
  102141. me.removeCls(me.cfg.cls);
  102142. me.callParent(arguments);
  102143. },
  102144. initComponent: function() {
  102145. var me = this,
  102146. baseId = me.id,
  102147. i, button,
  102148. tbLayout;
  102149. me.title = '&#160;';
  102150. me.topContainer = new Ext.container.Container({
  102151. layout: 'hbox',
  102152. style: {
  102153. padding: '10px',
  102154. overflow: 'hidden'
  102155. },
  102156. items: [
  102157. me.iconComponent = new Ext.Component({
  102158. cls: me.baseCls + '-icon',
  102159. width: 50,
  102160. height: me.iconHeight
  102161. }),
  102162. me.promptContainer = new Ext.container.Container({
  102163. flex: 1,
  102164. layout: {
  102165. type: 'anchor'
  102166. },
  102167. items: [
  102168. me.msg = new Ext.form.field.Display({
  102169. id: baseId + '-displayfield',
  102170. cls: me.baseCls + '-text'
  102171. }),
  102172. me.textField = new Ext.form.field.Text({
  102173. id: baseId + '-testfield',
  102174. anchor: '100%',
  102175. enableKeyEvents: true,
  102176. listeners: {
  102177. keydown: me.onPromptKey,
  102178. scope: me
  102179. }
  102180. }),
  102181. me.textArea = new Ext.form.field.TextArea({
  102182. id: baseId + '-textarea',
  102183. anchor: '100%',
  102184. height: 75
  102185. })
  102186. ]
  102187. })
  102188. ]
  102189. });
  102190. me.progressBar = new Ext.ProgressBar({
  102191. id: baseId + '-progressbar',
  102192. margins: '0 10 0 10'
  102193. });
  102194. me.items = [me.topContainer, me.progressBar];
  102195. // Create the buttons based upon passed bitwise config
  102196. me.msgButtons = [];
  102197. for (i = 0; i < 4; i++) {
  102198. button = me.makeButton(i);
  102199. me.msgButtons[button.itemId] = button;
  102200. me.msgButtons.push(button);
  102201. }
  102202. me.bottomTb = new Ext.toolbar.Toolbar({
  102203. id: baseId + '-toolbar',
  102204. ui: 'footer',
  102205. dock: 'bottom',
  102206. layout: {
  102207. pack: 'center'
  102208. },
  102209. items: [
  102210. me.msgButtons[0],
  102211. me.msgButtons[1],
  102212. me.msgButtons[2],
  102213. me.msgButtons[3]
  102214. ]
  102215. });
  102216. me.dockedItems = [me.bottomTb];
  102217. // Get control at Toolbar's finishedLayout call and snag the contentWidth to contribute to our auto width calculation
  102218. tbLayout = me.bottomTb.getLayout();
  102219. tbLayout.finishedLayout = Ext.Function.createInterceptor(tbLayout.finishedLayout, function(ownerContext) {
  102220. me.tbWidth = ownerContext.getProp('contentWidth');
  102221. });
  102222. me.on('close', me.onClose, me);
  102223. me.callParent();
  102224. },
  102225. onClose: function(){
  102226. var btn = this.header.child('[type=close]');
  102227. // Give a temporary itemId so it can act like the cancel button
  102228. btn.itemId = 'cancel';
  102229. this.btnCallback(btn);
  102230. delete btn.itemId;
  102231. },
  102232. onPromptKey: function(textField, e) {
  102233. var me = this,
  102234. blur;
  102235. if (e.keyCode === Ext.EventObject.RETURN || e.keyCode === 10) {
  102236. if (me.msgButtons.ok.isVisible()) {
  102237. blur = true;
  102238. me.msgButtons.ok.handler.call(me, me.msgButtons.ok);
  102239. } else if (me.msgButtons.yes.isVisible()) {
  102240. me.msgButtons.yes.handler.call(me, me.msgButtons.yes);
  102241. blur = true;
  102242. }
  102243. if (blur) {
  102244. me.textField.blur();
  102245. }
  102246. }
  102247. },
  102248. reconfigure: function(cfg) {
  102249. var me = this,
  102250. buttons = 0,
  102251. hideToolbar = true,
  102252. initialWidth = me.maxWidth,
  102253. oldButtonText = me.buttonText,
  102254. i;
  102255. // Restore default buttonText before reconfiguring.
  102256. me.updateButtonText();
  102257. cfg = cfg || {};
  102258. me.cfg = cfg;
  102259. if (cfg.width) {
  102260. initialWidth = cfg.width;
  102261. }
  102262. // Default to allowing the Window to take focus.
  102263. delete me.defaultFocus;
  102264. // clear any old animateTarget
  102265. me.animateTarget = cfg.animateTarget || undefined;
  102266. // Defaults to modal
  102267. me.modal = cfg.modal !== false;
  102268. // Show the title
  102269. if (cfg.title) {
  102270. me.setTitle(cfg.title||'&#160;');
  102271. }
  102272. // Extract button configs
  102273. if (Ext.isObject(cfg.buttons)) {
  102274. me.buttonText = cfg.buttons;
  102275. buttons = 0;
  102276. } else {
  102277. me.buttonText = cfg.buttonText || me.buttonText;
  102278. buttons = Ext.isNumber(cfg.buttons) ? cfg.buttons : 0;
  102279. }
  102280. // Apply custom-configured buttonText
  102281. // Infer additional buttons from the specified property names in the buttonText object
  102282. buttons = buttons | me.updateButtonText();
  102283. // Restore buttonText. Next run of reconfigure will restore to prototype's buttonText
  102284. me.buttonText = oldButtonText;
  102285. // During the on render, or size resetting layouts, and in subsequent hiding and showing, we need to
  102286. // suspend layouts, and flush at the end when the Window's children are at their final visibility.
  102287. Ext.suspendLayouts();
  102288. me.hidden = false;
  102289. if (!me.rendered) {
  102290. me.width = initialWidth;
  102291. me.render(Ext.getBody());
  102292. } else {
  102293. me.setSize(initialWidth, me.maxHeight);
  102294. }
  102295. // Hide or show the close tool
  102296. me.closable = cfg.closable && !cfg.wait;
  102297. me.header.child('[type=close]').setVisible(cfg.closable !== false);
  102298. // Hide or show the header
  102299. if (!cfg.title && !me.closable) {
  102300. me.header.hide();
  102301. } else {
  102302. me.header.show();
  102303. }
  102304. // Default to dynamic drag: drag the window, not a ghost
  102305. me.liveDrag = !cfg.proxyDrag;
  102306. // wrap the user callback
  102307. me.userCallback = Ext.Function.bind(cfg.callback ||cfg.fn || Ext.emptyFn, cfg.scope || Ext.global);
  102308. // Hide or show the icon Component
  102309. me.setIcon(cfg.icon);
  102310. // Hide or show the message area
  102311. if (cfg.msg) {
  102312. me.msg.setValue(cfg.msg);
  102313. me.msg.show();
  102314. } else {
  102315. me.msg.hide();
  102316. }
  102317. // flush the layout here to pick up
  102318. // height adjustments on the msg field
  102319. Ext.resumeLayouts(true);
  102320. Ext.suspendLayouts();
  102321. // Hide or show the input field
  102322. if (cfg.prompt || cfg.multiline) {
  102323. me.multiline = cfg.multiline;
  102324. if (cfg.multiline) {
  102325. me.textArea.setValue(cfg.value);
  102326. me.textArea.setHeight(cfg.defaultTextHeight || me.defaultTextHeight);
  102327. me.textArea.show();
  102328. me.textField.hide();
  102329. me.defaultFocus = me.textArea;
  102330. } else {
  102331. me.textField.setValue(cfg.value);
  102332. me.textArea.hide();
  102333. me.textField.show();
  102334. me.defaultFocus = me.textField;
  102335. }
  102336. } else {
  102337. me.textArea.hide();
  102338. me.textField.hide();
  102339. }
  102340. // Hide or show the progress bar
  102341. if (cfg.progress || cfg.wait) {
  102342. me.progressBar.show();
  102343. me.updateProgress(0, cfg.progressText);
  102344. if(cfg.wait === true){
  102345. me.progressBar.wait(cfg.waitConfig);
  102346. }
  102347. } else {
  102348. me.progressBar.hide();
  102349. }
  102350. // Hide or show buttons depending on flag value sent.
  102351. for (i = 0; i < 4; i++) {
  102352. if (buttons & Math.pow(2, i)) {
  102353. // Default to focus on the first visible button if focus not already set
  102354. if (!me.defaultFocus) {
  102355. me.defaultFocus = me.msgButtons[i];
  102356. }
  102357. me.msgButtons[i].show();
  102358. hideToolbar = false;
  102359. } else {
  102360. me.msgButtons[i].hide();
  102361. }
  102362. }
  102363. // Hide toolbar if no buttons to show
  102364. if (hideToolbar) {
  102365. me.bottomTb.hide();
  102366. } else {
  102367. me.bottomTb.show();
  102368. }
  102369. Ext.resumeLayouts(true);
  102370. },
  102371. /**
  102372. * @private
  102373. * Set button text according to current buttonText property object
  102374. * @return {Number} The buttons bitwise flag based upon the button IDs specified in the buttonText property.
  102375. */
  102376. updateButtonText: function() {
  102377. var me = this,
  102378. buttonText = me.buttonText,
  102379. buttons = 0,
  102380. btnId,
  102381. btn;
  102382. for (btnId in buttonText) {
  102383. if (buttonText.hasOwnProperty(btnId)) {
  102384. btn = me.msgButtons[btnId];
  102385. if (btn) {
  102386. if (me.cfg && me.cfg.buttonText) {
  102387. buttons = buttons | Math.pow(2, Ext.Array.indexOf(me.buttonIds, btnId));
  102388. }
  102389. if (btn.text != buttonText[btnId]) {
  102390. btn.setText(buttonText[btnId]);
  102391. }
  102392. }
  102393. }
  102394. }
  102395. return buttons;
  102396. },
  102397. /**
  102398. * Displays a new message box, or reinitializes an existing message box, based on the config options passed in. All
  102399. * display functions (e.g. prompt, alert, etc.) on MessageBox call this function internally, although those calls
  102400. * are basic shortcuts and do not support all of the config options allowed here.
  102401. *
  102402. * Example usage:
  102403. *
  102404. * Ext.Msg.show({
  102405. * title: 'Address',
  102406. * msg: 'Please enter your address:',
  102407. * width: 300,
  102408. * buttons: Ext.Msg.OKCANCEL,
  102409. * multiline: true,
  102410. * fn: saveAddress,
  102411. * animateTarget: 'addAddressBtn',
  102412. * icon: Ext.window.MessageBox.INFO
  102413. * });
  102414. *
  102415. * @param {Object} config The following config options are supported:
  102416. *
  102417. * @param {String/Ext.dom.Element} config.animateTarget
  102418. * An id or Element from which the message box should animate as it opens and closes.
  102419. *
  102420. * @param {Number} [config.buttons=false]
  102421. * A bitwise button specifier consisting of the sum of any of the following constants:
  102422. *
  102423. * - Ext.MessageBox.OK
  102424. * - Ext.MessageBox.YES
  102425. * - Ext.MessageBox.NO
  102426. * - Ext.MessageBox.CANCEL
  102427. *
  102428. * Some common combinations have already been predefined:
  102429. *
  102430. * - Ext.MessageBox.OKCANCEL
  102431. * - Ext.MessageBox.YESNO
  102432. * - Ext.MessageBox.YESNOCANCEL
  102433. *
  102434. * Or false to not show any buttons.
  102435. *
  102436. * This may also be specified as an object hash containing custom button text in the same format as the
  102437. * {@link #buttonText} config. Button IDs present as property names will be made visible.
  102438. *
  102439. * @param {Boolean} config.closable
  102440. * False to hide the top-right close button (defaults to true). Note that progress and wait dialogs will ignore this
  102441. * property and always hide the close button as they can only be closed programmatically.
  102442. *
  102443. * @param {String} config.cls
  102444. * A custom CSS class to apply to the message box's container element
  102445. *
  102446. * @param {Number} [config.defaultTextHeight=75]
  102447. * The default height in pixels of the message box's multiline textarea if displayed.
  102448. *
  102449. * @param {Function} config.fn
  102450. * A callback function which is called when the dialog is dismissed either by clicking on the configured buttons, or
  102451. * on the dialog close button, or by pressing the return button to enter input.
  102452. *
  102453. * Progress and wait dialogs will ignore this option since they do not respond to user actions and can only be
  102454. * closed programmatically, so any required function should be called by the same code after it closes the dialog.
  102455. * Parameters passed:
  102456. *
  102457. * @param {String} config.fn.buttonId The ID of the button pressed, one of:
  102458. *
  102459. * - ok
  102460. * - yes
  102461. * - no
  102462. * - cancel
  102463. *
  102464. * @param {String} config.fn.text Value of the input field if either `prompt` or `multiline` is true
  102465. * @param {Object} config.fn.opt The config object passed to show.
  102466. *
  102467. * @param {Object} config.buttonText
  102468. * An object containing string properties which override the system-supplied button text values just for this
  102469. * invocation. The property names are:
  102470. *
  102471. * - ok
  102472. * - yes
  102473. * - no
  102474. * - cancel
  102475. *
  102476. * @param {Object} config.scope
  102477. * The scope (`this` reference) in which the function will be executed.
  102478. *
  102479. * @param {String} config.icon
  102480. * A CSS class that provides a background image to be used as the body icon for the dialog.
  102481. * One can use a predefined icon class:
  102482. *
  102483. * - Ext.MessageBox.INFO
  102484. * - Ext.MessageBox.WARNING
  102485. * - Ext.MessageBox.QUESTION
  102486. * - Ext.MessageBox.ERROR
  102487. *
  102488. * or use just any `'custom-class'`. Defaults to empty string.
  102489. *
  102490. * @param {String} config.iconCls
  102491. * The standard {@link Ext.window.Window#iconCls} to add an optional header icon (defaults to '')
  102492. *
  102493. * @param {Number} config.maxWidth
  102494. * The maximum width in pixels of the message box (defaults to 600)
  102495. *
  102496. * @param {Number} config.minWidth
  102497. * The minimum width in pixels of the message box (defaults to 100)
  102498. *
  102499. * @param {Boolean} config.modal
  102500. * False to allow user interaction with the page while the message box is displayed (defaults to true)
  102501. *
  102502. * @param {String} config.msg
  102503. * A string that will replace the existing message box body text (defaults to the XHTML-compliant non-breaking space
  102504. * character '&#160;')
  102505. *
  102506. * @param {Boolean} config.multiline
  102507. * True to prompt the user to enter multi-line text (defaults to false)
  102508. *
  102509. * @param {Boolean} config.progress
  102510. * True to display a progress bar (defaults to false)
  102511. *
  102512. * @param {String} config.progressText
  102513. * The text to display inside the progress bar if progress = true (defaults to '')
  102514. *
  102515. * @param {Boolean} config.prompt
  102516. * True to prompt the user to enter single-line text (defaults to false)
  102517. *
  102518. * @param {Boolean} config.proxyDrag
  102519. * True to display a lightweight proxy while dragging (defaults to false)
  102520. *
  102521. * @param {String} config.title
  102522. * The title text
  102523. *
  102524. * @param {String} config.value
  102525. * The string value to set into the active textbox element if displayed
  102526. *
  102527. * @param {Boolean} config.wait
  102528. * True to display a progress bar (defaults to false)
  102529. *
  102530. * @param {Object} config.waitConfig
  102531. * A {@link Ext.ProgressBar#wait} config object (applies only if wait = true)
  102532. *
  102533. * @param {Number} config.width
  102534. * The width of the dialog in pixels
  102535. *
  102536. * @return {Ext.window.MessageBox} this
  102537. */
  102538. show: function(cfg) {
  102539. var me = this;
  102540. me.reconfigure(cfg);
  102541. me.addCls(cfg.cls);
  102542. me.doAutoSize();
  102543. // Set the flag, so that the parent show method performs the show procedure that we need.
  102544. // ie: animation from animTarget, onShow processing and focusing.
  102545. me.hidden = true;
  102546. me.callParent();
  102547. return me;
  102548. },
  102549. onShow: function() {
  102550. this.callParent(arguments);
  102551. this.center();
  102552. },
  102553. doAutoSize: function() {
  102554. var me = this,
  102555. headerVisible = me.header.rendered && me.header.isVisible(),
  102556. footerVisible = me.bottomTb.rendered && me.bottomTb.isVisible(),
  102557. width,
  102558. height;
  102559. if (!Ext.isDefined(me.frameWidth)) {
  102560. me.frameWidth = me.el.getWidth() - me.body.getWidth();
  102561. }
  102562. // Allow per-invocation override of minWidth
  102563. me.minWidth = me.cfg.minWidth || Ext.getClass(this).prototype.minWidth;
  102564. // Width must be max of titleWidth, message+icon width, and total button width
  102565. width = Math.max(
  102566. headerVisible ? me.header.getMinWidth() : 0, // title width
  102567. me.cfg.width || me.msg.getWidth() + me.iconComponent.getWidth() + 25, // msg + icon width + topContainer's layout padding */
  102568. (footerVisible ? me.tbWidth : 0)// total button width
  102569. );
  102570. height = (headerVisible ? me.header.getHeight() : 0) +
  102571. me.topContainer.getHeight() +
  102572. me.progressBar.getHeight() +
  102573. (footerVisible ? me.bottomTb.getHeight() + me.bottomTb.el.getMargin('tb') : 0);
  102574. me.setSize(width + me.frameWidth, height + me.frameWidth);
  102575. return me;
  102576. },
  102577. updateText: function(text) {
  102578. this.msg.setValue(text);
  102579. return this.doAutoSize(true);
  102580. },
  102581. /**
  102582. * Adds the specified icon to the dialog. By default, the class 'x-messagebox-icon' is applied for default
  102583. * styling, and the class passed in is expected to supply the background image url. Pass in empty string ('')
  102584. * to clear any existing icon. This method must be called before the MessageBox is shown.
  102585. * The following built-in icon classes are supported, but you can also pass in a custom class name:
  102586. *
  102587. * Ext.window.MessageBox.INFO
  102588. * Ext.window.MessageBox.WARNING
  102589. * Ext.window.MessageBox.QUESTION
  102590. * Ext.window.MessageBox.ERROR
  102591. *
  102592. * @param {String} icon A CSS classname specifying the icon's background image url, or empty string to clear the icon
  102593. * @return {Ext.window.MessageBox} this
  102594. */
  102595. setIcon : function(icon) {
  102596. var me = this;
  102597. me.iconComponent.removeCls(me.messageIconCls);
  102598. if (icon) {
  102599. me.iconComponent.show();
  102600. me.iconComponent.addCls(Ext.baseCSSPrefix + 'dlg-icon');
  102601. me.iconComponent.addCls(me.messageIconCls = icon);
  102602. } else {
  102603. me.iconComponent.removeCls(Ext.baseCSSPrefix + 'dlg-icon');
  102604. me.iconComponent.hide();
  102605. }
  102606. return me;
  102607. },
  102608. /**
  102609. * Updates a progress-style message box's text and progress bar. Only relevant on message boxes
  102610. * initiated via {@link Ext.window.MessageBox#progress} or {@link Ext.window.MessageBox#wait},
  102611. * or by calling {@link Ext.window.MessageBox#method-show} with progress: true.
  102612. *
  102613. * @param {Number} [value=0] Any number between 0 and 1 (e.g., .5)
  102614. * @param {String} [progressText=''] The progress text to display inside the progress bar.
  102615. * @param {String} [msg] The message box's body text is replaced with the specified string (defaults to undefined
  102616. * so that any existing body text will not get overwritten by default unless a new value is passed in)
  102617. * @return {Ext.window.MessageBox} this
  102618. */
  102619. updateProgress : function(value, progressText, msg){
  102620. this.progressBar.updateProgress(value, progressText);
  102621. if (msg){
  102622. this.updateText(msg);
  102623. }
  102624. return this;
  102625. },
  102626. onEsc: function() {
  102627. if (this.closable !== false) {
  102628. this.callParent(arguments);
  102629. }
  102630. },
  102631. /**
  102632. * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm).
  102633. * If a callback function is passed it will be called after the user clicks either button,
  102634. * and the id of the button that was clicked will be passed as the only parameter to the callback
  102635. * (could also be the top-right close button, which will always report as "cancel").
  102636. *
  102637. * @param {String} title The title bar text
  102638. * @param {String} msg The message box body text
  102639. * @param {Function} [fn] The callback function invoked after the message box is closed.
  102640. * See {@link #method-show} method for details.
  102641. * @param {Object} [scope=window] The scope (`this` reference) in which the callback is executed.
  102642. * @return {Ext.window.MessageBox} this
  102643. */
  102644. confirm: function(cfg, msg, fn, scope) {
  102645. if (Ext.isString(cfg)) {
  102646. cfg = {
  102647. title: cfg,
  102648. icon: this.QUESTION,
  102649. msg: msg,
  102650. buttons: this.YESNO,
  102651. callback: fn,
  102652. scope: scope
  102653. };
  102654. }
  102655. return this.show(cfg);
  102656. },
  102657. /**
  102658. * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to JavaScript's prompt).
  102659. * The prompt can be a single-line or multi-line textbox. If a callback function is passed it will be called after the user
  102660. * clicks either button, and the id of the button that was clicked (could also be the top-right
  102661. * close button, which will always report as "cancel") and the text that was entered will be passed as the two parameters to the callback.
  102662. *
  102663. * @param {String} title The title bar text
  102664. * @param {String} msg The message box body text
  102665. * @param {Function} [fn] The callback function invoked after the message box is closed.
  102666. * See {@link #method-show} method for details.
  102667. * @param {Object} [scope=window] The scope (`this` reference) in which the callback is executed.
  102668. * @param {Boolean/Number} [multiline=false] True to create a multiline textbox using the defaultTextHeight
  102669. * property, or the height in pixels to create the textbox/
  102670. * @param {String} [value=''] Default value of the text input element
  102671. * @return {Ext.window.MessageBox} this
  102672. */
  102673. prompt : function(cfg, msg, fn, scope, multiline, value){
  102674. if (Ext.isString(cfg)) {
  102675. cfg = {
  102676. prompt: true,
  102677. title: cfg,
  102678. minWidth: this.minPromptWidth,
  102679. msg: msg,
  102680. buttons: this.OKCANCEL,
  102681. callback: fn,
  102682. scope: scope,
  102683. multiline: multiline,
  102684. value: value
  102685. };
  102686. }
  102687. return this.show(cfg);
  102688. },
  102689. /**
  102690. * Displays a message box with an infinitely auto-updating progress bar. This can be used to block user
  102691. * interaction while waiting for a long-running process to complete that does not have defined intervals.
  102692. * You are responsible for closing the message box when the process is complete.
  102693. *
  102694. * @param {String} msg The message box body text
  102695. * @param {String} [title] The title bar text
  102696. * @param {Object} [config] A {@link Ext.ProgressBar#wait} config object
  102697. * @return {Ext.window.MessageBox} this
  102698. */
  102699. wait : function(cfg, title, config){
  102700. if (Ext.isString(cfg)) {
  102701. cfg = {
  102702. title : title,
  102703. msg : cfg,
  102704. closable: false,
  102705. wait: true,
  102706. modal: true,
  102707. minWidth: this.minProgressWidth,
  102708. waitConfig: config
  102709. };
  102710. }
  102711. return this.show(cfg);
  102712. },
  102713. /**
  102714. * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt).
  102715. * If a callback function is passed it will be called after the user clicks the button, and the
  102716. * id of the button that was clicked will be passed as the only parameter to the callback
  102717. * (could also be the top-right close button, which will always report as "cancel").
  102718. *
  102719. * @param {String} title The title bar text
  102720. * @param {String} msg The message box body text
  102721. * @param {Function} [fn] The callback function invoked after the message box is closed.
  102722. * See {@link #method-show} method for details.
  102723. * @param {Object} [scope=window] The scope (<code>this</code> reference) in which the callback is executed.
  102724. * @return {Ext.window.MessageBox} this
  102725. */
  102726. alert: function(cfg, msg, fn, scope) {
  102727. if (Ext.isString(cfg)) {
  102728. cfg = {
  102729. title : cfg,
  102730. msg : msg,
  102731. buttons: this.OK,
  102732. fn: fn,
  102733. scope : scope,
  102734. minWidth: this.minWidth
  102735. };
  102736. }
  102737. return this.show(cfg);
  102738. },
  102739. /**
  102740. * Displays a message box with a progress bar.
  102741. *
  102742. * You are responsible for updating the progress bar as needed via {@link Ext.window.MessageBox#updateProgress}
  102743. * and closing the message box when the process is complete.
  102744. *
  102745. * @param {String} title The title bar text
  102746. * @param {String} msg The message box body text
  102747. * @param {String} [progressText=''] The text to display inside the progress bar
  102748. * @return {Ext.window.MessageBox} this
  102749. */
  102750. progress : function(cfg, msg, progressText){
  102751. if (Ext.isString(cfg)) {
  102752. cfg = {
  102753. title: cfg,
  102754. msg: msg,
  102755. progress: true,
  102756. progressText: progressText
  102757. };
  102758. }
  102759. return this.show(cfg);
  102760. }
  102761. }, function() {
  102762. /**
  102763. * @class Ext.MessageBox
  102764. * @alternateClassName Ext.Msg
  102765. * @extends Ext.window.MessageBox
  102766. * @singleton
  102767. * Singleton instance of {@link Ext.window.MessageBox}.
  102768. */
  102769. Ext.MessageBox = Ext.Msg = new this();
  102770. });
  102771. /**
  102772. * Provides input field management, validation, submission, and form loading services for the collection
  102773. * of {@link Ext.form.field.Field Field} instances within a {@link Ext.container.Container}. It is recommended
  102774. * that you use a {@link Ext.form.Panel} as the form container, as that has logic to automatically
  102775. * hook up an instance of {@link Ext.form.Basic} (plus other conveniences related to field configuration.)
  102776. *
  102777. * ## Form Actions
  102778. *
  102779. * The Basic class delegates the handling of form loads and submits to instances of {@link Ext.form.action.Action}.
  102780. * See the various Action implementations for specific details of each one's functionality, as well as the
  102781. * documentation for {@link #doAction} which details the configuration options that can be specified in
  102782. * each action call.
  102783. *
  102784. * The default submit Action is {@link Ext.form.action.Submit}, which uses an Ajax request to submit the
  102785. * form's values to a configured URL. To enable normal browser submission of an Ext form, use the
  102786. * {@link #standardSubmit} config option.
  102787. *
  102788. * ## File uploads
  102789. *
  102790. * File uploads are not performed using normal 'Ajax' techniques; see the description for
  102791. * {@link #hasUpload} for details. If you're using file uploads you should read the method description.
  102792. *
  102793. * ## Example usage:
  102794. *
  102795. * @example
  102796. * Ext.create('Ext.form.Panel', {
  102797. * title: 'Basic Form',
  102798. * renderTo: Ext.getBody(),
  102799. * bodyPadding: 5,
  102800. * width: 350,
  102801. *
  102802. * // Any configuration items here will be automatically passed along to
  102803. * // the Ext.form.Basic instance when it gets created.
  102804. *
  102805. * // The form will submit an AJAX request to this URL when submitted
  102806. * url: 'save-form.php',
  102807. *
  102808. * items: [{
  102809. * xtype: 'textfield',
  102810. * fieldLabel: 'Field',
  102811. * name: 'theField'
  102812. * }],
  102813. *
  102814. * buttons: [{
  102815. * text: 'Submit',
  102816. * handler: function() {
  102817. * // The getForm() method returns the Ext.form.Basic instance:
  102818. * var form = this.up('form').getForm();
  102819. * if (form.isValid()) {
  102820. * // Submit the Ajax request and handle the response
  102821. * form.submit({
  102822. * success: function(form, action) {
  102823. * Ext.Msg.alert('Success', action.result.message);
  102824. * },
  102825. * failure: function(form, action) {
  102826. * Ext.Msg.alert('Failed', action.result ? action.result.message : 'No response');
  102827. * }
  102828. * });
  102829. * }
  102830. * }
  102831. * }]
  102832. * });
  102833. *
  102834. * @docauthor Jason Johnston <jason@sencha.com>
  102835. */
  102836. Ext.define('Ext.form.Basic', {
  102837. extend: 'Ext.util.Observable',
  102838. alternateClassName: 'Ext.form.BasicForm',
  102839. requires: ['Ext.util.MixedCollection', 'Ext.form.action.Load', 'Ext.form.action.Submit',
  102840. 'Ext.window.MessageBox', 'Ext.data.Errors', 'Ext.util.DelayedTask'],
  102841. /**
  102842. * Creates new form.
  102843. * @param {Ext.container.Container} owner The component that is the container for the form, usually a {@link Ext.form.Panel}
  102844. * @param {Object} config Configuration options. These are normally specified in the config to the
  102845. * {@link Ext.form.Panel} constructor, which passes them along to the BasicForm automatically.
  102846. */
  102847. constructor: function(owner, config) {
  102848. var me = this,
  102849. onItemAddOrRemove = me.onItemAddOrRemove,
  102850. api,
  102851. fn;
  102852. /**
  102853. * @property {Ext.container.Container} owner
  102854. * The container component to which this BasicForm is attached.
  102855. */
  102856. me.owner = owner;
  102857. // Listen for addition/removal of fields in the owner container
  102858. me.mon(owner, {
  102859. add: onItemAddOrRemove,
  102860. remove: onItemAddOrRemove,
  102861. scope: me
  102862. });
  102863. Ext.apply(me, config);
  102864. // Normalize the paramOrder to an Array
  102865. if (Ext.isString(me.paramOrder)) {
  102866. me.paramOrder = me.paramOrder.split(/[\s,|]/);
  102867. }
  102868. if (me.api) {
  102869. api = me.api = Ext.apply({}, me.api);
  102870. for (fn in api) {
  102871. if (api.hasOwnProperty(fn)) {
  102872. api[fn] = Ext.direct.Manager.parseMethod(api[fn]);
  102873. }
  102874. }
  102875. }
  102876. me.checkValidityTask = new Ext.util.DelayedTask(me.checkValidity, me);
  102877. me.addEvents(
  102878. /**
  102879. * @event beforeaction
  102880. * Fires before any action is performed. Return false to cancel the action.
  102881. * @param {Ext.form.Basic} this
  102882. * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} to be performed
  102883. */
  102884. 'beforeaction',
  102885. /**
  102886. * @event actionfailed
  102887. * Fires when an action fails.
  102888. * @param {Ext.form.Basic} this
  102889. * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that failed
  102890. */
  102891. 'actionfailed',
  102892. /**
  102893. * @event actioncomplete
  102894. * Fires when an action is completed.
  102895. * @param {Ext.form.Basic} this
  102896. * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that completed
  102897. */
  102898. 'actioncomplete',
  102899. /**
  102900. * @event validitychange
  102901. * Fires when the validity of the entire form changes.
  102902. * @param {Ext.form.Basic} this
  102903. * @param {Boolean} valid `true` if the form is now valid, `false` if it is now invalid.
  102904. */
  102905. 'validitychange',
  102906. /**
  102907. * @event dirtychange
  102908. * Fires when the dirty state of the entire form changes.
  102909. * @param {Ext.form.Basic} this
  102910. * @param {Boolean} dirty `true` if the form is now dirty, `false` if it is no longer dirty.
  102911. */
  102912. 'dirtychange'
  102913. );
  102914. me.callParent();
  102915. },
  102916. /**
  102917. * Do any post layout initialization
  102918. * @private
  102919. */
  102920. initialize : function() {
  102921. var me = this;
  102922. me.initialized = true;
  102923. me.onValidityChange(!me.hasInvalidField());
  102924. },
  102925. /**
  102926. * @cfg {String} method
  102927. * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
  102928. */
  102929. /**
  102930. * @cfg {Ext.data.reader.Reader} reader
  102931. * An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to read
  102932. * data when executing 'load' actions. This is optional as there is built-in
  102933. * support for processing JSON responses.
  102934. */
  102935. /**
  102936. * @cfg {Ext.data.reader.Reader} errorReader
  102937. * An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to
  102938. * read field error messages returned from 'submit' actions. This is optional
  102939. * as there is built-in support for processing JSON responses.
  102940. *
  102941. * The Records which provide messages for the invalid Fields must use the
  102942. * Field name (or id) as the Record ID, and must contain a field called 'msg'
  102943. * which contains the error message.
  102944. *
  102945. * The errorReader does not have to be a full-blown implementation of a
  102946. * Reader. It simply needs to implement a `read(xhr)` function
  102947. * which returns an Array of Records in an object with the following
  102948. * structure:
  102949. *
  102950. * {
  102951. * records: recordArray
  102952. * }
  102953. */
  102954. /**
  102955. * @cfg {String} url
  102956. * The URL to use for form actions if one isn't supplied in the
  102957. * {@link #doAction doAction} options.
  102958. */
  102959. /**
  102960. * @cfg {Object} baseParams
  102961. * Parameters to pass with all requests. e.g. baseParams: `{id: '123', foo: 'bar'}`.
  102962. *
  102963. * Parameters are encoded as standard HTTP parameters using {@link Ext.Object#toQueryString}.
  102964. */
  102965. /**
  102966. * @cfg {Number} timeout
  102967. * Timeout for form actions in seconds.
  102968. */
  102969. timeout: 30,
  102970. /**
  102971. * @cfg {Object} api
  102972. * If specified, load and submit actions will be handled with {@link Ext.form.action.DirectLoad DirectLoad}
  102973. * and {@link Ext.form.action.DirectSubmit DirectSubmit}. Methods which have been imported by
  102974. * {@link Ext.direct.Manager} can be specified here to load and submit forms. API methods may also be
  102975. * specified as strings. See {@link Ext.data.proxy.Direct#directFn}. Such as the following:
  102976. *
  102977. * api: {
  102978. * load: App.ss.MyProfile.load,
  102979. * submit: App.ss.MyProfile.submit
  102980. * }
  102981. *
  102982. * Load actions can use {@link #paramOrder} or {@link #paramsAsHash} to customize how the load method
  102983. * is invoked. Submit actions will always use a standard form submit. The `formHandler` configuration
  102984. * (see Ext.direct.RemotingProvider#action) must be set on the associated server-side method which has
  102985. * been imported by {@link Ext.direct.Manager}.
  102986. */
  102987. /**
  102988. * @cfg {String/String[]} paramOrder
  102989. * A list of params to be executed server side. Only used for the {@link #api} `load`
  102990. * configuration.
  102991. *
  102992. * Specify the params in the order in which they must be executed on the
  102993. * server-side as either (1) an Array of String values, or (2) a String of params
  102994. * delimited by either whitespace, comma, or pipe. For example,
  102995. * any of the following would be acceptable:
  102996. *
  102997. * paramOrder: ['param1','param2','param3']
  102998. * paramOrder: 'param1 param2 param3'
  102999. * paramOrder: 'param1,param2,param3'
  103000. * paramOrder: 'param1|param2|param'
  103001. */
  103002. /**
  103003. * @cfg {Boolean} paramsAsHash
  103004. * Only used for the {@link #api} `load` configuration. If true, parameters will be sent as a
  103005. * single hash collection of named arguments. Providing a {@link #paramOrder} nullifies this
  103006. * configuration.
  103007. */
  103008. paramsAsHash: false,
  103009. //<locale>
  103010. /**
  103011. * @cfg {String} waitTitle
  103012. * The default title to show for the waiting message box
  103013. */
  103014. waitTitle: 'Please Wait...',
  103015. //</locale>
  103016. /**
  103017. * @cfg {Boolean} trackResetOnLoad
  103018. * If set to true, {@link #reset}() resets to the last loaded or {@link #setValues}() data instead of
  103019. * when the form was first created.
  103020. */
  103021. trackResetOnLoad: false,
  103022. /**
  103023. * @cfg {Boolean} standardSubmit
  103024. * If set to true, a standard HTML form submit is used instead of a XHR (Ajax) style form submission.
  103025. * All of the field values, plus any additional params configured via {@link #baseParams}
  103026. * and/or the `options` to {@link #submit}, will be included in the values submitted in the form.
  103027. */
  103028. /**
  103029. * @cfg {String/HTMLElement/Ext.Element} waitMsgTarget
  103030. * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
  103031. * element by passing it or its id or mask the form itself by passing in true.
  103032. */
  103033. // Private
  103034. wasDirty: false,
  103035. /**
  103036. * Destroys this object.
  103037. */
  103038. destroy: function() {
  103039. this.clearListeners();
  103040. this.checkValidityTask.cancel();
  103041. },
  103042. /**
  103043. * @private
  103044. * Handle addition or removal of descendant items. Invalidates the cached list of fields
  103045. * so that {@link #getFields} will do a fresh query next time it is called. Also adds listeners
  103046. * for state change events on added fields, and tracks components with formBind=true.
  103047. */
  103048. onItemAddOrRemove: function(parent, child) {
  103049. var me = this,
  103050. isAdding = !!child.ownerCt,
  103051. isContainer = child.isContainer;
  103052. function handleField(field) {
  103053. // Listen for state change events on fields
  103054. me[isAdding ? 'mon' : 'mun'](field, {
  103055. validitychange: me.checkValidity,
  103056. dirtychange: me.checkDirty,
  103057. scope: me,
  103058. buffer: 100 //batch up sequential calls to avoid excessive full-form validation
  103059. });
  103060. // Flush the cached list of fields
  103061. delete me._fields;
  103062. }
  103063. if (child.isFormField) {
  103064. handleField(child);
  103065. } else if (isContainer) {
  103066. // Walk down
  103067. if (child.isDestroyed || child.destroying) {
  103068. // the container is destroyed, this means we may have child fields, so here
  103069. // we just invalidate all the fields to be sure.
  103070. delete me._fields;
  103071. } else {
  103072. Ext.Array.forEach(child.query('[isFormField]'), handleField);
  103073. }
  103074. }
  103075. // Flush the cached list of formBind components
  103076. delete this._boundItems;
  103077. // Check form bind, but only after initial add. Batch it to prevent excessive validation
  103078. // calls when many fields are being added at once.
  103079. if (me.initialized) {
  103080. me.checkValidityTask.delay(10);
  103081. }
  103082. },
  103083. /**
  103084. * Return all the {@link Ext.form.field.Field} components in the owner container.
  103085. * @return {Ext.util.MixedCollection} Collection of the Field objects
  103086. */
  103087. getFields: function() {
  103088. var fields = this._fields;
  103089. if (!fields) {
  103090. fields = this._fields = new Ext.util.MixedCollection();
  103091. fields.addAll(this.owner.query('[isFormField]'));
  103092. }
  103093. return fields;
  103094. },
  103095. /**
  103096. * @private
  103097. * Finds and returns the set of all items bound to fields inside this form
  103098. * @return {Ext.util.MixedCollection} The set of all bound form field items
  103099. */
  103100. getBoundItems: function() {
  103101. var boundItems = this._boundItems;
  103102. if (!boundItems || boundItems.getCount() === 0) {
  103103. boundItems = this._boundItems = new Ext.util.MixedCollection();
  103104. boundItems.addAll(this.owner.query('[formBind]'));
  103105. }
  103106. return boundItems;
  103107. },
  103108. /**
  103109. * Returns true if the form contains any invalid fields. No fields will be marked as invalid
  103110. * as a result of calling this; to trigger marking of fields use {@link #isValid} instead.
  103111. */
  103112. hasInvalidField: function() {
  103113. return !!this.getFields().findBy(function(field) {
  103114. var preventMark = field.preventMark,
  103115. isValid;
  103116. field.preventMark = true;
  103117. isValid = field.isValid();
  103118. field.preventMark = preventMark;
  103119. return !isValid;
  103120. });
  103121. },
  103122. /**
  103123. * Returns true if client-side validation on the form is successful. Any invalid fields will be
  103124. * marked as invalid. If you only want to determine overall form validity without marking anything,
  103125. * use {@link #hasInvalidField} instead.
  103126. * @return Boolean
  103127. */
  103128. isValid: function() {
  103129. var me = this,
  103130. invalid;
  103131. Ext.suspendLayouts();
  103132. invalid = me.getFields().filterBy(function(field) {
  103133. return !field.validate();
  103134. });
  103135. Ext.resumeLayouts(true);
  103136. return invalid.length < 1;
  103137. },
  103138. /**
  103139. * Check whether the validity of the entire form has changed since it was last checked, and
  103140. * if so fire the {@link #validitychange validitychange} event. This is automatically invoked
  103141. * when an individual field's validity changes.
  103142. */
  103143. checkValidity: function() {
  103144. var me = this,
  103145. valid = !me.hasInvalidField();
  103146. if (valid !== me.wasValid) {
  103147. me.onValidityChange(valid);
  103148. me.fireEvent('validitychange', me, valid);
  103149. me.wasValid = valid;
  103150. }
  103151. },
  103152. /**
  103153. * @private
  103154. * Handle changes in the form's validity. If there are any sub components with
  103155. * formBind=true then they are enabled/disabled based on the new validity.
  103156. * @param {Boolean} valid
  103157. */
  103158. onValidityChange: function(valid) {
  103159. var boundItems = this.getBoundItems(),
  103160. items, i, iLen, cmp;
  103161. if (boundItems) {
  103162. items = boundItems.items;
  103163. iLen = items.length;
  103164. for (i = 0; i < iLen; i++) {
  103165. cmp = items[i];
  103166. if (cmp.disabled === valid) {
  103167. cmp.setDisabled(!valid);
  103168. }
  103169. }
  103170. }
  103171. },
  103172. /**
  103173. * Returns true if any fields in this form have changed from their original values.
  103174. *
  103175. * Note that if this BasicForm was configured with {@link #trackResetOnLoad} then the
  103176. * Fields' *original values* are updated when the values are loaded by {@link #setValues}
  103177. * or {@link #loadRecord}.
  103178. *
  103179. * @return Boolean
  103180. */
  103181. isDirty: function() {
  103182. return !!this.getFields().findBy(function(f) {
  103183. return f.isDirty();
  103184. });
  103185. },
  103186. /**
  103187. * Check whether the dirty state of the entire form has changed since it was last checked, and
  103188. * if so fire the {@link #dirtychange dirtychange} event. This is automatically invoked
  103189. * when an individual field's dirty state changes.
  103190. */
  103191. checkDirty: function() {
  103192. var dirty = this.isDirty();
  103193. if (dirty !== this.wasDirty) {
  103194. this.fireEvent('dirtychange', this, dirty);
  103195. this.wasDirty = dirty;
  103196. }
  103197. },
  103198. /**
  103199. * Returns true if the form contains a file upload field. This is used to determine the method for submitting the
  103200. * form: File uploads are not performed using normal 'Ajax' techniques, that is they are **not** performed using
  103201. * XMLHttpRequests. Instead a hidden `<form>` element containing all the fields is created temporarily and submitted
  103202. * with its [target][1] set to refer to a dynamically generated, hidden `<iframe>` which is inserted into the document
  103203. * but removed after the return data has been gathered.
  103204. *
  103205. * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON
  103206. * to send the return object, then the [Content-Type][2] header must be set to "text/html" in order to tell the
  103207. * browser to insert the text unchanged into the document body.
  103208. *
  103209. * Characters which are significant to an HTML parser must be sent as HTML entities, so encode `"<"` as `"&lt;"`,
  103210. * `"&"` as `"&amp;"` etc.
  103211. *
  103212. * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
  103213. * responseText property in order to conform to the requirements of event handlers and callbacks.
  103214. *
  103215. * Be aware that file upload packets are sent with the content type [multipart/form][3] and some server technologies
  103216. * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from
  103217. * the packet content.
  103218. *
  103219. * [1]: http://www.w3.org/TR/REC-html40/present/frames.html#adef-target
  103220. * [2]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
  103221. * [3]: http://www.faqs.org/rfcs/rfc2388.html
  103222. *
  103223. * @return Boolean
  103224. */
  103225. hasUpload: function() {
  103226. return !!this.getFields().findBy(function(f) {
  103227. return f.isFileUpload();
  103228. });
  103229. },
  103230. /**
  103231. * Performs a predefined action (an implementation of {@link Ext.form.action.Action}) to perform application-
  103232. * specific processing.
  103233. *
  103234. * @param {String/Ext.form.action.Action} action The name of the predefined action type, or instance of {@link
  103235. * Ext.form.action.Action} to perform.
  103236. *
  103237. * @param {Object} [options] The options to pass to the {@link Ext.form.action.Action} that will get created,
  103238. * if the action argument is a String.
  103239. *
  103240. * All of the config options listed below are supported by both the {@link Ext.form.action.Submit submit} and
  103241. * {@link Ext.form.action.Load load} actions unless otherwise noted (custom actions could also accept other
  103242. * config options):
  103243. *
  103244. * @param {String} options.url
  103245. * The url for the action (defaults to the form's {@link #url}.)
  103246. *
  103247. * @param {String} options.method
  103248. * The form method to use (defaults to the form's method, or POST if not defined)
  103249. *
  103250. * @param {String/Object} options.params
  103251. * The params to pass (defaults to the form's baseParams, or none if not defined)
  103252. *
  103253. * Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.
  103254. *
  103255. * @param {Object} options.headers
  103256. * Request headers to set for the action.
  103257. *
  103258. * @param {Function} options.success
  103259. * The callback that will be invoked after a successful response (see top of {@link Ext.form.action.Submit submit}
  103260. * and {@link Ext.form.action.Load load} for a description of what constitutes a successful response).
  103261. * @param {Ext.form.Basic} options.success.form The form that requested the action.
  103262. * @param {Ext.form.action.Action} options.success.action The Action object which performed the operation.
  103263. * The action object contains these properties of interest:
  103264. *
  103265. * - {@link Ext.form.action.Action#response response}
  103266. * - {@link Ext.form.action.Action#result result} - interrogate for custom postprocessing
  103267. * - {@link Ext.form.action.Action#type type}
  103268. *
  103269. * @param {Function} options.failure
  103270. * The callback that will be invoked after a failed transaction attempt.
  103271. * @param {Ext.form.Basic} options.failure.form The form that requested the action.
  103272. * @param {Ext.form.action.Action} options.failure.action The Action object which performed the operation.
  103273. * The action object contains these properties of interest:
  103274. *
  103275. * - {@link Ext.form.action.Action#failureType failureType}
  103276. * - {@link Ext.form.action.Action#response response}
  103277. * - {@link Ext.form.action.Action#result result} - interrogate for custom postprocessing
  103278. * - {@link Ext.form.action.Action#type type}
  103279. *
  103280. * @param {Object} options.scope
  103281. * The scope in which to call the callback functions (The this reference for the callback functions).
  103282. *
  103283. * @param {Boolean} options.clientValidation
  103284. * Submit Action only. Determines whether a Form's fields are validated in a final call to {@link
  103285. * Ext.form.Basic#isValid isValid} prior to submission. Set to false to prevent this. If undefined, pre-submission
  103286. * field validation is performed.
  103287. *
  103288. * @return {Ext.form.Basic} this
  103289. */
  103290. doAction: function(action, options) {
  103291. if (Ext.isString(action)) {
  103292. action = Ext.ClassManager.instantiateByAlias('formaction.' + action, Ext.apply({}, options, {form: this}));
  103293. }
  103294. if (this.fireEvent('beforeaction', this, action) !== false) {
  103295. this.beforeAction(action);
  103296. Ext.defer(action.run, 100, action);
  103297. }
  103298. return this;
  103299. },
  103300. /**
  103301. * Shortcut to {@link #doAction do} a {@link Ext.form.action.Submit submit action}. This will use the
  103302. * {@link Ext.form.action.Submit AJAX submit action} by default. If the {@link #standardSubmit} config
  103303. * is enabled it will use a standard form element to submit, or if the {@link #api} config is present
  103304. * it will use the {@link Ext.form.action.DirectLoad Ext.direct.Direct submit action}.
  103305. *
  103306. * The following code:
  103307. *
  103308. * myFormPanel.getForm().submit({
  103309. * clientValidation: true,
  103310. * url: 'updateConsignment.php',
  103311. * params: {
  103312. * newStatus: 'delivered'
  103313. * },
  103314. * success: function(form, action) {
  103315. * Ext.Msg.alert('Success', action.result.msg);
  103316. * },
  103317. * failure: function(form, action) {
  103318. * switch (action.failureType) {
  103319. * case Ext.form.action.Action.CLIENT_INVALID:
  103320. * Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
  103321. * break;
  103322. * case Ext.form.action.Action.CONNECT_FAILURE:
  103323. * Ext.Msg.alert('Failure', 'Ajax communication failed');
  103324. * break;
  103325. * case Ext.form.action.Action.SERVER_INVALID:
  103326. * Ext.Msg.alert('Failure', action.result.msg);
  103327. * }
  103328. * }
  103329. * });
  103330. *
  103331. * would process the following server response for a successful submission:
  103332. *
  103333. * {
  103334. * "success":true, // note this is Boolean, not string
  103335. * "msg":"Consignment updated"
  103336. * }
  103337. *
  103338. * and the following server response for a failed submission:
  103339. *
  103340. * {
  103341. * "success":false, // note this is Boolean, not string
  103342. * "msg":"You do not have permission to perform this operation"
  103343. * }
  103344. *
  103345. * @param {Object} options The options to pass to the action (see {@link #doAction} for details).
  103346. * @return {Ext.form.Basic} this
  103347. */
  103348. submit: function(options) {
  103349. options = options || {};
  103350. var me = this,
  103351. action;
  103352. if (options.standardSubmit || me.standardSubmit) {
  103353. action = 'standardsubmit';
  103354. } else {
  103355. action = me.api ? 'directsubmit' : 'submit';
  103356. }
  103357. return me.doAction(action, options);
  103358. },
  103359. /**
  103360. * Shortcut to {@link #doAction do} a {@link Ext.form.action.Load load action}.
  103361. * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
  103362. * @return {Ext.form.Basic} this
  103363. */
  103364. load: function(options) {
  103365. return this.doAction(this.api ? 'directload' : 'load', options);
  103366. },
  103367. /**
  103368. * Persists the values in this form into the passed {@link Ext.data.Model} object in a beginEdit/endEdit block.
  103369. * If the record is not specified, it will attempt to update (if it exists) the record provided to loadRecord.
  103370. * @param {Ext.data.Model} [record] The record to edit
  103371. * @return {Ext.form.Basic} this
  103372. */
  103373. updateRecord: function(record) {
  103374. record = record || this._record;
  103375. if (!record) {
  103376. Ext.Error.raise("A record is required.");
  103377. }
  103378. var fields = record.fields.items,
  103379. values = this.getFieldValues(),
  103380. obj = {},
  103381. i = 0,
  103382. len = fields.length,
  103383. name;
  103384. for (; i < len; ++i) {
  103385. name = fields[i].name;
  103386. if (values.hasOwnProperty(name)) {
  103387. obj[name] = values[name];
  103388. }
  103389. }
  103390. record.beginEdit();
  103391. record.set(obj);
  103392. record.endEdit();
  103393. return this;
  103394. },
  103395. /**
  103396. * Loads an {@link Ext.data.Model} into this form by calling {@link #setValues} with the
  103397. * {@link Ext.data.Model#raw record data}.
  103398. * See also {@link #trackResetOnLoad}.
  103399. * @param {Ext.data.Model} record The record to load
  103400. * @return {Ext.form.Basic} this
  103401. */
  103402. loadRecord: function(record) {
  103403. this._record = record;
  103404. return this.setValues(record.data);
  103405. },
  103406. /**
  103407. * Returns the last Ext.data.Model instance that was loaded via {@link #loadRecord}
  103408. * @return {Ext.data.Model} The record
  103409. */
  103410. getRecord: function() {
  103411. return this._record;
  103412. },
  103413. /**
  103414. * @private
  103415. * Called before an action is performed via {@link #doAction}.
  103416. * @param {Ext.form.action.Action} action The Action instance that was invoked
  103417. */
  103418. beforeAction: function(action) {
  103419. var waitMsg = action.waitMsg,
  103420. maskCls = Ext.baseCSSPrefix + 'mask-loading',
  103421. fields = this.getFields().items,
  103422. f,
  103423. fLen = fields.length,
  103424. field, waitMsgTarget;
  103425. // Call HtmlEditor's syncValue before actions
  103426. for (f = 0; f < fLen; f++) {
  103427. field = fields[f];
  103428. if (field.isFormField && field.syncValue) {
  103429. field.syncValue();
  103430. }
  103431. }
  103432. if (waitMsg) {
  103433. waitMsgTarget = this.waitMsgTarget;
  103434. if (waitMsgTarget === true) {
  103435. this.owner.el.mask(waitMsg, maskCls);
  103436. } else if (waitMsgTarget) {
  103437. waitMsgTarget = this.waitMsgTarget = Ext.get(waitMsgTarget);
  103438. waitMsgTarget.mask(waitMsg, maskCls);
  103439. } else {
  103440. Ext.MessageBox.wait(waitMsg, action.waitTitle || this.waitTitle);
  103441. }
  103442. }
  103443. },
  103444. /**
  103445. * @private
  103446. * Called after an action is performed via {@link #doAction}.
  103447. * @param {Ext.form.action.Action} action The Action instance that was invoked
  103448. * @param {Boolean} success True if the action completed successfully, false, otherwise.
  103449. */
  103450. afterAction: function(action, success) {
  103451. if (action.waitMsg) {
  103452. var messageBox = Ext.MessageBox,
  103453. waitMsgTarget = this.waitMsgTarget;
  103454. if (waitMsgTarget === true) {
  103455. this.owner.el.unmask();
  103456. } else if (waitMsgTarget) {
  103457. waitMsgTarget.unmask();
  103458. } else {
  103459. // Do not fire the hide event because that triggers complex processing
  103460. // which is not necessary just for the wait window, and which may interfere with the app.
  103461. messageBox.suspendEvents();
  103462. messageBox.hide();
  103463. messageBox.resumeEvents();
  103464. }
  103465. }
  103466. if (success) {
  103467. if (action.reset) {
  103468. this.reset();
  103469. }
  103470. Ext.callback(action.success, action.scope || action, [this, action]);
  103471. this.fireEvent('actioncomplete', this, action);
  103472. } else {
  103473. Ext.callback(action.failure, action.scope || action, [this, action]);
  103474. this.fireEvent('actionfailed', this, action);
  103475. }
  103476. },
  103477. /**
  103478. * Find a specific {@link Ext.form.field.Field} in this form by id or name.
  103479. * @param {String} id The value to search for (specify either a {@link Ext.Component#id id} or
  103480. * {@link Ext.form.field.Field#getName name or hiddenName}).
  103481. * @return {Ext.form.field.Field} The first matching field, or `null` if none was found.
  103482. */
  103483. findField: function(id) {
  103484. return this.getFields().findBy(function(f) {
  103485. return f.id === id || f.getName() === id;
  103486. });
  103487. },
  103488. /**
  103489. * Mark fields in this form invalid in bulk.
  103490. * @param {Object/Object[]/Ext.data.Errors} errors
  103491. * Either an array in the form `[{id:'fieldId', msg:'The message'}, ...]`,
  103492. * an object hash of `{id: msg, id2: msg2}`, or a {@link Ext.data.Errors} object.
  103493. * @return {Ext.form.Basic} this
  103494. */
  103495. markInvalid: function(errors) {
  103496. var me = this,
  103497. e, eLen, error, value,
  103498. key;
  103499. function mark(fieldId, msg) {
  103500. var field = me.findField(fieldId);
  103501. if (field) {
  103502. field.markInvalid(msg);
  103503. }
  103504. }
  103505. if (Ext.isArray(errors)) {
  103506. eLen = errors.length;
  103507. for (e = 0; e < eLen; e++) {
  103508. error = errors[e];
  103509. mark(error.id, error.msg);
  103510. }
  103511. } else if (errors instanceof Ext.data.Errors) {
  103512. eLen = errors.items.length;
  103513. for (e = 0; e < eLen; e++) {
  103514. error = errors.items[e];
  103515. mark(error.field, error.message);
  103516. }
  103517. } else {
  103518. for (key in errors) {
  103519. if (errors.hasOwnProperty(key)) {
  103520. value = errors[key];
  103521. mark(key, value, errors);
  103522. }
  103523. }
  103524. }
  103525. return this;
  103526. },
  103527. /**
  103528. * Set values for fields in this form in bulk.
  103529. *
  103530. * @param {Object/Object[]} values Either an array in the form:
  103531. *
  103532. * [{id:'clientName', value:'Fred. Olsen Lines'},
  103533. * {id:'portOfLoading', value:'FXT'},
  103534. * {id:'portOfDischarge', value:'OSL'} ]
  103535. *
  103536. * or an object hash of the form:
  103537. *
  103538. * {
  103539. * clientName: 'Fred. Olsen Lines',
  103540. * portOfLoading: 'FXT',
  103541. * portOfDischarge: 'OSL'
  103542. * }
  103543. *
  103544. * @return {Ext.form.Basic} this
  103545. */
  103546. setValues: function(values) {
  103547. var me = this,
  103548. v, vLen, val, field;
  103549. function setVal(fieldId, val) {
  103550. var field = me.findField(fieldId);
  103551. if (field) {
  103552. field.setValue(val);
  103553. if (me.trackResetOnLoad) {
  103554. field.resetOriginalValue();
  103555. }
  103556. }
  103557. }
  103558. if (Ext.isArray(values)) {
  103559. // array of objects
  103560. vLen = values.length;
  103561. for (v = 0; v < vLen; v++) {
  103562. val = values[v];
  103563. setVal(val.id, val.value);
  103564. }
  103565. } else {
  103566. // object hash
  103567. Ext.iterate(values, setVal);
  103568. }
  103569. return this;
  103570. },
  103571. /**
  103572. * Retrieves the fields in the form as a set of key/value pairs, using their
  103573. * {@link Ext.form.field.Field#getSubmitData getSubmitData()} method to collect the values.
  103574. * If multiple fields return values under the same name those values will be combined into an Array.
  103575. * This is similar to {@link Ext.form.Basic#getFieldValues getFieldValues} except that this method
  103576. * collects only String values for submission, while getFieldValues collects type-specific data
  103577. * values (e.g. Date objects for date fields.)
  103578. *
  103579. * @param {Boolean} [asString=false] If true, will return the key/value collection as a single
  103580. * URL-encoded param string.
  103581. * @param {Boolean} [dirtyOnly=false] If true, only fields that are dirty will be included in the result.
  103582. * @param {Boolean} [includeEmptyText=false] If true, the configured emptyText of empty fields will be used.
  103583. * @param {Boolean} [useDataValues=false] If true, the {@link Ext.form.field.Field#getModelData getModelData}
  103584. * method is used to retrieve values from fields, otherwise the {@link Ext.form.field.Field#getSubmitData getSubmitData}
  103585. * method is used.
  103586. * @return {String/Object}
  103587. */
  103588. getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
  103589. var values = {},
  103590. fields = this.getFields().items,
  103591. f,
  103592. fLen = fields.length,
  103593. isArray = Ext.isArray,
  103594. field, data, val, bucket, name;
  103595. for (f = 0; f < fLen; f++) {
  103596. field = fields[f];
  103597. if (!dirtyOnly || field.isDirty()) {
  103598. data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText);
  103599. if (Ext.isObject(data)) {
  103600. for (name in data) {
  103601. if (data.hasOwnProperty(name)) {
  103602. val = data[name];
  103603. if (includeEmptyText && val === '') {
  103604. val = field.emptyText || '';
  103605. }
  103606. if (values.hasOwnProperty(name)) {
  103607. bucket = values[name];
  103608. if (!isArray(bucket)) {
  103609. bucket = values[name] = [bucket];
  103610. }
  103611. if (isArray(val)) {
  103612. values[name] = values[name] = bucket.concat(val);
  103613. } else {
  103614. bucket.push(val);
  103615. }
  103616. } else {
  103617. values[name] = val;
  103618. }
  103619. }
  103620. }
  103621. }
  103622. }
  103623. }
  103624. if (asString) {
  103625. values = Ext.Object.toQueryString(values);
  103626. }
  103627. return values;
  103628. },
  103629. /**
  103630. * Retrieves the fields in the form as a set of key/value pairs, using their
  103631. * {@link Ext.form.field.Field#getModelData getModelData()} method to collect the values.
  103632. * If multiple fields return values under the same name those values will be combined into an Array.
  103633. * This is similar to {@link #getValues} except that this method collects type-specific data values
  103634. * (e.g. Date objects for date fields) while getValues returns only String values for submission.
  103635. *
  103636. * @param {Boolean} [dirtyOnly=false] If true, only fields that are dirty will be included in the result.
  103637. * @return {Object}
  103638. */
  103639. getFieldValues: function(dirtyOnly) {
  103640. return this.getValues(false, dirtyOnly, false, true);
  103641. },
  103642. /**
  103643. * Clears all invalid field messages in this form.
  103644. * @return {Ext.form.Basic} this
  103645. */
  103646. clearInvalid: function() {
  103647. Ext.suspendLayouts();
  103648. var me = this,
  103649. fields = me.getFields().items,
  103650. f,
  103651. fLen = fields.length;
  103652. for (f = 0; f < fLen; f++) {
  103653. fields[f].clearInvalid();
  103654. }
  103655. Ext.resumeLayouts(true);
  103656. return me;
  103657. },
  103658. /**
  103659. * Resets all fields in this form.
  103660. * @return {Ext.form.Basic} this
  103661. */
  103662. reset: function() {
  103663. Ext.suspendLayouts();
  103664. var me = this,
  103665. fields = me.getFields().items,
  103666. f,
  103667. fLen = fields.length;
  103668. for (f = 0; f < fLen; f++) {
  103669. fields[f].reset();
  103670. }
  103671. Ext.resumeLayouts(true);
  103672. return me;
  103673. },
  103674. /**
  103675. * Calls {@link Ext#apply Ext.apply} for all fields in this form with the passed object.
  103676. * @param {Object} obj The object to be applied
  103677. * @return {Ext.form.Basic} this
  103678. */
  103679. applyToFields: function(obj) {
  103680. var fields = this.getFields().items,
  103681. f,
  103682. fLen = fields.length;
  103683. for (f = 0; f < fLen; f++) {
  103684. Ext.apply(fields[f], obj);
  103685. }
  103686. return this;
  103687. },
  103688. /**
  103689. * Calls {@link Ext#applyIf Ext.applyIf} for all field in this form with the passed object.
  103690. * @param {Object} obj The object to be applied
  103691. * @return {Ext.form.Basic} this
  103692. */
  103693. applyIfToFields: function(obj) {
  103694. var fields = this.getFields().items,
  103695. f,
  103696. fLen = fields.length;
  103697. for (f = 0; f < fLen; f++) {
  103698. Ext.applyIf(fields[f], obj);
  103699. }
  103700. return this;
  103701. }
  103702. });
  103703. /**
  103704. * This layout implements the column arrangement for {@link Ext.form.CheckboxGroup} and {@link Ext.form.RadioGroup}.
  103705. * It groups the component's sub-items into columns based on the component's
  103706. * {@link Ext.form.CheckboxGroup#columns columns} and {@link Ext.form.CheckboxGroup#vertical} config properties.
  103707. */
  103708. Ext.define('Ext.layout.container.CheckboxGroup', {
  103709. extend: 'Ext.layout.container.Container',
  103710. alias: ['layout.checkboxgroup'],
  103711. /**
  103712. * @cfg {Boolean} [autoFlex=true]
  103713. * By default, CheckboxGroup allocates all available space to the configured columns meaning that
  103714. * column are evenly spaced across the container.
  103715. *
  103716. * To have each column only be wide enough to fit the container Checkboxes (or Radios), set `autoFlex` to `false`
  103717. */
  103718. autoFlex: true,
  103719. type: 'checkboxgroup',
  103720. childEls: [
  103721. 'innerCt'
  103722. ],
  103723. renderTpl: [
  103724. '<table id="{ownerId}-innerCt" role="presentation" style="{tableStyle}"><tbody><tr>',
  103725. '<tpl for="columns">',
  103726. '<td class="{parent.colCls}" valign="top" style="{style}">',
  103727. '{% this.renderColumn(out,parent,xindex-1) %}',
  103728. '</td>',
  103729. '</tpl>',
  103730. '</tr></tbody></table>'
  103731. ],
  103732. lastOwnerItemsGeneration : null,
  103733. beginLayout: function(ownerContext) {
  103734. var me = this,
  103735. columns,
  103736. numCols,
  103737. i, width, cwidth,
  103738. totalFlex = 0, flexedCols = 0,
  103739. autoFlex = me.autoFlex,
  103740. innerCtStyle = me.innerCt.dom.style;
  103741. me.callParent(arguments);
  103742. columns = me.columnNodes;
  103743. ownerContext.innerCtContext = ownerContext.getEl('innerCt', me);
  103744. // The columns config may be an array of widths. Any value < 1 is taken to be a fraction:
  103745. if (!ownerContext.widthModel.shrinkWrap) {
  103746. numCols = columns.length;
  103747. // If columns is an array of numeric widths
  103748. if (me.columnsArray) {
  103749. // first calculate total flex
  103750. for (i = 0; i < numCols; i++) {
  103751. width = me.owner.columns[i];
  103752. if (width < 1) {
  103753. totalFlex += width;
  103754. flexedCols++;
  103755. }
  103756. }
  103757. // now apply widths
  103758. for (i = 0; i < numCols; i++) {
  103759. width = me.owner.columns[i];
  103760. if (width < 1) {
  103761. cwidth = ((width / totalFlex) * 100) + '%';
  103762. } else {
  103763. cwidth = width + 'px';
  103764. }
  103765. columns[i].style.width = cwidth;
  103766. }
  103767. }
  103768. // Otherwise it's the *number* of columns, so distributed the widths evenly
  103769. else {
  103770. for (i = 0; i < numCols; i++) {
  103771. // autoFlex: true will automatically calculate % widths
  103772. // autoFlex: false allows the table to decide (shrinkWrap, in effect)
  103773. // on a per-column basis
  103774. cwidth = autoFlex
  103775. ? (1 / numCols * 100) + '%'
  103776. : '';
  103777. columns[i].style.width = cwidth;
  103778. flexedCols++;
  103779. }
  103780. }
  103781. // no flexed cols -- all widths are fixed
  103782. if (!flexedCols) {
  103783. innerCtStyle.tableLayout = 'fixed';
  103784. innerCtStyle.width = '';
  103785. // some flexed cols -- need to fix some
  103786. } else if (flexedCols < numCols) {
  103787. innerCtStyle.tableLayout = 'fixed';
  103788. innerCtStyle.width = '100%';
  103789. // let the table decide
  103790. } else {
  103791. innerCtStyle.tableLayout = 'auto';
  103792. // if autoFlex, fill available space, else compact down
  103793. if (autoFlex) {
  103794. innerCtStyle.width = '100%';
  103795. } else {
  103796. innerCtStyle.width = '';
  103797. }
  103798. }
  103799. } else {
  103800. innerCtStyle.tableLayout = 'auto';
  103801. innerCtStyle.width = '';
  103802. }
  103803. },
  103804. cacheElements: function () {
  103805. var me = this;
  103806. // Grab defined childEls
  103807. me.callParent();
  103808. me.rowEl = me.innerCt.down('tr');
  103809. // Grab columns TDs
  103810. me.columnNodes = me.rowEl.dom.childNodes;
  103811. },
  103812. /*
  103813. * Just wait for the child items to all lay themselves out in the width we are configured
  103814. * to make available to them. Then we can measure our height.
  103815. */
  103816. calculate: function(ownerContext) {
  103817. var me = this,
  103818. targetContext, widthShrinkWrap, heightShrinkWrap, shrinkWrap, table, targetPadding;
  103819. // The columnNodes are widthed using their own width attributes, we just need to wait
  103820. // for all children to have arranged themselves in that width, and then collect our height.
  103821. if (!ownerContext.getDomProp('containerChildrenDone')) {
  103822. me.done = false;
  103823. } else {
  103824. targetContext = ownerContext.innerCtContext;
  103825. widthShrinkWrap = ownerContext.widthModel.shrinkWrap;
  103826. heightShrinkWrap = ownerContext.heightModel.shrinkWrap;
  103827. shrinkWrap = heightShrinkWrap || widthShrinkWrap;
  103828. table = targetContext.el.dom;
  103829. targetPadding = shrinkWrap && targetContext.getPaddingInfo();
  103830. if (widthShrinkWrap) {
  103831. ownerContext.setContentWidth(table.offsetWidth + targetPadding.width, true);
  103832. }
  103833. if (heightShrinkWrap) {
  103834. ownerContext.setContentHeight(table.offsetHeight + targetPadding.height, true);
  103835. }
  103836. }
  103837. },
  103838. doRenderColumn: function (out, renderData, columnIndex) {
  103839. // Careful! This method is bolted on to the renderTpl so all we get for context is
  103840. // the renderData! The "this" pointer is the renderTpl instance!
  103841. var me = renderData.$layout,
  103842. owner = me.owner,
  103843. columnCount = renderData.columnCount,
  103844. items = owner.items.items,
  103845. itemCount = items.length,
  103846. item, itemIndex, rowCount, increment, tree;
  103847. // Example:
  103848. // columnCount = 3
  103849. // items.length = 10
  103850. if (owner.vertical) {
  103851. // 0 1 2
  103852. // +---+---+---+
  103853. // 0 | 0 | 4 | 8 |
  103854. // +---+---+---+
  103855. // 1 | 1 | 5 | 9 |
  103856. // +---+---+---+
  103857. // 2 | 2 | 6 | |
  103858. // +---+---+---+
  103859. // 3 | 3 | 7 | |
  103860. // +---+---+---+
  103861. rowCount = Math.ceil(itemCount / columnCount); // = 4
  103862. itemIndex = columnIndex * rowCount;
  103863. itemCount = Math.min(itemCount, itemIndex + rowCount);
  103864. increment = 1;
  103865. } else {
  103866. // 0 1 2
  103867. // +---+---+---+
  103868. // 0 | 0 | 1 | 2 |
  103869. // +---+---+---+
  103870. // 1 | 3 | 4 | 5 |
  103871. // +---+---+---+
  103872. // 2 | 6 | 7 | 8 |
  103873. // +---+---+---+
  103874. // 3 | 9 | | |
  103875. // +---+---+---+
  103876. itemIndex = columnIndex;
  103877. increment = columnCount;
  103878. }
  103879. for ( ; itemIndex < itemCount; itemIndex += increment) {
  103880. item = items[itemIndex];
  103881. me.configureItem(item);
  103882. tree = item.getRenderTree();
  103883. Ext.DomHelper.generateMarkup(tree, out);
  103884. }
  103885. },
  103886. /**
  103887. * Returns the number of columns in the checkbox group.
  103888. * @private
  103889. */
  103890. getColumnCount: function() {
  103891. var me = this,
  103892. owner = me.owner,
  103893. ownerColumns = owner.columns;
  103894. // Our columns config is an array of numeric widths.
  103895. // Calculate our total width
  103896. if (me.columnsArray) {
  103897. return ownerColumns.length;
  103898. }
  103899. if (Ext.isNumber(ownerColumns)) {
  103900. return ownerColumns;
  103901. }
  103902. return owner.items.length;
  103903. },
  103904. getItemSizePolicy: function (item) {
  103905. return this.autoSizePolicy;
  103906. },
  103907. getRenderData: function () {
  103908. var me = this,
  103909. data = me.callParent(),
  103910. owner = me.owner,
  103911. i, columns = me.getColumnCount(),
  103912. width, column, cwidth,
  103913. autoFlex = me.autoFlex,
  103914. totalFlex = 0, flexedCols = 0;
  103915. // calculate total flex
  103916. if (me.columnsArray) {
  103917. for (i=0; i < columns; i++) {
  103918. width = me.owner.columns[i];
  103919. if (width < 1) {
  103920. totalFlex += width;
  103921. flexedCols++;
  103922. }
  103923. }
  103924. }
  103925. data.colCls = owner.groupCls;
  103926. data.columnCount = columns;
  103927. data.columns = [];
  103928. for (i = 0; i < columns; i++) {
  103929. column = (data.columns[i] = {});
  103930. if (me.columnsArray) {
  103931. width = me.owner.columns[i];
  103932. if (width < 1) {
  103933. cwidth = ((width / totalFlex) * 100) + '%';
  103934. } else {
  103935. cwidth = width + 'px';
  103936. }
  103937. column.style = 'width:' + cwidth;
  103938. } else {
  103939. column.style = 'width:' + (1 / columns * 100) + '%';
  103940. flexedCols++;
  103941. }
  103942. }
  103943. // If the columns config was an array of column widths, allow table to auto width
  103944. data.tableStyle =
  103945. !flexedCols ? 'table-layout:fixed;' :
  103946. (flexedCols < columns) ? 'table-layout:fixed;width:100%' :
  103947. (autoFlex) ? 'table-layout:auto;width:100%' : 'table-layout:auto;';
  103948. return data;
  103949. },
  103950. initLayout: function () {
  103951. var me = this,
  103952. owner = me.owner;
  103953. me.columnsArray = Ext.isArray(owner.columns);
  103954. me.autoColumns = !owner.columns || owner.columns === 'auto';
  103955. me.vertical = owner.vertical;
  103956. me.callParent();
  103957. },
  103958. // Always valid. beginLayout ensures the encapsulating elements of all children are in the correct place
  103959. isValidParent: function() {
  103960. return true;
  103961. },
  103962. setupRenderTpl: function (renderTpl) {
  103963. this.callParent(arguments);
  103964. renderTpl.renderColumn = this.doRenderColumn;
  103965. },
  103966. renderChildren: function () {
  103967. var me = this,
  103968. generation = me.owner.items.generation;
  103969. if (me.lastOwnerItemsGeneration !== generation) {
  103970. me.lastOwnerItemsGeneration = generation;
  103971. me.renderItems(me.getLayoutItems());
  103972. }
  103973. },
  103974. /**
  103975. * Iterates over all passed items, ensuring they are rendered. If the items are already rendered,
  103976. * also determines if the items are in the proper place in the dom.
  103977. * @protected
  103978. */
  103979. renderItems : function(items) {
  103980. var me = this,
  103981. itemCount = items.length,
  103982. i,
  103983. item,
  103984. rowCount,
  103985. columnCount,
  103986. rowIndex,
  103987. columnIndex;
  103988. if (itemCount) {
  103989. Ext.suspendLayouts();
  103990. if (me.autoColumns) {
  103991. me.addMissingColumns(itemCount);
  103992. }
  103993. columnCount = me.columnNodes.length;
  103994. rowCount = Math.ceil(itemCount / columnCount);
  103995. for (i = 0; i < itemCount; i++) {
  103996. item = items[i];
  103997. rowIndex = me.getRenderRowIndex(i, rowCount, columnCount);
  103998. columnIndex = me.getRenderColumnIndex(i, rowCount, columnCount);
  103999. if (!item.rendered) {
  104000. me.renderItem(item, rowIndex, columnIndex);
  104001. } else if (!me.isItemAtPosition(item, rowIndex, columnIndex)) {
  104002. me.moveItem(item, rowIndex, columnIndex);
  104003. }
  104004. }
  104005. if (me.autoColumns) {
  104006. me.removeExceedingColumns(itemCount);
  104007. }
  104008. Ext.resumeLayouts(true);
  104009. }
  104010. },
  104011. isItemAtPosition : function(item, rowIndex, columnIndex) {
  104012. return item.el.dom === this.getNodeAt(rowIndex, columnIndex);
  104013. },
  104014. getRenderColumnIndex : function(itemIndex, rowCount, columnCount) {
  104015. if (this.vertical) {
  104016. return Math.floor(itemIndex / rowCount);
  104017. } else {
  104018. return itemIndex % columnCount;
  104019. }
  104020. },
  104021. getRenderRowIndex : function(itemIndex, rowCount, columnCount) {
  104022. var me = this;
  104023. if (me.vertical) {
  104024. return itemIndex % rowCount;
  104025. } else {
  104026. return Math.floor(itemIndex / columnCount);
  104027. }
  104028. },
  104029. getNodeAt : function(rowIndex, columnIndex) {
  104030. return this.columnNodes[columnIndex].childNodes[rowIndex];
  104031. },
  104032. addMissingColumns : function(itemsCount) {
  104033. var me = this,
  104034. existingColumnsCount = me.columnNodes.length,
  104035. missingColumnsCount,
  104036. row,
  104037. cls,
  104038. i;
  104039. if (existingColumnsCount < itemsCount) {
  104040. missingColumnsCount = itemsCount - existingColumnsCount;
  104041. row = me.rowEl;
  104042. cls = me.owner.groupCls;
  104043. for (i = 0; i < missingColumnsCount; i++) {
  104044. row.createChild({
  104045. cls: cls,
  104046. tag: 'td',
  104047. vAlign: 'top'
  104048. });
  104049. }
  104050. }
  104051. },
  104052. removeExceedingColumns : function(itemsCount) {
  104053. var me = this,
  104054. existingColumnsCount = me.columnNodes.length,
  104055. exceedingColumnsCount,
  104056. row,
  104057. i;
  104058. if (existingColumnsCount > itemsCount) {
  104059. exceedingColumnsCount = existingColumnsCount - itemsCount;
  104060. row = me.rowEl;
  104061. for (i = 0; i < exceedingColumnsCount; i++) {
  104062. row.last().remove();
  104063. }
  104064. }
  104065. },
  104066. /**
  104067. * Renders the given Component into the specified row and column
  104068. * @param {Ext.Component} item The Component to render
  104069. * @param {number} rowIndex row index
  104070. * @param {number} columnIndex column index
  104071. * @private
  104072. */
  104073. renderItem : function(item, rowIndex, columnIndex) {
  104074. var me = this;
  104075. me.configureItem(item);
  104076. item.render(Ext.get(me.columnNodes[columnIndex]), rowIndex);
  104077. me.afterRenderItem(item);
  104078. },
  104079. /**
  104080. * Moves the given already rendered Component to the specified row and column
  104081. * @param {Ext.Component} item The Component to move
  104082. * @param {number} rowIndex row index
  104083. * @param {number} columnIndex column index
  104084. * @private
  104085. */
  104086. moveItem : function(item, rowIndex, columnIndex) {
  104087. var me = this,
  104088. column = me.columnNodes[columnIndex],
  104089. targetNode = column.childNodes[rowIndex];
  104090. column.insertBefore(item.el.dom, targetNode || null);
  104091. }
  104092. });
  104093. /**
  104094. * @private
  104095. */
  104096. Ext.define('Ext.layout.component.field.FieldContainer', {
  104097. /* Begin Definitions */
  104098. extend: 'Ext.layout.component.field.Field',
  104099. alias: 'layout.fieldcontainer',
  104100. /* End Definitions */
  104101. type: 'fieldcontainer',
  104102. waitForOuterHeightInDom: true,
  104103. waitForOuterWidthInDom: true,
  104104. beginLayout: function(ownerContext) {
  104105. this.callParent(arguments);
  104106. // Tell Component.measureAutoDimensions to measure the DOM when containerChildrenDone is true
  104107. ownerContext.hasRawContent = true;
  104108. ownerContext.target.bodyEl.setStyle('height', '');
  104109. },
  104110. measureContentHeight: function (ownerContext) {
  104111. // since we are measuring the outer el, we have to wait for whatever is in our
  104112. // container to be flushed to the DOM... especially for things like box layouts
  104113. // that size the innerCt since that is all that will contribute to our size!
  104114. return ownerContext.hasDomProp('containerLayoutDone') ? this.callParent(arguments) : NaN;
  104115. },
  104116. measureContentWidth: function (ownerContext) {
  104117. // see measureContentHeight
  104118. return ownerContext.hasDomProp('containerLayoutDone') ? this.callParent(arguments) : NaN;
  104119. },
  104120. publishInnerWidth: function (ownerContext, width) {
  104121. var bodyContext = ownerContext.bodyCellContext;
  104122. bodyContext.setWidth(bodyContext.el.getWidth(), false);
  104123. },
  104124. publishInnerHeight: function (ownerContext, height) {
  104125. var bodyContext = ownerContext.bodyCellContext;
  104126. bodyContext.setHeight(height - this.measureLabelErrorHeight(ownerContext));
  104127. }
  104128. });
  104129. /**
  104130. * A mixin for {@link Ext.container.Container} components that are likely to have form fields in their
  104131. * items subtree. Adds the following capabilities:
  104132. *
  104133. * - Methods for handling the addition and removal of {@link Ext.form.Labelable} and {@link Ext.form.field.Field}
  104134. * instances at any depth within the container.
  104135. * - Events ({@link #fieldvaliditychange} and {@link #fielderrorchange}) for handling changes to the state
  104136. * of individual fields at the container level.
  104137. * - Automatic application of {@link #fieldDefaults} config properties to each field added within the
  104138. * container, to facilitate uniform configuration of all fields.
  104139. *
  104140. * This mixin is primarily for internal use by {@link Ext.form.Panel} and {@link Ext.form.FieldContainer},
  104141. * and should not normally need to be used directly. @docauthor Jason Johnston <jason@sencha.com>
  104142. */
  104143. Ext.define('Ext.form.FieldAncestor', {
  104144. /**
  104145. * @cfg {Object} fieldDefaults
  104146. * If specified, the properties in this object are used as default config values for each {@link Ext.form.Labelable}
  104147. * instance (e.g. {@link Ext.form.field.Base} or {@link Ext.form.FieldContainer}) that is added as a descendant of
  104148. * this container. Corresponding values specified in an individual field's own configuration, or from the {@link
  104149. * Ext.container.Container#defaults defaults config} of its parent container, will take precedence. See the
  104150. * documentation for {@link Ext.form.Labelable} to see what config options may be specified in the fieldDefaults.
  104151. *
  104152. * Example:
  104153. *
  104154. * new Ext.form.Panel({
  104155. * fieldDefaults: {
  104156. * labelAlign: 'left',
  104157. * labelWidth: 100
  104158. * },
  104159. * items: [{
  104160. * xtype: 'fieldset',
  104161. * defaults: {
  104162. * labelAlign: 'top'
  104163. * },
  104164. * items: [{
  104165. * name: 'field1'
  104166. * }, {
  104167. * name: 'field2'
  104168. * }]
  104169. * }, {
  104170. * xtype: 'fieldset',
  104171. * items: [{
  104172. * name: 'field3',
  104173. * labelWidth: 150
  104174. * }, {
  104175. * name: 'field4'
  104176. * }]
  104177. * }]
  104178. * });
  104179. *
  104180. * In this example, field1 and field2 will get labelAlign:'top' (from the fieldset's defaults) and labelWidth:100
  104181. * (from fieldDefaults), field3 and field4 will both get labelAlign:'left' (from fieldDefaults and field3 will use
  104182. * the labelWidth:150 from its own config.
  104183. */
  104184. /**
  104185. * Initializes the FieldAncestor's state; this must be called from the initComponent method of any components
  104186. * importing this mixin.
  104187. * @protected
  104188. */
  104189. initFieldAncestor: function() {
  104190. var me = this,
  104191. onSubtreeChange = me.onFieldAncestorSubtreeChange;
  104192. me.addEvents(
  104193. /**
  104194. * @event fieldvaliditychange
  104195. * Fires when the validity state of any one of the {@link Ext.form.field.Field} instances within this
  104196. * container changes.
  104197. * @param {Ext.form.FieldAncestor} this
  104198. * @param {Ext.form.Labelable} The Field instance whose validity changed
  104199. * @param {String} isValid The field's new validity state
  104200. */
  104201. 'fieldvaliditychange',
  104202. /**
  104203. * @event fielderrorchange
  104204. * Fires when the active error message is changed for any one of the {@link Ext.form.Labelable} instances
  104205. * within this container.
  104206. * @param {Ext.form.FieldAncestor} this
  104207. * @param {Ext.form.Labelable} The Labelable instance whose active error was changed
  104208. * @param {String} error The active error message
  104209. */
  104210. 'fielderrorchange'
  104211. );
  104212. // Catch addition and removal of descendant fields
  104213. me.on('add', onSubtreeChange, me);
  104214. me.on('remove', onSubtreeChange, me);
  104215. me.initFieldDefaults();
  104216. },
  104217. /**
  104218. * @private Initialize the {@link #fieldDefaults} object
  104219. */
  104220. initFieldDefaults: function() {
  104221. if (!this.fieldDefaults) {
  104222. this.fieldDefaults = {};
  104223. }
  104224. },
  104225. /**
  104226. * @private
  104227. * Handle the addition and removal of components in the FieldAncestor component's child tree.
  104228. */
  104229. onFieldAncestorSubtreeChange: function(parent, child) {
  104230. var me = this,
  104231. isAdding = !!child.ownerCt;
  104232. function handleCmp(cmp) {
  104233. var isLabelable = cmp.isFieldLabelable,
  104234. isField = cmp.isFormField;
  104235. if (isLabelable || isField) {
  104236. if (isLabelable) {
  104237. me['onLabelable' + (isAdding ? 'Added' : 'Removed')](cmp);
  104238. }
  104239. if (isField) {
  104240. me['onField' + (isAdding ? 'Added' : 'Removed')](cmp);
  104241. }
  104242. }
  104243. else if (cmp.isContainer) {
  104244. Ext.Array.forEach(cmp.getRefItems(), handleCmp);
  104245. }
  104246. }
  104247. handleCmp(child);
  104248. },
  104249. /**
  104250. * Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
  104251. * @param {Ext.form.Labelable} labelable The instance that was added
  104252. * @protected
  104253. */
  104254. onLabelableAdded: function(labelable) {
  104255. var me = this;
  104256. // buffer slightly to avoid excessive firing while sub-fields are changing en masse
  104257. me.mon(labelable, 'errorchange', me.handleFieldErrorChange, me, {buffer: 10});
  104258. labelable.setFieldDefaults(me.fieldDefaults);
  104259. },
  104260. /**
  104261. * Called when a {@link Ext.form.field.Field} instance is added to the container's subtree.
  104262. * @param {Ext.form.field.Field} field The field which was added
  104263. * @protected
  104264. */
  104265. onFieldAdded: function(field) {
  104266. var me = this;
  104267. me.mon(field, 'validitychange', me.handleFieldValidityChange, me);
  104268. },
  104269. /**
  104270. * Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
  104271. * @param {Ext.form.Labelable} labelable The instance that was removed
  104272. * @protected
  104273. */
  104274. onLabelableRemoved: function(labelable) {
  104275. var me = this;
  104276. me.mun(labelable, 'errorchange', me.handleFieldErrorChange, me);
  104277. },
  104278. /**
  104279. * Called when a {@link Ext.form.field.Field} instance is removed from the container's subtree.
  104280. * @param {Ext.form.field.Field} field The field which was removed
  104281. * @protected
  104282. */
  104283. onFieldRemoved: function(field) {
  104284. var me = this;
  104285. me.mun(field, 'validitychange', me.handleFieldValidityChange, me);
  104286. },
  104287. /**
  104288. * @private Handle validitychange events on sub-fields; invoke the aggregated event and method
  104289. */
  104290. handleFieldValidityChange: function(field, isValid) {
  104291. var me = this;
  104292. me.fireEvent('fieldvaliditychange', me, field, isValid);
  104293. me.onFieldValidityChange(field, isValid);
  104294. },
  104295. /**
  104296. * @private Handle errorchange events on sub-fields; invoke the aggregated event and method
  104297. */
  104298. handleFieldErrorChange: function(labelable, activeError) {
  104299. var me = this;
  104300. me.fireEvent('fielderrorchange', me, labelable, activeError);
  104301. me.onFieldErrorChange(labelable, activeError);
  104302. },
  104303. /**
  104304. * Fired when the validity of any field within the container changes.
  104305. * @param {Ext.form.field.Field} field The sub-field whose validity changed
  104306. * @param {Boolean} valid The new validity state
  104307. * @protected
  104308. */
  104309. onFieldValidityChange: Ext.emptyFn,
  104310. /**
  104311. * Fired when the error message of any field within the container changes.
  104312. * @param {Ext.form.Labelable} field The sub-field whose active error changed
  104313. * @param {String} error The new active error message
  104314. * @protected
  104315. */
  104316. onFieldErrorChange: Ext.emptyFn
  104317. });
  104318. /**
  104319. * FieldContainer is a derivation of {@link Ext.container.Container Container} that implements the
  104320. * {@link Ext.form.Labelable Labelable} mixin. This allows it to be configured so that it is rendered with
  104321. * a {@link #fieldLabel field label} and optional {@link #msgTarget error message} around its sub-items.
  104322. * This is useful for arranging a group of fields or other components within a single item in a form, so
  104323. * that it lines up nicely with other fields. A common use is for grouping a set of related fields under
  104324. * a single label in a form.
  104325. *
  104326. * The container's configured {@link #cfg-items} will be layed out within the field body area according to the
  104327. * configured {@link #layout} type. The default layout is `'autocontainer'`.
  104328. *
  104329. * Like regular fields, FieldContainer can inherit its decoration configuration from the
  104330. * {@link Ext.form.Panel#fieldDefaults fieldDefaults} of an enclosing FormPanel. In addition,
  104331. * FieldContainer itself can pass {@link #fieldDefaults} to any {@link Ext.form.Labelable fields}
  104332. * it may itself contain.
  104333. *
  104334. * If you are grouping a set of {@link Ext.form.field.Checkbox Checkbox} or {@link Ext.form.field.Radio Radio}
  104335. * fields in a single labeled container, consider using a {@link Ext.form.CheckboxGroup}
  104336. * or {@link Ext.form.RadioGroup} instead as they are specialized for handling those types.
  104337. *
  104338. * # Example
  104339. *
  104340. * @example
  104341. * Ext.create('Ext.form.Panel', {
  104342. * title: 'FieldContainer Example',
  104343. * width: 550,
  104344. * bodyPadding: 10,
  104345. *
  104346. * items: [{
  104347. * xtype: 'fieldcontainer',
  104348. * fieldLabel: 'Last Three Jobs',
  104349. * labelWidth: 100,
  104350. *
  104351. * // The body area will contain three text fields, arranged
  104352. * // horizontally, separated by draggable splitters.
  104353. * layout: 'hbox',
  104354. * items: [{
  104355. * xtype: 'textfield',
  104356. * flex: 1
  104357. * }, {
  104358. * xtype: 'splitter'
  104359. * }, {
  104360. * xtype: 'textfield',
  104361. * flex: 1
  104362. * }, {
  104363. * xtype: 'splitter'
  104364. * }, {
  104365. * xtype: 'textfield',
  104366. * flex: 1
  104367. * }]
  104368. * }],
  104369. * renderTo: Ext.getBody()
  104370. * });
  104371. *
  104372. * # Usage of fieldDefaults
  104373. *
  104374. * @example
  104375. * Ext.create('Ext.form.Panel', {
  104376. * title: 'FieldContainer Example',
  104377. * width: 350,
  104378. * bodyPadding: 10,
  104379. *
  104380. * items: [{
  104381. * xtype: 'fieldcontainer',
  104382. * fieldLabel: 'Your Name',
  104383. * labelWidth: 75,
  104384. * defaultType: 'textfield',
  104385. *
  104386. * // Arrange fields vertically, stretched to full width
  104387. * layout: 'anchor',
  104388. * defaults: {
  104389. * layout: '100%'
  104390. * },
  104391. *
  104392. * // These config values will be applied to both sub-fields, except
  104393. * // for Last Name which will use its own msgTarget.
  104394. * fieldDefaults: {
  104395. * msgTarget: 'under',
  104396. * labelAlign: 'top'
  104397. * },
  104398. *
  104399. * items: [{
  104400. * fieldLabel: 'First Name',
  104401. * name: 'firstName'
  104402. * }, {
  104403. * fieldLabel: 'Last Name',
  104404. * name: 'lastName',
  104405. * msgTarget: 'under'
  104406. * }]
  104407. * }],
  104408. * renderTo: Ext.getBody()
  104409. * });
  104410. *
  104411. * @docauthor Jason Johnston <jason@sencha.com>
  104412. */
  104413. Ext.define('Ext.form.FieldContainer', {
  104414. extend: 'Ext.container.Container',
  104415. mixins: {
  104416. labelable: 'Ext.form.Labelable',
  104417. fieldAncestor: 'Ext.form.FieldAncestor'
  104418. },
  104419. requires: 'Ext.layout.component.field.FieldContainer',
  104420. alias: 'widget.fieldcontainer',
  104421. componentLayout: 'fieldcontainer',
  104422. componentCls: Ext.baseCSSPrefix + 'form-fieldcontainer',
  104423. /**
  104424. * @cfg {Boolean} combineLabels
  104425. * If set to true, and there is no defined {@link #fieldLabel}, the field container will automatically
  104426. * generate its label by combining the labels of all the fields it contains. Defaults to false.
  104427. */
  104428. combineLabels: false,
  104429. //<locale>
  104430. /**
  104431. * @cfg {String} labelConnector
  104432. * The string to use when joining the labels of individual sub-fields, when {@link #combineLabels} is
  104433. * set to true. Defaults to ', '.
  104434. */
  104435. labelConnector: ', ',
  104436. //</locale>
  104437. /**
  104438. * @cfg {Boolean} combineErrors
  104439. * If set to true, the field container will automatically combine and display the validation errors from
  104440. * all the fields it contains as a single error on the container, according to the configured
  104441. * {@link #msgTarget}. Defaults to false.
  104442. */
  104443. combineErrors: false,
  104444. maskOnDisable: false,
  104445. fieldSubTpl: '{%this.renderContainer(out,values)%}',
  104446. initComponent: function() {
  104447. var me = this;
  104448. // Init mixins
  104449. me.initLabelable();
  104450. me.initFieldAncestor();
  104451. me.callParent();
  104452. },
  104453. beforeRender: function(){
  104454. this.callParent(arguments);
  104455. this.beforeLabelableRender(arguments);
  104456. },
  104457. /**
  104458. * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
  104459. * @param {Ext.form.Labelable} labelable The instance that was added
  104460. */
  104461. onLabelableAdded: function(labelable) {
  104462. var me = this;
  104463. me.mixins.fieldAncestor.onLabelableAdded.call(this, labelable);
  104464. me.updateLabel();
  104465. },
  104466. /**
  104467. * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
  104468. * @param {Ext.form.Labelable} labelable The instance that was removed
  104469. */
  104470. onLabelableRemoved: function(labelable) {
  104471. var me = this;
  104472. me.mixins.fieldAncestor.onLabelableRemoved.call(this, labelable);
  104473. me.updateLabel();
  104474. },
  104475. initRenderTpl: function() {
  104476. var me = this;
  104477. if (!me.hasOwnProperty('renderTpl')) {
  104478. me.renderTpl = me.getTpl('labelableRenderTpl');
  104479. }
  104480. return me.callParent();
  104481. },
  104482. initRenderData: function() {
  104483. return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
  104484. },
  104485. /**
  104486. * Returns the combined field label if {@link #combineLabels} is set to true and if there is no
  104487. * set {@link #fieldLabel}. Otherwise returns the fieldLabel like normal. You can also override
  104488. * this method to provide a custom generated label.
  104489. * @template
  104490. * @return {String} The label, or empty string if none.
  104491. */
  104492. getFieldLabel: function() {
  104493. var label = this.fieldLabel || '';
  104494. if (!label && this.combineLabels) {
  104495. label = Ext.Array.map(this.query('[isFieldLabelable]'), function(field) {
  104496. return field.getFieldLabel();
  104497. }).join(this.labelConnector);
  104498. }
  104499. return label;
  104500. },
  104501. getSubTplData: function() {
  104502. var ret = this.initRenderData();
  104503. Ext.apply(ret, this.subTplData);
  104504. return ret;
  104505. },
  104506. getSubTplMarkup: function() {
  104507. var me = this,
  104508. tpl = me.getTpl('fieldSubTpl'),
  104509. html;
  104510. if (!tpl.renderContent) {
  104511. me.setupRenderTpl(tpl);
  104512. }
  104513. html = tpl.apply(me.getSubTplData());
  104514. return html;
  104515. },
  104516. /**
  104517. * @private Updates the content of the labelEl if it is rendered
  104518. */
  104519. updateLabel: function() {
  104520. var me = this,
  104521. label = me.labelEl;
  104522. if (label) {
  104523. me.setFieldLabel(me.getFieldLabel());
  104524. }
  104525. },
  104526. /**
  104527. * @private Fired when the error message of any field within the container changes, and updates the
  104528. * combined error message to match.
  104529. */
  104530. onFieldErrorChange: function(field, activeError) {
  104531. if (this.combineErrors) {
  104532. var me = this,
  104533. oldError = me.getActiveError(),
  104534. invalidFields = Ext.Array.filter(me.query('[isFormField]'), function(field) {
  104535. return field.hasActiveError();
  104536. }),
  104537. newErrors = me.getCombinedErrors(invalidFields);
  104538. if (newErrors) {
  104539. me.setActiveErrors(newErrors);
  104540. } else {
  104541. me.unsetActiveError();
  104542. }
  104543. if (oldError !== me.getActiveError()) {
  104544. me.doComponentLayout();
  104545. }
  104546. }
  104547. },
  104548. /**
  104549. * Takes an Array of invalid {@link Ext.form.field.Field} objects and builds a combined list of error
  104550. * messages from them. Defaults to prepending each message by the field name and a colon. This
  104551. * can be overridden to provide custom combined error message handling, for instance changing
  104552. * the format of each message or sorting the array (it is sorted in order of appearance by default).
  104553. * @param {Ext.form.field.Field[]} invalidFields An Array of the sub-fields which are currently invalid.
  104554. * @return {String[]} The combined list of error messages
  104555. */
  104556. getCombinedErrors: function(invalidFields) {
  104557. var errors = [],
  104558. f,
  104559. fLen = invalidFields.length,
  104560. field,
  104561. activeErrors, a, aLen,
  104562. error, label;
  104563. for (f = 0; f < fLen; f++) {
  104564. field = invalidFields[f];
  104565. activeErrors = field.getActiveErrors();
  104566. aLen = activeErrors.length;
  104567. for (a = 0; a < aLen; a++) {
  104568. error = activeErrors[a];
  104569. label = field.getFieldLabel();
  104570. errors.push((label ? label + ': ' : '') + error);
  104571. }
  104572. }
  104573. return errors;
  104574. },
  104575. getTargetEl: function() {
  104576. return this.bodyEl || this.callParent();
  104577. }
  104578. });
  104579. /**
  104580. * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
  104581. * {@link Ext.form.field.Checkbox} controls into columns, and provides convenience
  104582. * {@link Ext.form.field.Field} methods for {@link #getValue getting}, {@link #setValue setting},
  104583. * and {@link #validate validating} the group of checkboxes as a whole.
  104584. *
  104585. * # Validation
  104586. *
  104587. * Individual checkbox fields themselves have no default validation behavior, but
  104588. * sometimes you want to require a user to select at least one of a group of checkboxes. CheckboxGroup
  104589. * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
  104590. * least one of the checkboxes, the entire group will be highlighted as invalid and the
  104591. * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.
  104592. *
  104593. * # Layout
  104594. *
  104595. * The default layout for CheckboxGroup makes it easy to arrange the checkboxes into
  104596. * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
  104597. * use a completely different layout by setting the {@link #layout} to one of the other supported layout
  104598. * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
  104599. * the checkbox components at any depth will still be managed by the CheckboxGroup's validation.
  104600. *
  104601. * @example
  104602. * Ext.create('Ext.form.Panel', {
  104603. * title: 'Checkbox Group',
  104604. * width: 300,
  104605. * height: 125,
  104606. * bodyPadding: 10,
  104607. * renderTo: Ext.getBody(),
  104608. * items:[{
  104609. * xtype: 'checkboxgroup',
  104610. * fieldLabel: 'Two Columns',
  104611. * // Arrange checkboxes into two columns, distributed vertically
  104612. * columns: 2,
  104613. * vertical: true,
  104614. * items: [
  104615. * { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
  104616. * { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true },
  104617. * { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
  104618. * { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
  104619. * { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
  104620. * { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
  104621. * ]
  104622. * }]
  104623. * });
  104624. */
  104625. Ext.define('Ext.form.CheckboxGroup', {
  104626. extend:'Ext.form.FieldContainer',
  104627. mixins: {
  104628. field: 'Ext.form.field.Field'
  104629. },
  104630. alias: 'widget.checkboxgroup',
  104631. requires: ['Ext.layout.container.CheckboxGroup', 'Ext.form.field.Base'],
  104632. /**
  104633. * @cfg {String} name
  104634. * @private
  104635. */
  104636. /**
  104637. * @cfg {Ext.form.field.Checkbox[]/Object[]} items
  104638. * An Array of {@link Ext.form.field.Checkbox Checkbox}es or Checkbox config objects to arrange in the group.
  104639. */
  104640. /**
  104641. * @cfg {String/Number/Number[]} columns
  104642. * Specifies the number of columns to use when displaying grouped checkbox/radio controls using automatic layout.
  104643. * This config can take several types of values:
  104644. *
  104645. * - 'auto' - The controls will be rendered one per column on one row and the width of each column will be evenly
  104646. * distributed based on the width of the overall field container. This is the default.
  104647. * - Number - If you specific a number (e.g., 3) that number of columns will be created and the contained controls
  104648. * will be automatically distributed based on the value of {@link #vertical}.
  104649. * - Array - You can also specify an array of column widths, mixing integer (fixed width) and float (percentage
  104650. * width) values as needed (e.g., [100, .25, .75]). Any integer values will be rendered first, then any float
  104651. * values will be calculated as a percentage of the remaining space. Float values do not have to add up to 1
  104652. * (100%) although if you want the controls to take up the entire field container you should do so.
  104653. */
  104654. columns : 'auto',
  104655. /**
  104656. * @cfg {Boolean} vertical
  104657. * True to distribute contained controls across columns, completely filling each column top to bottom before
  104658. * starting on the next column. The number of controls in each column will be automatically calculated to keep
  104659. * columns as even as possible. The default value is false, so that controls will be added to columns one at a time,
  104660. * completely filling each row left to right before starting on the next row.
  104661. */
  104662. vertical : false,
  104663. /**
  104664. * @cfg {Boolean} allowBlank
  104665. * False to validate that at least one item in the group is checked. If no items are selected at
  104666. * validation time, {@link #blankText} will be used as the error text.
  104667. */
  104668. allowBlank : true,
  104669. //<locale>
  104670. /**
  104671. * @cfg {String} blankText
  104672. * Error text to display if the {@link #allowBlank} validation fails
  104673. */
  104674. blankText : "You must select at least one item in this group",
  104675. //</locale>
  104676. // private
  104677. defaultType : 'checkboxfield',
  104678. // private
  104679. groupCls : Ext.baseCSSPrefix + 'form-check-group',
  104680. /**
  104681. * @cfg {String} [fieldBodyCls='x-form-checkboxgroup-body']
  104682. * An extra CSS class to be applied to the body content element in addition to {@link #baseBodyCls}.
  104683. */
  104684. fieldBodyCls: Ext.baseCSSPrefix + 'form-checkboxgroup-body',
  104685. // private
  104686. layout: 'checkboxgroup',
  104687. initComponent: function() {
  104688. var me = this;
  104689. me.callParent();
  104690. me.initField();
  104691. },
  104692. /**
  104693. * Initializes the field's value based on the initial config. If the {@link #value} config is specified then we use
  104694. * that to set the value; otherwise we initialize the originalValue by querying the values of all sub-checkboxes
  104695. * after they have been initialized.
  104696. * @protected
  104697. */
  104698. initValue: function() {
  104699. var me = this,
  104700. valueCfg = me.value;
  104701. me.originalValue = me.lastValue = valueCfg || me.getValue();
  104702. if (valueCfg) {
  104703. me.setValue(valueCfg);
  104704. }
  104705. },
  104706. /**
  104707. * When a checkbox is added to the group, monitor it for changes
  104708. * @param {Object} field
  104709. * @protected
  104710. */
  104711. onFieldAdded: function(field) {
  104712. var me = this;
  104713. if (field.isCheckbox) {
  104714. me.mon(field, 'change', me.checkChange, me);
  104715. }
  104716. me.callParent(arguments);
  104717. },
  104718. onFieldRemoved: function(field) {
  104719. var me = this;
  104720. if (field.isCheckbox) {
  104721. me.mun(field, 'change', me.checkChange, me);
  104722. }
  104723. me.callParent(arguments);
  104724. },
  104725. // private override - the group value is a complex object, compare using object serialization
  104726. isEqual: function(value1, value2) {
  104727. var toQueryString = Ext.Object.toQueryString;
  104728. return toQueryString(value1) === toQueryString(value2);
  104729. },
  104730. /**
  104731. * Runs CheckboxGroup's validations and returns an array of any errors. The only error by default is if allowBlank
  104732. * is set to true and no items are checked.
  104733. * @return {String[]} Array of all validation errors
  104734. */
  104735. getErrors: function() {
  104736. var errors = [];
  104737. if (!this.allowBlank && Ext.isEmpty(this.getChecked())) {
  104738. errors.push(this.blankText);
  104739. }
  104740. return errors;
  104741. },
  104742. /**
  104743. * @private Returns all checkbox components within the container
  104744. * @param {String} [query] An additional query to add to the selector.
  104745. */
  104746. getBoxes: function(query) {
  104747. return this.query('[isCheckbox]' + (query||''));
  104748. },
  104749. /**
  104750. * @private Convenience function which calls the given function for every checkbox in the group
  104751. * @param {Function} fn The function to call
  104752. * @param {Object} [scope] scope object
  104753. */
  104754. eachBox: function(fn, scope) {
  104755. Ext.Array.forEach(this.getBoxes(), fn, scope || this);
  104756. },
  104757. /**
  104758. * Returns an Array of all checkboxes in the container which are currently checked
  104759. * @return {Ext.form.field.Checkbox[]} Array of Ext.form.field.Checkbox components
  104760. */
  104761. getChecked: function() {
  104762. return this.getBoxes('[checked]');
  104763. },
  104764. // private override
  104765. isDirty: function(){
  104766. var boxes = this.getBoxes(),
  104767. b ,
  104768. bLen = boxes.length;
  104769. for (b = 0; b < bLen; b++) {
  104770. if (boxes[b].isDirty()) {
  104771. return true;
  104772. }
  104773. }
  104774. },
  104775. // private override
  104776. setReadOnly: function(readOnly) {
  104777. var boxes = this.getBoxes(),
  104778. b,
  104779. bLen = boxes.length;
  104780. for (b = 0; b < bLen; b++) {
  104781. boxes[b].setReadOnly(readOnly);
  104782. }
  104783. this.readOnly = readOnly;
  104784. },
  104785. /**
  104786. * Resets the checked state of all {@link Ext.form.field.Checkbox checkboxes} in the group to their originally
  104787. * loaded values and clears any validation messages.
  104788. * See {@link Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
  104789. */
  104790. reset: function() {
  104791. var me = this,
  104792. hadError = me.hasActiveError(),
  104793. preventMark = me.preventMark;
  104794. me.preventMark = true;
  104795. me.batchChanges(function() {
  104796. var boxes = me.getBoxes(),
  104797. b,
  104798. bLen = boxes.length;
  104799. for (b = 0; b < bLen; b++) {
  104800. boxes[b].reset();
  104801. }
  104802. });
  104803. me.preventMark = preventMark;
  104804. me.unsetActiveError();
  104805. if (hadError) {
  104806. me.updateLayout();
  104807. }
  104808. },
  104809. resetOriginalValue: function(){
  104810. var me = this,
  104811. boxes = me.getBoxes(),
  104812. b,
  104813. bLen = boxes.length;
  104814. for (b = 0; b < bLen; b++) {
  104815. boxes[b].resetOriginalValue();
  104816. }
  104817. me.originalValue = me.getValue();
  104818. me.checkDirty();
  104819. },
  104820. /**
  104821. * Sets the value(s) of all checkboxes in the group. The expected format is an Object of name-value pairs
  104822. * corresponding to the names of the checkboxes in the group. Each pair can have either a single or multiple values:
  104823. *
  104824. * - A single Boolean or String value will be passed to the `setValue` method of the checkbox with that name.
  104825. * See the rules in {@link Ext.form.field.Checkbox#setValue} for accepted values.
  104826. * - An Array of String values will be matched against the {@link Ext.form.field.Checkbox#inputValue inputValue}
  104827. * of checkboxes in the group with that name; those checkboxes whose inputValue exists in the array will be
  104828. * checked and others will be unchecked.
  104829. *
  104830. * If a checkbox's name is not in the mapping at all, it will be unchecked.
  104831. *
  104832. * An example:
  104833. *
  104834. * var myCheckboxGroup = new Ext.form.CheckboxGroup({
  104835. * columns: 3,
  104836. * items: [{
  104837. * name: 'cb1',
  104838. * boxLabel: 'Single 1'
  104839. * }, {
  104840. * name: 'cb2',
  104841. * boxLabel: 'Single 2'
  104842. * }, {
  104843. * name: 'cb3',
  104844. * boxLabel: 'Single 3'
  104845. * }, {
  104846. * name: 'cbGroup',
  104847. * boxLabel: 'Grouped 1'
  104848. * inputValue: 'value1'
  104849. * }, {
  104850. * name: 'cbGroup',
  104851. * boxLabel: 'Grouped 2'
  104852. * inputValue: 'value2'
  104853. * }, {
  104854. * name: 'cbGroup',
  104855. * boxLabel: 'Grouped 3'
  104856. * inputValue: 'value3'
  104857. * }]
  104858. * });
  104859. *
  104860. * myCheckboxGroup.setValue({
  104861. * cb1: true,
  104862. * cb3: false,
  104863. * cbGroup: ['value1', 'value3']
  104864. * });
  104865. *
  104866. * The above code will cause the checkbox named 'cb1' to be checked, as well as the first and third checkboxes named
  104867. * 'cbGroup'. The other three checkboxes will be unchecked.
  104868. *
  104869. * @param {Object} value The mapping of checkbox names to values.
  104870. * @return {Ext.form.CheckboxGroup} this
  104871. */
  104872. setValue: function(value) {
  104873. var me = this,
  104874. boxes = me.getBoxes(),
  104875. b,
  104876. bLen = boxes.length,
  104877. box, name,
  104878. cbValue;
  104879. me.batchChanges(function() {
  104880. for (b = 0; b < bLen; b++) {
  104881. box = boxes[b];
  104882. name = box.getName();
  104883. cbValue = false;
  104884. if (value && value.hasOwnProperty(name)) {
  104885. if (Ext.isArray(value[name])) {
  104886. cbValue = Ext.Array.contains(value[name], box.inputValue);
  104887. } else {
  104888. // single value, let the checkbox's own setValue handle conversion
  104889. cbValue = value[name];
  104890. }
  104891. }
  104892. box.setValue(cbValue);
  104893. }
  104894. });
  104895. return me;
  104896. },
  104897. /**
  104898. * Returns an object containing the values of all checked checkboxes within the group. Each key-value pair in the
  104899. * object corresponds to a checkbox {@link Ext.form.field.Checkbox#name name}. If there is only one checked checkbox
  104900. * with a particular name, the value of that pair will be the String {@link Ext.form.field.Checkbox#inputValue
  104901. * inputValue} of that checkbox. If there are multiple checked checkboxes with that name, the value of that pair
  104902. * will be an Array of the selected inputValues.
  104903. *
  104904. * The object format returned from this method can also be passed directly to the {@link #setValue} method.
  104905. *
  104906. * NOTE: In Ext 3, this method returned an array of Checkbox components; this was changed to make it more consistent
  104907. * with other field components and with the {@link #setValue} argument signature. If you need the old behavior in
  104908. * Ext 4+, use the {@link #getChecked} method instead.
  104909. */
  104910. getValue: function() {
  104911. var values = {},
  104912. boxes = this.getBoxes(),
  104913. b,
  104914. bLen = boxes.length,
  104915. box, name, inputValue, bucket;
  104916. for (b = 0; b < bLen; b++) {
  104917. box = boxes[b];
  104918. name = box.getName();
  104919. inputValue = box.inputValue;
  104920. if (box.getValue()) {
  104921. if (values.hasOwnProperty(name)) {
  104922. bucket = values[name];
  104923. if (!Ext.isArray(bucket)) {
  104924. bucket = values[name] = [bucket];
  104925. }
  104926. bucket.push(inputValue);
  104927. } else {
  104928. values[name] = inputValue;
  104929. }
  104930. }
  104931. }
  104932. return values;
  104933. },
  104934. /*
  104935. * Don't return any data for submit; the form will get the info from the individual checkboxes themselves.
  104936. */
  104937. getSubmitData: function() {
  104938. return null;
  104939. },
  104940. /*
  104941. * Don't return any data for the model; the form will get the info from the individual checkboxes themselves.
  104942. */
  104943. getModelData: function() {
  104944. return null;
  104945. },
  104946. validate: function() {
  104947. var me = this,
  104948. errors,
  104949. isValid,
  104950. wasValid;
  104951. if (me.disabled) {
  104952. isValid = true;
  104953. } else {
  104954. errors = me.getErrors();
  104955. isValid = Ext.isEmpty(errors);
  104956. wasValid = !me.hasActiveError();
  104957. if (isValid) {
  104958. me.unsetActiveError();
  104959. } else {
  104960. me.setActiveError(errors);
  104961. }
  104962. }
  104963. if (isValid !== wasValid) {
  104964. me.fireEvent('validitychange', me, isValid);
  104965. me.updateLayout();
  104966. }
  104967. return isValid;
  104968. }
  104969. }, function() {
  104970. this.borrow(Ext.form.field.Base, ['markInvalid', 'clearInvalid']);
  104971. });
  104972. /**
  104973. * @private
  104974. * Private utility class for managing all {@link Ext.form.field.Checkbox} fields grouped by name.
  104975. */
  104976. Ext.define('Ext.form.CheckboxManager', {
  104977. extend: 'Ext.util.MixedCollection',
  104978. singleton: true,
  104979. getByName: function(name) {
  104980. return this.filterBy(function(item) {
  104981. return item.name == name;
  104982. });
  104983. },
  104984. getWithValue: function(name, value) {
  104985. return this.filterBy(function(item) {
  104986. return item.name == name && item.inputValue == value;
  104987. });
  104988. },
  104989. getChecked: function(name) {
  104990. return this.filterBy(function(item) {
  104991. return item.name == name && item.checked;
  104992. });
  104993. }
  104994. });
  104995. /**
  104996. * Component layout for Ext.form.FieldSet components
  104997. * @private
  104998. */
  104999. Ext.define('Ext.layout.component.FieldSet', {
  105000. extend: 'Ext.layout.component.Body',
  105001. alias: ['layout.fieldset'],
  105002. type: 'fieldset',
  105003. beforeLayoutCycle: function (ownerContext) {
  105004. if (ownerContext.target.collapsed) {
  105005. ownerContext.heightModel = this.sizeModels.shrinkWrap;
  105006. }
  105007. },
  105008. beginLayoutCycle: function (ownerContext) {
  105009. var target = ownerContext.target,
  105010. lastSize;
  105011. this.callParent(arguments);
  105012. // Each time we begin (2nd+ would be due to invalidate) we need to publish the
  105013. // known contentHeight if we are collapsed:
  105014. //
  105015. if (target.collapsed) {
  105016. ownerContext.setContentHeight(0);
  105017. // If we are also shrinkWrap width, we must provide a contentWidth (since the
  105018. // container layout is not going to run).
  105019. //
  105020. if (ownerContext.widthModel.shrinkWrap) {
  105021. lastSize = target.lastComponentSize;
  105022. ownerContext.setContentWidth((lastSize && lastSize.contentWidth) || 100);
  105023. }
  105024. }
  105025. },
  105026. calculateOwnerHeightFromContentHeight: function (ownerContext, contentHeight) {
  105027. var border = ownerContext.getBorderInfo(),
  105028. legend = ownerContext.target.legend;
  105029. // Height of fieldset is content height plus top border width (which is either the legend height or top border width) plus bottom border width
  105030. return ownerContext.getProp('contentHeight') + ownerContext.getPaddingInfo().height + (legend ? legend.getHeight() : border.top) + border.bottom;
  105031. },
  105032. publishInnerHeight: function (ownerContext, height) {
  105033. // Subtract the legend off here and pass it up to the body
  105034. // We do this because we don't want to set an incorrect body height
  105035. // and then setting it again with the correct value
  105036. var legend = ownerContext.target.legend;
  105037. if (legend) {
  105038. height -= legend.getHeight();
  105039. }
  105040. this.callParent([ownerContext, height]);
  105041. },
  105042. getLayoutItems : function() {
  105043. var legend = this.owner.legend;
  105044. if (legend) {
  105045. return [legend];
  105046. }
  105047. return [];
  105048. }
  105049. });
  105050. /**
  105051. * @docauthor Jason Johnston <jason@sencha.com>
  105052. *
  105053. * A container for grouping sets of fields, rendered as a HTML `fieldset` element. The {@link #title}
  105054. * config will be rendered as the fieldset's `legend`.
  105055. *
  105056. * While FieldSets commonly contain simple groups of fields, they are general {@link Ext.container.Container Containers}
  105057. * and may therefore contain any type of components in their {@link #cfg-items}, including other nested containers.
  105058. * The default {@link #layout} for the FieldSet's items is `'anchor'`, but it can be configured to use any other
  105059. * layout type.
  105060. *
  105061. * FieldSets may also be collapsed if configured to do so; this can be done in two ways:
  105062. *
  105063. * 1. Set the {@link #collapsible} config to true; this will result in a collapse button being rendered next to
  105064. * the {@link #title legend title}, or:
  105065. * 2. Set the {@link #checkboxToggle} config to true; this is similar to using {@link #collapsible} but renders
  105066. * a {@link Ext.form.field.Checkbox checkbox} in place of the toggle button. The fieldset will be expanded when the
  105067. * checkbox is checked and collapsed when it is unchecked. The checkbox will also be included in the
  105068. * {@link Ext.form.Basic#submit form submit parameters} using the {@link #checkboxName} as its parameter name.
  105069. *
  105070. * # Example usage
  105071. *
  105072. * @example
  105073. * Ext.create('Ext.form.Panel', {
  105074. * title: 'Simple Form with FieldSets',
  105075. * labelWidth: 75, // label settings here cascade unless overridden
  105076. * url: 'save-form.php',
  105077. * frame: true,
  105078. * bodyStyle: 'padding:5px 5px 0',
  105079. * width: 550,
  105080. * renderTo: Ext.getBody(),
  105081. * layout: 'column', // arrange fieldsets side by side
  105082. * defaults: {
  105083. * bodyPadding: 4
  105084. * },
  105085. * items: [{
  105086. * // Fieldset in Column 1 - collapsible via toggle button
  105087. * xtype:'fieldset',
  105088. * columnWidth: 0.5,
  105089. * title: 'Fieldset 1',
  105090. * collapsible: true,
  105091. * defaultType: 'textfield',
  105092. * defaults: {anchor: '100%'},
  105093. * layout: 'anchor',
  105094. * items :[{
  105095. * fieldLabel: 'Field 1',
  105096. * name: 'field1'
  105097. * }, {
  105098. * fieldLabel: 'Field 2',
  105099. * name: 'field2'
  105100. * }]
  105101. * }, {
  105102. * // Fieldset in Column 2 - collapsible via checkbox, collapsed by default, contains a panel
  105103. * xtype:'fieldset',
  105104. * title: 'Show Panel', // title or checkboxToggle creates fieldset header
  105105. * columnWidth: 0.5,
  105106. * checkboxToggle: true,
  105107. * collapsed: true, // fieldset initially collapsed
  105108. * layout:'anchor',
  105109. * items :[{
  105110. * xtype: 'panel',
  105111. * anchor: '100%',
  105112. * title: 'Panel inside a fieldset',
  105113. * frame: true,
  105114. * height: 52
  105115. * }]
  105116. * }]
  105117. * });
  105118. */
  105119. Ext.define('Ext.form.FieldSet', {
  105120. extend: 'Ext.container.Container',
  105121. alias: 'widget.fieldset',
  105122. uses: ['Ext.form.field.Checkbox', 'Ext.panel.Tool', 'Ext.layout.container.Anchor', 'Ext.layout.component.FieldSet'],
  105123. /**
  105124. * @cfg {String} title
  105125. * A title to be displayed in the fieldset's legend. May contain HTML markup.
  105126. */
  105127. /**
  105128. * @cfg {Boolean} [checkboxToggle=false]
  105129. * Set to true to render a checkbox into the fieldset frame just in front of the legend to expand/collapse the
  105130. * fieldset when the checkbox is toggled.. This checkbox will be included in form submits using
  105131. * the {@link #checkboxName}.
  105132. */
  105133. /**
  105134. * @cfg {String} checkboxName
  105135. * The name to assign to the fieldset's checkbox if {@link #checkboxToggle} = true
  105136. * (defaults to '[fieldset id]-checkbox').
  105137. */
  105138. /**
  105139. * @cfg {Boolean} [collapsible=false]
  105140. * Set to true to make the fieldset collapsible and have the expand/collapse toggle button automatically rendered
  105141. * into the legend element, false to keep the fieldset statically sized with no collapse button.
  105142. * Another option is to configure {@link #checkboxToggle}. Use the {@link #collapsed} config to collapse the
  105143. * fieldset by default.
  105144. */
  105145. /**
  105146. * @cfg {Boolean} collapsed
  105147. * Set to true to render the fieldset as collapsed by default. If {@link #checkboxToggle} is specified, the checkbox
  105148. * will also be unchecked by default.
  105149. */
  105150. collapsed: false,
  105151. /**
  105152. * @cfg {Boolean} [toggleOnTitleClick=true]
  105153. * Set to true will add a listener to the titleCmp property for the click event which will execute the
  105154. * {@link #toggle} method. This option is only used when the {@link #collapsible} property is set to true.
  105155. */
  105156. toggleOnTitleClick : true,
  105157. /**
  105158. * @property {Ext.Component} legend
  105159. * The component for the fieldset's legend. Will only be defined if the configuration requires a legend to be
  105160. * created, by setting the {@link #title} or {@link #checkboxToggle} options.
  105161. */
  105162. /**
  105163. * @cfg {String} [baseCls='x-fieldset']
  105164. * The base CSS class applied to the fieldset.
  105165. */
  105166. baseCls: Ext.baseCSSPrefix + 'fieldset',
  105167. /**
  105168. * @cfg {String} layout
  105169. * The {@link Ext.container.Container#layout} for the fieldset's immediate child items.
  105170. */
  105171. layout: 'anchor',
  105172. border: 1,
  105173. componentLayout: 'fieldset',
  105174. autoEl: 'fieldset',
  105175. childEls: [
  105176. 'body'
  105177. ],
  105178. renderTpl: [
  105179. '{%this.renderLegend(out,values);%}',
  105180. '<div id="{id}-body" class="{baseCls}-body">',
  105181. '{%this.renderContainer(out,values);%}',
  105182. '</div>'
  105183. ],
  105184. stateEvents : [ 'collapse', 'expand' ],
  105185. maskOnDisable: false,
  105186. beforeDestroy: function(){
  105187. var me = this,
  105188. legend = me.legend;
  105189. if (legend) {
  105190. // get rid of the ownerCt since it's not a proper item
  105191. delete legend.ownerCt;
  105192. legend.destroy();
  105193. me.legend = null;
  105194. }
  105195. me.callParent();
  105196. },
  105197. initComponent: function() {
  105198. var me = this,
  105199. baseCls = me.baseCls;
  105200. me.callParent();
  105201. me.addEvents(
  105202. /**
  105203. * @event beforeexpand
  105204. * Fires before this FieldSet is expanded. Return false to prevent the expand.
  105205. * @param {Ext.form.FieldSet} f The FieldSet being expanded.
  105206. */
  105207. "beforeexpand",
  105208. /**
  105209. * @event beforecollapse
  105210. * Fires before this FieldSet is collapsed. Return false to prevent the collapse.
  105211. * @param {Ext.form.FieldSet} f The FieldSet being collapsed.
  105212. */
  105213. "beforecollapse",
  105214. /**
  105215. * @event expand
  105216. * Fires after this FieldSet has expanded.
  105217. * @param {Ext.form.FieldSet} f The FieldSet that has been expanded.
  105218. */
  105219. "expand",
  105220. /**
  105221. * @event collapse
  105222. * Fires after this FieldSet has collapsed.
  105223. * @param {Ext.form.FieldSet} f The FieldSet that has been collapsed.
  105224. */
  105225. "collapse"
  105226. );
  105227. if (me.collapsed) {
  105228. me.addCls(baseCls + '-collapsed');
  105229. me.collapse();
  105230. }
  105231. if (me.title) {
  105232. me.addCls(baseCls + '-with-title');
  105233. }
  105234. if (me.title || me.checkboxToggle || me.collapsible) {
  105235. me.addCls(baseCls + '-with-legend');
  105236. me.legend = Ext.widget(me.createLegendCt());
  105237. }
  105238. },
  105239. /**
  105240. * Initialized the renderData to be used when rendering the renderTpl.
  105241. * @return {Object} Object with keys and values that are going to be applied to the renderTpl
  105242. * @private
  105243. */
  105244. initRenderData: function() {
  105245. var data = this.callParent();
  105246. data.baseCls = this.baseCls;
  105247. return data;
  105248. },
  105249. getState: function () {
  105250. var state = this.callParent();
  105251. state = this.addPropertyToState(state, 'collapsed');
  105252. return state;
  105253. },
  105254. afterCollapse: Ext.emptyFn,
  105255. afterExpand: Ext.emptyFn,
  105256. collapsedHorizontal: function () {
  105257. return true;
  105258. },
  105259. collapsedVertical: function () {
  105260. return true;
  105261. },
  105262. createLegendCt: function () {
  105263. var me = this,
  105264. items = [],
  105265. legend = {
  105266. xtype: 'container',
  105267. baseCls: me.baseCls + '-header',
  105268. id: me.id + '-legend',
  105269. autoEl: 'legend',
  105270. items: items,
  105271. ownerCt: me,
  105272. ownerLayout: me.componentLayout
  105273. };
  105274. // Checkbox
  105275. if (me.checkboxToggle) {
  105276. items.push(me.createCheckboxCmp());
  105277. } else if (me.collapsible) {
  105278. // Toggle button
  105279. items.push(me.createToggleCmp());
  105280. }
  105281. // Title
  105282. items.push(me.createTitleCmp());
  105283. return legend;
  105284. },
  105285. /**
  105286. * Creates the legend title component. This is only called internally, but could be overridden in subclasses to
  105287. * customize the title component. If {@link #toggleOnTitleClick} is set to true, a listener for the click event
  105288. * will toggle the collapsed state of the FieldSet.
  105289. * @return Ext.Component
  105290. * @protected
  105291. */
  105292. createTitleCmp: function() {
  105293. var me = this,
  105294. cfg = {
  105295. xtype : 'component',
  105296. html : me.title,
  105297. cls : me.baseCls + '-header-text',
  105298. id : me.id + '-legendTitle'
  105299. };
  105300. if (me.collapsible && me.toggleOnTitleClick) {
  105301. cfg.listeners = {
  105302. el : {
  105303. scope : me,
  105304. click : me.toggle
  105305. }
  105306. };
  105307. cfg.cls += ' ' + me.baseCls + '-header-text-collapsible';
  105308. }
  105309. return (me.titleCmp = Ext.widget(cfg));
  105310. },
  105311. /**
  105312. * @property {Ext.form.field.Checkbox} checkboxCmp
  105313. * Refers to the {@link Ext.form.field.Checkbox} component that is added next to the title in the legend. Only
  105314. * populated if the fieldset is configured with {@link #checkboxToggle}:true.
  105315. */
  105316. /**
  105317. * Creates the checkbox component. This is only called internally, but could be overridden in subclasses to
  105318. * customize the checkbox's configuration or even return an entirely different component type.
  105319. * @return Ext.Component
  105320. * @protected
  105321. */
  105322. createCheckboxCmp: function() {
  105323. var me = this,
  105324. suffix = '-checkbox';
  105325. me.checkboxCmp = Ext.widget({
  105326. xtype: 'checkbox',
  105327. hideEmptyLabel: true,
  105328. name: me.checkboxName || me.id + suffix,
  105329. cls: me.baseCls + '-header' + suffix,
  105330. id: me.id + '-legendChk',
  105331. checked: !me.collapsed,
  105332. listeners: {
  105333. change: me.onCheckChange,
  105334. scope: me
  105335. }
  105336. });
  105337. return me.checkboxCmp;
  105338. },
  105339. /**
  105340. * @property {Ext.panel.Tool} toggleCmp
  105341. * Refers to the {@link Ext.panel.Tool} component that is added as the collapse/expand button next to the title in
  105342. * the legend. Only populated if the fieldset is configured with {@link #collapsible}:true.
  105343. */
  105344. /**
  105345. * Creates the toggle button component. This is only called internally, but could be overridden in subclasses to
  105346. * customize the toggle component.
  105347. * @return Ext.Component
  105348. * @protected
  105349. */
  105350. createToggleCmp: function() {
  105351. var me = this;
  105352. me.toggleCmp = Ext.widget({
  105353. xtype: 'tool',
  105354. type: 'toggle',
  105355. handler: me.toggle,
  105356. id: me.id + '-legendToggle',
  105357. scope: me
  105358. });
  105359. return me.toggleCmp;
  105360. },
  105361. doRenderLegend: function (out, renderData) {
  105362. // Careful! This method is bolted on to the renderTpl so all we get for context is
  105363. // the renderData! The "this" pointer is the renderTpl instance!
  105364. var me = renderData.$comp,
  105365. legend = me.legend,
  105366. tree;
  105367. // Create the Legend component if needed
  105368. if (legend) {
  105369. legend.ownerLayout.configureItem(legend);
  105370. tree = legend.getRenderTree();
  105371. Ext.DomHelper.generateMarkup(tree, out);
  105372. }
  105373. },
  105374. finishRender: function () {
  105375. var legend = this.legend;
  105376. this.callParent();
  105377. if (legend) {
  105378. legend.finishRender();
  105379. }
  105380. },
  105381. getCollapsed: function () {
  105382. return this.collapsed ? 'top' : false;
  105383. },
  105384. getCollapsedDockedItems: function () {
  105385. var legend = this.legend;
  105386. return legend ? [ legend ] : [];
  105387. },
  105388. /**
  105389. * Sets the title of this fieldset
  105390. * @param {String} title The new title
  105391. * @return {Ext.form.FieldSet} this
  105392. */
  105393. setTitle: function(title) {
  105394. var me = this,
  105395. legend = me.legend;
  105396. me.title = title;
  105397. if (me.rendered) {
  105398. if (!me.legend) {
  105399. me.legend = legend = Ext.widget(me.createLegendCt());
  105400. legend.ownerLayout.configureItem(legend);
  105401. legend.render(me.el, 0);
  105402. }
  105403. me.titleCmp.update(title);
  105404. }
  105405. return me;
  105406. },
  105407. getTargetEl : function() {
  105408. return this.body || this.frameBody || this.el;
  105409. },
  105410. getContentTarget: function() {
  105411. return this.body;
  105412. },
  105413. /**
  105414. * Expands the fieldset.
  105415. * @return {Ext.form.FieldSet} this
  105416. */
  105417. expand : function(){
  105418. return this.setExpanded(true);
  105419. },
  105420. /**
  105421. * Collapses the fieldset.
  105422. * @return {Ext.form.FieldSet} this
  105423. */
  105424. collapse : function() {
  105425. return this.setExpanded(false);
  105426. },
  105427. /**
  105428. * @private Collapse or expand the fieldset
  105429. */
  105430. setExpanded: function(expanded) {
  105431. var me = this,
  105432. checkboxCmp = me.checkboxCmp,
  105433. operation = expanded ? 'expand' : 'collapse';
  105434. if (!me.rendered || me.fireEvent('before' + operation, me) !== false) {
  105435. expanded = !!expanded;
  105436. if (checkboxCmp) {
  105437. checkboxCmp.setValue(expanded);
  105438. }
  105439. if (expanded) {
  105440. me.removeCls(me.baseCls + '-collapsed');
  105441. } else {
  105442. me.addCls(me.baseCls + '-collapsed');
  105443. }
  105444. me.collapsed = !expanded;
  105445. if (me.rendered) {
  105446. // say explicitly we are not root because when we have a fixed/configured height
  105447. // our ownerLayout would say we are root and so would not have it's height
  105448. // updated since it's not included in the layout cycle
  105449. me.updateLayout({ isRoot: false });
  105450. me.fireEvent(operation, me);
  105451. }
  105452. }
  105453. return me;
  105454. },
  105455. getRefItems: function(deep) {
  105456. var refItems = this.callParent(arguments),
  105457. legend = this.legend;
  105458. // Prepend legend items to ensure correct order
  105459. if (legend) {
  105460. refItems.unshift(legend);
  105461. if (deep) {
  105462. refItems.unshift.apply(refItems, legend.getRefItems(true));
  105463. }
  105464. }
  105465. return refItems;
  105466. },
  105467. /**
  105468. * Toggle the fieldset's collapsed state to the opposite of what it is currently
  105469. */
  105470. toggle: function() {
  105471. this.setExpanded(!!this.collapsed);
  105472. },
  105473. /**
  105474. * @private
  105475. * Handle changes in the checkbox checked state
  105476. */
  105477. onCheckChange: function(cmp, checked) {
  105478. this.setExpanded(checked);
  105479. },
  105480. setupRenderTpl: function (renderTpl) {
  105481. this.callParent(arguments);
  105482. renderTpl.renderLegend = this.doRenderLegend;
  105483. }
  105484. });
  105485. /**
  105486. * @docauthor Jason Johnston <jason@sencha.com>
  105487. *
  105488. * Produces a standalone `<label />` element which can be inserted into a form and be associated with a field
  105489. * in that form using the {@link #forId} property.
  105490. *
  105491. * **NOTE:** in most cases it will be more appropriate to use the {@link Ext.form.Labelable#fieldLabel fieldLabel}
  105492. * and associated config properties ({@link Ext.form.Labelable#labelAlign}, {@link Ext.form.Labelable#labelWidth},
  105493. * etc.) in field components themselves, as that allows labels to be uniformly sized throughout the form.
  105494. * Ext.form.Label should only be used when your layout can not be achieved with the standard
  105495. * {@link Ext.form.Labelable field layout}.
  105496. *
  105497. * You will likely be associating the label with a field component that extends {@link Ext.form.field.Base}, so
  105498. * you should make sure the {@link #forId} is set to the same value as the {@link Ext.form.field.Base#inputId inputId}
  105499. * of that field.
  105500. *
  105501. * The label's text can be set using either the {@link #text} or {@link #html} configuration properties; the
  105502. * difference between the two is that the former will automatically escape HTML characters when rendering, while
  105503. * the latter will not.
  105504. *
  105505. * # Example
  105506. *
  105507. * This example creates a Label after its associated Text field, an arrangement that cannot currently
  105508. * be achieved using the standard Field layout's labelAlign.
  105509. *
  105510. * @example
  105511. * Ext.create('Ext.form.Panel', {
  105512. * title: 'Field with Label',
  105513. * width: 400,
  105514. * bodyPadding: 10,
  105515. * renderTo: Ext.getBody(),
  105516. * layout: {
  105517. * type: 'hbox',
  105518. * align: 'middle'
  105519. * },
  105520. * items: [{
  105521. * xtype: 'textfield',
  105522. * hideLabel: true,
  105523. * flex: 1
  105524. * }, {
  105525. * xtype: 'label',
  105526. * forId: 'myFieldId',
  105527. * text: 'My Awesome Field',
  105528. * margin: '0 0 0 10'
  105529. * }]
  105530. * });
  105531. */
  105532. Ext.define('Ext.form.Label', {
  105533. extend:'Ext.Component',
  105534. alias: 'widget.label',
  105535. requires: ['Ext.util.Format'],
  105536. autoEl: 'label',
  105537. /**
  105538. * @cfg {String} [text='']
  105539. * The plain text to display within the label. If you need to include HTML
  105540. * tags within the label's innerHTML, use the {@link #html} config instead.
  105541. */
  105542. /**
  105543. * @cfg {String} forId
  105544. * The id of the input element to which this label will be bound via the standard HTML 'for'
  105545. * attribute. If not specified, the attribute will not be added to the label. In most cases you will be
  105546. * associating the label with a {@link Ext.form.field.Base} component, so you should make sure this matches
  105547. * the {@link Ext.form.field.Base#inputId inputId} of that field.
  105548. */
  105549. /**
  105550. * @cfg {String} [html='']
  105551. * An HTML fragment that will be used as the label's innerHTML.
  105552. * Note that if {@link #text} is specified it will take precedence and this value will be ignored.
  105553. */
  105554. maskOnDisable: false,
  105555. getElConfig: function(){
  105556. var me = this;
  105557. me.html = me.text ? Ext.util.Format.htmlEncode(me.text) : (me.html || '');
  105558. return Ext.apply(me.callParent(), {
  105559. htmlFor: me.forId || ''
  105560. });
  105561. },
  105562. /**
  105563. * Updates the label's innerHTML with the specified string.
  105564. * @param {String} text The new label text
  105565. * @param {Boolean} [encode=true] False to skip HTML-encoding the text when rendering it
  105566. * to the label. This might be useful if you want to include tags in the label's innerHTML rather
  105567. * than rendering them as string literals per the default logic.
  105568. * @return {Ext.form.Label} this
  105569. */
  105570. setText : function(text, encode){
  105571. var me = this;
  105572. encode = encode !== false;
  105573. if(encode) {
  105574. me.text = text;
  105575. delete me.html;
  105576. } else {
  105577. me.html = text;
  105578. delete me.text;
  105579. }
  105580. if(me.rendered){
  105581. me.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(text) : text;
  105582. me.updateLayout();
  105583. }
  105584. return me;
  105585. }
  105586. });
  105587. /**
  105588. * @docauthor Jason Johnston <jason@sencha.com>
  105589. *
  105590. * FormPanel provides a standard container for forms. It is essentially a standard {@link Ext.panel.Panel} which
  105591. * automatically creates a {@link Ext.form.Basic BasicForm} for managing any {@link Ext.form.field.Field}
  105592. * objects that are added as descendants of the panel. It also includes conveniences for configuring and
  105593. * working with the BasicForm and the collection of Fields.
  105594. *
  105595. * # Layout
  105596. *
  105597. * By default, FormPanel is configured with `{@link Ext.layout.container.Anchor layout:'anchor'}` for
  105598. * the layout of its immediate child items. This can be changed to any of the supported container layouts.
  105599. * The layout of sub-containers is configured in {@link Ext.container.Container#layout the standard way}.
  105600. *
  105601. * # BasicForm
  105602. *
  105603. * Although **not listed** as configuration options of FormPanel, the FormPanel class accepts all
  105604. * of the config options supported by the {@link Ext.form.Basic} class, and will pass them along to
  105605. * the internal BasicForm when it is created.
  105606. *
  105607. * The following events fired by the BasicForm will be re-fired by the FormPanel and can therefore be
  105608. * listened for on the FormPanel itself:
  105609. *
  105610. * - {@link Ext.form.Basic#beforeaction beforeaction}
  105611. * - {@link Ext.form.Basic#actionfailed actionfailed}
  105612. * - {@link Ext.form.Basic#actioncomplete actioncomplete}
  105613. * - {@link Ext.form.Basic#validitychange validitychange}
  105614. * - {@link Ext.form.Basic#dirtychange dirtychange}
  105615. *
  105616. * # Field Defaults
  105617. *
  105618. * The {@link #fieldDefaults} config option conveniently allows centralized configuration of default values
  105619. * for all fields added as descendants of the FormPanel. Any config option recognized by implementations
  105620. * of {@link Ext.form.Labelable} may be included in this object. See the {@link #fieldDefaults} documentation
  105621. * for details of how the defaults are applied.
  105622. *
  105623. * # Form Validation
  105624. *
  105625. * With the default configuration, form fields are validated on-the-fly while the user edits their values.
  105626. * This can be controlled on a per-field basis (or via the {@link #fieldDefaults} config) with the field
  105627. * config properties {@link Ext.form.field.Field#validateOnChange} and {@link Ext.form.field.Base#checkChangeEvents},
  105628. * and the FormPanel's config properties {@link #pollForChanges} and {@link #pollInterval}.
  105629. *
  105630. * Any component within the FormPanel can be configured with `formBind: true`. This will cause that
  105631. * component to be automatically disabled when the form is invalid, and enabled when it is valid. This is most
  105632. * commonly used for Button components to prevent submitting the form in an invalid state, but can be used on
  105633. * any component type.
  105634. *
  105635. * For more information on form validation see the following:
  105636. *
  105637. * - {@link Ext.form.field.Field#validateOnChange}
  105638. * - {@link #pollForChanges} and {@link #pollInterval}
  105639. * - {@link Ext.form.field.VTypes}
  105640. * - {@link Ext.form.Basic#doAction BasicForm.doAction clientValidation notes}
  105641. *
  105642. * # Form Submission
  105643. *
  105644. * By default, Ext Forms are submitted through Ajax, using {@link Ext.form.action.Action}. See the documentation for
  105645. * {@link Ext.form.Basic} for details.
  105646. *
  105647. * # Example usage
  105648. *
  105649. * @example
  105650. * Ext.create('Ext.form.Panel', {
  105651. * title: 'Simple Form',
  105652. * bodyPadding: 5,
  105653. * width: 350,
  105654. *
  105655. * // The form will submit an AJAX request to this URL when submitted
  105656. * url: 'save-form.php',
  105657. *
  105658. * // Fields will be arranged vertically, stretched to full width
  105659. * layout: 'anchor',
  105660. * defaults: {
  105661. * anchor: '100%'
  105662. * },
  105663. *
  105664. * // The fields
  105665. * defaultType: 'textfield',
  105666. * items: [{
  105667. * fieldLabel: 'First Name',
  105668. * name: 'first',
  105669. * allowBlank: false
  105670. * },{
  105671. * fieldLabel: 'Last Name',
  105672. * name: 'last',
  105673. * allowBlank: false
  105674. * }],
  105675. *
  105676. * // Reset and Submit buttons
  105677. * buttons: [{
  105678. * text: 'Reset',
  105679. * handler: function() {
  105680. * this.up('form').getForm().reset();
  105681. * }
  105682. * }, {
  105683. * text: 'Submit',
  105684. * formBind: true, //only enabled once the form is valid
  105685. * disabled: true,
  105686. * handler: function() {
  105687. * var form = this.up('form').getForm();
  105688. * if (form.isValid()) {
  105689. * form.submit({
  105690. * success: function(form, action) {
  105691. * Ext.Msg.alert('Success', action.result.msg);
  105692. * },
  105693. * failure: function(form, action) {
  105694. * Ext.Msg.alert('Failed', action.result.msg);
  105695. * }
  105696. * });
  105697. * }
  105698. * }
  105699. * }],
  105700. * renderTo: Ext.getBody()
  105701. * });
  105702. *
  105703. */
  105704. Ext.define('Ext.form.Panel', {
  105705. extend:'Ext.panel.Panel',
  105706. mixins: {
  105707. fieldAncestor: 'Ext.form.FieldAncestor'
  105708. },
  105709. alias: 'widget.form',
  105710. alternateClassName: ['Ext.FormPanel', 'Ext.form.FormPanel'],
  105711. requires: ['Ext.form.Basic', 'Ext.util.TaskRunner'],
  105712. /**
  105713. * @cfg {Boolean} pollForChanges
  105714. * If set to `true`, sets up an interval task (using the {@link #pollInterval}) in which the
  105715. * panel's fields are repeatedly checked for changes in their values. This is in addition to the normal detection
  105716. * each field does on its own input element, and is not needed in most cases. It does, however, provide a
  105717. * means to absolutely guarantee detection of all changes including some edge cases in some browsers which
  105718. * do not fire native events. Defaults to `false`.
  105719. */
  105720. /**
  105721. * @cfg {Number} pollInterval
  105722. * Interval in milliseconds at which the form's fields are checked for value changes. Only used if
  105723. * the {@link #pollForChanges} option is set to `true`. Defaults to 500 milliseconds.
  105724. */
  105725. /**
  105726. * @cfg {String} layout
  105727. * The {@link Ext.container.Container#layout} for the form panel's immediate child items.
  105728. * Defaults to `'anchor'`.
  105729. */
  105730. layout: 'anchor',
  105731. ariaRole: 'form',
  105732. basicFormConfigs: [
  105733. 'api',
  105734. 'baseParams',
  105735. 'errorReader',
  105736. 'method',
  105737. 'paramOrder',
  105738. 'paramsAsHash',
  105739. 'reader',
  105740. 'standardSubmit',
  105741. 'timeout',
  105742. 'trackResetOnLoad',
  105743. 'url',
  105744. 'waitMsgTarget',
  105745. 'waitTitle'
  105746. ],
  105747. initComponent: function() {
  105748. var me = this;
  105749. if (me.frame) {
  105750. me.border = false;
  105751. }
  105752. me.initFieldAncestor();
  105753. me.callParent();
  105754. me.relayEvents(me.form, [
  105755. /**
  105756. * @event beforeaction
  105757. * @inheritdoc Ext.form.Basic#beforeaction
  105758. */
  105759. 'beforeaction',
  105760. /**
  105761. * @event actionfailed
  105762. * @inheritdoc Ext.form.Basic#actionfailed
  105763. */
  105764. 'actionfailed',
  105765. /**
  105766. * @event actioncomplete
  105767. * @inheritdoc Ext.form.Basic#actioncomplete
  105768. */
  105769. 'actioncomplete',
  105770. /**
  105771. * @event validitychange
  105772. * @inheritdoc Ext.form.Basic#validitychange
  105773. */
  105774. 'validitychange',
  105775. /**
  105776. * @event dirtychange
  105777. * @inheritdoc Ext.form.Basic#dirtychange
  105778. */
  105779. 'dirtychange'
  105780. ]);
  105781. // Start polling if configured
  105782. if (me.pollForChanges) {
  105783. me.startPolling(me.pollInterval || 500);
  105784. }
  105785. },
  105786. initItems: function() {
  105787. // Create the BasicForm
  105788. var me = this;
  105789. me.form = me.createForm();
  105790. me.callParent();
  105791. },
  105792. // Initialize the BasicForm after all layouts have been completed.
  105793. afterFirstLayout: function() {
  105794. this.callParent();
  105795. this.form.initialize();
  105796. },
  105797. /**
  105798. * @private
  105799. */
  105800. createForm: function() {
  105801. var cfg = {},
  105802. props = this.basicFormConfigs,
  105803. len = props.length,
  105804. i = 0,
  105805. prop;
  105806. for (; i < len; ++i) {
  105807. prop = props[i];
  105808. cfg[prop] = this[prop];
  105809. }
  105810. return new Ext.form.Basic(this, cfg);
  105811. },
  105812. /**
  105813. * Provides access to the {@link Ext.form.Basic Form} which this Panel contains.
  105814. * @return {Ext.form.Basic} The {@link Ext.form.Basic Form} which this Panel contains.
  105815. */
  105816. getForm: function() {
  105817. return this.form;
  105818. },
  105819. /**
  105820. * Loads an {@link Ext.data.Model} into this form (internally just calls {@link Ext.form.Basic#loadRecord})
  105821. * See also {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}.
  105822. * @param {Ext.data.Model} record The record to load
  105823. * @return {Ext.form.Basic} The Ext.form.Basic attached to this FormPanel
  105824. */
  105825. loadRecord: function(record) {
  105826. return this.getForm().loadRecord(record);
  105827. },
  105828. /**
  105829. * Returns the currently loaded Ext.data.Model instance if one was loaded via {@link #loadRecord}.
  105830. * @return {Ext.data.Model} The loaded instance
  105831. */
  105832. getRecord: function() {
  105833. return this.getForm().getRecord();
  105834. },
  105835. /**
  105836. * Convenience function for fetching the current value of each field in the form. This is the same as calling
  105837. * {@link Ext.form.Basic#getValues this.getForm().getValues()}.
  105838. *
  105839. * @inheritdoc Ext.form.Basic#getValues
  105840. */
  105841. getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
  105842. return this.getForm().getValues(asString, dirtyOnly, includeEmptyText, useDataValues);
  105843. },
  105844. beforeDestroy: function() {
  105845. this.stopPolling();
  105846. this.form.destroy();
  105847. this.callParent();
  105848. },
  105849. /**
  105850. * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#load} call.
  105851. * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#load} and
  105852. * {@link Ext.form.Basic#doAction} for details)
  105853. */
  105854. load: function(options) {
  105855. this.form.load(options);
  105856. },
  105857. /**
  105858. * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#submit} call.
  105859. * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#submit} and
  105860. * {@link Ext.form.Basic#doAction} for details)
  105861. */
  105862. submit: function(options) {
  105863. this.form.submit(options);
  105864. },
  105865. /**
  105866. * Start an interval task to continuously poll all the fields in the form for changes in their
  105867. * values. This is normally started automatically by setting the {@link #pollForChanges} config.
  105868. * @param {Number} interval The interval in milliseconds at which the check should run.
  105869. */
  105870. startPolling: function(interval) {
  105871. this.stopPolling();
  105872. var task = new Ext.util.TaskRunner(interval);
  105873. task.start({
  105874. interval: 0,
  105875. run: this.checkChange,
  105876. scope: this
  105877. });
  105878. this.pollTask = task;
  105879. },
  105880. /**
  105881. * Stop a running interval task that was started by {@link #startPolling}.
  105882. */
  105883. stopPolling: function() {
  105884. var task = this.pollTask;
  105885. if (task) {
  105886. task.stopAll();
  105887. delete this.pollTask;
  105888. }
  105889. },
  105890. /**
  105891. * Forces each field within the form panel to
  105892. * {@link Ext.form.field.Field#checkChange check if its value has changed}.
  105893. */
  105894. checkChange: function() {
  105895. var fields = this.form.getFields().items,
  105896. f,
  105897. fLen = fields.length,
  105898. field;
  105899. for (f = 0; f < fLen; f++) {
  105900. fields[f].checkChange();
  105901. }
  105902. }
  105903. });
  105904. /**
  105905. * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
  105906. * {@link Ext.form.field.Radio} controls into columns, and provides convenience {@link Ext.form.field.Field}
  105907. * methods for {@link #getValue getting}, {@link #setValue setting}, and {@link #validate validating} the
  105908. * group of radio buttons as a whole.
  105909. *
  105910. * # Validation
  105911. *
  105912. * Individual radio buttons themselves have no default validation behavior, but
  105913. * sometimes you want to require a user to select one of a group of radios. RadioGroup
  105914. * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
  105915. * one of the radio buttons, the entire group will be highlighted as invalid and the
  105916. * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.
  105917. *
  105918. * # Layout
  105919. *
  105920. * The default layout for RadioGroup makes it easy to arrange the radio buttons into
  105921. * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
  105922. * use a completely different layout by setting the {@link #layout} to one of the other supported layout
  105923. * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
  105924. * the Radio components at any depth will still be managed by the RadioGroup's validation.
  105925. *
  105926. * # Example usage
  105927. *
  105928. * @example
  105929. * Ext.create('Ext.form.Panel', {
  105930. * title: 'RadioGroup Example',
  105931. * width: 300,
  105932. * height: 125,
  105933. * bodyPadding: 10,
  105934. * renderTo: Ext.getBody(),
  105935. * items:[{
  105936. * xtype: 'radiogroup',
  105937. * fieldLabel: 'Two Columns',
  105938. * // Arrange radio buttons into two columns, distributed vertically
  105939. * columns: 2,
  105940. * vertical: true,
  105941. * items: [
  105942. * { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
  105943. * { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true},
  105944. * { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
  105945. * { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
  105946. * { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
  105947. * { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
  105948. * ]
  105949. * }]
  105950. * });
  105951. *
  105952. */
  105953. Ext.define('Ext.form.RadioGroup', {
  105954. extend: 'Ext.form.CheckboxGroup',
  105955. alias: 'widget.radiogroup',
  105956. /**
  105957. * @cfg {Ext.form.field.Radio[]/Object[]} items
  105958. * An Array of {@link Ext.form.field.Radio Radio}s or Radio config objects to arrange in the group.
  105959. */
  105960. /**
  105961. * @cfg {Boolean} allowBlank
  105962. * True to allow every item in the group to be blank.
  105963. * If allowBlank = false and no items are selected at validation time, {@link #blankText} will
  105964. * be used as the error text.
  105965. */
  105966. allowBlank : true,
  105967. //<locale>
  105968. /**
  105969. * @cfg {String} blankText
  105970. * Error text to display if the {@link #allowBlank} validation fails
  105971. */
  105972. blankText : 'You must select one item in this group',
  105973. //</locale>
  105974. // private
  105975. defaultType : 'radiofield',
  105976. // private
  105977. groupCls : Ext.baseCSSPrefix + 'form-radio-group',
  105978. getBoxes: function(query) {
  105979. return this.query('[isRadio]' + (query||''));
  105980. },
  105981. checkChange: function() {
  105982. var value = this.getValue(),
  105983. key = Ext.Object.getKeys(value)[0];
  105984. // If the value is an array we skip out here because it's during a change
  105985. // between multiple items, so we never want to fire a change
  105986. if (Ext.isArray(value[key])) {
  105987. return;
  105988. }
  105989. this.callParent(arguments);
  105990. },
  105991. /**
  105992. * Sets the value of the radio group. The radio with corresponding name and value will be set.
  105993. * This method is simpler than {@link Ext.form.CheckboxGroup#setValue} because only 1 value is allowed
  105994. * for each name.
  105995. *
  105996. * @param {Object} value The map from names to values to be set.
  105997. * @return {Ext.form.CheckboxGroup} this
  105998. */
  105999. setValue: function(value) {
  106000. var cbValue, first, formId, radios,
  106001. i, len, name;
  106002. if (Ext.isObject(value)) {
  106003. for (name in value) {
  106004. if (value.hasOwnProperty(name)) {
  106005. cbValue = value[name];
  106006. first = this.items.first();
  106007. formId = first ? first.getFormId() : null;
  106008. radios = Ext.form.RadioManager.getWithValue(name, cbValue, formId).items;
  106009. len = radios.length;
  106010. for (i = 0; i < len; ++i) {
  106011. radios[i].setValue(true);
  106012. }
  106013. }
  106014. }
  106015. }
  106016. return this;
  106017. }
  106018. });
  106019. /**
  106020. * @private
  106021. * Private utility class for managing all {@link Ext.form.field.Radio} fields grouped by name.
  106022. */
  106023. Ext.define('Ext.form.RadioManager', {
  106024. extend: 'Ext.util.MixedCollection',
  106025. singleton: true,
  106026. getByName: function(name, formId) {
  106027. return this.filterBy(function(item) {
  106028. return item.name == name && item.getFormId() == formId;
  106029. });
  106030. },
  106031. getWithValue: function(name, value, formId) {
  106032. return this.filterBy(function(item) {
  106033. return item.name == name && item.inputValue == value && item.getFormId() == formId;
  106034. });
  106035. },
  106036. getChecked: function(name, formId) {
  106037. return this.findBy(function(item) {
  106038. return item.name == name && item.checked && item.getFormId() == formId;
  106039. });
  106040. }
  106041. });
  106042. /**
  106043. * Provides {@link Ext.direct.Manager} support for loading form data.
  106044. *
  106045. * This example illustrates usage of Ext.direct.Direct to **load** a form through Ext.Direct.
  106046. *
  106047. * var myFormPanel = new Ext.form.Panel({
  106048. * // configs for FormPanel
  106049. * title: 'Basic Information',
  106050. * renderTo: document.body,
  106051. * width: 300, height: 160,
  106052. * padding: 10,
  106053. *
  106054. * // configs apply to child items
  106055. * defaults: {anchor: '100%'},
  106056. * defaultType: 'textfield',
  106057. * items: [{
  106058. * fieldLabel: 'Name',
  106059. * name: 'name'
  106060. * },{
  106061. * fieldLabel: 'Email',
  106062. * name: 'email'
  106063. * },{
  106064. * fieldLabel: 'Company',
  106065. * name: 'company'
  106066. * }],
  106067. *
  106068. * // configs for BasicForm
  106069. * api: {
  106070. * // The server-side method to call for load() requests
  106071. * load: Profile.getBasicInfo,
  106072. * // The server-side must mark the submit handler as a 'formHandler'
  106073. * submit: Profile.updateBasicInfo
  106074. * },
  106075. * // specify the order for the passed params
  106076. * paramOrder: ['uid', 'foo']
  106077. * });
  106078. *
  106079. * // load the form
  106080. * myFormPanel.getForm().load({
  106081. * // pass 2 arguments to server side getBasicInfo method (len=2)
  106082. * params: {
  106083. * foo: 'bar',
  106084. * uid: 34
  106085. * }
  106086. * });
  106087. *
  106088. * The data packet sent to the server will resemble something like:
  106089. *
  106090. * [
  106091. * {
  106092. * "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
  106093. * "data":[34,"bar"] // note the order of the params
  106094. * }
  106095. * ]
  106096. *
  106097. * The form will process a data packet returned by the server that is similar to the following format:
  106098. *
  106099. * [
  106100. * {
  106101. * "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
  106102. * "result":{
  106103. * "success":true,
  106104. * "data":{
  106105. * "name":"Fred Flintstone",
  106106. * "company":"Slate Rock and Gravel",
  106107. * "email":"fred.flintstone@slaterg.com"
  106108. * }
  106109. * }
  106110. * }
  106111. * ]
  106112. */
  106113. Ext.define('Ext.form.action.DirectLoad', {
  106114. extend:'Ext.form.action.Load',
  106115. requires: ['Ext.direct.Manager'],
  106116. alternateClassName: 'Ext.form.Action.DirectLoad',
  106117. alias: 'formaction.directload',
  106118. type: 'directload',
  106119. run: function() {
  106120. var me = this,
  106121. form = me.form,
  106122. fn = form.api.load,
  106123. method = fn.directCfg.method,
  106124. args = method.getArgs(me.getParams(), form.paramOrder, form.paramsAsHash);
  106125. args.push(me.onComplete, me);
  106126. fn.apply(window, args);
  106127. },
  106128. // Direct actions have already been processed and therefore
  106129. // we can directly set the result; Direct Actions do not have
  106130. // a this.response property.
  106131. processResponse: function(result) {
  106132. return (this.result = result);
  106133. },
  106134. onComplete: function(data, response) {
  106135. if (data) {
  106136. this.onSuccess(data);
  106137. } else {
  106138. this.onFailure(null);
  106139. }
  106140. }
  106141. });
  106142. /**
  106143. * Provides Ext.direct support for submitting form data.
  106144. *
  106145. * This example illustrates usage of Ext.direct.Direct to **submit** a form through Ext.Direct.
  106146. *
  106147. * var myFormPanel = new Ext.form.Panel({
  106148. * // configs for FormPanel
  106149. * title: 'Basic Information',
  106150. * renderTo: document.body,
  106151. * width: 300, height: 160,
  106152. * padding: 10,
  106153. * buttons:[{
  106154. * text: 'Submit',
  106155. * handler: function(){
  106156. * myFormPanel.getForm().submit({
  106157. * params: {
  106158. * foo: 'bar',
  106159. * uid: 34
  106160. * }
  106161. * });
  106162. * }
  106163. * }],
  106164. *
  106165. * // configs apply to child items
  106166. * defaults: {anchor: '100%'},
  106167. * defaultType: 'textfield',
  106168. * items: [{
  106169. * fieldLabel: 'Name',
  106170. * name: 'name'
  106171. * },{
  106172. * fieldLabel: 'Email',
  106173. * name: 'email'
  106174. * },{
  106175. * fieldLabel: 'Company',
  106176. * name: 'company'
  106177. * }],
  106178. *
  106179. * // configs for BasicForm
  106180. * api: {
  106181. * // The server-side method to call for load() requests
  106182. * load: Profile.getBasicInfo,
  106183. * // The server-side must mark the submit handler as a 'formHandler'
  106184. * submit: Profile.updateBasicInfo
  106185. * },
  106186. * // specify the order for the passed params
  106187. * paramOrder: ['uid', 'foo']
  106188. * });
  106189. *
  106190. * The data packet sent to the server will resemble something like:
  106191. *
  106192. * {
  106193. * "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
  106194. * "result":{
  106195. * "success":true,
  106196. * "id":{
  106197. * "extAction":"Profile","extMethod":"updateBasicInfo",
  106198. * "extType":"rpc","extTID":"6","extUpload":"false",
  106199. * "name":"Aaron Conran","email":"aaron@sencha.com","company":"Sencha Inc."
  106200. * }
  106201. * }
  106202. * }
  106203. *
  106204. * The form will process a data packet returned by the server that is similar to the following:
  106205. *
  106206. * // sample success packet (batched requests)
  106207. * [
  106208. * {
  106209. * "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":3,
  106210. * "result":{
  106211. * "success":true
  106212. * }
  106213. * }
  106214. * ]
  106215. *
  106216. * // sample failure packet (one request)
  106217. * {
  106218. * "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
  106219. * "result":{
  106220. * "errors":{
  106221. * "email":"already taken"
  106222. * },
  106223. * "success":false,
  106224. * "foo":"bar"
  106225. * }
  106226. * }
  106227. *
  106228. * Also see the discussion in {@link Ext.form.action.DirectLoad}.
  106229. */
  106230. Ext.define('Ext.form.action.DirectSubmit', {
  106231. extend:'Ext.form.action.Submit',
  106232. requires: ['Ext.direct.Manager'],
  106233. alternateClassName: 'Ext.form.Action.DirectSubmit',
  106234. alias: 'formaction.directsubmit',
  106235. type: 'directsubmit',
  106236. doSubmit: function() {
  106237. var me = this,
  106238. callback = Ext.Function.bind(me.onComplete, me),
  106239. formEl = me.buildForm();
  106240. me.form.api.submit(formEl, callback, me);
  106241. Ext.removeNode(formEl);
  106242. },
  106243. // Direct actions have already been processed and therefore
  106244. // we can directly set the result; Direct Actions do not have
  106245. // a this.response property.
  106246. processResponse: function(result) {
  106247. return (this.result = result);
  106248. },
  106249. onComplete: function(data, response){
  106250. if (data) {
  106251. this.onSuccess(data);
  106252. } else {
  106253. this.onFailure(null);
  106254. }
  106255. }
  106256. });
  106257. /**
  106258. * A class which handles submission of data from {@link Ext.form.Basic Form}s using a standard `<form>` element submit.
  106259. * It does not handle the response from the submit.
  106260. *
  106261. * If validation of the form fields fails, the Form's afterAction method will be called. Otherwise, afterAction will not
  106262. * be called.
  106263. *
  106264. * Instances of this class are only created by a {@link Ext.form.Basic Form} when
  106265. * {@link Ext.form.Basic#submit submit}ting, when the form's {@link Ext.form.Basic#standardSubmit} config option is true.
  106266. */
  106267. Ext.define('Ext.form.action.StandardSubmit', {
  106268. extend:'Ext.form.action.Submit',
  106269. alias: 'formaction.standardsubmit',
  106270. /**
  106271. * @cfg {String} target
  106272. * Optional target attribute to be used for the form when submitting.
  106273. *
  106274. * Defaults to the current window/frame.
  106275. */
  106276. /**
  106277. * @private
  106278. * Perform the form submit. Creates and submits a temporary form element containing an input element for each
  106279. * field value returned by {@link Ext.form.Basic#getValues}, plus any configured {@link #params params} or
  106280. * {@link Ext.form.Basic#baseParams baseParams}.
  106281. */
  106282. doSubmit: function() {
  106283. var form = this.buildForm();
  106284. form.submit();
  106285. Ext.removeNode(form);
  106286. }
  106287. });
  106288. /**
  106289. * @docauthor Robert Dougan <rob@sencha.com>
  106290. *
  106291. * Single checkbox field. Can be used as a direct replacement for traditional checkbox fields. Also serves as a
  106292. * parent class for {@link Ext.form.field.Radio radio buttons}.
  106293. *
  106294. * # Labeling
  106295. *
  106296. * In addition to the {@link Ext.form.Labelable standard field labeling options}, checkboxes
  106297. * may be given an optional {@link #boxLabel} which will be displayed immediately after checkbox. Also see
  106298. * {@link Ext.form.CheckboxGroup} for a convenient method of grouping related checkboxes.
  106299. *
  106300. * # Values
  106301. *
  106302. * The main value of a checkbox is a boolean, indicating whether or not the checkbox is checked.
  106303. * The following values will check the checkbox:
  106304. *
  106305. * - `true`
  106306. * - `'true'`
  106307. * - `'1'`
  106308. * - `'on'`
  106309. *
  106310. * Any other value will uncheck the checkbox.
  106311. *
  106312. * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be
  106313. * sent as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set
  106314. * this value if you have multiple checkboxes with the same {@link #name}. If not specified, the value `on`
  106315. * will be used.
  106316. *
  106317. * # Example usage
  106318. *
  106319. * @example
  106320. * Ext.create('Ext.form.Panel', {
  106321. * bodyPadding: 10,
  106322. * width: 300,
  106323. * title: 'Pizza Order',
  106324. * items: [
  106325. * {
  106326. * xtype: 'fieldcontainer',
  106327. * fieldLabel: 'Toppings',
  106328. * defaultType: 'checkboxfield',
  106329. * items: [
  106330. * {
  106331. * boxLabel : 'Anchovies',
  106332. * name : 'topping',
  106333. * inputValue: '1',
  106334. * id : 'checkbox1'
  106335. * }, {
  106336. * boxLabel : 'Artichoke Hearts',
  106337. * name : 'topping',
  106338. * inputValue: '2',
  106339. * checked : true,
  106340. * id : 'checkbox2'
  106341. * }, {
  106342. * boxLabel : 'Bacon',
  106343. * name : 'topping',
  106344. * inputValue: '3',
  106345. * id : 'checkbox3'
  106346. * }
  106347. * ]
  106348. * }
  106349. * ],
  106350. * bbar: [
  106351. * {
  106352. * text: 'Select Bacon',
  106353. * handler: function() {
  106354. * Ext.getCmp('checkbox3').setValue(true);
  106355. * }
  106356. * },
  106357. * '-',
  106358. * {
  106359. * text: 'Select All',
  106360. * handler: function() {
  106361. * Ext.getCmp('checkbox1').setValue(true);
  106362. * Ext.getCmp('checkbox2').setValue(true);
  106363. * Ext.getCmp('checkbox3').setValue(true);
  106364. * }
  106365. * },
  106366. * {
  106367. * text: 'Deselect All',
  106368. * handler: function() {
  106369. * Ext.getCmp('checkbox1').setValue(false);
  106370. * Ext.getCmp('checkbox2').setValue(false);
  106371. * Ext.getCmp('checkbox3').setValue(false);
  106372. * }
  106373. * }
  106374. * ],
  106375. * renderTo: Ext.getBody()
  106376. * });
  106377. */
  106378. Ext.define('Ext.form.field.Checkbox', {
  106379. extend: 'Ext.form.field.Base',
  106380. alias: ['widget.checkboxfield', 'widget.checkbox'],
  106381. alternateClassName: 'Ext.form.Checkbox',
  106382. requires: ['Ext.XTemplate', 'Ext.form.CheckboxManager' ],
  106383. componentLayout: 'field',
  106384. childEls: [
  106385. /**
  106386. * @property {Ext.Element} boxLabelEl
  106387. * A reference to the label element created for the {@link #boxLabel}. Only present if the component has been
  106388. * rendered and has a boxLabel configured.
  106389. */
  106390. 'boxLabelEl'
  106391. ],
  106392. // note: {id} here is really {inputId}, but {cmpId} is available
  106393. fieldSubTpl: [
  106394. '<tpl if="boxLabel && boxLabelAlign == \'before\'">',
  106395. '{beforeBoxLabelTpl}',
  106396. '<label id="{cmpId}-boxLabelEl" {boxLabelAttrTpl} class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">',
  106397. '{beforeBoxLabelTextTpl}',
  106398. '{boxLabel}',
  106399. '{afterBoxLabelTextTpl}',
  106400. '</label>',
  106401. '{afterBoxLabelTpl}',
  106402. '</tpl>',
  106403. // Creates not an actual checkbox, but a button which is given aria role="checkbox" (If ARIA is required) and
  106404. // styled with a custom checkbox image. This allows greater control and consistency in
  106405. // styling, and using a button allows it to gain focus and handle keyboard nav properly.
  106406. '<input type="button" id="{id}" {inputAttrTpl}',
  106407. '<tpl if="tabIdx"> tabIndex="{tabIdx}"</tpl>',
  106408. '<tpl if="disabled"> disabled="disabled"</tpl>',
  106409. '<tpl if="fieldStyle"> style="{fieldStyle}"</tpl>',
  106410. ' class="{fieldCls} {typeCls}" autocomplete="off" hidefocus="true" />',
  106411. '<tpl if="boxLabel && boxLabelAlign == \'after\'">',
  106412. '{beforeBoxLabelTpl}',
  106413. '<label id="{cmpId}-boxLabelEl" {boxLabelAttrTpl} class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">',
  106414. '{beforeBoxLabelTextTpl}',
  106415. '{boxLabel}',
  106416. '{afterBoxLabelTextTpl}',
  106417. '</label>',
  106418. '{afterBoxLabelTpl}',
  106419. '</tpl>',
  106420. {
  106421. disableFormats: true,
  106422. compiled: true
  106423. }
  106424. ],
  106425. subTplInsertions: [
  106426. /**
  106427. * @cfg {String/Array/Ext.XTemplate} beforeBoxLabelTpl
  106428. * An optional string or `XTemplate` configuration to insert in the field markup
  106429. * before the box label element. If an `XTemplate` is used, the component's
  106430. * {@link Ext.form.field.Base#getSubTplData subTpl data} serves as the context.
  106431. */
  106432. 'beforeBoxLabelTpl',
  106433. /**
  106434. * @cfg {String/Array/Ext.XTemplate} afterBoxLabelTpl
  106435. * An optional string or `XTemplate` configuration to insert in the field markup
  106436. * after the box label element. If an `XTemplate` is used, the component's
  106437. * {@link Ext.form.field.Base#getSubTplData subTpl data} serves as the context.
  106438. */
  106439. 'afterBoxLabelTpl',
  106440. /**
  106441. * @cfg {String/Array/Ext.XTemplate} beforeBoxLabelTextTpl
  106442. * An optional string or `XTemplate` configuration to insert in the field markup
  106443. * before the box label text. If an `XTemplate` is used, the component's
  106444. * {@link Ext.form.field.Base#getSubTplData subTpl data} serves as the context.
  106445. */
  106446. 'beforeBoxLabelTextTpl',
  106447. /**
  106448. * @cfg {String/Array/Ext.XTemplate} afterBoxLabelTextTpl
  106449. * An optional string or `XTemplate` configuration to insert in the field markup
  106450. * after the box label text. If an `XTemplate` is used, the component's
  106451. * {@link Ext.form.field.Base#getSubTplData subTpl data} serves as the context.
  106452. */
  106453. 'afterBoxLabelTextTpl',
  106454. /**
  106455. * @cfg {String/Array/Ext.XTemplate} boxLabelAttrTpl
  106456. * An optional string or `XTemplate` configuration to insert in the field markup
  106457. * inside the box label element (as attributes). If an `XTemplate` is used, the component's
  106458. * {@link Ext.form.field.Base#getSubTplData subTpl data} serves as the context.
  106459. */
  106460. 'boxLabelAttrTpl',
  106461. // inherited
  106462. 'inputAttrTpl'
  106463. ],
  106464. /*
  106465. * @property {Boolean} isCheckbox
  106466. * `true` in this class to identify an object as an instantiated Checkbox, or subclass thereof.
  106467. */
  106468. isCheckbox: true,
  106469. /**
  106470. * @cfg {String} [focusCls='x-form-cb-focus']
  106471. * The CSS class to use when the checkbox receives focus
  106472. */
  106473. focusCls: 'form-cb-focus',
  106474. /**
  106475. * @cfg {String} [fieldCls='x-form-field']
  106476. * The default CSS class for the checkbox
  106477. */
  106478. /**
  106479. * @cfg {String} [fieldBodyCls='x-form-cb-wrap']
  106480. * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
  106481. * .
  106482. */
  106483. fieldBodyCls: Ext.baseCSSPrefix + 'form-cb-wrap',
  106484. /**
  106485. * @cfg {Boolean} checked
  106486. * true if the checkbox should render initially checked
  106487. */
  106488. checked: false,
  106489. /**
  106490. * @cfg {String} [checkedCls='x-form-cb-checked']
  106491. * The CSS class added to the component's main element when it is in the checked state.
  106492. */
  106493. checkedCls: Ext.baseCSSPrefix + 'form-cb-checked',
  106494. /**
  106495. * @cfg {String} boxLabel
  106496. * An optional text label that will appear next to the checkbox. Whether it appears before or after the checkbox is
  106497. * determined by the {@link #boxLabelAlign} config.
  106498. */
  106499. /**
  106500. * @cfg {String} [boxLabelCls='x-form-cb-label']
  106501. * The CSS class to be applied to the {@link #boxLabel} element
  106502. */
  106503. boxLabelCls: Ext.baseCSSPrefix + 'form-cb-label',
  106504. /**
  106505. * @cfg {String} boxLabelAlign
  106506. * The position relative to the checkbox where the {@link #boxLabel} should appear. Recognized values are 'before'
  106507. * and 'after'.
  106508. */
  106509. boxLabelAlign: 'after',
  106510. /**
  106511. * @cfg {String} inputValue
  106512. * The value that should go into the generated input element's value attribute and should be used as the parameter
  106513. * value when submitting as part of a form.
  106514. */
  106515. inputValue: 'on',
  106516. /**
  106517. * @cfg {String} uncheckedValue
  106518. * If configured, this will be submitted as the checkbox's value during form submit if the checkbox is unchecked. By
  106519. * default this is undefined, which results in nothing being submitted for the checkbox field when the form is
  106520. * submitted (the default behavior of HTML checkboxes).
  106521. */
  106522. /**
  106523. * @cfg {Function} handler
  106524. * A function called when the {@link #checked} value changes (can be used instead of handling the {@link #change
  106525. * change event}).
  106526. * @cfg {Ext.form.field.Checkbox} handler.checkbox The Checkbox being toggled.
  106527. * @cfg {Boolean} handler.checked The new checked state of the checkbox.
  106528. */
  106529. /**
  106530. * @cfg {Object} scope
  106531. * An object to use as the scope ('this' reference) of the {@link #handler} function.
  106532. *
  106533. * Defaults to this Checkbox.
  106534. */
  106535. // private overrides
  106536. checkChangeEvents: [],
  106537. inputType: 'checkbox',
  106538. // private
  106539. onRe: /^on$/i,
  106540. initComponent: function() {
  106541. this.callParent(arguments);
  106542. this.getManager().add(this);
  106543. },
  106544. initValue: function() {
  106545. var me = this,
  106546. checked = !!me.checked;
  106547. /**
  106548. * @property {Object} originalValue
  106549. * The original value of the field as configured in the {@link #checked} configuration, or as loaded by the last
  106550. * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
  106551. */
  106552. me.originalValue = me.lastValue = checked;
  106553. // Set the initial checked state
  106554. me.setValue(checked);
  106555. },
  106556. getElConfig: function() {
  106557. var me = this;
  106558. // Add the checked class if this begins checked
  106559. if (me.isChecked(me.rawValue, me.inputValue)) {
  106560. me.addCls(me.checkedCls);
  106561. }
  106562. return me.callParent();
  106563. },
  106564. getFieldStyle: function() {
  106565. return Ext.isObject(this.fieldStyle) ? Ext.DomHelper.generateStyles(this.fieldStyle) : this.fieldStyle ||'';
  106566. },
  106567. getSubTplData: function() {
  106568. var me = this;
  106569. return Ext.apply(me.callParent(), {
  106570. disabled : me.readOnly || me.disabled,
  106571. boxLabel : me.boxLabel,
  106572. boxLabelCls : me.boxLabelCls,
  106573. boxLabelAlign : me.boxLabelAlign
  106574. });
  106575. },
  106576. initEvents: function() {
  106577. var me = this;
  106578. me.callParent();
  106579. me.mon(me.inputEl, 'click', me.onBoxClick, me);
  106580. },
  106581. /**
  106582. * @private Handle click on the checkbox button
  106583. */
  106584. onBoxClick: function(e) {
  106585. var me = this;
  106586. if (!me.disabled && !me.readOnly) {
  106587. this.setValue(!this.checked);
  106588. }
  106589. },
  106590. /**
  106591. * Returns the checked state of the checkbox.
  106592. * @return {Boolean} True if checked, else false
  106593. */
  106594. getRawValue: function() {
  106595. return this.checked;
  106596. },
  106597. /**
  106598. * Returns the checked state of the checkbox.
  106599. * @return {Boolean} True if checked, else false
  106600. */
  106601. getValue: function() {
  106602. return this.checked;
  106603. },
  106604. /**
  106605. * Returns the submit value for the checkbox which can be used when submitting forms.
  106606. * @return {String} If checked the {@link #inputValue} is returned; otherwise the {@link #uncheckedValue}
  106607. * (or null if the latter is not configured).
  106608. */
  106609. getSubmitValue: function() {
  106610. var unchecked = this.uncheckedValue,
  106611. uncheckedVal = Ext.isDefined(unchecked) ? unchecked : null;
  106612. return this.checked ? this.inputValue : uncheckedVal;
  106613. },
  106614. isChecked: function(rawValue, inputValue) {
  106615. return (rawValue === true || rawValue === 'true' || rawValue === '1' || rawValue === 1 ||
  106616. (((Ext.isString(rawValue) || Ext.isNumber(rawValue)) && inputValue) ? rawValue == inputValue : this.onRe.test(rawValue)));
  106617. },
  106618. /**
  106619. * Sets the checked state of the checkbox.
  106620. *
  106621. * @param {Boolean/String/Number} value The following values will check the checkbox:
  106622. * `true, 'true', '1', 1, or 'on'`, as well as a String that matches the {@link #inputValue}.
  106623. * Any other value will uncheck the checkbox.
  106624. * @return {Boolean} the new checked state of the checkbox
  106625. */
  106626. setRawValue: function(value) {
  106627. var me = this,
  106628. inputEl = me.inputEl,
  106629. checked = me.isChecked(value, me.inputValue);
  106630. if (inputEl) {
  106631. me[checked ? 'addCls' : 'removeCls'](me.checkedCls);
  106632. }
  106633. me.checked = me.rawValue = checked;
  106634. return checked;
  106635. },
  106636. /**
  106637. * Sets the checked state of the checkbox, and invokes change detection.
  106638. * @param {Boolean/String} checked The following values will check the checkbox: `true, 'true', '1', or 'on'`, as
  106639. * well as a String that matches the {@link #inputValue}. Any other value will uncheck the checkbox.
  106640. * @return {Ext.form.field.Checkbox} this
  106641. */
  106642. setValue: function(checked) {
  106643. var me = this,
  106644. boxes, i, len, box;
  106645. // If an array of strings is passed, find all checkboxes in the group with the same name as this
  106646. // one and check all those whose inputValue is in the array, unchecking all the others. This is to
  106647. // facilitate setting values from Ext.form.Basic#setValues, but is not publicly documented as we
  106648. // don't want users depending on this behavior.
  106649. if (Ext.isArray(checked)) {
  106650. boxes = me.getManager().getByName(me.name, me.getFormId()).items;
  106651. len = boxes.length;
  106652. for (i = 0; i < len; ++i) {
  106653. box = boxes[i];
  106654. box.setValue(Ext.Array.contains(checked, box.inputValue));
  106655. }
  106656. } else {
  106657. me.callParent(arguments);
  106658. }
  106659. return me;
  106660. },
  106661. // private
  106662. valueToRaw: function(value) {
  106663. // No extra conversion for checkboxes
  106664. return value;
  106665. },
  106666. /**
  106667. * @private
  106668. * Called when the checkbox's checked state changes. Invokes the {@link #handler} callback
  106669. * function if specified.
  106670. */
  106671. onChange: function(newVal, oldVal) {
  106672. var me = this,
  106673. handler = me.handler;
  106674. if (handler) {
  106675. handler.call(me.scope || me, me, newVal);
  106676. }
  106677. me.callParent(arguments);
  106678. },
  106679. resetOriginalValue: function(/* private */ fromBoxInGroup){
  106680. var me = this,
  106681. boxes,
  106682. box,
  106683. len,
  106684. i;
  106685. // If we're resetting the value of a field in a group, also reset the others.
  106686. if (!fromBoxInGroup) {
  106687. boxes = me.getManager().getByName(me.name, me.getFormId()).items;
  106688. len = boxes.length;
  106689. for (i = 0; i < len; ++i) {
  106690. box = boxes[i];
  106691. if (box !== me) {
  106692. boxes[i].resetOriginalValue(true);
  106693. }
  106694. }
  106695. }
  106696. me.callParent();
  106697. },
  106698. // inherit docs
  106699. beforeDestroy: function(){
  106700. this.callParent();
  106701. this.getManager().removeAtKey(this.id);
  106702. },
  106703. // inherit docs
  106704. getManager: function() {
  106705. return Ext.form.CheckboxManager;
  106706. },
  106707. onEnable: function() {
  106708. var me = this,
  106709. inputEl = me.inputEl;
  106710. me.callParent();
  106711. if (inputEl) {
  106712. // Can still be disabled if the field is readOnly
  106713. inputEl.dom.disabled = me.readOnly;
  106714. }
  106715. },
  106716. setReadOnly: function(readOnly) {
  106717. var me = this,
  106718. inputEl = me.inputEl;
  106719. if (inputEl) {
  106720. // Set the button to disabled when readonly
  106721. inputEl.dom.disabled = !!readOnly || me.disabled;
  106722. }
  106723. me.callParent(arguments);
  106724. },
  106725. getFormId: function(){
  106726. var me = this,
  106727. form;
  106728. if (!me.formId) {
  106729. form = me.up('form');
  106730. if (form) {
  106731. me.formId = form.id;
  106732. }
  106733. }
  106734. return me.formId;
  106735. }
  106736. });
  106737. /**
  106738. * Layout class for {@link Ext.form.field.Trigger} fields. Adjusts the input field size to accommodate
  106739. * the trigger button(s).
  106740. * @private
  106741. */
  106742. Ext.define('Ext.layout.component.field.Trigger', {
  106743. /* Begin Definitions */
  106744. alias: 'layout.triggerfield',
  106745. extend: 'Ext.layout.component.field.Field',
  106746. /* End Definitions */
  106747. type: 'triggerfield',
  106748. beginLayout: function(ownerContext) {
  106749. var me = this,
  106750. owner = me.owner,
  106751. flags;
  106752. ownerContext.triggerWrap = ownerContext.getEl('triggerWrap');
  106753. me.callParent(arguments);
  106754. // if any of these important states have changed, sync them now:
  106755. flags = owner.getTriggerStateFlags();
  106756. if (flags != owner.lastTriggerStateFlags) {
  106757. owner.lastTriggerStateFlags = flags;
  106758. me.updateEditState();
  106759. }
  106760. },
  106761. beginLayoutFixed: function (ownerContext, width, suffix) {
  106762. var me = this,
  106763. owner = ownerContext.target,
  106764. ieInputWidthAdjustment = me.ieInputWidthAdjustment || 0,
  106765. inputWidth = '100%',
  106766. triggerWrap = owner.triggerWrap;
  106767. me.callParent(arguments);
  106768. owner.inputCell.setStyle('width', '100%');
  106769. if(ieInputWidthAdjustment) {
  106770. // adjust for IE 6/7 strict content-box model
  106771. // RTL: This might have to be padding-left unless the senses of the padding styles switch when in RTL mode.
  106772. owner.inputCell.setStyle('padding-right', ieInputWidthAdjustment + 'px');
  106773. if(suffix === 'px') {
  106774. if (owner.inputWidth) {
  106775. inputWidth = owner.inputWidth - owner.getTriggerWidth();
  106776. } else {
  106777. inputWidth = width - ieInputWidthAdjustment - owner.getTriggerWidth();
  106778. }
  106779. inputWidth += 'px';
  106780. }
  106781. }
  106782. owner.inputEl.setStyle('width', inputWidth);
  106783. inputWidth = owner.inputWidth;
  106784. if (inputWidth) {
  106785. triggerWrap.setStyle('width', inputWidth + (ieInputWidthAdjustment) + 'px');
  106786. } else {
  106787. triggerWrap.setStyle('width', width + suffix);
  106788. }
  106789. triggerWrap.setStyle('table-layout', 'fixed');
  106790. },
  106791. beginLayoutShrinkWrap: function (ownerContext) {
  106792. var owner = ownerContext.target,
  106793. emptyString = '',
  106794. inputWidth = owner.inputWidth,
  106795. triggerWrap = owner.triggerWrap,
  106796. ieInputWidthAdjustment = this.ieInputWidthAdjustment || 0;
  106797. this.callParent(arguments);
  106798. if (inputWidth) {
  106799. triggerWrap.setStyle('width', inputWidth + 'px');
  106800. inputWidth = (inputWidth - owner.getTriggerWidth()) + 'px';
  106801. owner.inputEl.setStyle('width', inputWidth);
  106802. owner.inputCell.setStyle('width', inputWidth);
  106803. } else {
  106804. owner.inputCell.setStyle('width', emptyString);
  106805. owner.inputEl.setStyle('width', emptyString);
  106806. triggerWrap.setStyle('width', emptyString);
  106807. triggerWrap.setStyle('table-layout', 'auto');
  106808. }
  106809. },
  106810. getTextWidth: function () {
  106811. var me = this,
  106812. owner = me.owner,
  106813. inputEl = owner.inputEl,
  106814. value;
  106815. // Find the width that contains the whole text value
  106816. value = (inputEl.dom.value || (owner.hasFocus ? '' : owner.emptyText) || '') + owner.growAppend;
  106817. return inputEl.getTextWidth(value);
  106818. },
  106819. measureContentWidth: function (ownerContext) {
  106820. var me = this,
  106821. owner = me.owner,
  106822. width = me.callParent(arguments),
  106823. inputContext = ownerContext.inputContext,
  106824. calcWidth, max, min;
  106825. if (owner.grow && !ownerContext.state.growHandled) {
  106826. calcWidth = me.getTextWidth() + ownerContext.inputContext.getFrameInfo().width;
  106827. max = owner.growMax;
  106828. min = Math.min(max, width);
  106829. max = Math.max(owner.growMin, max, min);
  106830. // Constrain
  106831. calcWidth = Ext.Number.constrain(calcWidth, owner.growMin, max);
  106832. inputContext.setWidth(calcWidth);
  106833. ownerContext.state.growHandled = true;
  106834. // Now that we've set the inputContext, we need to recalculate the width
  106835. inputContext.domBlock(me, 'width');
  106836. width = NaN;
  106837. }
  106838. return width;
  106839. },
  106840. updateEditState: function() {
  106841. var me = this,
  106842. owner = me.owner,
  106843. inputEl = owner.inputEl,
  106844. noeditCls = Ext.baseCSSPrefix + 'trigger-noedit',
  106845. displayed,
  106846. readOnly;
  106847. if (me.owner.readOnly) {
  106848. inputEl.addCls(noeditCls);
  106849. readOnly = true;
  106850. displayed = false;
  106851. } else {
  106852. if (me.owner.editable) {
  106853. inputEl.removeCls(noeditCls);
  106854. readOnly = false;
  106855. } else {
  106856. inputEl.addCls(noeditCls);
  106857. readOnly = true;
  106858. }
  106859. displayed = !me.owner.hideTrigger;
  106860. }
  106861. owner.triggerCell.setDisplayed(displayed);
  106862. inputEl.dom.readOnly = readOnly;
  106863. }
  106864. });
  106865. /**
  106866. * Layout class for {@link Ext.form.field.ComboBox} fields. Handles sizing the input field.
  106867. * @private
  106868. */
  106869. Ext.define('Ext.layout.component.field.ComboBox', {
  106870. extend: 'Ext.layout.component.field.Trigger',
  106871. alias: 'layout.combobox',
  106872. requires: ['Ext.util.TextMetrics'],
  106873. type: 'combobox',
  106874. startingWidth: null,
  106875. getTextWidth: function () {
  106876. var me = this,
  106877. owner = me.owner,
  106878. store = owner.store,
  106879. field = owner.displayField,
  106880. storeLn = store.data.length,
  106881. value = '',
  106882. i = 0, n = 0, ln, item, width;
  106883. for (; i < storeLn; i++) {
  106884. item = store.getAt(i).data[field];
  106885. ln = item.length;
  106886. // compare the current item's length with the current longest length and store the value
  106887. if (ln > n) {
  106888. n = ln;
  106889. value = item;
  106890. }
  106891. }
  106892. width = Math.max(me.callParent(arguments), owner.inputEl.getTextWidth(value + owner.growAppend));
  106893. // it's important to know the starting width else the inputEl could be resized smaller than the boundlist
  106894. // NOTE that when removing items from the store that the startingWidth needs to be recalculated
  106895. if (!me.startingWidth || owner.removingRecords) {
  106896. me.startingWidth = width;
  106897. // also, if the width is less than growMin reset the default boundlist width
  106898. // or it will appear wider than the component if the trigger is clicked
  106899. if (width < owner.growMin) {
  106900. owner.defaultListConfig.minWidth = owner.growMin;
  106901. }
  106902. owner.removingRecords = false;
  106903. }
  106904. // only resize if the new width is greater than the starting width
  106905. return (width < me.startingWidth) ? me.startingWidth : width;
  106906. }
  106907. });
  106908. /**
  106909. * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
  106910. * The trigger has no default action, so you must assign a function to implement the trigger click handler by overriding
  106911. * {@link #onTriggerClick}. You can create a Trigger field directly, as it renders exactly like a combobox for which you
  106912. * can provide a custom implementation.
  106913. *
  106914. * For example:
  106915. *
  106916. * @example
  106917. * Ext.define('Ext.ux.CustomTrigger', {
  106918. * extend: 'Ext.form.field.Trigger',
  106919. * alias: 'widget.customtrigger',
  106920. *
  106921. * // override onTriggerClick
  106922. * onTriggerClick: function() {
  106923. * Ext.Msg.alert('Status', 'You clicked my trigger!');
  106924. * }
  106925. * });
  106926. *
  106927. * Ext.create('Ext.form.FormPanel', {
  106928. * title: 'Form with TriggerField',
  106929. * bodyPadding: 5,
  106930. * width: 350,
  106931. * renderTo: Ext.getBody(),
  106932. * items:[{
  106933. * xtype: 'customtrigger',
  106934. * fieldLabel: 'Sample Trigger',
  106935. * emptyText: 'click the trigger'
  106936. * }]
  106937. * });
  106938. *
  106939. * However, in general you will most likely want to use Trigger as the base class for a reusable component.
  106940. * {@link Ext.form.field.Date} and {@link Ext.form.field.ComboBox} are perfect examples of this.
  106941. */
  106942. Ext.define('Ext.form.field.Trigger', {
  106943. extend:'Ext.form.field.Text',
  106944. alias: ['widget.triggerfield', 'widget.trigger'],
  106945. requires: ['Ext.DomHelper', 'Ext.util.ClickRepeater', 'Ext.layout.component.field.Trigger'],
  106946. alternateClassName: ['Ext.form.TriggerField', 'Ext.form.TwinTriggerField', 'Ext.form.Trigger'],
  106947. childEls: [
  106948. /**
  106949. * @property {Ext.CompositeElement} triggerEl
  106950. * A composite of all the trigger button elements. Only set after the field has been rendered.
  106951. */
  106952. { name: 'triggerCell', select: '.' + Ext.baseCSSPrefix + 'trigger-cell' },
  106953. { name: 'triggerEl', select: '.' + Ext.baseCSSPrefix + 'form-trigger' },
  106954. /**
  106955. * @property {Ext.Element} triggerWrap
  106956. * A reference to the `TABLE` element which encapsulates the input field and all trigger button(s). Only set after the field has been rendered.
  106957. */
  106958. 'triggerWrap',
  106959. /**
  106960. * @property {Ext.Element} inputCell
  106961. * A reference to the `TD` element wrapping the input element. Only set after the field has been rendered.
  106962. */
  106963. 'inputCell'
  106964. ],
  106965. /**
  106966. * @cfg {String} triggerCls
  106967. * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
  106968. * by default and triggerCls will be **appended** if specified.
  106969. */
  106970. /**
  106971. * @cfg
  106972. * The base CSS class that is always added to the trigger button. The {@link #triggerCls} will be appended in
  106973. * addition to this class.
  106974. */
  106975. triggerBaseCls: Ext.baseCSSPrefix + 'form-trigger',
  106976. /**
  106977. * @cfg
  106978. * The CSS class that is added to the div wrapping the trigger button(s).
  106979. */
  106980. triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap',
  106981. /**
  106982. * @cfg
  106983. * The CSS class that is added to the text field when component is read-only or not editable.
  106984. */
  106985. triggerNoEditCls: Ext.baseCSSPrefix + 'trigger-noedit',
  106986. /**
  106987. * @cfg {Boolean} hideTrigger
  106988. * true to hide the trigger element and display only the base text field
  106989. */
  106990. hideTrigger: false,
  106991. /**
  106992. * @cfg {Boolean} editable
  106993. * false to prevent the user from typing text directly into the field; the field can only have its value set via an
  106994. * action invoked by the trigger.
  106995. */
  106996. editable: true,
  106997. /**
  106998. * @cfg {Boolean} readOnly
  106999. * true to prevent the user from changing the field, and hides the trigger. Supercedes the editable and hideTrigger
  107000. * options if the value is true.
  107001. */
  107002. readOnly: false,
  107003. /**
  107004. * @cfg {Boolean} [selectOnFocus=false]
  107005. * true to select any existing text in the field immediately on focus. Only applies when
  107006. * {@link #editable editable} = true
  107007. */
  107008. /**
  107009. * @cfg {Boolean} repeatTriggerClick
  107010. * true to attach a {@link Ext.util.ClickRepeater click repeater} to the trigger.
  107011. */
  107012. repeatTriggerClick: false,
  107013. /**
  107014. * @method autoSize
  107015. * @private
  107016. */
  107017. autoSize: Ext.emptyFn,
  107018. // private
  107019. monitorTab: true,
  107020. // private
  107021. mimicing: false,
  107022. // private
  107023. triggerIndexRe: /trigger-index-(\d+)/,
  107024. componentLayout: 'triggerfield',
  107025. initComponent: function() {
  107026. this.wrapFocusCls = this.triggerWrapCls + '-focus';
  107027. this.callParent(arguments);
  107028. },
  107029. getSubTplMarkup: function() {
  107030. var me = this,
  107031. field = me.callParent(arguments);
  107032. return '<table id="' + me.id + '-triggerWrap" class="' + Ext.baseCSSPrefix + 'form-trigger-wrap" cellpadding="0" cellspacing="0"><tbody><tr>' +
  107033. '<td id="' + me.id + '-inputCell" class="' + Ext.baseCSSPrefix + 'form-trigger-input-cell">' + field + '</td>' +
  107034. me.getTriggerMarkup() +
  107035. '</tr></tbody></table>';
  107036. },
  107037. getSubTplData: function(){
  107038. var me = this,
  107039. data = me.callParent(),
  107040. readOnly = me.readOnly === true,
  107041. editable = me.editable !== false;
  107042. return Ext.apply(data, {
  107043. editableCls: (readOnly || !editable) ? ' ' + me.triggerNoEditCls : '',
  107044. readOnly: !editable || readOnly
  107045. });
  107046. },
  107047. getLabelableRenderData: function() {
  107048. var me = this,
  107049. triggerWrapCls = me.triggerWrapCls,
  107050. result = me.callParent(arguments);
  107051. return Ext.applyIf(result, {
  107052. triggerWrapCls: triggerWrapCls,
  107053. triggerMarkup: me.getTriggerMarkup()
  107054. });
  107055. },
  107056. getTriggerMarkup: function() {
  107057. var me = this,
  107058. i = 0,
  107059. hideTrigger = (me.readOnly || me.hideTrigger),
  107060. triggerCls,
  107061. triggerBaseCls = me.triggerBaseCls,
  107062. triggerConfigs = [];
  107063. // TODO this trigger<n>Cls API design doesn't feel clean, especially where it butts up against the
  107064. // single triggerCls config. Should rethink this, perhaps something more structured like a list of
  107065. // trigger config objects that hold cls, handler, etc.
  107066. // triggerCls is a synonym for trigger1Cls, so copy it.
  107067. if (!me.trigger1Cls) {
  107068. me.trigger1Cls = me.triggerCls;
  107069. }
  107070. // Create as many trigger elements as we have trigger<n>Cls configs, but always at least one
  107071. for (i = 0; (triggerCls = me['trigger' + (i + 1) + 'Cls']) || i < 1; i++) {
  107072. triggerConfigs.push({
  107073. tag: 'td',
  107074. valign: 'top',
  107075. cls: Ext.baseCSSPrefix + 'trigger-cell',
  107076. style: 'width:' + me.triggerWidth + (hideTrigger ? 'px;display:none' : 'px'),
  107077. cn: {
  107078. cls: [Ext.baseCSSPrefix + 'trigger-index-' + i, triggerBaseCls, triggerCls].join(' '),
  107079. role: 'button'
  107080. }
  107081. });
  107082. }
  107083. triggerConfigs[i - 1].cn.cls += ' ' + triggerBaseCls + '-last';
  107084. return Ext.DomHelper.markup(triggerConfigs);
  107085. },
  107086. disableCheck: function() {
  107087. return !this.disabled;
  107088. },
  107089. // private
  107090. beforeRender: function() {
  107091. var me = this,
  107092. triggerBaseCls = me.triggerBaseCls,
  107093. tempEl;
  107094. /**
  107095. * @property {Number} triggerWidth
  107096. * Width of the trigger element. Unless set explicitly, it will be
  107097. * automatically calculated through creating a temporary element
  107098. * on page. (That will be done just once per app run.)
  107099. * @private
  107100. */
  107101. if (!me.triggerWidth) {
  107102. tempEl = Ext.resetElement.createChild({
  107103. style: 'position: absolute;',
  107104. cls: Ext.baseCSSPrefix + 'form-trigger'
  107105. });
  107106. Ext.form.field.Trigger.prototype.triggerWidth = tempEl.getWidth();
  107107. tempEl.remove();
  107108. }
  107109. me.callParent();
  107110. if (triggerBaseCls != Ext.baseCSSPrefix + 'form-trigger') {
  107111. // we may need to change the selectors by which we extract trigger elements if is triggerBaseCls isn't the value we
  107112. // stuck in childEls
  107113. me.addChildEls({ name: 'triggerEl', select: '.' + triggerBaseCls });
  107114. }
  107115. // these start correct in the fieldSubTpl:
  107116. me.lastTriggerStateFlags = me.getTriggerStateFlags();
  107117. },
  107118. onRender: function() {
  107119. var me = this;
  107120. me.callParent(arguments);
  107121. me.doc = Ext.getDoc();
  107122. me.initTrigger();
  107123. me.triggerEl.unselectable();
  107124. },
  107125. /**
  107126. * Get the total width of the trigger button area.
  107127. * @return {Number} The total trigger width
  107128. */
  107129. getTriggerWidth: function() {
  107130. var me = this,
  107131. totalTriggerWidth = 0;
  107132. if (me.triggerWrap && !me.hideTrigger && !me.readOnly) {
  107133. totalTriggerWidth = me.triggerEl.getCount() * me.triggerWidth;
  107134. }
  107135. return totalTriggerWidth;
  107136. },
  107137. setHideTrigger: function(hideTrigger) {
  107138. if (hideTrigger != this.hideTrigger) {
  107139. this.hideTrigger = hideTrigger;
  107140. this.updateLayout();
  107141. }
  107142. },
  107143. /**
  107144. * Sets the editable state of this field. This method is the runtime equivalent of setting the 'editable' config
  107145. * option at config time.
  107146. * @param {Boolean} editable True to allow the user to directly edit the field text. If false is passed, the user
  107147. * will only be able to modify the field using the trigger. Will also add a click event to the text field which
  107148. * will call the trigger.
  107149. */
  107150. setEditable: function(editable) {
  107151. if (editable != this.editable) {
  107152. this.editable = editable;
  107153. this.updateLayout();
  107154. }
  107155. },
  107156. /**
  107157. * Sets the read-only state of this field. This method is the runtime equivalent of setting the 'readOnly' config
  107158. * option at config time.
  107159. * @param {Boolean} readOnly True to prevent the user changing the field and explicitly hide the trigger. Setting
  107160. * this to true will supercede settings editable and hideTrigger. Setting this to false will defer back to editable
  107161. * and hideTrigger.
  107162. */
  107163. setReadOnly: function(readOnly) {
  107164. if (readOnly != this.readOnly) {
  107165. this.readOnly = readOnly;
  107166. this.updateLayout();
  107167. }
  107168. },
  107169. // private
  107170. initTrigger: function() {
  107171. var me = this,
  107172. triggerWrap = me.triggerWrap,
  107173. triggerEl = me.triggerEl,
  107174. disableCheck = me.disableCheck,
  107175. els, eLen, el, e, idx;
  107176. if (me.repeatTriggerClick) {
  107177. me.triggerRepeater = new Ext.util.ClickRepeater(triggerWrap, {
  107178. preventDefault: true,
  107179. handler: me.onTriggerWrapClick,
  107180. listeners: {
  107181. mouseup: me.onTriggerWrapMouseup,
  107182. scope: me
  107183. },
  107184. scope: me
  107185. });
  107186. } else {
  107187. me.mon(triggerWrap, {
  107188. click: me.onTriggerWrapClick,
  107189. mouseup: me.onTriggerWrapMouseup,
  107190. scope: me
  107191. });
  107192. }
  107193. triggerEl.setVisibilityMode(Ext.Element.DISPLAY);
  107194. triggerEl.addClsOnOver(me.triggerBaseCls + '-over', disableCheck, me);
  107195. els = triggerEl.elements;
  107196. eLen = els.length;
  107197. for (e = 0; e < eLen; e++) {
  107198. el = els[e];
  107199. idx = e+1;
  107200. el.addClsOnOver(me['trigger' + (idx) + 'Cls'] + '-over', disableCheck, me);
  107201. el.addClsOnClick(me['trigger' + (idx) + 'Cls'] + '-click', disableCheck, me);
  107202. }
  107203. triggerEl.addClsOnClick(me.triggerBaseCls + '-click', disableCheck, me);
  107204. },
  107205. // private
  107206. onDestroy: function() {
  107207. var me = this;
  107208. Ext.destroyMembers(me, 'triggerRepeater', 'triggerWrap', 'triggerEl');
  107209. delete me.doc;
  107210. me.callParent();
  107211. },
  107212. // private
  107213. onFocus: function() {
  107214. var me = this;
  107215. me.callParent(arguments);
  107216. if (!me.mimicing) {
  107217. me.bodyEl.addCls(me.wrapFocusCls);
  107218. me.mimicing = true;
  107219. me.mon(me.doc, 'mousedown', me.mimicBlur, me, {
  107220. delay: 10
  107221. });
  107222. if (me.monitorTab) {
  107223. me.on('specialkey', me.checkTab, me);
  107224. }
  107225. }
  107226. },
  107227. // private
  107228. checkTab: function(me, e) {
  107229. if (!this.ignoreMonitorTab && e.getKey() == e.TAB) {
  107230. this.triggerBlur();
  107231. }
  107232. },
  107233. /**
  107234. * Returns a set of flags that describe the trigger state. These are just used to easily
  107235. * determine if the DOM is out-of-sync with the component's properties.
  107236. * @private
  107237. */
  107238. getTriggerStateFlags: function () {
  107239. var me = this,
  107240. state = 0;
  107241. if (me.readOnly) {
  107242. state += 1;
  107243. }
  107244. if (me.editable) {
  107245. state += 2;
  107246. }
  107247. if (me.hideTrigger) {
  107248. state += 4;
  107249. }
  107250. return state;
  107251. },
  107252. /**
  107253. * @private
  107254. * The default blur handling must not occur for a TriggerField, implementing this template method as emptyFn disables that.
  107255. * Instead the tab key is monitored, and the superclass's onBlur is called when tab is detected
  107256. */
  107257. onBlur: Ext.emptyFn,
  107258. // private
  107259. mimicBlur: function(e) {
  107260. if (!this.isDestroyed && !this.bodyEl.contains(e.target) && this.validateBlur(e)) {
  107261. this.triggerBlur(e);
  107262. }
  107263. },
  107264. // private
  107265. triggerBlur: function(e) {
  107266. var me = this;
  107267. me.mimicing = false;
  107268. me.mun(me.doc, 'mousedown', me.mimicBlur, me);
  107269. if (me.monitorTab && me.inputEl) {
  107270. me.un('specialkey', me.checkTab, me);
  107271. }
  107272. Ext.form.field.Trigger.superclass.onBlur.call(me, e);
  107273. if (me.bodyEl) {
  107274. me.bodyEl.removeCls(me.wrapFocusCls);
  107275. }
  107276. },
  107277. // private
  107278. // This should be overridden by any subclass that needs to check whether or not the field can be blurred.
  107279. validateBlur: function(e) {
  107280. return true;
  107281. },
  107282. // private
  107283. // process clicks upon triggers.
  107284. // determine which trigger index, and dispatch to the appropriate click handler
  107285. onTriggerWrapClick: function() {
  107286. var me = this,
  107287. targetEl, match,
  107288. triggerClickMethod,
  107289. event;
  107290. event = arguments[me.triggerRepeater ? 1 : 0];
  107291. if (event && !me.readOnly && !me.disabled) {
  107292. targetEl = event.getTarget('.' + me.triggerBaseCls, null);
  107293. match = targetEl && targetEl.className.match(me.triggerIndexRe);
  107294. if (match) {
  107295. triggerClickMethod = me['onTrigger' + (parseInt(match[1], 10) + 1) + 'Click'] || me.onTriggerClick;
  107296. if (triggerClickMethod) {
  107297. triggerClickMethod.call(me, event);
  107298. }
  107299. }
  107300. }
  107301. },
  107302. // private
  107303. // Handle trigger mouse up gesture. To be implemented in subclasses.
  107304. // Currently the Spinner subclass refocuses the input element upon end of spin.
  107305. onTriggerWrapMouseup: Ext.emptyFn,
  107306. /**
  107307. * @method onTriggerClick
  107308. * @protected
  107309. * The function that should handle the trigger's click event. This method does nothing by default until overridden
  107310. * by an implementing function. See Ext.form.field.ComboBox and Ext.form.field.Date for sample implementations.
  107311. * @param {Ext.EventObject} e
  107312. */
  107313. onTriggerClick: Ext.emptyFn
  107314. /**
  107315. * @cfg {Boolean} grow
  107316. * @private
  107317. */
  107318. /**
  107319. * @cfg {Number} growMin
  107320. * @private
  107321. */
  107322. /**
  107323. * @cfg {Number} growMax
  107324. * @private
  107325. */
  107326. });
  107327. /**
  107328. * An abstract class for fields that have a single trigger which opens a "picker" popup below the field, e.g. a combobox
  107329. * menu list or a date picker. It provides a base implementation for toggling the picker's visibility when the trigger
  107330. * is clicked, as well as keyboard navigation and some basic events. Sizing and alignment of the picker can be
  107331. * controlled via the {@link #matchFieldWidth} and {@link #pickerAlign}/{@link #pickerOffset} config properties
  107332. * respectively.
  107333. *
  107334. * You would not normally use this class directly, but instead use it as the parent class for a specific picker field
  107335. * implementation. Subclasses must implement the {@link #createPicker} method to create a picker component appropriate
  107336. * for the field.
  107337. */
  107338. Ext.define('Ext.form.field.Picker', {
  107339. extend: 'Ext.form.field.Trigger',
  107340. alias: 'widget.pickerfield',
  107341. alternateClassName: 'Ext.form.Picker',
  107342. requires: ['Ext.util.KeyNav'],
  107343. /**
  107344. * @cfg {Boolean} matchFieldWidth
  107345. * Whether the picker dropdown's width should be explicitly set to match the width of the field. Defaults to true.
  107346. */
  107347. matchFieldWidth: true,
  107348. /**
  107349. * @cfg {String} pickerAlign
  107350. * The {@link Ext.Element#alignTo alignment position} with which to align the picker. Defaults to "tl-bl?"
  107351. */
  107352. pickerAlign: 'tl-bl?',
  107353. /**
  107354. * @cfg {Number[]} pickerOffset
  107355. * An offset [x,y] to use in addition to the {@link #pickerAlign} when positioning the picker.
  107356. * Defaults to undefined.
  107357. */
  107358. /**
  107359. * @cfg {String} [openCls='x-pickerfield-open']
  107360. * A class to be added to the field's {@link #bodyEl} element when the picker is opened.
  107361. */
  107362. openCls: Ext.baseCSSPrefix + 'pickerfield-open',
  107363. /**
  107364. * @property {Boolean} isExpanded
  107365. * True if the picker is currently expanded, false if not.
  107366. */
  107367. /**
  107368. * @cfg {Boolean} editable
  107369. * False to prevent the user from typing text directly into the field; the field can only have its value set via
  107370. * selecting a value from the picker. In this state, the picker can also be opened by clicking directly on the input
  107371. * field itself.
  107372. */
  107373. editable: true,
  107374. initComponent: function() {
  107375. this.callParent();
  107376. // Custom events
  107377. this.addEvents(
  107378. /**
  107379. * @event expand
  107380. * Fires when the field's picker is expanded.
  107381. * @param {Ext.form.field.Picker} field This field instance
  107382. */
  107383. 'expand',
  107384. /**
  107385. * @event collapse
  107386. * Fires when the field's picker is collapsed.
  107387. * @param {Ext.form.field.Picker} field This field instance
  107388. */
  107389. 'collapse',
  107390. /**
  107391. * @event select
  107392. * Fires when a value is selected via the picker.
  107393. * @param {Ext.form.field.Picker} field This field instance
  107394. * @param {Object} value The value that was selected. The exact type of this value is dependent on
  107395. * the individual field and picker implementations.
  107396. */
  107397. 'select'
  107398. );
  107399. },
  107400. initEvents: function() {
  107401. var me = this;
  107402. me.callParent();
  107403. // Add handlers for keys to expand/collapse the picker
  107404. me.keyNav = new Ext.util.KeyNav(me.inputEl, {
  107405. down: me.onDownArrow,
  107406. esc: {
  107407. handler: me.onEsc,
  107408. scope: me,
  107409. defaultEventAction: false
  107410. },
  107411. scope: me,
  107412. forceKeyDown: true
  107413. });
  107414. // Non-editable allows opening the picker by clicking the field
  107415. if (!me.editable) {
  107416. me.mon(me.inputEl, 'click', me.onTriggerClick, me);
  107417. }
  107418. // Disable native browser autocomplete
  107419. if (Ext.isGecko) {
  107420. me.inputEl.dom.setAttribute('autocomplete', 'off');
  107421. }
  107422. },
  107423. // private
  107424. onEsc: function(e) {
  107425. var me = this;
  107426. if (me.isExpanded) {
  107427. me.collapse();
  107428. e.stopEvent();
  107429. } else {
  107430. // If there's an ancestor Window which will see the ESC event and hide, ensure this Field blurs
  107431. // so that a down arrow will not pop up a disembodied dropdown list.
  107432. if (me.up('window')) {
  107433. me.blur();
  107434. }
  107435. // Otherwise, only stop the ESC key event if it's not going to bubble up to the FocusManager
  107436. else if ((!Ext.FocusManager || !Ext.FocusManager.enabled)) {
  107437. e.stopEvent();
  107438. }
  107439. }
  107440. },
  107441. onDownArrow: function(e) {
  107442. if (!this.isExpanded) {
  107443. // Don't call expand() directly as there may be additional processing involved before
  107444. // expanding, e.g. in the case of a ComboBox query.
  107445. this.onTriggerClick();
  107446. }
  107447. },
  107448. /**
  107449. * Expands this field's picker dropdown.
  107450. */
  107451. expand: function() {
  107452. var me = this,
  107453. bodyEl, picker, collapseIf;
  107454. if (me.rendered && !me.isExpanded && !me.isDestroyed) {
  107455. bodyEl = me.bodyEl;
  107456. picker = me.getPicker();
  107457. collapseIf = me.collapseIf;
  107458. // show the picker and set isExpanded flag
  107459. picker.show();
  107460. me.isExpanded = true;
  107461. me.alignPicker();
  107462. bodyEl.addCls(me.openCls);
  107463. // monitor clicking and mousewheel
  107464. me.mon(Ext.getDoc(), {
  107465. mousewheel: collapseIf,
  107466. mousedown: collapseIf,
  107467. scope: me
  107468. });
  107469. Ext.EventManager.onWindowResize(me.alignPicker, me);
  107470. me.fireEvent('expand', me);
  107471. me.onExpand();
  107472. }
  107473. },
  107474. onExpand: Ext.emptyFn,
  107475. /**
  107476. * Aligns the picker to the input element
  107477. * @protected
  107478. */
  107479. alignPicker: function() {
  107480. var me = this,
  107481. picker = me.getPicker();
  107482. if (me.isExpanded) {
  107483. if (me.matchFieldWidth) {
  107484. // Auto the height (it will be constrained by min and max width) unless there are no records to display.
  107485. picker.setWidth(me.bodyEl.getWidth());
  107486. }
  107487. if (picker.isFloating()) {
  107488. me.doAlign();
  107489. }
  107490. }
  107491. },
  107492. /**
  107493. * Performs the alignment on the picker using the class defaults
  107494. * @private
  107495. */
  107496. doAlign: function(){
  107497. var me = this,
  107498. picker = me.picker,
  107499. aboveSfx = '-above',
  107500. isAbove;
  107501. me.picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset);
  107502. // add the {openCls}-above class if the picker was aligned above
  107503. // the field due to hitting the bottom of the viewport
  107504. isAbove = picker.el.getY() < me.inputEl.getY();
  107505. me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
  107506. picker[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
  107507. },
  107508. /**
  107509. * Collapses this field's picker dropdown.
  107510. */
  107511. collapse: function() {
  107512. if (this.isExpanded && !this.isDestroyed) {
  107513. var me = this,
  107514. openCls = me.openCls,
  107515. picker = me.picker,
  107516. doc = Ext.getDoc(),
  107517. collapseIf = me.collapseIf,
  107518. aboveSfx = '-above';
  107519. // hide the picker and set isExpanded flag
  107520. picker.hide();
  107521. me.isExpanded = false;
  107522. // remove the openCls
  107523. me.bodyEl.removeCls([openCls, openCls + aboveSfx]);
  107524. picker.el.removeCls(picker.baseCls + aboveSfx);
  107525. // remove event listeners
  107526. doc.un('mousewheel', collapseIf, me);
  107527. doc.un('mousedown', collapseIf, me);
  107528. Ext.EventManager.removeResizeListener(me.alignPicker, me);
  107529. me.fireEvent('collapse', me);
  107530. me.onCollapse();
  107531. }
  107532. },
  107533. onCollapse: Ext.emptyFn,
  107534. /**
  107535. * @private
  107536. * Runs on mousewheel and mousedown of doc to check to see if we should collapse the picker
  107537. */
  107538. collapseIf: function(e) {
  107539. var me = this;
  107540. if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !e.within(me.picker.el, false, true) && !me.isEventWithinPickerLoadMask(e)) {
  107541. me.collapse();
  107542. }
  107543. },
  107544. /**
  107545. * Returns a reference to the picker component for this field, creating it if necessary by
  107546. * calling {@link #createPicker}.
  107547. * @return {Ext.Component} The picker component
  107548. */
  107549. getPicker: function() {
  107550. var me = this;
  107551. return me.picker || (me.picker = me.createPicker());
  107552. },
  107553. /**
  107554. * @method
  107555. * Creates and returns the component to be used as this field's picker. Must be implemented by subclasses of Picker.
  107556. * The current field should also be passed as a configuration option to the picker component as the pickerField
  107557. * property.
  107558. */
  107559. createPicker: Ext.emptyFn,
  107560. /**
  107561. * Handles the trigger click; by default toggles between expanding and collapsing the picker component.
  107562. * @protected
  107563. */
  107564. onTriggerClick: function() {
  107565. var me = this;
  107566. if (!me.readOnly && !me.disabled) {
  107567. if (me.isExpanded) {
  107568. me.collapse();
  107569. } else {
  107570. me.expand();
  107571. }
  107572. me.inputEl.focus();
  107573. }
  107574. },
  107575. mimicBlur: function(e) {
  107576. var me = this,
  107577. picker = me.picker;
  107578. // ignore mousedown events within the picker element
  107579. if (!picker || !e.within(picker.el, false, true) && !me.isEventWithinPickerLoadMask(e)) {
  107580. me.callParent(arguments);
  107581. }
  107582. },
  107583. onDestroy : function(){
  107584. var me = this,
  107585. picker = me.picker;
  107586. Ext.EventManager.removeResizeListener(me.alignPicker, me);
  107587. Ext.destroy(me.keyNav);
  107588. if (picker) {
  107589. delete picker.pickerField;
  107590. picker.destroy();
  107591. }
  107592. me.callParent();
  107593. },
  107594. /**
  107595. * returns true if the picker has a load mask and the passed event is within the load mask
  107596. * @private
  107597. * @param {Ext.EventObject} e
  107598. * @return {Boolean}
  107599. */
  107600. isEventWithinPickerLoadMask: function(e) {
  107601. var loadMask = this.picker.loadMask;
  107602. return loadMask ? e.within(loadMask.maskEl, false, true) || e.within(loadMask.el, false, true) : false;
  107603. }
  107604. });
  107605. /**
  107606. * Component layout for {@link Ext.view.BoundList}.
  107607. * @private
  107608. */
  107609. Ext.define('Ext.layout.component.BoundList', {
  107610. extend: 'Ext.layout.component.Auto',
  107611. alias: 'layout.boundlist',
  107612. type: 'component',
  107613. beginLayout: function(ownerContext) {
  107614. var me = this,
  107615. owner = me.owner,
  107616. toolbar = owner.pagingToolbar;
  107617. me.callParent(arguments);
  107618. if (owner.floating) {
  107619. ownerContext.savedXY = owner.el.getXY();
  107620. // move way offscreen to prevent any constraining
  107621. owner.el.setXY([-9999, -9999]);
  107622. }
  107623. if (toolbar) {
  107624. ownerContext.toolbarContext = ownerContext.context.getCmp(toolbar);
  107625. }
  107626. ownerContext.listContext = ownerContext.getEl('listEl');
  107627. },
  107628. beginLayoutCycle: function(ownerContext){
  107629. var owner = this.owner;
  107630. this.callParent(arguments);
  107631. if (ownerContext.heightModel.auto) {
  107632. // Set the el/listEl to be autoHeight since they may have been previously sized
  107633. // by another layout process. If the el was at maxHeight first, the listEl will
  107634. // always size to the maxHeight regardless of the content.
  107635. owner.el.setHeight('auto');
  107636. owner.listEl.setHeight('auto');
  107637. }
  107638. },
  107639. getLayoutItems: function() {
  107640. var toolbar = this.owner.pagingToolbar;
  107641. return toolbar ? [toolbar] : [];
  107642. },
  107643. isValidParent: function() {
  107644. // this only ever gets called with the toolbar, since it's rendered inside we
  107645. // know the parent is always valid
  107646. return true;
  107647. },
  107648. finishedLayout: function(ownerContext) {
  107649. var xy = ownerContext.savedXY;
  107650. this.callParent(arguments);
  107651. if (xy) {
  107652. this.owner.el.setXY(xy);
  107653. }
  107654. },
  107655. measureContentWidth: function(ownerContext) {
  107656. return this.owner.listEl.getWidth();
  107657. },
  107658. measureContentHeight: function(ownerContext) {
  107659. return this.owner.listEl.getHeight();
  107660. },
  107661. publishInnerHeight: function(ownerContext, height) {
  107662. var toolbar = ownerContext.toolbarContext,
  107663. toolbarHeight = 0;
  107664. if (toolbar) {
  107665. toolbarHeight = toolbar.getProp('height');
  107666. }
  107667. if (toolbarHeight === undefined) {
  107668. this.done = false;
  107669. } else {
  107670. ownerContext.listContext.setHeight(height - ownerContext.getFrameInfo().height - toolbarHeight);
  107671. }
  107672. },
  107673. calculateOwnerHeightFromContentHeight: function(ownerContext){
  107674. var height = this.callParent(arguments),
  107675. toolbar = ownerContext.toolbarContext;
  107676. if (toolbar) {
  107677. height += toolbar.getProp('height');
  107678. }
  107679. return height;
  107680. }
  107681. });
  107682. /**
  107683. * Tracks what records are currently selected in a databound component.
  107684. *
  107685. * This is an abstract class and is not meant to be directly used. Databound UI widgets such as
  107686. * {@link Ext.grid.Panel Grid} and {@link Ext.tree.Panel Tree} should subclass Ext.selection.Model
  107687. * and provide a way to binding to the component.
  107688. *
  107689. * The abstract methods `onSelectChange` and `onLastFocusChanged` should be implemented in these
  107690. * subclasses to update the UI widget.
  107691. */
  107692. Ext.define('Ext.selection.Model', {
  107693. extend: 'Ext.util.Observable',
  107694. alternateClassName: 'Ext.AbstractSelectionModel',
  107695. requires: ['Ext.data.StoreManager'],
  107696. mixins: {
  107697. bindable: 'Ext.util.Bindable'
  107698. },
  107699. // lastSelected
  107700. /**
  107701. * @cfg {String} mode
  107702. * Mode of selection. Valid values are:
  107703. *
  107704. * - **SINGLE** - Only allows selecting one item at a time. Use {@link #allowDeselect} to allow
  107705. * deselecting that item. This is the default.
  107706. * - **SIMPLE** - Allows simple selection of multiple items one-by-one. Each click in grid will either
  107707. * select or deselect an item.
  107708. * - **MULTI** - Allows complex selection of multiple items using Ctrl and Shift keys.
  107709. */
  107710. /**
  107711. * @cfg {Boolean} allowDeselect
  107712. * Allow users to deselect a record in a DataView, List or Grid.
  107713. * Only applicable when the {@link #mode} is 'SINGLE'.
  107714. */
  107715. allowDeselect: false,
  107716. /**
  107717. * @property {Ext.util.MixedCollection} [selected=undefined]
  107718. * A MixedCollection that maintains all of the currently selected records.
  107719. * @readonly
  107720. */
  107721. selected: null,
  107722. /**
  107723. * @cfg {Boolean} pruneRemoved
  107724. * Prune records when they are removed from the store from the selection.
  107725. * This is a private flag. For an example of its usage, take a look at
  107726. * Ext.selection.TreeModel.
  107727. */
  107728. pruneRemoved: true,
  107729. constructor: function(cfg) {
  107730. var me = this;
  107731. cfg = cfg || {};
  107732. Ext.apply(me, cfg);
  107733. me.addEvents(
  107734. /**
  107735. * @event
  107736. * Fired after a selection change has occurred
  107737. * @param {Ext.selection.Model} this
  107738. * @param {Ext.data.Model[]} selected The selected records
  107739. */
  107740. 'selectionchange',
  107741. /**
  107742. * @event
  107743. * Fired when a row is focused
  107744. * @param {Ext.selection.Model} this
  107745. * @param {Ext.data.Model} oldFocused The previously focused record
  107746. * @param {Ext.data.Model} newFocused The newly focused record
  107747. */
  107748. 'focuschange'
  107749. );
  107750. me.modes = {
  107751. SINGLE: true,
  107752. SIMPLE: true,
  107753. MULTI: true
  107754. };
  107755. // sets this.selectionMode
  107756. me.setSelectionMode(cfg.mode || me.mode);
  107757. // maintains the currently selected records.
  107758. me.selected = new Ext.util.MixedCollection();
  107759. me.callParent(arguments);
  107760. },
  107761. // binds the store to the selModel.
  107762. bindStore: function(store, initial){
  107763. var me = this;
  107764. me.mixins.bindable.bindStore.apply(me, arguments);
  107765. if(me.store && !initial) {
  107766. me.refresh();
  107767. }
  107768. },
  107769. getStoreListeners: function() {
  107770. var me = this;
  107771. return {
  107772. add: me.onStoreAdd,
  107773. clear: me.onStoreClear,
  107774. remove: me.onStoreRemove,
  107775. update: me.onStoreUpdate
  107776. };
  107777. },
  107778. /**
  107779. * Selects all records in the view.
  107780. * @param {Boolean} suppressEvent True to suppress any select events
  107781. */
  107782. selectAll: function(suppressEvent) {
  107783. var me = this,
  107784. selections = me.store.getRange(),
  107785. i = 0,
  107786. len = selections.length,
  107787. start = me.getSelection().length;
  107788. me.bulkChange = true;
  107789. for (; i < len; i++) {
  107790. me.doSelect(selections[i], true, suppressEvent);
  107791. }
  107792. delete me.bulkChange;
  107793. // fire selection change only if the number of selections differs
  107794. me.maybeFireSelectionChange(me.getSelection().length !== start);
  107795. },
  107796. /**
  107797. * Deselects all records in the view.
  107798. * @param {Boolean} suppressEvent True to suppress any deselect events
  107799. */
  107800. deselectAll: function(suppressEvent) {
  107801. var me = this,
  107802. selections = me.getSelection(),
  107803. i = 0,
  107804. len = selections.length,
  107805. start = me.getSelection().length;
  107806. me.bulkChange = true;
  107807. for (; i < len; i++) {
  107808. me.doDeselect(selections[i], suppressEvent);
  107809. }
  107810. delete me.bulkChange;
  107811. // fire selection change only if the number of selections differs
  107812. me.maybeFireSelectionChange(me.getSelection().length !== start);
  107813. },
  107814. // Provides differentiation of logic between MULTI, SIMPLE and SINGLE
  107815. // selection modes. Requires that an event be passed so that we can know
  107816. // if user held ctrl or shift.
  107817. selectWithEvent: function(record, e, keepExisting) {
  107818. var me = this;
  107819. switch (me.selectionMode) {
  107820. case 'MULTI':
  107821. if (e.ctrlKey && me.isSelected(record)) {
  107822. me.doDeselect(record, false);
  107823. } else if (e.shiftKey && me.lastFocused) {
  107824. me.selectRange(me.lastFocused, record, e.ctrlKey);
  107825. } else if (e.ctrlKey) {
  107826. me.doSelect(record, true, false);
  107827. } else if (me.isSelected(record) && !e.shiftKey && !e.ctrlKey && me.selected.getCount() > 1) {
  107828. me.doSelect(record, keepExisting, false);
  107829. } else {
  107830. me.doSelect(record, false);
  107831. }
  107832. break;
  107833. case 'SIMPLE':
  107834. if (me.isSelected(record)) {
  107835. me.doDeselect(record);
  107836. } else {
  107837. me.doSelect(record, true);
  107838. }
  107839. break;
  107840. case 'SINGLE':
  107841. // if allowDeselect is on and this record isSelected, deselect it
  107842. if (me.allowDeselect && me.isSelected(record)) {
  107843. me.doDeselect(record);
  107844. // select the record and do NOT maintain existing selections
  107845. } else {
  107846. me.doSelect(record, false);
  107847. }
  107848. break;
  107849. }
  107850. },
  107851. /**
  107852. * Selects a range of rows if the selection model {@link #isLocked is not locked}.
  107853. * All rows in between startRow and endRow are also selected.
  107854. * @param {Ext.data.Model/Number} startRow The record or index of the first row in the range
  107855. * @param {Ext.data.Model/Number} endRow The record or index of the last row in the range
  107856. * @param {Boolean} keepExisting (optional) True to retain existing selections
  107857. */
  107858. selectRange : function(startRow, endRow, keepExisting, dir){
  107859. var me = this,
  107860. store = me.store,
  107861. selectedCount = 0,
  107862. i,
  107863. tmp,
  107864. dontDeselect,
  107865. records = [];
  107866. if (me.isLocked()){
  107867. return;
  107868. }
  107869. if (!keepExisting) {
  107870. me.deselectAll(true);
  107871. }
  107872. if (!Ext.isNumber(startRow)) {
  107873. startRow = store.indexOf(startRow);
  107874. }
  107875. if (!Ext.isNumber(endRow)) {
  107876. endRow = store.indexOf(endRow);
  107877. }
  107878. // swap values
  107879. if (startRow > endRow){
  107880. tmp = endRow;
  107881. endRow = startRow;
  107882. startRow = tmp;
  107883. }
  107884. for (i = startRow; i <= endRow; i++) {
  107885. if (me.isSelected(store.getAt(i))) {
  107886. selectedCount++;
  107887. }
  107888. }
  107889. if (!dir) {
  107890. dontDeselect = -1;
  107891. } else {
  107892. dontDeselect = (dir == 'up') ? startRow : endRow;
  107893. }
  107894. for (i = startRow; i <= endRow; i++){
  107895. if (selectedCount == (endRow - startRow + 1)) {
  107896. if (i != dontDeselect) {
  107897. me.doDeselect(i, true);
  107898. }
  107899. } else {
  107900. records.push(store.getAt(i));
  107901. }
  107902. }
  107903. me.doMultiSelect(records, true);
  107904. },
  107905. /**
  107906. * Selects a record instance by record instance or index.
  107907. * @param {Ext.data.Model[]/Number} records An array of records or an index
  107908. * @param {Boolean} [keepExisting=false] True to retain existing selections
  107909. * @param {Boolean} [suppressEvent=false] True to not fire a select event
  107910. */
  107911. select: function(records, keepExisting, suppressEvent) {
  107912. // Automatically selecting eg store.first() or store.last() will pass undefined, so that must just return;
  107913. if (Ext.isDefined(records)) {
  107914. this.doSelect(records, keepExisting, suppressEvent);
  107915. }
  107916. },
  107917. /**
  107918. * Deselects a record instance by record instance or index.
  107919. * @param {Ext.data.Model[]/Number} records An array of records or an index
  107920. * @param {Boolean} [suppressEvent=false] True to not fire a deselect event
  107921. */
  107922. deselect: function(records, suppressEvent) {
  107923. this.doDeselect(records, suppressEvent);
  107924. },
  107925. doSelect: function(records, keepExisting, suppressEvent) {
  107926. var me = this,
  107927. record;
  107928. if (me.locked || !me.store) {
  107929. return;
  107930. }
  107931. if (typeof records === "number") {
  107932. records = [me.store.getAt(records)];
  107933. }
  107934. if (me.selectionMode == "SINGLE" && records) {
  107935. record = records.length ? records[0] : records;
  107936. me.doSingleSelect(record, suppressEvent);
  107937. } else {
  107938. me.doMultiSelect(records, keepExisting, suppressEvent);
  107939. }
  107940. },
  107941. doMultiSelect: function(records, keepExisting, suppressEvent) {
  107942. var me = this,
  107943. selected = me.selected,
  107944. change = false,
  107945. i = 0,
  107946. len, record;
  107947. if (me.locked) {
  107948. return;
  107949. }
  107950. records = !Ext.isArray(records) ? [records] : records;
  107951. len = records.length;
  107952. if (!keepExisting && selected.getCount() > 0) {
  107953. if (me.doDeselect(me.getSelection(), suppressEvent) === false) {
  107954. return;
  107955. }
  107956. // TODO - coalesce the selectionchange event in deselect w/the one below...
  107957. }
  107958. function commit () {
  107959. selected.add(record);
  107960. change = true;
  107961. }
  107962. for (; i < len; i++) {
  107963. record = records[i];
  107964. if (keepExisting && me.isSelected(record)) {
  107965. continue;
  107966. }
  107967. me.lastSelected = record;
  107968. me.onSelectChange(record, true, suppressEvent, commit);
  107969. }
  107970. if (!me.preventFocus) {
  107971. me.setLastFocused(record, suppressEvent);
  107972. }
  107973. // fire selchange if there was a change and there is no suppressEvent flag
  107974. me.maybeFireSelectionChange(change && !suppressEvent);
  107975. },
  107976. // records can be an index, a record or an array of records
  107977. doDeselect: function(records, suppressEvent) {
  107978. var me = this,
  107979. selected = me.selected,
  107980. i = 0,
  107981. len, record,
  107982. attempted = 0,
  107983. accepted = 0;
  107984. if (me.locked || !me.store) {
  107985. return false;
  107986. }
  107987. if (typeof records === "number") {
  107988. records = [me.store.getAt(records)];
  107989. } else if (!Ext.isArray(records)) {
  107990. records = [records];
  107991. }
  107992. function commit () {
  107993. ++accepted;
  107994. selected.remove(record);
  107995. }
  107996. len = records.length;
  107997. for (; i < len; i++) {
  107998. record = records[i];
  107999. if (me.isSelected(record)) {
  108000. if (me.lastSelected == record) {
  108001. me.lastSelected = selected.last();
  108002. }
  108003. ++attempted;
  108004. me.onSelectChange(record, false, suppressEvent, commit);
  108005. }
  108006. }
  108007. // fire selchange if there was a change and there is no suppressEvent flag
  108008. me.maybeFireSelectionChange(accepted > 0 && !suppressEvent);
  108009. return accepted === attempted;
  108010. },
  108011. doSingleSelect: function(record, suppressEvent) {
  108012. var me = this,
  108013. changed = false,
  108014. selected = me.selected;
  108015. if (me.locked) {
  108016. return;
  108017. }
  108018. // already selected.
  108019. // should we also check beforeselect?
  108020. if (me.isSelected(record)) {
  108021. return;
  108022. }
  108023. function commit () {
  108024. me.bulkChange = true;
  108025. if (selected.getCount() > 0 && me.doDeselect(me.lastSelected, suppressEvent) === false) {
  108026. delete me.bulkChange;
  108027. return false;
  108028. }
  108029. delete me.bulkChange;
  108030. selected.add(record);
  108031. me.lastSelected = record;
  108032. changed = true;
  108033. }
  108034. me.onSelectChange(record, true, suppressEvent, commit);
  108035. if (changed) {
  108036. if (!suppressEvent) {
  108037. me.setLastFocused(record);
  108038. }
  108039. me.maybeFireSelectionChange(!suppressEvent);
  108040. }
  108041. },
  108042. /**
  108043. * Sets a record as the last focused record. This does NOT mean
  108044. * that the record has been selected.
  108045. * @param {Ext.data.Model} record
  108046. */
  108047. setLastFocused: function(record, supressFocus) {
  108048. var me = this,
  108049. recordBeforeLast = me.lastFocused;
  108050. me.lastFocused = record;
  108051. // Only call the changed method if in fact the selected record *has* changed.
  108052. if (record !== recordBeforeLast) {
  108053. me.onLastFocusChanged(recordBeforeLast, record, supressFocus);
  108054. }
  108055. },
  108056. /**
  108057. * Determines if this record is currently focused.
  108058. * @param {Ext.data.Model} record
  108059. */
  108060. isFocused: function(record) {
  108061. return record === this.getLastFocused();
  108062. },
  108063. // fire selection change as long as true is not passed
  108064. // into maybeFireSelectionChange
  108065. maybeFireSelectionChange: function(fireEvent) {
  108066. var me = this;
  108067. if (fireEvent && !me.bulkChange) {
  108068. me.fireEvent('selectionchange', me, me.getSelection());
  108069. }
  108070. },
  108071. /**
  108072. * Returns the last selected record.
  108073. */
  108074. getLastSelected: function() {
  108075. return this.lastSelected;
  108076. },
  108077. getLastFocused: function() {
  108078. return this.lastFocused;
  108079. },
  108080. /**
  108081. * Returns an array of the currently selected records.
  108082. * @return {Ext.data.Model[]} The selected records
  108083. */
  108084. getSelection: function() {
  108085. return this.selected.getRange();
  108086. },
  108087. /**
  108088. * Returns the current selectionMode.
  108089. * @return {String} The selectionMode: 'SINGLE', 'MULTI' or 'SIMPLE'.
  108090. */
  108091. getSelectionMode: function() {
  108092. return this.selectionMode;
  108093. },
  108094. /**
  108095. * Sets the current selectionMode.
  108096. * @param {String} selMode 'SINGLE', 'MULTI' or 'SIMPLE'.
  108097. */
  108098. setSelectionMode: function(selMode) {
  108099. selMode = selMode ? selMode.toUpperCase() : 'SINGLE';
  108100. // set to mode specified unless it doesnt exist, in that case
  108101. // use single.
  108102. this.selectionMode = this.modes[selMode] ? selMode : 'SINGLE';
  108103. },
  108104. /**
  108105. * Returns true if the selections are locked.
  108106. * @return {Boolean}
  108107. */
  108108. isLocked: function() {
  108109. return this.locked;
  108110. },
  108111. /**
  108112. * Locks the current selection and disables any changes from happening to the selection.
  108113. * @param {Boolean} locked True to lock, false to unlock.
  108114. */
  108115. setLocked: function(locked) {
  108116. this.locked = !!locked;
  108117. },
  108118. /**
  108119. * Returns true if the specified row is selected.
  108120. * @param {Ext.data.Model/Number} record The record or index of the record to check
  108121. * @return {Boolean}
  108122. */
  108123. isSelected: function(record) {
  108124. record = Ext.isNumber(record) ? this.store.getAt(record) : record;
  108125. return this.selected.indexOf(record) !== -1;
  108126. },
  108127. /**
  108128. * Returns true if there are any a selected records.
  108129. * @return {Boolean}
  108130. */
  108131. hasSelection: function() {
  108132. return this.selected.getCount() > 0;
  108133. },
  108134. refresh: function() {
  108135. var me = this,
  108136. store = me.store,
  108137. toBeSelected = [],
  108138. oldSelections = me.getSelection(),
  108139. len = oldSelections.length,
  108140. selection,
  108141. change,
  108142. i = 0,
  108143. lastFocused = me.getLastFocused();
  108144. // Not been bound yet.
  108145. if (!store) {
  108146. return;
  108147. }
  108148. // check to make sure that there are no records
  108149. // missing after the refresh was triggered, prune
  108150. // them from what is to be selected if so
  108151. for (; i < len; i++) {
  108152. selection = oldSelections[i];
  108153. if (!me.pruneRemoved || store.indexOf(selection) !== -1) {
  108154. toBeSelected.push(selection);
  108155. }
  108156. }
  108157. // there was a change from the old selected and
  108158. // the new selection
  108159. if (me.selected.getCount() != toBeSelected.length) {
  108160. change = true;
  108161. }
  108162. me.clearSelections();
  108163. if (store.indexOf(lastFocused) !== -1) {
  108164. // restore the last focus but supress restoring focus
  108165. me.setLastFocused(lastFocused, true);
  108166. }
  108167. if (toBeSelected.length) {
  108168. // perform the selection again
  108169. me.doSelect(toBeSelected, false, true);
  108170. }
  108171. me.maybeFireSelectionChange(change);
  108172. },
  108173. /**
  108174. * A fast reset of the selections without firing events, updating the ui, etc.
  108175. * For private usage only.
  108176. * @private
  108177. */
  108178. clearSelections: function() {
  108179. // reset the entire selection to nothing
  108180. this.selected.clear();
  108181. this.lastSelected = null;
  108182. this.setLastFocused(null);
  108183. },
  108184. // when a record is added to a store
  108185. onStoreAdd: Ext.emptyFn,
  108186. // when a store is cleared remove all selections
  108187. // (if there were any)
  108188. onStoreClear: function() {
  108189. if (this.selected.getCount > 0) {
  108190. this.clearSelections();
  108191. this.maybeFireSelectionChange(true);
  108192. }
  108193. },
  108194. // prune records from the SelectionModel if
  108195. // they were selected at the time they were
  108196. // removed.
  108197. onStoreRemove: function(store, record, index) {
  108198. var me = this,
  108199. selected = me.selected;
  108200. if (me.locked || !me.pruneRemoved) {
  108201. return;
  108202. }
  108203. if (selected.remove(record)) {
  108204. if (me.lastSelected == record) {
  108205. me.lastSelected = null;
  108206. }
  108207. if (me.getLastFocused() == record) {
  108208. me.setLastFocused(null);
  108209. }
  108210. me.maybeFireSelectionChange(true);
  108211. }
  108212. },
  108213. /**
  108214. * Returns the count of selected records.
  108215. * @return {Number} The number of selected records
  108216. */
  108217. getCount: function() {
  108218. return this.selected.getCount();
  108219. },
  108220. // cleanup.
  108221. destroy: Ext.emptyFn,
  108222. // if records are updated
  108223. onStoreUpdate: Ext.emptyFn,
  108224. /**
  108225. * @abstract
  108226. * @private
  108227. */
  108228. onStoreLoad: Ext.emptyFn,
  108229. // @abstract
  108230. onSelectChange: Ext.emptyFn,
  108231. // @abstract
  108232. onLastFocusChanged: function(oldFocused, newFocused) {
  108233. this.fireEvent('focuschange', this, oldFocused, newFocused);
  108234. },
  108235. // @abstract
  108236. onEditorKey: Ext.emptyFn,
  108237. // @abstract
  108238. bindComponent: Ext.emptyFn,
  108239. // @abstract
  108240. beforeViewRender: Ext.emptyFn
  108241. });
  108242. /**
  108243. * @private
  108244. */
  108245. Ext.define('Ext.selection.DataViewModel', {
  108246. extend: 'Ext.selection.Model',
  108247. requires: ['Ext.util.KeyNav'],
  108248. deselectOnContainerClick: true,
  108249. /**
  108250. * @cfg {Boolean} enableKeyNav
  108251. *
  108252. * Turns on/off keyboard navigation within the DataView.
  108253. */
  108254. enableKeyNav: true,
  108255. constructor: function(cfg){
  108256. this.addEvents(
  108257. /**
  108258. * @event beforedeselect
  108259. * Fired before a record is deselected. If any listener returns false, the
  108260. * deselection is cancelled.
  108261. * @param {Ext.selection.DataViewModel} this
  108262. * @param {Ext.data.Model} record The deselected record
  108263. */
  108264. 'beforedeselect',
  108265. /**
  108266. * @event beforeselect
  108267. * Fired before a record is selected. If any listener returns false, the
  108268. * selection is cancelled.
  108269. * @param {Ext.selection.DataViewModel} this
  108270. * @param {Ext.data.Model} record The selected record
  108271. */
  108272. 'beforeselect',
  108273. /**
  108274. * @event deselect
  108275. * Fired after a record is deselected
  108276. * @param {Ext.selection.DataViewModel} this
  108277. * @param {Ext.data.Model} record The deselected record
  108278. */
  108279. 'deselect',
  108280. /**
  108281. * @event select
  108282. * Fired after a record is selected
  108283. * @param {Ext.selection.DataViewModel} this
  108284. * @param {Ext.data.Model} record The selected record
  108285. */
  108286. 'select'
  108287. );
  108288. this.callParent(arguments);
  108289. },
  108290. bindComponent: function(view) {
  108291. var me = this,
  108292. eventListeners = {
  108293. refresh: me.refresh,
  108294. scope: me
  108295. };
  108296. me.view = view;
  108297. me.bindStore(view.getStore());
  108298. eventListeners[view.triggerEvent] = me.onItemClick;
  108299. eventListeners[view.triggerCtEvent] = me.onContainerClick;
  108300. view.on(eventListeners);
  108301. if (me.enableKeyNav) {
  108302. me.initKeyNav(view);
  108303. }
  108304. },
  108305. onItemClick: function(view, record, item, index, e) {
  108306. this.selectWithEvent(record, e);
  108307. },
  108308. onContainerClick: function() {
  108309. if (this.deselectOnContainerClick) {
  108310. this.deselectAll();
  108311. }
  108312. },
  108313. initKeyNav: function(view) {
  108314. var me = this;
  108315. if (!view.rendered) {
  108316. view.on({
  108317. render: Ext.Function.bind(me.initKeyNav, me, [view]),
  108318. single: true
  108319. });
  108320. return;
  108321. }
  108322. view.el.set({
  108323. tabIndex: -1
  108324. });
  108325. me.keyNav = new Ext.util.KeyNav({
  108326. target: view.el,
  108327. ignoreInputFields: true,
  108328. down: Ext.pass(me.onNavKey, [1], me),
  108329. right: Ext.pass(me.onNavKey, [1], me),
  108330. left: Ext.pass(me.onNavKey, [-1], me),
  108331. up: Ext.pass(me.onNavKey, [-1], me),
  108332. scope: me
  108333. });
  108334. },
  108335. onNavKey: function(step) {
  108336. step = step || 1;
  108337. var me = this,
  108338. view = me.view,
  108339. selected = me.getSelection()[0],
  108340. numRecords = me.view.store.getCount(),
  108341. idx;
  108342. if (selected) {
  108343. idx = view.indexOf(view.getNode(selected)) + step;
  108344. } else {
  108345. idx = 0;
  108346. }
  108347. if (idx < 0) {
  108348. idx = numRecords - 1;
  108349. } else if (idx >= numRecords) {
  108350. idx = 0;
  108351. }
  108352. me.select(idx);
  108353. },
  108354. // Allow the DataView to update the ui
  108355. onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
  108356. var me = this,
  108357. view = me.view,
  108358. eventName = isSelected ? 'select' : 'deselect';
  108359. if ((suppressEvent || me.fireEvent('before' + eventName, me, record)) !== false &&
  108360. commitFn() !== false) {
  108361. if (view) {
  108362. if (isSelected) {
  108363. view.onItemSelect(record);
  108364. } else {
  108365. view.onItemDeselect(record);
  108366. }
  108367. }
  108368. if (!suppressEvent) {
  108369. me.fireEvent(eventName, me, record);
  108370. }
  108371. }
  108372. },
  108373. destroy: function(){
  108374. Ext.destroy(this.keyNav);
  108375. this.callParent();
  108376. }
  108377. });
  108378. /**
  108379. * @class Ext.view.AbstractView
  108380. * This is an abstract superclass and should not be used directly. Please see {@link Ext.view.View}.
  108381. * @private
  108382. */
  108383. Ext.define('Ext.view.AbstractView', {
  108384. extend: 'Ext.Component',
  108385. requires: [
  108386. 'Ext.LoadMask',
  108387. 'Ext.data.StoreManager',
  108388. 'Ext.CompositeElementLite',
  108389. 'Ext.DomQuery',
  108390. 'Ext.selection.DataViewModel'
  108391. ],
  108392. mixins: {
  108393. bindable: 'Ext.util.Bindable'
  108394. },
  108395. inheritableStatics: {
  108396. getRecord: function(node) {
  108397. return this.getBoundView(node).getRecord(node);
  108398. },
  108399. getBoundView: function(node) {
  108400. return Ext.getCmp(node.boundView);
  108401. }
  108402. },
  108403. /**
  108404. * @cfg {String/String[]/Ext.XTemplate} tpl (required)
  108405. * The HTML fragment or an array of fragments that will make up the template used by this DataView. This should
  108406. * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
  108407. */
  108408. /**
  108409. * @cfg {Ext.data.Store} store (required)
  108410. * The {@link Ext.data.Store} to bind this DataView to.
  108411. */
  108412. /**
  108413. * @cfg {Boolean} deferInitialRefresh
  108414. * <p>Defaults to <code>true</code> to defer the initial refresh of the view.</p>
  108415. * <p>This allows the View to execute its render and initial layout more quickly because the process will not be encumbered
  108416. * by the expensive update of the view structure.</p>
  108417. * <p><b>Important: </b>Be aware that this will mean that the View's item elements will not be available immediately upon render, so
  108418. * <i>selection</i> may not take place at render time. To access a View's item elements as soon as possible, use the {@link #viewready} event.
  108419. * Or set <code>deferInitialrefresh</code> to false, but this will be at the cost of slower rendering.</p>
  108420. */
  108421. deferInitialRefresh: true,
  108422. /**
  108423. * @cfg {String} itemSelector (required)
  108424. * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
  108425. * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
  108426. * working with. The itemSelector is used to map DOM nodes to records. As such, there should
  108427. * only be one root level element that matches the selector for each record.
  108428. */
  108429. /**
  108430. * @cfg {String} itemCls
  108431. * Specifies the class to be assigned to each element in the view when used in conjunction with the
  108432. * {@link #itemTpl} configuration.
  108433. */
  108434. itemCls: Ext.baseCSSPrefix + 'dataview-item',
  108435. /**
  108436. * @cfg {String/String[]/Ext.XTemplate} itemTpl
  108437. * The inner portion of the item template to be rendered. Follows an XTemplate
  108438. * structure and will be placed inside of a tpl.
  108439. */
  108440. /**
  108441. * @cfg {String} overItemCls
  108442. * A CSS class to apply to each item in the view on mouseover.
  108443. * Setting this will automatically set {@link #trackOver} to `true`.
  108444. */
  108445. //<locale>
  108446. /**
  108447. * @cfg {String} loadingText
  108448. * A string to display during data load operations. If specified, this text will be
  108449. * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
  108450. * contents will continue to display normally until the new data is loaded and the contents are replaced.
  108451. */
  108452. loadingText: 'Loading...',
  108453. //</locale>
  108454. /**
  108455. * @cfg {Boolean/Object} loadMask
  108456. * False to disable a load mask from displaying while the view is loading. This can also be a
  108457. * {@link Ext.LoadMask} configuration object.
  108458. */
  108459. loadMask: true,
  108460. /**
  108461. * @cfg {String} loadingCls
  108462. * The CSS class to apply to the loading message element. Defaults to Ext.LoadMask.prototype.msgCls "x-mask-loading".
  108463. */
  108464. /**
  108465. * @cfg {Boolean} loadingUseMsg
  108466. * Whether or not to use the loading message.
  108467. * @private
  108468. */
  108469. loadingUseMsg: true,
  108470. /**
  108471. * @cfg {Number} loadingHeight
  108472. * If specified, gives an explicit height for the data view when it is showing the {@link #loadingText},
  108473. * if that is specified. This is useful to prevent the view's height from collapsing to zero when the
  108474. * loading mask is applied and there are no other contents in the data view.
  108475. */
  108476. /**
  108477. * @cfg {String} selectedItemCls
  108478. * A CSS class to apply to each selected item in the view.
  108479. */
  108480. selectedItemCls: Ext.baseCSSPrefix + 'item-selected',
  108481. //<locale>
  108482. /**
  108483. * @cfg {String} emptyText
  108484. * The text to display in the view when there is no data to display.
  108485. * Note that when using local data the emptyText will not be displayed unless you set
  108486. * the {@link #deferEmptyText} option to false.
  108487. */
  108488. emptyText: "",
  108489. //</locale>
  108490. /**
  108491. * @cfg {Boolean} deferEmptyText
  108492. * True to defer emptyText being applied until the store's first load.
  108493. */
  108494. deferEmptyText: true,
  108495. /**
  108496. * @cfg {Boolean} trackOver
  108497. * When `true` the {@link #overItemCls} will be applied to rows when hovered over.
  108498. * This in return will also cause {@link Ext.view.View#highlightitem highlightitem} and
  108499. * {@link Ext.view.View#unhighlightitem unhighlightitem} events to be fired.
  108500. *
  108501. * Enabled automatically when the {@link #overItemCls} config is set.
  108502. */
  108503. trackOver: false,
  108504. /**
  108505. * @cfg {Boolean} blockRefresh
  108506. * Set this to true to ignore refresh events on the bound store. This is useful if
  108507. * you wish to provide custom transition animations via a plugin
  108508. */
  108509. blockRefresh: false,
  108510. /**
  108511. * @cfg {Boolean} disableSelection
  108512. * True to disable selection within the DataView. This configuration will lock the selection model
  108513. * that the DataView uses.
  108514. */
  108515. /**
  108516. * @cfg {Boolean} preserveScrollOnRefresh=false
  108517. * True to preserve scroll position across refresh operations.
  108518. */
  108519. preserveScrollOnRefresh: false,
  108520. //private
  108521. last: false,
  108522. triggerEvent: 'itemclick',
  108523. triggerCtEvent: 'containerclick',
  108524. addCmpEvents: function() {
  108525. },
  108526. // private
  108527. initComponent : function(){
  108528. var me = this,
  108529. isDef = Ext.isDefined,
  108530. itemTpl = me.itemTpl,
  108531. memberFn = {};
  108532. if (itemTpl) {
  108533. if (Ext.isArray(itemTpl)) {
  108534. // string array
  108535. itemTpl = itemTpl.join('');
  108536. } else if (Ext.isObject(itemTpl)) {
  108537. // tpl instance
  108538. memberFn = Ext.apply(memberFn, itemTpl.initialConfig);
  108539. itemTpl = itemTpl.html;
  108540. }
  108541. if (!me.itemSelector) {
  108542. me.itemSelector = '.' + me.itemCls;
  108543. }
  108544. itemTpl = Ext.String.format('<tpl for="."><div class="{0}">{1}</div></tpl>', me.itemCls, itemTpl);
  108545. me.tpl = new Ext.XTemplate(itemTpl, memberFn);
  108546. }
  108547. if (!isDef(me.tpl) || !isDef(me.itemSelector)) {
  108548. Ext.Error.raise({
  108549. sourceClass: 'Ext.view.View',
  108550. tpl: me.tpl,
  108551. itemSelector: me.itemSelector,
  108552. msg: "DataView requires both tpl and itemSelector configurations to be defined."
  108553. });
  108554. }
  108555. me.callParent();
  108556. if(Ext.isString(me.tpl) || Ext.isArray(me.tpl)){
  108557. me.tpl = new Ext.XTemplate(me.tpl);
  108558. }
  108559. // backwards compat alias for overClass/selectedClass
  108560. // TODO: Consider support for overCls generation Ext.Component config
  108561. if (isDef(me.overCls) || isDef(me.overClass)) {
  108562. if (Ext.isDefined(Ext.global.console)) {
  108563. Ext.global.console.warn('Ext.view.View: Using the deprecated overCls or overClass configuration. Use overItemCls instead.');
  108564. }
  108565. me.overItemCls = me.overCls || me.overClass;
  108566. delete me.overCls;
  108567. delete me.overClass;
  108568. }
  108569. if (me.overItemCls) {
  108570. me.trackOver = true;
  108571. }
  108572. if (isDef(me.selectedCls) || isDef(me.selectedClass)) {
  108573. if (Ext.isDefined(Ext.global.console)) {
  108574. Ext.global.console.warn('Ext.view.View: Using the deprecated selectedCls or selectedClass configuration. Use selectedItemCls instead.');
  108575. }
  108576. me.selectedItemCls = me.selectedCls || me.selectedClass;
  108577. delete me.selectedCls;
  108578. delete me.selectedClass;
  108579. }
  108580. me.addEvents(
  108581. /**
  108582. * @event beforerefresh
  108583. * Fires before the view is refreshed
  108584. * @param {Ext.view.View} this The DataView object
  108585. */
  108586. 'beforerefresh',
  108587. /**
  108588. * @event refresh
  108589. * Fires when the view is refreshed
  108590. * @param {Ext.view.View} this The DataView object
  108591. */
  108592. 'refresh',
  108593. /**
  108594. * @event viewready
  108595. * Fires when the View's item elements representing Store items has been rendered. If the {@link #deferInitialRefresh} flag
  108596. * was set (and it is <code>true</code> by default), this will be <b>after</b> initial render, and no items will be available
  108597. * for selection until this event fires.
  108598. * @param {Ext.view.View} this
  108599. */
  108600. 'viewready',
  108601. /**
  108602. * @event itemupdate
  108603. * Fires when the node associated with an individual record is updated
  108604. * @param {Ext.data.Model} record The model instance
  108605. * @param {Number} index The index of the record/node
  108606. * @param {HTMLElement} node The node that has just been updated
  108607. */
  108608. 'itemupdate',
  108609. /**
  108610. * @event itemadd
  108611. * Fires when the nodes associated with an recordset have been added to the underlying store
  108612. * @param {Ext.data.Model[]} records The model instance
  108613. * @param {Number} index The index at which the set of record/nodes starts
  108614. * @param {HTMLElement[]} node The node that has just been updated
  108615. */
  108616. 'itemadd',
  108617. /**
  108618. * @event itemremove
  108619. * Fires when the node associated with an individual record is removed
  108620. * @param {Ext.data.Model} record The model instance
  108621. * @param {Number} index The index of the record/node
  108622. */
  108623. 'itemremove'
  108624. );
  108625. me.addCmpEvents();
  108626. // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
  108627. me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
  108628. me.bindStore(me.store, true);
  108629. me.all = new Ext.CompositeElementLite();
  108630. // We track the scroll position
  108631. me.scrollState = {
  108632. top: 0,
  108633. left: 0
  108634. };
  108635. me.on({
  108636. scroll: me.onViewScroll,
  108637. element: 'el',
  108638. scope: me
  108639. });
  108640. },
  108641. onRender: function() {
  108642. var me = this,
  108643. mask = me.loadMask,
  108644. cfg = {
  108645. msg: me.loadingText,
  108646. msgCls: me.loadingCls,
  108647. useMsg: me.loadingUseMsg,
  108648. // The store gets bound in initComponent, so while
  108649. // rendering let's push on the store
  108650. store: me.getMaskStore()
  108651. };
  108652. me.callParent(arguments);
  108653. if (mask) {
  108654. // either a config object
  108655. if (Ext.isObject(mask)) {
  108656. cfg = Ext.apply(cfg, mask);
  108657. }
  108658. // Attach the LoadMask to a *Component* so that it can be sensitive to resizing during long loads.
  108659. // If this DataView is floating, then mask this DataView.
  108660. // Otherwise, mask its owning Container (or this, if there *is* no owning Container).
  108661. // LoadMask captures the element upon render.
  108662. me.loadMask = new Ext.LoadMask(me, cfg);
  108663. me.loadMask.on({
  108664. scope: me,
  108665. beforeshow: me.onMaskBeforeShow,
  108666. hide: me.onMaskHide
  108667. });
  108668. }
  108669. },
  108670. finishRender: function(){
  108671. var me = this;
  108672. me.callParent(arguments);
  108673. // Kick off the refresh before layouts are resumed after the render
  108674. // completes, but after afterrender is fired on the view
  108675. if (!me.up('[collapsed],[hidden]')) {
  108676. me.doFirstRefresh(me.store);
  108677. }
  108678. },
  108679. onBoxReady: function() {
  108680. var me = this;
  108681. me.callParent(arguments);
  108682. // If the refresh was not kicked off on render due to a collapsed or hidden ancestor,
  108683. // kick it off as soon as we get layed out
  108684. if (!me.firstRefreshDone) {
  108685. me.doFirstRefresh(me.store);
  108686. }
  108687. },
  108688. getMaskStore: function(){
  108689. return this.store;
  108690. },
  108691. onMaskBeforeShow: function(){
  108692. var me = this,
  108693. loadingHeight = me.loadingHeight;
  108694. me.getSelectionModel().deselectAll();
  108695. me.all.clear();
  108696. if (loadingHeight && loadingHeight > me.getHeight()) {
  108697. me.hasLoadingHeight = true;
  108698. me.oldMinHeight = me.minHeight;
  108699. me.minHeight = loadingHeight;
  108700. me.updateLayout();
  108701. }
  108702. },
  108703. onMaskHide: function(){
  108704. var me = this;
  108705. if (!me.destroying && me.hasLoadingHeight) {
  108706. me.minHeight = me.oldMinHeight;
  108707. me.updateLayout();
  108708. delete me.hasLoadingHeight;
  108709. }
  108710. },
  108711. beforeRender: function() {
  108712. this.callParent(arguments);
  108713. this.getSelectionModel().beforeViewRender(this);
  108714. },
  108715. afterRender: function() {
  108716. this.callParent(arguments);
  108717. // Init the SelectionModel after any on('render') listeners have been added.
  108718. // Drag plugins create a DragDrop instance in a render listener, and that needs
  108719. // to see an itemmousedown event first.
  108720. this.getSelectionModel().bindComponent(this);
  108721. },
  108722. /**
  108723. * Gets the selection model for this view.
  108724. * @return {Ext.selection.Model} The selection model
  108725. */
  108726. getSelectionModel: function(){
  108727. var me = this,
  108728. mode = 'SINGLE';
  108729. if (!me.selModel) {
  108730. me.selModel = {};
  108731. }
  108732. if (me.simpleSelect) {
  108733. mode = 'SIMPLE';
  108734. } else if (me.multiSelect) {
  108735. mode = 'MULTI';
  108736. }
  108737. Ext.applyIf(me.selModel, {
  108738. allowDeselect: me.allowDeselect,
  108739. mode: mode
  108740. });
  108741. if (!me.selModel.events) {
  108742. me.selModel = new Ext.selection.DataViewModel(me.selModel);
  108743. }
  108744. if (!me.selModel.hasRelaySetup) {
  108745. me.relayEvents(me.selModel, [
  108746. 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect', 'focuschange'
  108747. ]);
  108748. me.selModel.hasRelaySetup = true;
  108749. }
  108750. // lock the selection model if user
  108751. // has disabled selection
  108752. if (me.disableSelection) {
  108753. me.selModel.locked = true;
  108754. }
  108755. return me.selModel;
  108756. },
  108757. /**
  108758. * Refreshes the view by reloading the data from the store and re-rendering the template.
  108759. */
  108760. refresh: function() {
  108761. var me = this,
  108762. targetEl,
  108763. targetParent,
  108764. oldDisplay,
  108765. nextSibling,
  108766. dom,
  108767. records;
  108768. if (!me.rendered || me.isDestroyed) {
  108769. return;
  108770. }
  108771. if (!me.hasListeners.beforerefresh || me.fireEvent('beforerefresh', me) !== false) {
  108772. targetEl = me.getTargetEl();
  108773. records = me.store.getRange();
  108774. dom = targetEl.dom;
  108775. // Updating is much quicker if done when the targetEl is detached from the document, and not displayed.
  108776. // But this resets the scroll position, so when preserving scroll position, this cannot be done.
  108777. if (!me.preserveScrollOnRefresh) {
  108778. targetParent = dom.parentNode;
  108779. oldDisplay = dom.style.display;
  108780. dom.style.display = 'none';
  108781. nextSibling = dom.nextSibling;
  108782. targetParent.removeChild(dom);
  108783. }
  108784. if (me.refreshCounter) {
  108785. me.clearViewEl();
  108786. } else {
  108787. me.fixedNodes = targetEl.dom.childNodes.length;
  108788. me.refreshCounter = 1;
  108789. }
  108790. // Always attempt to create the required markup after the fixedNodes.
  108791. // Usually, for an empty record set, this would be blank, but when the Template
  108792. // Creates markup outside of the record loop, this must still be honoured even if there are no
  108793. // records.
  108794. me.tpl.append(targetEl, me.collectData(records, 0));
  108795. // The emptyText is now appended to the View's element
  108796. // after any fixedNodes.
  108797. if (records.length < 1) {
  108798. if (!me.deferEmptyText || me.hasSkippedEmptyText) {
  108799. Ext.core.DomHelper.insertHtml('beforeEnd', targetEl.dom, me.emptyText);
  108800. }
  108801. me.all.clear();
  108802. } else {
  108803. me.all.fill(Ext.query(me.getItemSelector(), targetEl.dom));
  108804. me.updateIndexes(0);
  108805. }
  108806. me.selModel.refresh();
  108807. me.hasSkippedEmptyText = true;
  108808. if (!me.preserveScrollOnRefresh) {
  108809. targetParent.insertBefore(dom, nextSibling);
  108810. dom.style.display = oldDisplay;
  108811. }
  108812. // Ensure layout system knows about new content size
  108813. this.refreshSize();
  108814. me.fireEvent('refresh', me);
  108815. // Upon first refresh, fire the viewready event.
  108816. // Reconfiguring the grid "renews" this event.
  108817. if (!me.viewReady) {
  108818. // Fire an event when deferred content becomes available.
  108819. // This supports grid Panel's deferRowRender capability
  108820. me.viewReady = true;
  108821. me.fireEvent('viewready', me);
  108822. }
  108823. }
  108824. },
  108825. /**
  108826. * @private
  108827. * Called by the framework when the view is refreshed, or when rows are added or deleted.
  108828. *
  108829. * These operations may cause the view's dimensions to change, and if the owning container
  108830. * is shrinkwrapping this view, then the layout must be updated to accommodate these new dimensions.
  108831. */
  108832. refreshSize: function() {
  108833. var sizeModel = this.getSizeModel();
  108834. if (sizeModel.height.shrinkWrap || sizeModel.width.shrinkWrap) {
  108835. this.updateLayout();
  108836. }
  108837. },
  108838. clearViewEl: function(){
  108839. // The purpose of this is to allow boilerplate HTML nodes to remain in place inside a View
  108840. // while the transient, templated data can be discarded and recreated.
  108841. // The first time through this code, we take a count of the number of existing child nodes.
  108842. // Subsequent refreshes then do not clear the entire element, but remove all nodes
  108843. // *after* the fixedNodes count.
  108844. // In particular, this is used in infinite grid scrolling: A very tall "stretcher" element is
  108845. // inserted into the View's element to create a scrollbar of the correct proportion.
  108846. var me = this,
  108847. el = me.getTargetEl();
  108848. if (me.fixedNodes) {
  108849. while (el.dom.childNodes[me.fixedNodes]) {
  108850. el.dom.removeChild(el.dom.childNodes[me.fixedNodes]);
  108851. }
  108852. } else {
  108853. el.update('');
  108854. }
  108855. me.refreshCounter++;
  108856. },
  108857. // Private template method to be overridden in subclasses.
  108858. onViewScroll: Ext.emptyFn,
  108859. /**
  108860. * Saves the scrollState in a private variable. Must be used in conjunction with restoreScrollState.
  108861. * @private
  108862. */
  108863. saveScrollState: function() {
  108864. if (this.rendered) {
  108865. var dom = this.el.dom,
  108866. state = this.scrollState;
  108867. state.left = dom.scrollLeft;
  108868. state.top = dom.scrollTop;
  108869. }
  108870. },
  108871. /**
  108872. * Restores the scrollState.
  108873. * Must be used in conjunction with saveScrollState
  108874. * @private
  108875. */
  108876. restoreScrollState: function() {
  108877. if (this.rendered) {
  108878. var dom = this.el.dom,
  108879. state = this.scrollState;
  108880. dom.scrollLeft = state.left;
  108881. dom.scrollTop = state.top;
  108882. }
  108883. },
  108884. /**
  108885. * Function which can be overridden to provide custom formatting for each Record that is used by this
  108886. * DataView's {@link #tpl template} to render each node.
  108887. * @param {Object/Object[]} data The raw data object that was used to create the Record.
  108888. * @param {Number} recordIndex the index number of the Record being prepared for rendering.
  108889. * @param {Ext.data.Model} record The Record being prepared for rendering.
  108890. * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
  108891. * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
  108892. */
  108893. prepareData: function(data, index, record) {
  108894. var associatedData, attr;
  108895. if (record) {
  108896. associatedData = record.getAssociatedData();
  108897. for (attr in associatedData) {
  108898. if (associatedData.hasOwnProperty(attr)) {
  108899. data[attr] = associatedData[attr];
  108900. }
  108901. }
  108902. }
  108903. return data;
  108904. },
  108905. /**
  108906. * <p>Function which can be overridden which returns the data object passed to this
  108907. * DataView's {@link #tpl template} to render the whole DataView.</p>
  108908. * <p>This is usually an Array of data objects, each element of which is processed by an
  108909. * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied
  108910. * data object as an Array. However, <i>named</i> properties may be placed into the data object to
  108911. * provide non-repeating data such as headings, totals etc.</p>
  108912. * @param {Ext.data.Model[]} records An Array of {@link Ext.data.Model}s to be rendered into the DataView.
  108913. * @param {Number} startIndex the index number of the Record being prepared for rendering.
  108914. * @return {Object[]} An Array of data objects to be processed by a repeating XTemplate. May also
  108915. * contain <i>named</i> properties.
  108916. */
  108917. collectData : function(records, startIndex){
  108918. var data = [],
  108919. i = 0,
  108920. len = records.length,
  108921. record;
  108922. for (; i < len; i++) {
  108923. record = records[i];
  108924. data[i] = this.prepareData(record.data, startIndex + i, record);
  108925. }
  108926. return data;
  108927. },
  108928. // private
  108929. bufferRender : function(records, index){
  108930. var me = this,
  108931. div = me.renderBuffer || (me.renderBuffer = document.createElement('div'));
  108932. me.tpl.overwrite(div, me.collectData(records, index));
  108933. return Ext.query(me.getItemSelector(), div);
  108934. },
  108935. // private
  108936. onUpdate : function(ds, record){
  108937. var me = this,
  108938. index,
  108939. node;
  108940. if (me.viewReady) {
  108941. index = me.store.indexOf(record);
  108942. if (index > -1) {
  108943. node = me.bufferRender([record], index)[0];
  108944. // ensure the node actually exists in the DOM
  108945. if (me.getNode(record)) {
  108946. me.all.replaceElement(index, node, true);
  108947. me.updateIndexes(index, index);
  108948. // Maintain selection after update
  108949. // TODO: Move to approriate event handler.
  108950. me.selModel.refresh();
  108951. if (me.hasListeners.itemupdate) {
  108952. me.fireEvent('itemupdate', record, index, node);
  108953. }
  108954. return node;
  108955. }
  108956. }
  108957. }
  108958. },
  108959. // private
  108960. onAdd : function(ds, records, index) {
  108961. var me = this,
  108962. nodes;
  108963. if (me.rendered) {
  108964. // If we are adding into an empty view, we must refresh in order that the *full tpl* is applied
  108965. // which might create boilerplate content *around* the record nodes.
  108966. if (me.all.getCount() === 0) {
  108967. me.refresh();
  108968. return;
  108969. }
  108970. nodes = me.bufferRender(records, index);
  108971. me.doAdd(nodes, records, index);
  108972. me.selModel.refresh();
  108973. me.updateIndexes(index);
  108974. // Ensure layout system knows about new content size
  108975. me.refreshSize();
  108976. if (me.hasListeners.itemadd) {
  108977. me.fireEvent('itemadd', records, index, nodes);
  108978. }
  108979. }
  108980. },
  108981. doAdd: function(nodes, records, index) {
  108982. var all = this.all,
  108983. count = all.getCount();
  108984. if (count === 0) {
  108985. this.clearViewEl();
  108986. this.getTargetEl().appendChild(nodes);
  108987. } else if (index < count) {
  108988. if (index === 0) {
  108989. all.item(index).insertSibling(nodes, 'before', true);
  108990. } else {
  108991. all.item(index - 1).insertSibling(nodes, 'after', true);
  108992. }
  108993. } else {
  108994. all.last().insertSibling(nodes, 'after', true);
  108995. }
  108996. Ext.Array.insert(all.elements, index, nodes);
  108997. },
  108998. // private
  108999. onRemove : function(ds, record, index) {
  109000. var me = this;
  109001. if (me.all.getCount()) {
  109002. if (me.store.getCount() === 0) {
  109003. // Refresh so emptyText can be applied if necessary
  109004. me.refresh();
  109005. } else {
  109006. // Just remove the element which corresponds to the removed record
  109007. // The tpl's full HTML will still be in place.
  109008. me.doRemove(record, index);
  109009. if (me.selModel.refreshOnRemove) {
  109010. me.selModel.refresh();
  109011. }
  109012. me.updateIndexes(index);
  109013. }
  109014. // Ensure layout system knows about new content height
  109015. this.refreshSize();
  109016. if (me.hasListeners.itemremove) {
  109017. me.fireEvent('itemremove', record, index);
  109018. }
  109019. }
  109020. },
  109021. doRemove: function(record, index) {
  109022. this.all.removeElement(index, true);
  109023. },
  109024. /**
  109025. * Refreshes an individual node's data from the store.
  109026. * @param {Number} index The item's data index in the store
  109027. */
  109028. refreshNode : function(index){
  109029. this.onUpdate(this.store, this.store.getAt(index));
  109030. },
  109031. // private
  109032. updateIndexes : function(startIndex, endIndex) {
  109033. var ns = this.all.elements,
  109034. records = this.store.getRange(),
  109035. i;
  109036. startIndex = startIndex || 0;
  109037. endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
  109038. for (i = startIndex; i <= endIndex; i++) {
  109039. ns[i].viewIndex = i;
  109040. ns[i].viewRecordId = records[i].internalId;
  109041. if (!ns[i].boundView) {
  109042. ns[i].boundView = this.id;
  109043. }
  109044. }
  109045. },
  109046. /**
  109047. * Returns the store associated with this DataView.
  109048. * @return {Ext.data.Store} The store
  109049. */
  109050. getStore : function() {
  109051. return this.store;
  109052. },
  109053. /**
  109054. * Changes the data store bound to this view and refreshes it.
  109055. * @param {Ext.data.Store} store The store to bind to this view
  109056. */
  109057. bindStore : function(store, initial) {
  109058. var me = this;
  109059. me.mixins.bindable.bindStore.apply(me, arguments);
  109060. // Bind the store to our selection model unless it's the initial bind.
  109061. // Initial bind takes place afterRender
  109062. if (!initial) {
  109063. me.getSelectionModel().bindStore(me.store);
  109064. }
  109065. // If we have already achieved our first layout, refresh immediately.
  109066. // If we have bound to the Store before the first layout, then onBoxReady will
  109067. // call doFirstRefresh
  109068. if (me.componentLayoutCounter) {
  109069. me.doFirstRefresh(store);
  109070. }
  109071. },
  109072. /**
  109073. * @private
  109074. * Perform the first refresh of the View from a newly bound store.
  109075. *
  109076. * This is called when this View has been sized for the first time.
  109077. */
  109078. doFirstRefresh: function(store) {
  109079. var me = this;
  109080. // Flag to prevent a second "first" refresh from onBoxReady
  109081. me.firstRefreshDone = true;
  109082. // 4.1.0: If we have a store, and the Store is *NOT* already loading (a refresh is on the way), then
  109083. // on first layout, refresh regardless of record count.
  109084. // Template may contain boilerplate HTML outside of record iteration loop.
  109085. // Also, emptyText is appended by the refresh method.
  109086. // We call refresh on a defer if this is the initial call, and we are configured to defer the initial refresh.
  109087. if (store && !store.loading) {
  109088. if (me.deferInitialRefresh) {
  109089. me.applyFirstRefresh();
  109090. } else {
  109091. me.refresh();
  109092. }
  109093. }
  109094. },
  109095. applyFirstRefresh: function(){
  109096. var me = this;
  109097. if (me.isDestroyed) {
  109098. return;
  109099. }
  109100. // In the case of an animated collapse/expand, the layout will
  109101. // be marked as though it's complete, yet the element itself may
  109102. // still be animating, which means we could trigger a layout while
  109103. // everything is not in the correct place. As such, wait until the
  109104. // animation has finished before kicking off the refresh. The problem
  109105. // occurs because both the refresh and the animation are running on
  109106. // a timer which makes it impossible to control the order of when
  109107. // the refresh is fired.
  109108. if (me.up('[isCollapsingOrExpanding]')) {
  109109. Ext.Function.defer(me.applyFirstRefresh, 100, me);
  109110. } else {
  109111. Ext.Function.defer(function () {
  109112. if (!me.isDestroyed) {
  109113. me.refresh();
  109114. }
  109115. }, 1);
  109116. }
  109117. },
  109118. onUnbindStore: function(store) {
  109119. this.setMaskBind(null);
  109120. },
  109121. onBindStore: function(store) {
  109122. this.setMaskBind(store);
  109123. },
  109124. setMaskBind: function(store) {
  109125. var mask = this.loadMask;
  109126. if (mask && mask.bindStore) {
  109127. mask.bindStore(store);
  109128. }
  109129. },
  109130. getStoreListeners: function() {
  109131. var me = this;
  109132. return {
  109133. refresh: me.onDataRefresh,
  109134. add: me.onAdd,
  109135. remove: me.onRemove,
  109136. update: me.onUpdate,
  109137. clear: me.refresh
  109138. };
  109139. },
  109140. /**
  109141. * @private
  109142. * Calls this.refresh if this.blockRefresh is not true
  109143. */
  109144. onDataRefresh: function() {
  109145. var me = this,
  109146. // If we have an ancestor in a non-boxready state (collapsed or in-transition, or hidden), and we are still waiting
  109147. // for the first refresh, then block the refresh because that first visible, expanded layout will trigger the refresh
  109148. blockedByAncestor = !me.firstRefreshDone && (!me.rendered || me.up('[collapsed],[isCollapsingOrExpanding],[hidden]'));
  109149. // If are blocking *an initial refresh* because of an ancestor in a non-boxready state,
  109150. // then cancel any defer on the initial refresh that is going to happen on boxReady - that will be a data-driven refresh, NOT
  109151. // a render-time, delayable refresh. This is particularly important if the boxready occurs because of the "preflight" layout
  109152. // of an animated expand. If refresh is delayed it occur during the expand animation and cause errors.
  109153. if (blockedByAncestor) {
  109154. me.deferInitialRefresh = false;
  109155. } else if (me.blockRefresh !== true) {
  109156. me.firstRefreshDone = true;
  109157. me.refresh.apply(me, arguments);
  109158. }
  109159. },
  109160. /**
  109161. * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
  109162. * @param {HTMLElement} node
  109163. * @return {HTMLElement} The template node
  109164. */
  109165. findItemByChild: function(node){
  109166. return Ext.fly(node).findParent(this.getItemSelector(), this.getTargetEl());
  109167. },
  109168. /**
  109169. * Returns the template node by the Ext.EventObject or null if it is not found.
  109170. * @param {Ext.EventObject} e
  109171. */
  109172. findTargetByEvent: function(e) {
  109173. return e.getTarget(this.getItemSelector(), this.getTargetEl());
  109174. },
  109175. /**
  109176. * Gets the currently selected nodes.
  109177. * @return {HTMLElement[]} An array of HTMLElements
  109178. */
  109179. getSelectedNodes: function(){
  109180. var nodes = [],
  109181. records = this.selModel.getSelection(),
  109182. ln = records.length,
  109183. i = 0;
  109184. for (; i < ln; i++) {
  109185. nodes.push(this.getNode(records[i]));
  109186. }
  109187. return nodes;
  109188. },
  109189. /**
  109190. * Gets an array of the records from an array of nodes
  109191. * @param {HTMLElement[]} nodes The nodes to evaluate
  109192. * @return {Ext.data.Model[]} records The {@link Ext.data.Model} objects
  109193. */
  109194. getRecords: function(nodes) {
  109195. var records = [],
  109196. i = 0,
  109197. len = nodes.length,
  109198. data = this.store.data;
  109199. for (; i < len; i++) {
  109200. records[records.length] = data.getByKey(nodes[i].viewRecordId);
  109201. }
  109202. return records;
  109203. },
  109204. /**
  109205. * Gets a record from a node
  109206. * @param {Ext.Element/HTMLElement} node The node to evaluate
  109207. *
  109208. * @return {Ext.data.Model} record The {@link Ext.data.Model} object
  109209. */
  109210. getRecord: function(node){
  109211. return this.store.data.getByKey(Ext.getDom(node).viewRecordId);
  109212. },
  109213. /**
  109214. * Returns true if the passed node is selected, else false.
  109215. * @param {HTMLElement/Number/Ext.data.Model} node The node, node index or record to check
  109216. * @return {Boolean} True if selected, else false
  109217. */
  109218. isSelected : function(node) {
  109219. // TODO: El/Idx/Record
  109220. var r = this.getRecord(node);
  109221. return this.selModel.isSelected(r);
  109222. },
  109223. /**
  109224. * Selects a record instance by record instance or index.
  109225. * @param {Ext.data.Model[]/Number} records An array of records or an index
  109226. * @param {Boolean} keepExisting
  109227. * @param {Boolean} suppressEvent Set to false to not fire a select event
  109228. * @deprecated 4.0 Use {@link Ext.selection.Model#select} instead.
  109229. */
  109230. select: function(records, keepExisting, suppressEvent) {
  109231. this.selModel.select(records, keepExisting, suppressEvent);
  109232. },
  109233. /**
  109234. * Deselects a record instance by record instance or index.
  109235. * @param {Ext.data.Model[]/Number} records An array of records or an index
  109236. * @param {Boolean} suppressEvent Set to false to not fire a deselect event
  109237. */
  109238. deselect: function(records, suppressEvent) {
  109239. this.selModel.deselect(records, suppressEvent);
  109240. },
  109241. /**
  109242. * Gets a template node.
  109243. * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node,
  109244. * the id of a template node or the record associated with the node.
  109245. * @return {HTMLElement} The node or null if it wasn't found
  109246. */
  109247. getNode : function(nodeInfo) {
  109248. if ((!nodeInfo && nodeInfo !== 0) || !this.rendered) {
  109249. return null;
  109250. }
  109251. if (Ext.isString(nodeInfo)) {
  109252. return document.getElementById(nodeInfo);
  109253. }
  109254. if (Ext.isNumber(nodeInfo)) {
  109255. return this.all.elements[nodeInfo];
  109256. }
  109257. if (nodeInfo.isModel) {
  109258. return this.getNodeByRecord(nodeInfo);
  109259. }
  109260. return nodeInfo; // already an HTMLElement
  109261. },
  109262. /**
  109263. * @private
  109264. */
  109265. getNodeByRecord: function(record) {
  109266. var ns = this.all.elements,
  109267. ln = ns.length,
  109268. i = 0;
  109269. for (; i < ln; i++) {
  109270. if (ns[i].viewRecordId === record.internalId) {
  109271. return ns[i];
  109272. }
  109273. }
  109274. return null;
  109275. },
  109276. /**
  109277. * Gets a range nodes.
  109278. * @param {Number} start (optional) The index of the first node in the range
  109279. * @param {Number} end (optional) The index of the last node in the range
  109280. * @return {HTMLElement[]} An array of nodes
  109281. */
  109282. getNodes: function(start, end) {
  109283. var ns = this.all.elements;
  109284. if (end === undefined) {
  109285. end = ns.length;
  109286. } else {
  109287. end++;
  109288. }
  109289. return this.all.elements.slice(start||0, end);
  109290. },
  109291. /**
  109292. * Finds the index of the passed node.
  109293. * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node, the id of a template node
  109294. * or a record associated with a node.
  109295. * @return {Number} The index of the node or -1
  109296. */
  109297. indexOf: function(node) {
  109298. node = this.getNode(node);
  109299. if (!node && node !== 0) {
  109300. return -1;
  109301. }
  109302. if (Ext.isNumber(node.viewIndex)) {
  109303. return node.viewIndex;
  109304. }
  109305. return this.all.indexOf(node);
  109306. },
  109307. onDestroy : function() {
  109308. var me = this;
  109309. me.all.clear();
  109310. me.callParent();
  109311. me.bindStore(null);
  109312. me.selModel.destroy();
  109313. },
  109314. // invoked by the selection model to maintain visual UI cues
  109315. onItemSelect: function(record) {
  109316. var node = this.getNode(record);
  109317. if (node) {
  109318. Ext.fly(node).addCls(this.selectedItemCls);
  109319. }
  109320. },
  109321. // invoked by the selection model to maintain visual UI cues
  109322. onItemDeselect: function(record) {
  109323. var node = this.getNode(record);
  109324. if (node) {
  109325. Ext.fly(node).removeCls(this.selectedItemCls);
  109326. }
  109327. },
  109328. getItemSelector: function() {
  109329. return this.itemSelector;
  109330. }
  109331. }, function() {
  109332. // all of this information is available directly
  109333. // from the SelectionModel itself, the only added methods
  109334. // to DataView regarding selection will perform some transformation/lookup
  109335. // between HTMLElement/Nodes to records and vice versa.
  109336. Ext.deprecate('extjs', '4.0', function() {
  109337. Ext.view.AbstractView.override({
  109338. /**
  109339. * @cfg {Boolean} [multiSelect=false]
  109340. * True to allow selection of more than one item at a time, false to allow selection of only a single item
  109341. * at a time or no selection at all, depending on the value of {@link #singleSelect}.
  109342. * @deprecated 4.1.1 Use {@link Ext.selection.Model#mode} 'MULTI' instead.
  109343. */
  109344. /**
  109345. * @cfg {Boolean} [singleSelect]
  109346. * Allows selection of exactly one item at a time. As this is the default selection mode anyway, this config
  109347. * is completely ignored.
  109348. * @removed 4.1.1 Use {@link Ext.selection.Model#mode} 'SINGLE' instead.
  109349. */
  109350. /**
  109351. * @cfg {Boolean} [simpleSelect=false]
  109352. * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
  109353. * false to force the user to hold Ctrl or Shift to select more than on item.
  109354. * @deprecated 4.1.1 Use {@link Ext.selection.Model#mode} 'SIMPLE' instead.
  109355. */
  109356. /**
  109357. * Gets the number of selected nodes.
  109358. * @return {Number} The node count
  109359. * @deprecated 4.0 Use {@link Ext.selection.Model#getCount} instead.
  109360. */
  109361. getSelectionCount : function(){
  109362. if (Ext.global.console) {
  109363. Ext.global.console.warn("DataView: getSelectionCount will be removed, please interact with the Ext.selection.DataViewModel");
  109364. }
  109365. return this.selModel.getSelection().length;
  109366. },
  109367. /**
  109368. * Gets an array of the selected records
  109369. * @return {Ext.data.Model[]} An array of {@link Ext.data.Model} objects
  109370. * @deprecated 4.0 Use {@link Ext.selection.Model#getSelection} instead.
  109371. */
  109372. getSelectedRecords : function(){
  109373. if (Ext.global.console) {
  109374. Ext.global.console.warn("DataView: getSelectedRecords will be removed, please interact with the Ext.selection.DataViewModel");
  109375. }
  109376. return this.selModel.getSelection();
  109377. },
  109378. select: function(records, keepExisting, supressEvents) {
  109379. if (Ext.global.console) {
  109380. Ext.global.console.warn("DataView: select will be removed, please access select through a DataView's SelectionModel, ie: view.getSelectionModel().select()");
  109381. }
  109382. var sm = this.getSelectionModel();
  109383. return sm.select.apply(sm, arguments);
  109384. },
  109385. /**
  109386. * Deselects all selected records.
  109387. * @deprecated 4.0 Use {@link Ext.selection.Model#deselectAll} instead.
  109388. */
  109389. clearSelections: function() {
  109390. if (Ext.global.console) {
  109391. Ext.global.console.warn("DataView: clearSelections will be removed, please access deselectAll through DataView's SelectionModel, ie: view.getSelectionModel().deselectAll()");
  109392. }
  109393. var sm = this.getSelectionModel();
  109394. return sm.deselectAll();
  109395. }
  109396. });
  109397. });
  109398. });
  109399. /**
  109400. * A mechanism for displaying data using custom layout templates and formatting.
  109401. *
  109402. * The View uses an {@link Ext.XTemplate} as its internal templating mechanism, and is bound to an
  109403. * {@link Ext.data.Store} so that as the data in the store changes the view is automatically updated
  109404. * to reflect the changes. The view also provides built-in behavior for many common events that can
  109405. * occur for its contained items including click, doubleclick, mouseover, mouseout, etc. as well as a
  109406. * built-in selection model. **In order to use these features, an {@link #itemSelector} config must
  109407. * be provided for the View to determine what nodes it will be working with.**
  109408. *
  109409. * The example below binds a View to a {@link Ext.data.Store} and renders it into an {@link Ext.panel.Panel}.
  109410. *
  109411. * @example
  109412. * Ext.define('Image', {
  109413. * extend: 'Ext.data.Model',
  109414. * fields: [
  109415. * { name:'src', type:'string' },
  109416. * { name:'caption', type:'string' }
  109417. * ]
  109418. * });
  109419. *
  109420. * Ext.create('Ext.data.Store', {
  109421. * id:'imagesStore',
  109422. * model: 'Image',
  109423. * data: [
  109424. * { src:'http://www.sencha.com/img/20110215-feat-drawing.png', caption:'Drawing & Charts' },
  109425. * { src:'http://www.sencha.com/img/20110215-feat-data.png', caption:'Advanced Data' },
  109426. * { src:'http://www.sencha.com/img/20110215-feat-html5.png', caption:'Overhauled Theme' },
  109427. * { src:'http://www.sencha.com/img/20110215-feat-perf.png', caption:'Performance Tuned' }
  109428. * ]
  109429. * });
  109430. *
  109431. * var imageTpl = new Ext.XTemplate(
  109432. * '<tpl for=".">',
  109433. * '<div style="margin-bottom: 10px;" class="thumb-wrap">',
  109434. * '<img src="{src}" />',
  109435. * '<br/><span>{caption}</span>',
  109436. * '</div>',
  109437. * '</tpl>'
  109438. * );
  109439. *
  109440. * Ext.create('Ext.view.View', {
  109441. * store: Ext.data.StoreManager.lookup('imagesStore'),
  109442. * tpl: imageTpl,
  109443. * itemSelector: 'div.thumb-wrap',
  109444. * emptyText: 'No images available',
  109445. * renderTo: Ext.getBody()
  109446. * });
  109447. */
  109448. Ext.define('Ext.view.View', {
  109449. extend: 'Ext.view.AbstractView',
  109450. alternateClassName: 'Ext.DataView',
  109451. alias: 'widget.dataview',
  109452. deferHighlight: (Ext.isIE6 || Ext.isIE7) ? 100 : 0,
  109453. inputTagRe: /^textarea$|^input$/i,
  109454. inheritableStatics: {
  109455. EventMap: {
  109456. mousedown: 'MouseDown',
  109457. mouseup: 'MouseUp',
  109458. click: 'Click',
  109459. dblclick: 'DblClick',
  109460. contextmenu: 'ContextMenu',
  109461. mouseover: 'MouseOver',
  109462. mouseout: 'MouseOut',
  109463. mouseenter: 'MouseEnter',
  109464. mouseleave: 'MouseLeave',
  109465. keydown: 'KeyDown',
  109466. focus: 'Focus'
  109467. }
  109468. },
  109469. initComponent: function() {
  109470. var me = this;
  109471. me.callParent();
  109472. if (me.deferHighlight){
  109473. me.setHighlightedItem =
  109474. Ext.Function.createBuffered(me.setHighlightedItem, me.deferHighlight, me);
  109475. }
  109476. },
  109477. addCmpEvents: function() {
  109478. this.addEvents(
  109479. /**
  109480. * @event beforeitemmousedown
  109481. * Fires before the mousedown event on an item is processed. Returns false to cancel the default action.
  109482. * @param {Ext.view.View} this
  109483. * @param {Ext.data.Model} record The record that belongs to the item
  109484. * @param {HTMLElement} item The item's element
  109485. * @param {Number} index The item's index
  109486. * @param {Ext.EventObject} e The raw event object
  109487. */
  109488. 'beforeitemmousedown',
  109489. /**
  109490. * @event beforeitemmouseup
  109491. * Fires before the mouseup event on an item is processed. Returns false to cancel the default action.
  109492. * @param {Ext.view.View} this
  109493. * @param {Ext.data.Model} record The record that belongs to the item
  109494. * @param {HTMLElement} item The item's element
  109495. * @param {Number} index The item's index
  109496. * @param {Ext.EventObject} e The raw event object
  109497. */
  109498. 'beforeitemmouseup',
  109499. /**
  109500. * @event beforeitemmouseenter
  109501. * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action.
  109502. * @param {Ext.view.View} this
  109503. * @param {Ext.data.Model} record The record that belongs to the item
  109504. * @param {HTMLElement} item The item's element
  109505. * @param {Number} index The item's index
  109506. * @param {Ext.EventObject} e The raw event object
  109507. */
  109508. 'beforeitemmouseenter',
  109509. /**
  109510. * @event beforeitemmouseleave
  109511. * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action.
  109512. * @param {Ext.view.View} this
  109513. * @param {Ext.data.Model} record The record that belongs to the item
  109514. * @param {HTMLElement} item The item's element
  109515. * @param {Number} index The item's index
  109516. * @param {Ext.EventObject} e The raw event object
  109517. */
  109518. 'beforeitemmouseleave',
  109519. /**
  109520. * @event beforeitemclick
  109521. * Fires before the click event on an item is processed. Returns false to cancel the default action.
  109522. * @param {Ext.view.View} this
  109523. * @param {Ext.data.Model} record The record that belongs to the item
  109524. * @param {HTMLElement} item The item's element
  109525. * @param {Number} index The item's index
  109526. * @param {Ext.EventObject} e The raw event object
  109527. */
  109528. 'beforeitemclick',
  109529. /**
  109530. * @event beforeitemdblclick
  109531. * Fires before the dblclick event on an item is processed. Returns false to cancel the default action.
  109532. * @param {Ext.view.View} this
  109533. * @param {Ext.data.Model} record The record that belongs to the item
  109534. * @param {HTMLElement} item The item's element
  109535. * @param {Number} index The item's index
  109536. * @param {Ext.EventObject} e The raw event object
  109537. */
  109538. 'beforeitemdblclick',
  109539. /**
  109540. * @event beforeitemcontextmenu
  109541. * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action.
  109542. * @param {Ext.view.View} this
  109543. * @param {Ext.data.Model} record The record that belongs to the item
  109544. * @param {HTMLElement} item The item's element
  109545. * @param {Number} index The item's index
  109546. * @param {Ext.EventObject} e The raw event object
  109547. */
  109548. 'beforeitemcontextmenu',
  109549. /**
  109550. * @event beforeitemkeydown
  109551. * Fires before the keydown event on an item is processed. Returns false to cancel the default action.
  109552. * @param {Ext.view.View} this
  109553. * @param {Ext.data.Model} record The record that belongs to the item
  109554. * @param {HTMLElement} item The item's element
  109555. * @param {Number} index The item's index
  109556. * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
  109557. */
  109558. 'beforeitemkeydown',
  109559. /**
  109560. * @event itemmousedown
  109561. * Fires when there is a mouse down on an item
  109562. * @param {Ext.view.View} this
  109563. * @param {Ext.data.Model} record The record that belongs to the item
  109564. * @param {HTMLElement} item The item's element
  109565. * @param {Number} index The item's index
  109566. * @param {Ext.EventObject} e The raw event object
  109567. */
  109568. 'itemmousedown',
  109569. /**
  109570. * @event itemmouseup
  109571. * Fires when there is a mouse up on an item
  109572. * @param {Ext.view.View} this
  109573. * @param {Ext.data.Model} record The record that belongs to the item
  109574. * @param {HTMLElement} item The item's element
  109575. * @param {Number} index The item's index
  109576. * @param {Ext.EventObject} e The raw event object
  109577. */
  109578. 'itemmouseup',
  109579. /**
  109580. * @event itemmouseenter
  109581. * Fires when the mouse enters an item.
  109582. * @param {Ext.view.View} this
  109583. * @param {Ext.data.Model} record The record that belongs to the item
  109584. * @param {HTMLElement} item The item's element
  109585. * @param {Number} index The item's index
  109586. * @param {Ext.EventObject} e The raw event object
  109587. */
  109588. 'itemmouseenter',
  109589. /**
  109590. * @event itemmouseleave
  109591. * Fires when the mouse leaves an item.
  109592. * @param {Ext.view.View} this
  109593. * @param {Ext.data.Model} record The record that belongs to the item
  109594. * @param {HTMLElement} item The item's element
  109595. * @param {Number} index The item's index
  109596. * @param {Ext.EventObject} e The raw event object
  109597. */
  109598. 'itemmouseleave',
  109599. /**
  109600. * @event itemclick
  109601. * Fires when an item is clicked.
  109602. * @param {Ext.view.View} this
  109603. * @param {Ext.data.Model} record The record that belongs to the item
  109604. * @param {HTMLElement} item The item's element
  109605. * @param {Number} index The item's index
  109606. * @param {Ext.EventObject} e The raw event object
  109607. */
  109608. 'itemclick',
  109609. /**
  109610. * @event itemdblclick
  109611. * Fires when an item is double clicked.
  109612. * @param {Ext.view.View} this
  109613. * @param {Ext.data.Model} record The record that belongs to the item
  109614. * @param {HTMLElement} item The item's element
  109615. * @param {Number} index The item's index
  109616. * @param {Ext.EventObject} e The raw event object
  109617. */
  109618. 'itemdblclick',
  109619. /**
  109620. * @event itemcontextmenu
  109621. * Fires when an item is right clicked.
  109622. * @param {Ext.view.View} this
  109623. * @param {Ext.data.Model} record The record that belongs to the item
  109624. * @param {HTMLElement} item The item's element
  109625. * @param {Number} index The item's index
  109626. * @param {Ext.EventObject} e The raw event object
  109627. */
  109628. 'itemcontextmenu',
  109629. /**
  109630. * @event itemkeydown
  109631. * Fires when a key is pressed while an item is currently selected.
  109632. * @param {Ext.view.View} this
  109633. * @param {Ext.data.Model} record The record that belongs to the item
  109634. * @param {HTMLElement} item The item's element
  109635. * @param {Number} index The item's index
  109636. * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
  109637. */
  109638. 'itemkeydown',
  109639. /**
  109640. * @event beforecontainermousedown
  109641. * Fires before the mousedown event on the container is processed. Returns false to cancel the default action.
  109642. * @param {Ext.view.View} this
  109643. * @param {Ext.EventObject} e The raw event object
  109644. */
  109645. 'beforecontainermousedown',
  109646. /**
  109647. * @event beforecontainermouseup
  109648. * Fires before the mouseup event on the container is processed. Returns false to cancel the default action.
  109649. * @param {Ext.view.View} this
  109650. * @param {Ext.EventObject} e The raw event object
  109651. */
  109652. 'beforecontainermouseup',
  109653. /**
  109654. * @event beforecontainermouseover
  109655. * Fires before the mouseover event on the container is processed. Returns false to cancel the default action.
  109656. * @param {Ext.view.View} this
  109657. * @param {Ext.EventObject} e The raw event object
  109658. */
  109659. 'beforecontainermouseover',
  109660. /**
  109661. * @event beforecontainermouseout
  109662. * Fires before the mouseout event on the container is processed. Returns false to cancel the default action.
  109663. * @param {Ext.view.View} this
  109664. * @param {Ext.EventObject} e The raw event object
  109665. */
  109666. 'beforecontainermouseout',
  109667. /**
  109668. * @event beforecontainerclick
  109669. * Fires before the click event on the container is processed. Returns false to cancel the default action.
  109670. * @param {Ext.view.View} this
  109671. * @param {Ext.EventObject} e The raw event object
  109672. */
  109673. 'beforecontainerclick',
  109674. /**
  109675. * @event beforecontainerdblclick
  109676. * Fires before the dblclick event on the container is processed. Returns false to cancel the default action.
  109677. * @param {Ext.view.View} this
  109678. * @param {Ext.EventObject} e The raw event object
  109679. */
  109680. 'beforecontainerdblclick',
  109681. /**
  109682. * @event beforecontainercontextmenu
  109683. * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action.
  109684. * @param {Ext.view.View} this
  109685. * @param {Ext.EventObject} e The raw event object
  109686. */
  109687. 'beforecontainercontextmenu',
  109688. /**
  109689. * @event beforecontainerkeydown
  109690. * Fires before the keydown event on the container is processed. Returns false to cancel the default action.
  109691. * @param {Ext.view.View} this
  109692. * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
  109693. */
  109694. 'beforecontainerkeydown',
  109695. /**
  109696. * @event containermouseup
  109697. * Fires when there is a mouse up on the container
  109698. * @param {Ext.view.View} this
  109699. * @param {Ext.EventObject} e The raw event object
  109700. */
  109701. 'containermouseup',
  109702. /**
  109703. * @event containermouseover
  109704. * Fires when you move the mouse over the container.
  109705. * @param {Ext.view.View} this
  109706. * @param {Ext.EventObject} e The raw event object
  109707. */
  109708. 'containermouseover',
  109709. /**
  109710. * @event containermouseout
  109711. * Fires when you move the mouse out of the container.
  109712. * @param {Ext.view.View} this
  109713. * @param {Ext.EventObject} e The raw event object
  109714. */
  109715. 'containermouseout',
  109716. /**
  109717. * @event containerclick
  109718. * Fires when the container is clicked.
  109719. * @param {Ext.view.View} this
  109720. * @param {Ext.EventObject} e The raw event object
  109721. */
  109722. 'containerclick',
  109723. /**
  109724. * @event containerdblclick
  109725. * Fires when the container is double clicked.
  109726. * @param {Ext.view.View} this
  109727. * @param {Ext.EventObject} e The raw event object
  109728. */
  109729. 'containerdblclick',
  109730. /**
  109731. * @event containercontextmenu
  109732. * Fires when the container is right clicked.
  109733. * @param {Ext.view.View} this
  109734. * @param {Ext.EventObject} e The raw event object
  109735. */
  109736. 'containercontextmenu',
  109737. /**
  109738. * @event containerkeydown
  109739. * Fires when a key is pressed while the container is focused, and no item is currently selected.
  109740. * @param {Ext.view.View} this
  109741. * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
  109742. */
  109743. 'containerkeydown',
  109744. /**
  109745. * @event
  109746. * @inheritdoc Ext.selection.DataViewModel#selectionchange
  109747. */
  109748. 'selectionchange',
  109749. /**
  109750. * @event
  109751. * @inheritdoc Ext.selection.DataViewModel#beforeselect
  109752. */
  109753. 'beforeselect',
  109754. /**
  109755. * @event
  109756. * @inheritdoc Ext.selection.DataViewModel#beforedeselect
  109757. */
  109758. 'beforedeselect',
  109759. /**
  109760. * @event
  109761. * @inheritdoc Ext.selection.DataViewModel#select
  109762. */
  109763. 'select',
  109764. /**
  109765. * @event
  109766. * @inheritdoc Ext.selection.DataViewModel#deselect
  109767. */
  109768. 'deselect',
  109769. /**
  109770. * @event
  109771. * @inheritdoc Ext.selection.DataViewModel#focuschange
  109772. */
  109773. 'focuschange',
  109774. /**
  109775. * @event highlightitem
  109776. * Fires when a node is highlighted using keyboard navigation, or mouseover.
  109777. * @param {Ext.view.View} view This View Component.
  109778. * @param {Ext.Element} node The highlighted node.
  109779. */
  109780. 'highlightitem',
  109781. /**
  109782. * @event unhighlightitem
  109783. * Fires when a node is unhighlighted using keyboard navigation, or mouseout.
  109784. * @param {Ext.view.View} view This View Component.
  109785. * @param {Ext.Element} node The previously highlighted node.
  109786. */
  109787. 'unhighlightitem'
  109788. );
  109789. },
  109790. getFocusEl: function() {
  109791. return this.getTargetEl();
  109792. },
  109793. // private
  109794. afterRender: function(){
  109795. var me = this;
  109796. me.callParent();
  109797. me.mon(me.getTargetEl(), {
  109798. scope: me,
  109799. /*
  109800. * We need to make copies of this since some of the events fired here will end up triggering
  109801. * a new event to be called and the shared event object will be mutated. In future we should
  109802. * investigate if there are any issues with creating a new event object for each event that
  109803. * is fired.
  109804. */
  109805. freezeEvent: true,
  109806. click: me.handleEvent,
  109807. mousedown: me.handleEvent,
  109808. mouseup: me.handleEvent,
  109809. dblclick: me.handleEvent,
  109810. contextmenu: me.handleEvent,
  109811. mouseover: me.handleEvent,
  109812. mouseout: me.handleEvent,
  109813. keydown: me.handleEvent
  109814. });
  109815. },
  109816. handleEvent: function(e) {
  109817. var me = this,
  109818. key = e.type == 'keydown' && e.getKey();
  109819. if (me.processUIEvent(e) !== false) {
  109820. me.processSpecialEvent(e);
  109821. }
  109822. // After all listeners have processed the event, then unless the user is typing into an input field,
  109823. // prevent browser's default action on SPACE which is to focus the event's target element.
  109824. // Focusing causes the browser to attempt to scroll the element into view.
  109825. if (key === e.SPACE) {
  109826. if (!me.inputTagRe.test(e.getTarget().tagName)) {
  109827. e.stopEvent();
  109828. }
  109829. }
  109830. },
  109831. // Private template method
  109832. processItemEvent: Ext.emptyFn,
  109833. processContainerEvent: Ext.emptyFn,
  109834. processSpecialEvent: Ext.emptyFn,
  109835. /*
  109836. * Returns true if this mouseover/out event is still over the overItem.
  109837. */
  109838. stillOverItem: function (event, overItem) {
  109839. var nowOver;
  109840. // There is this weird bug when you hover over the border of a cell it is saying
  109841. // the target is the table.
  109842. // BrowserBug: IE6 & 7. If me.mouseOverItem has been removed and is no longer
  109843. // in the DOM then accessing .offsetParent will throw an "Unspecified error." exception.
  109844. // typeof'ng and checking to make sure the offsetParent is an object will NOT throw
  109845. // this hard exception.
  109846. if (overItem && typeof(overItem.offsetParent) === "object") {
  109847. // mouseout : relatedTarget == nowOver, target == wasOver
  109848. // mouseover: relatedTarget == wasOver, target == nowOver
  109849. nowOver = (event.type == 'mouseout') ? event.getRelatedTarget() : event.getTarget();
  109850. return Ext.fly(overItem).contains(nowOver);
  109851. }
  109852. return false;
  109853. },
  109854. processUIEvent: function(e) {
  109855. var me = this,
  109856. item = e.getTarget(me.getItemSelector(), me.getTargetEl()),
  109857. map = this.statics().EventMap,
  109858. index, record,
  109859. type = e.type,
  109860. overItem = me.mouseOverItem,
  109861. newType;
  109862. if (!item) {
  109863. if (type == 'mouseover' && me.stillOverItem(e, overItem)) {
  109864. item = overItem;
  109865. }
  109866. // Try to get the selected item to handle the keydown event, otherwise we'll just fire a container keydown event
  109867. if (type == 'keydown') {
  109868. record = me.getSelectionModel().getLastSelected();
  109869. if (record) {
  109870. item = me.getNode(record);
  109871. }
  109872. }
  109873. }
  109874. if (item) {
  109875. index = me.indexOf(item);
  109876. if (!record) {
  109877. record = me.getRecord(item);
  109878. }
  109879. // It is possible for an event to arrive for which there is no record... this
  109880. // can happen with dblclick where the clicks are on removal actions (think a
  109881. // grid w/"delete row" action column)
  109882. if (!record || me.processItemEvent(record, item, index, e) === false) {
  109883. return false;
  109884. }
  109885. newType = me.isNewItemEvent(item, e);
  109886. if (newType === false) {
  109887. return false;
  109888. }
  109889. if (
  109890. (me['onBeforeItem' + map[newType]](record, item, index, e) === false) ||
  109891. (me.fireEvent('beforeitem' + newType, me, record, item, index, e) === false) ||
  109892. (me['onItem' + map[newType]](record, item, index, e) === false)
  109893. ) {
  109894. return false;
  109895. }
  109896. me.fireEvent('item' + newType, me, record, item, index, e);
  109897. }
  109898. else {
  109899. if (
  109900. (me.processContainerEvent(e) === false) ||
  109901. (me['onBeforeContainer' + map[type]](e) === false) ||
  109902. (me.fireEvent('beforecontainer' + type, me, e) === false) ||
  109903. (me['onContainer' + map[type]](e) === false)
  109904. ) {
  109905. return false;
  109906. }
  109907. me.fireEvent('container' + type, me, e);
  109908. }
  109909. return true;
  109910. },
  109911. isNewItemEvent: function (item, e) {
  109912. var me = this,
  109913. overItem = me.mouseOverItem,
  109914. type = e.type;
  109915. switch (type) {
  109916. case 'mouseover':
  109917. if (item === overItem) {
  109918. return false;
  109919. }
  109920. me.mouseOverItem = item;
  109921. return 'mouseenter';
  109922. case 'mouseout':
  109923. // If the currently mouseovered item contains the mouseover target, it's *NOT* a mouseleave
  109924. if (me.stillOverItem(e, overItem)) {
  109925. return false;
  109926. }
  109927. me.mouseOverItem = null;
  109928. return 'mouseleave';
  109929. }
  109930. return type;
  109931. },
  109932. // private
  109933. onItemMouseEnter: function(record, item, index, e) {
  109934. if (this.trackOver) {
  109935. this.highlightItem(item);
  109936. }
  109937. },
  109938. // private
  109939. onItemMouseLeave : function(record, item, index, e) {
  109940. if (this.trackOver) {
  109941. this.clearHighlight();
  109942. }
  109943. },
  109944. // @private, template methods
  109945. onItemMouseDown: Ext.emptyFn,
  109946. onItemMouseUp: Ext.emptyFn,
  109947. onItemFocus: Ext.emptyFn,
  109948. onItemClick: Ext.emptyFn,
  109949. onItemDblClick: Ext.emptyFn,
  109950. onItemContextMenu: Ext.emptyFn,
  109951. onItemKeyDown: Ext.emptyFn,
  109952. onBeforeItemMouseDown: Ext.emptyFn,
  109953. onBeforeItemMouseUp: Ext.emptyFn,
  109954. onBeforeItemFocus: Ext.emptyFn,
  109955. onBeforeItemMouseEnter: Ext.emptyFn,
  109956. onBeforeItemMouseLeave: Ext.emptyFn,
  109957. onBeforeItemClick: Ext.emptyFn,
  109958. onBeforeItemDblClick: Ext.emptyFn,
  109959. onBeforeItemContextMenu: Ext.emptyFn,
  109960. onBeforeItemKeyDown: Ext.emptyFn,
  109961. // @private, template methods
  109962. onContainerMouseDown: Ext.emptyFn,
  109963. onContainerMouseUp: Ext.emptyFn,
  109964. onContainerMouseOver: Ext.emptyFn,
  109965. onContainerMouseOut: Ext.emptyFn,
  109966. onContainerClick: Ext.emptyFn,
  109967. onContainerDblClick: Ext.emptyFn,
  109968. onContainerContextMenu: Ext.emptyFn,
  109969. onContainerKeyDown: Ext.emptyFn,
  109970. onBeforeContainerMouseDown: Ext.emptyFn,
  109971. onBeforeContainerMouseUp: Ext.emptyFn,
  109972. onBeforeContainerMouseOver: Ext.emptyFn,
  109973. onBeforeContainerMouseOut: Ext.emptyFn,
  109974. onBeforeContainerClick: Ext.emptyFn,
  109975. onBeforeContainerDblClick: Ext.emptyFn,
  109976. onBeforeContainerContextMenu: Ext.emptyFn,
  109977. onBeforeContainerKeyDown: Ext.emptyFn,
  109978. //private
  109979. setHighlightedItem: function(item){
  109980. var me = this,
  109981. highlighted = me.highlightedItem;
  109982. if (highlighted != item){
  109983. if (highlighted) {
  109984. Ext.fly(highlighted).removeCls(me.overItemCls);
  109985. me.fireEvent('unhighlightitem', me, highlighted);
  109986. }
  109987. me.highlightedItem = item;
  109988. if (item) {
  109989. //console.log(item.viewIndex);
  109990. Ext.fly(item).addCls(me.overItemCls);
  109991. me.fireEvent('highlightitem', me, item);
  109992. }
  109993. }
  109994. },
  109995. /**
  109996. * Highlights a given item in the View. This is called by the mouseover handler if {@link #overItemCls}
  109997. * and {@link #trackOver} are configured, but can also be called manually by other code, for instance to
  109998. * handle stepping through the list via keyboard navigation.
  109999. * @param {HTMLElement} item The item to highlight
  110000. */
  110001. highlightItem: function(item) {
  110002. this.setHighlightedItem(item);
  110003. },
  110004. /**
  110005. * Un-highlights the currently highlighted item, if any.
  110006. */
  110007. clearHighlight: function() {
  110008. this.setHighlightedItem(undefined);
  110009. },
  110010. onUpdate: function(store, record){
  110011. var me = this,
  110012. node,
  110013. newNode,
  110014. highlighted;
  110015. if (me.viewReady) {
  110016. node = me.getNode(record);
  110017. newNode = me.callParent(arguments);
  110018. highlighted = me.highlightedItem;
  110019. if (highlighted && highlighted === node) {
  110020. delete me.highlightedItem;
  110021. if (newNode) {
  110022. me.highlightItem(newNode);
  110023. }
  110024. }
  110025. }
  110026. },
  110027. refresh: function() {
  110028. this.clearHighlight();
  110029. this.callParent(arguments);
  110030. }
  110031. });
  110032. /**
  110033. * A simple class that renders text directly into a toolbar.
  110034. *
  110035. * @example
  110036. * Ext.create('Ext.panel.Panel', {
  110037. * title: 'Panel with TextItem',
  110038. * width: 300,
  110039. * height: 200,
  110040. * tbar: [
  110041. * { xtype: 'tbtext', text: 'Sample Text Item' }
  110042. * ],
  110043. * renderTo: Ext.getBody()
  110044. * });
  110045. *
  110046. * @constructor
  110047. * Creates a new TextItem
  110048. * @param {Object} text A text string, or a config object containing a #text property
  110049. */
  110050. Ext.define('Ext.toolbar.TextItem', {
  110051. extend: 'Ext.toolbar.Item',
  110052. requires: ['Ext.XTemplate'],
  110053. alias: 'widget.tbtext',
  110054. alternateClassName: 'Ext.Toolbar.TextItem',
  110055. /**
  110056. * @cfg {String} text
  110057. * The text to be used as innerHTML (html tags are accepted).
  110058. */
  110059. text: '',
  110060. renderTpl: '{text}',
  110061. //
  110062. baseCls: Ext.baseCSSPrefix + 'toolbar-text',
  110063. beforeRender : function() {
  110064. var me = this;
  110065. me.callParent();
  110066. Ext.apply(me.renderData, {
  110067. text: me.text
  110068. });
  110069. },
  110070. /**
  110071. * Updates this item's text, setting the text to be used as innerHTML.
  110072. * @param {String} text The text to display (html accepted).
  110073. */
  110074. setText : function(text) {
  110075. var me = this;
  110076. if (me.rendered) {
  110077. me.el.update(text);
  110078. me.updateLayout();
  110079. } else {
  110080. this.text = text;
  110081. }
  110082. }
  110083. });
  110084. /**
  110085. * A field with a pair of up/down spinner buttons. This class is not normally instantiated directly,
  110086. * instead it is subclassed and the {@link #onSpinUp} and {@link #onSpinDown} methods are implemented
  110087. * to handle when the buttons are clicked. A good example of this is the {@link Ext.form.field.Number}
  110088. * field which uses the spinner to increment and decrement the field's value by its
  110089. * {@link Ext.form.field.Number#step step} config value.
  110090. *
  110091. * For example:
  110092. *
  110093. * @example
  110094. * Ext.define('Ext.ux.CustomSpinner', {
  110095. * extend: 'Ext.form.field.Spinner',
  110096. * alias: 'widget.customspinner',
  110097. *
  110098. * // override onSpinUp (using step isn't neccessary)
  110099. * onSpinUp: function() {
  110100. * var me = this;
  110101. * if (!me.readOnly) {
  110102. * var val = parseInt(me.getValue().split(' '), 10)||0; // gets rid of " Pack", defaults to zero on parse failure
  110103. * me.setValue((val + me.step) + ' Pack');
  110104. * }
  110105. * },
  110106. *
  110107. * // override onSpinDown
  110108. * onSpinDown: function() {
  110109. * var val, me = this;
  110110. * if (!me.readOnly) {
  110111. * var val = parseInt(me.getValue().split(' '), 10)||0; // gets rid of " Pack", defaults to zero on parse failure
  110112. * if (val <= me.step) {
  110113. * me.setValue('Dry!');
  110114. * } else {
  110115. * me.setValue((val - me.step) + ' Pack');
  110116. * }
  110117. * }
  110118. * }
  110119. * });
  110120. *
  110121. * Ext.create('Ext.form.FormPanel', {
  110122. * title: 'Form with SpinnerField',
  110123. * bodyPadding: 5,
  110124. * width: 350,
  110125. * renderTo: Ext.getBody(),
  110126. * items:[{
  110127. * xtype: 'customspinner',
  110128. * fieldLabel: 'How Much Beer?',
  110129. * step: 6
  110130. * }]
  110131. * });
  110132. *
  110133. * By default, pressing the up and down arrow keys will also trigger the onSpinUp and onSpinDown methods;
  110134. * to prevent this, set `{@link #keyNavEnabled} = false`.
  110135. */
  110136. Ext.define('Ext.form.field.Spinner', {
  110137. extend: 'Ext.form.field.Trigger',
  110138. alias: 'widget.spinnerfield',
  110139. alternateClassName: 'Ext.form.Spinner',
  110140. requires: ['Ext.util.KeyNav'],
  110141. trigger1Cls: Ext.baseCSSPrefix + 'form-spinner-up',
  110142. trigger2Cls: Ext.baseCSSPrefix + 'form-spinner-down',
  110143. /**
  110144. * @cfg {Boolean} spinUpEnabled
  110145. * Specifies whether the up spinner button is enabled. Defaults to true. To change this after the component is
  110146. * created, use the {@link #setSpinUpEnabled} method.
  110147. */
  110148. spinUpEnabled: true,
  110149. /**
  110150. * @cfg {Boolean} spinDownEnabled
  110151. * Specifies whether the down spinner button is enabled. Defaults to true. To change this after the component is
  110152. * created, use the {@link #setSpinDownEnabled} method.
  110153. */
  110154. spinDownEnabled: true,
  110155. /**
  110156. * @cfg {Boolean} keyNavEnabled
  110157. * Specifies whether the up and down arrow keys should trigger spinning up and down. Defaults to true.
  110158. */
  110159. keyNavEnabled: true,
  110160. /**
  110161. * @cfg {Boolean} mouseWheelEnabled
  110162. * Specifies whether the mouse wheel should trigger spinning up and down while the field has focus.
  110163. * Defaults to true.
  110164. */
  110165. mouseWheelEnabled: true,
  110166. /**
  110167. * @cfg {Boolean} repeatTriggerClick
  110168. * Whether a {@link Ext.util.ClickRepeater click repeater} should be attached to the spinner buttons.
  110169. * Defaults to true.
  110170. */
  110171. repeatTriggerClick: true,
  110172. /**
  110173. * @method
  110174. * @protected
  110175. * This method is called when the spinner up button is clicked, or when the up arrow key is pressed if
  110176. * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
  110177. */
  110178. onSpinUp: Ext.emptyFn,
  110179. /**
  110180. * @method
  110181. * @protected
  110182. * This method is called when the spinner down button is clicked, or when the down arrow key is pressed if
  110183. * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
  110184. */
  110185. onSpinDown: Ext.emptyFn,
  110186. triggerTpl: '<td style="{triggerStyle}">' +
  110187. '<div class="' + Ext.baseCSSPrefix + 'trigger-index-0 ' + Ext.baseCSSPrefix + 'form-trigger ' + Ext.baseCSSPrefix + 'form-spinner-up" role="button"></div>' +
  110188. '<div class="' + Ext.baseCSSPrefix + 'trigger-index-1 ' + Ext.baseCSSPrefix + 'form-trigger ' + Ext.baseCSSPrefix + 'form-spinner-down" role="button"></div>' +
  110189. '</td>' +
  110190. '</tr>',
  110191. initComponent: function() {
  110192. this.callParent();
  110193. this.addEvents(
  110194. /**
  110195. * @event spin
  110196. * Fires when the spinner is made to spin up or down.
  110197. * @param {Ext.form.field.Spinner} this
  110198. * @param {String} direction Either 'up' if spinning up, or 'down' if spinning down.
  110199. */
  110200. 'spin',
  110201. /**
  110202. * @event spinup
  110203. * Fires when the spinner is made to spin up.
  110204. * @param {Ext.form.field.Spinner} this
  110205. */
  110206. 'spinup',
  110207. /**
  110208. * @event spindown
  110209. * Fires when the spinner is made to spin down.
  110210. * @param {Ext.form.field.Spinner} this
  110211. */
  110212. 'spindown'
  110213. );
  110214. },
  110215. /**
  110216. * @private
  110217. * Override.
  110218. */
  110219. onRender: function() {
  110220. var me = this,
  110221. triggers;
  110222. me.callParent(arguments);
  110223. triggers = me.triggerEl;
  110224. /**
  110225. * @property {Ext.Element} spinUpEl
  110226. * The spinner up button element
  110227. */
  110228. me.spinUpEl = triggers.item(0);
  110229. /**
  110230. * @property {Ext.Element} spinDownEl
  110231. * The spinner down button element
  110232. */
  110233. me.spinDownEl = triggers.item(1);
  110234. me.triggerCell = me.spinUpEl.parent();
  110235. // Set initial enabled/disabled states
  110236. me.setSpinUpEnabled(me.spinUpEnabled);
  110237. me.setSpinDownEnabled(me.spinDownEnabled);
  110238. // Init up/down arrow keys
  110239. if (me.keyNavEnabled) {
  110240. me.spinnerKeyNav = new Ext.util.KeyNav(me.inputEl, {
  110241. scope: me,
  110242. up: me.spinUp,
  110243. down: me.spinDown
  110244. });
  110245. }
  110246. // Init mouse wheel
  110247. if (me.mouseWheelEnabled) {
  110248. me.mon(me.bodyEl, 'mousewheel', me.onMouseWheel, me);
  110249. }
  110250. },
  110251. getSubTplMarkup: function() {
  110252. var me = this,
  110253. field = Ext.form.field.Base.prototype.getSubTplMarkup.apply(me, arguments);
  110254. return '<table id="' + me.id + '-triggerWrap" class="' + Ext.baseCSSPrefix + 'form-trigger-wrap" cellpadding="0" cellspacing="0">' +
  110255. '<tbody>' +
  110256. '<tr><td id="' + me.id + '-inputCell" class="' + Ext.baseCSSPrefix + 'form-trigger-input-cell">' + field + '</td>' +
  110257. me.getTriggerMarkup() +
  110258. '</tbody></table>';
  110259. },
  110260. getTriggerMarkup: function() {
  110261. var me = this,
  110262. hideTrigger = (me.readOnly || me.hideTrigger);
  110263. return me.getTpl('triggerTpl').apply({
  110264. triggerStyle: 'width:' + me.triggerWidth + (hideTrigger ? 'px;display:none' : 'px')
  110265. });
  110266. },
  110267. /**
  110268. * Get the total width of the spinner button area.
  110269. * @return {Number} The total spinner button width
  110270. */
  110271. getTriggerWidth: function() {
  110272. var me = this,
  110273. totalTriggerWidth = 0;
  110274. if (me.triggerWrap && !me.hideTrigger && !me.readOnly) {
  110275. totalTriggerWidth = me.triggerWidth;
  110276. }
  110277. return totalTriggerWidth;
  110278. },
  110279. /**
  110280. * @private
  110281. * Handles the spinner up button clicks.
  110282. */
  110283. onTrigger1Click: function() {
  110284. this.spinUp();
  110285. },
  110286. /**
  110287. * @private
  110288. * Handles the spinner down button clicks.
  110289. */
  110290. onTrigger2Click: function() {
  110291. this.spinDown();
  110292. },
  110293. // private
  110294. // Handle trigger mouse up gesture; refocuses the input element upon end of spin.
  110295. onTriggerWrapMouseup: function() {
  110296. this.inputEl.focus();
  110297. },
  110298. /**
  110299. * Triggers the spinner to step up; fires the {@link #spin} and {@link #spinup} events and calls the
  110300. * {@link #onSpinUp} method. Does nothing if the field is {@link #disabled} or if {@link #spinUpEnabled}
  110301. * is false.
  110302. */
  110303. spinUp: function() {
  110304. var me = this;
  110305. if (me.spinUpEnabled && !me.disabled) {
  110306. me.fireEvent('spin', me, 'up');
  110307. me.fireEvent('spinup', me);
  110308. me.onSpinUp();
  110309. }
  110310. },
  110311. /**
  110312. * Triggers the spinner to step down; fires the {@link #spin} and {@link #spindown} events and calls the
  110313. * {@link #onSpinDown} method. Does nothing if the field is {@link #disabled} or if {@link #spinDownEnabled}
  110314. * is false.
  110315. */
  110316. spinDown: function() {
  110317. var me = this;
  110318. if (me.spinDownEnabled && !me.disabled) {
  110319. me.fireEvent('spin', me, 'down');
  110320. me.fireEvent('spindown', me);
  110321. me.onSpinDown();
  110322. }
  110323. },
  110324. /**
  110325. * Sets whether the spinner up button is enabled.
  110326. * @param {Boolean} enabled true to enable the button, false to disable it.
  110327. */
  110328. setSpinUpEnabled: function(enabled) {
  110329. var me = this,
  110330. wasEnabled = me.spinUpEnabled;
  110331. me.spinUpEnabled = enabled;
  110332. if (wasEnabled !== enabled && me.rendered) {
  110333. me.spinUpEl[enabled ? 'removeCls' : 'addCls'](me.trigger1Cls + '-disabled');
  110334. }
  110335. },
  110336. /**
  110337. * Sets whether the spinner down button is enabled.
  110338. * @param {Boolean} enabled true to enable the button, false to disable it.
  110339. */
  110340. setSpinDownEnabled: function(enabled) {
  110341. var me = this,
  110342. wasEnabled = me.spinDownEnabled;
  110343. me.spinDownEnabled = enabled;
  110344. if (wasEnabled !== enabled && me.rendered) {
  110345. me.spinDownEl[enabled ? 'removeCls' : 'addCls'](me.trigger2Cls + '-disabled');
  110346. }
  110347. },
  110348. /**
  110349. * @private
  110350. * Handles mousewheel events on the field
  110351. */
  110352. onMouseWheel: function(e) {
  110353. var me = this,
  110354. delta;
  110355. if (me.hasFocus) {
  110356. delta = e.getWheelDelta();
  110357. if (delta > 0) {
  110358. me.spinUp();
  110359. }
  110360. else if (delta < 0) {
  110361. me.spinDown();
  110362. }
  110363. e.stopEvent();
  110364. }
  110365. },
  110366. onDestroy: function() {
  110367. Ext.destroyMembers(this, 'spinnerKeyNav', 'spinUpEl', 'spinDownEl');
  110368. this.callParent();
  110369. }
  110370. });
  110371. /**
  110372. * @docauthor Jason Johnston <jason@sencha.com>
  110373. *
  110374. * A numeric text field that provides automatic keystroke filtering to disallow non-numeric characters,
  110375. * and numeric validation to limit the value to a range of valid numbers. The range of acceptable number
  110376. * values can be controlled by setting the {@link #minValue} and {@link #maxValue} configs, and fractional
  110377. * decimals can be disallowed by setting {@link #allowDecimals} to `false`.
  110378. *
  110379. * By default, the number field is also rendered with a set of up/down spinner buttons and has
  110380. * up/down arrow key and mouse wheel event listeners attached for incrementing/decrementing the value by the
  110381. * {@link #step} value. To hide the spinner buttons set `{@link #hideTrigger hideTrigger}:true`; to disable
  110382. * the arrow key and mouse wheel handlers set `{@link #keyNavEnabled keyNavEnabled}:false` and
  110383. * `{@link #mouseWheelEnabled mouseWheelEnabled}:false`. See the example below.
  110384. *
  110385. * # Example usage
  110386. *
  110387. * @example
  110388. * Ext.create('Ext.form.Panel', {
  110389. * title: 'On The Wall',
  110390. * width: 300,
  110391. * bodyPadding: 10,
  110392. * renderTo: Ext.getBody(),
  110393. * items: [{
  110394. * xtype: 'numberfield',
  110395. * anchor: '100%',
  110396. * name: 'bottles',
  110397. * fieldLabel: 'Bottles of Beer',
  110398. * value: 99,
  110399. * maxValue: 99,
  110400. * minValue: 0
  110401. * }],
  110402. * buttons: [{
  110403. * text: 'Take one down, pass it around',
  110404. * handler: function() {
  110405. * this.up('form').down('[name=bottles]').spinDown();
  110406. * }
  110407. * }]
  110408. * });
  110409. *
  110410. * # Removing UI Enhancements
  110411. *
  110412. * @example
  110413. * Ext.create('Ext.form.Panel', {
  110414. * title: 'Personal Info',
  110415. * width: 300,
  110416. * bodyPadding: 10,
  110417. * renderTo: Ext.getBody(),
  110418. * items: [{
  110419. * xtype: 'numberfield',
  110420. * anchor: '100%',
  110421. * name: 'age',
  110422. * fieldLabel: 'Age',
  110423. * minValue: 0, //prevents negative numbers
  110424. *
  110425. * // Remove spinner buttons, and arrow key and mouse wheel listeners
  110426. * hideTrigger: true,
  110427. * keyNavEnabled: false,
  110428. * mouseWheelEnabled: false
  110429. * }]
  110430. * });
  110431. *
  110432. * # Using Step
  110433. *
  110434. * @example
  110435. * Ext.create('Ext.form.Panel', {
  110436. * renderTo: Ext.getBody(),
  110437. * title: 'Step',
  110438. * width: 300,
  110439. * bodyPadding: 10,
  110440. * items: [{
  110441. * xtype: 'numberfield',
  110442. * anchor: '100%',
  110443. * name: 'evens',
  110444. * fieldLabel: 'Even Numbers',
  110445. *
  110446. * // Set step so it skips every other number
  110447. * step: 2,
  110448. * value: 0,
  110449. *
  110450. * // Add change handler to force user-entered numbers to evens
  110451. * listeners: {
  110452. * change: function(field, value) {
  110453. * value = parseInt(value, 10);
  110454. * field.setValue(value + value % 2);
  110455. * }
  110456. * }
  110457. * }]
  110458. * });
  110459. */
  110460. Ext.define('Ext.form.field.Number', {
  110461. extend:'Ext.form.field.Spinner',
  110462. alias: 'widget.numberfield',
  110463. alternateClassName: ['Ext.form.NumberField', 'Ext.form.Number'],
  110464. /**
  110465. * @cfg {RegExp} stripCharsRe
  110466. * @private
  110467. */
  110468. /**
  110469. * @cfg {RegExp} maskRe
  110470. * @private
  110471. */
  110472. /**
  110473. * @cfg {Boolean} allowDecimals
  110474. * False to disallow decimal values
  110475. */
  110476. allowDecimals : true,
  110477. //<locale>
  110478. /**
  110479. * @cfg {String} decimalSeparator
  110480. * Character(s) to allow as the decimal separator
  110481. */
  110482. decimalSeparator : '.',
  110483. //</locale>
  110484. //<locale>
  110485. /**
  110486. * @cfg {Boolean} [submitLocaleSeparator=true]
  110487. * False to ensure that the {@link #getSubmitValue} method strips
  110488. * always uses `.` as the separator, regardless of the {@link #decimalSeparator}
  110489. * configuration.
  110490. */
  110491. submitLocaleSeparator: true,
  110492. //</locale>
  110493. //<locale>
  110494. /**
  110495. * @cfg {Number} decimalPrecision
  110496. * The maximum precision to display after the decimal separator
  110497. */
  110498. decimalPrecision : 2,
  110499. //</locale>
  110500. /**
  110501. * @cfg {Number} minValue
  110502. * The minimum allowed value. Will be used by the field's validation logic,
  110503. * and for {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the down spinner button}.
  110504. *
  110505. * Defaults to Number.NEGATIVE_INFINITY.
  110506. */
  110507. minValue: Number.NEGATIVE_INFINITY,
  110508. /**
  110509. * @cfg {Number} maxValue
  110510. * The maximum allowed value. Will be used by the field's validation logic, and for
  110511. * {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the up spinner button}.
  110512. *
  110513. * Defaults to Number.MAX_VALUE.
  110514. */
  110515. maxValue: Number.MAX_VALUE,
  110516. /**
  110517. * @cfg {Number} step
  110518. * Specifies a numeric interval by which the field's value will be incremented or decremented when the user invokes
  110519. * the spinner.
  110520. */
  110521. step: 1,
  110522. //<locale>
  110523. /**
  110524. * @cfg {String} minText
  110525. * Error text to display if the minimum value validation fails.
  110526. */
  110527. minText : 'The minimum value for this field is {0}',
  110528. //</locale>
  110529. //<locale>
  110530. /**
  110531. * @cfg {String} maxText
  110532. * Error text to display if the maximum value validation fails.
  110533. */
  110534. maxText : 'The maximum value for this field is {0}',
  110535. //</locale>
  110536. //<locale>
  110537. /**
  110538. * @cfg {String} nanText
  110539. * Error text to display if the value is not a valid number. For example, this can happen if a valid character like
  110540. * '.' or '-' is left in the field with no number.
  110541. */
  110542. nanText : '{0} is not a valid number',
  110543. //</locale>
  110544. //<locale>
  110545. /**
  110546. * @cfg {String} negativeText
  110547. * Error text to display if the value is negative and {@link #minValue} is set to 0. This is used instead of the
  110548. * {@link #minText} in that circumstance only.
  110549. */
  110550. negativeText : 'The value cannot be negative',
  110551. //</locale>
  110552. /**
  110553. * @cfg {String} baseChars
  110554. * The base set of characters to evaluate as valid numbers.
  110555. */
  110556. baseChars : '0123456789',
  110557. /**
  110558. * @cfg {Boolean} autoStripChars
  110559. * True to automatically strip not allowed characters from the field.
  110560. */
  110561. autoStripChars: false,
  110562. initComponent: function() {
  110563. var me = this,
  110564. allowed;
  110565. me.callParent();
  110566. me.setMinValue(me.minValue);
  110567. me.setMaxValue(me.maxValue);
  110568. // Build regexes for masking and stripping based on the configured options
  110569. if (me.disableKeyFilter !== true) {
  110570. allowed = me.baseChars + '';
  110571. if (me.allowDecimals) {
  110572. allowed += me.decimalSeparator;
  110573. }
  110574. if (me.minValue < 0) {
  110575. allowed += '-';
  110576. }
  110577. allowed = Ext.String.escapeRegex(allowed);
  110578. me.maskRe = new RegExp('[' + allowed + ']');
  110579. if (me.autoStripChars) {
  110580. me.stripCharsRe = new RegExp('[^' + allowed + ']', 'gi');
  110581. }
  110582. }
  110583. },
  110584. /**
  110585. * Runs all of Number's validations and returns an array of any errors. Note that this first runs Text's
  110586. * validations, so the returned array is an amalgamation of all field errors. The additional validations run test
  110587. * that the value is a number, and that it is within the configured min and max values.
  110588. * @param {Object} [value] The value to get errors for (defaults to the current field value)
  110589. * @return {String[]} All validation errors for this field
  110590. */
  110591. getErrors: function(value) {
  110592. var me = this,
  110593. errors = me.callParent(arguments),
  110594. format = Ext.String.format,
  110595. num;
  110596. value = Ext.isDefined(value) ? value : this.processRawValue(this.getRawValue());
  110597. if (value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
  110598. return errors;
  110599. }
  110600. value = String(value).replace(me.decimalSeparator, '.');
  110601. if(isNaN(value)){
  110602. errors.push(format(me.nanText, value));
  110603. }
  110604. num = me.parseValue(value);
  110605. if (me.minValue === 0 && num < 0) {
  110606. errors.push(this.negativeText);
  110607. }
  110608. else if (num < me.minValue) {
  110609. errors.push(format(me.minText, me.minValue));
  110610. }
  110611. if (num > me.maxValue) {
  110612. errors.push(format(me.maxText, me.maxValue));
  110613. }
  110614. return errors;
  110615. },
  110616. rawToValue: function(rawValue) {
  110617. var value = this.fixPrecision(this.parseValue(rawValue));
  110618. if (value === null) {
  110619. value = rawValue || null;
  110620. }
  110621. return value;
  110622. },
  110623. valueToRaw: function(value) {
  110624. var me = this,
  110625. decimalSeparator = me.decimalSeparator;
  110626. value = me.parseValue(value);
  110627. value = me.fixPrecision(value);
  110628. value = Ext.isNumber(value) ? value : parseFloat(String(value).replace(decimalSeparator, '.'));
  110629. value = isNaN(value) ? '' : String(value).replace('.', decimalSeparator);
  110630. return value;
  110631. },
  110632. getSubmitValue: function() {
  110633. var me = this,
  110634. value = me.callParent();
  110635. if (!me.submitLocaleSeparator) {
  110636. value = value.replace(me.decimalSeparator, '.');
  110637. }
  110638. return value;
  110639. },
  110640. onChange: function() {
  110641. this.toggleSpinners();
  110642. this.callParent(arguments);
  110643. },
  110644. toggleSpinners: function(){
  110645. var me = this,
  110646. value = me.getValue(),
  110647. valueIsNull = value === null;
  110648. me.setSpinUpEnabled(valueIsNull || value < me.maxValue);
  110649. me.setSpinDownEnabled(valueIsNull || value > me.minValue);
  110650. },
  110651. /**
  110652. * Replaces any existing {@link #minValue} with the new value.
  110653. * @param {Number} value The minimum value
  110654. */
  110655. setMinValue : function(value) {
  110656. this.minValue = Ext.Number.from(value, Number.NEGATIVE_INFINITY);
  110657. this.toggleSpinners();
  110658. },
  110659. /**
  110660. * Replaces any existing {@link #maxValue} with the new value.
  110661. * @param {Number} value The maximum value
  110662. */
  110663. setMaxValue: function(value) {
  110664. this.maxValue = Ext.Number.from(value, Number.MAX_VALUE);
  110665. this.toggleSpinners();
  110666. },
  110667. // private
  110668. parseValue : function(value) {
  110669. value = parseFloat(String(value).replace(this.decimalSeparator, '.'));
  110670. return isNaN(value) ? null : value;
  110671. },
  110672. /**
  110673. * @private
  110674. */
  110675. fixPrecision : function(value) {
  110676. var me = this,
  110677. nan = isNaN(value),
  110678. precision = me.decimalPrecision;
  110679. if (nan || !value) {
  110680. return nan ? '' : value;
  110681. } else if (!me.allowDecimals || precision <= 0) {
  110682. precision = 0;
  110683. }
  110684. return parseFloat(Ext.Number.toFixed(parseFloat(value), precision));
  110685. },
  110686. beforeBlur : function() {
  110687. var me = this,
  110688. v = me.parseValue(me.getRawValue());
  110689. if (!Ext.isEmpty(v)) {
  110690. me.setValue(v);
  110691. }
  110692. },
  110693. onSpinUp: function() {
  110694. var me = this;
  110695. if (!me.readOnly) {
  110696. me.setValue(Ext.Number.constrain(me.getValue() + me.step, me.minValue, me.maxValue));
  110697. }
  110698. },
  110699. onSpinDown: function() {
  110700. var me = this;
  110701. if (!me.readOnly) {
  110702. me.setValue(Ext.Number.constrain(me.getValue() - me.step, me.minValue, me.maxValue));
  110703. }
  110704. }
  110705. });
  110706. /**
  110707. * As the number of records increases, the time required for the browser to render them increases. Paging is used to
  110708. * reduce the amount of data exchanged with the client. Note: if there are more records/rows than can be viewed in the
  110709. * available screen area, vertical scrollbars will be added.
  110710. *
  110711. * Paging is typically handled on the server side (see exception below). The client sends parameters to the server side,
  110712. * which the server needs to interpret and then respond with the appropriate data.
  110713. *
  110714. * Ext.toolbar.Paging is a specialized toolbar that is bound to a {@link Ext.data.Store} and provides automatic
  110715. * paging control. This Component {@link Ext.data.Store#method-load load}s blocks of data into the {@link #store} by passing
  110716. * parameters used for paging criteria.
  110717. *
  110718. * {@img Ext.toolbar.Paging/Ext.toolbar.Paging.png Ext.toolbar.Paging component}
  110719. *
  110720. * Paging Toolbar is typically used as one of the Grid's toolbars:
  110721. *
  110722. * @example
  110723. * var itemsPerPage = 2; // set the number of items you want per page
  110724. *
  110725. * var store = Ext.create('Ext.data.Store', {
  110726. * id:'simpsonsStore',
  110727. * autoLoad: false,
  110728. * fields:['name', 'email', 'phone'],
  110729. * pageSize: itemsPerPage, // items per page
  110730. * proxy: {
  110731. * type: 'ajax',
  110732. * url: 'pagingstore.js', // url that will load data with respect to start and limit params
  110733. * reader: {
  110734. * type: 'json',
  110735. * root: 'items',
  110736. * totalProperty: 'total'
  110737. * }
  110738. * }
  110739. * });
  110740. *
  110741. * // specify segment of data you want to load using params
  110742. * store.load({
  110743. * params:{
  110744. * start:0,
  110745. * limit: itemsPerPage
  110746. * }
  110747. * });
  110748. *
  110749. * Ext.create('Ext.grid.Panel', {
  110750. * title: 'Simpsons',
  110751. * store: store,
  110752. * columns: [
  110753. * { header: 'Name', dataIndex: 'name' },
  110754. * { header: 'Email', dataIndex: 'email', flex: 1 },
  110755. * { header: 'Phone', dataIndex: 'phone' }
  110756. * ],
  110757. * width: 400,
  110758. * height: 125,
  110759. * dockedItems: [{
  110760. * xtype: 'pagingtoolbar',
  110761. * store: store, // same store GridPanel is using
  110762. * dock: 'bottom',
  110763. * displayInfo: true
  110764. * }],
  110765. * renderTo: Ext.getBody()
  110766. * });
  110767. *
  110768. * To use paging, you need to set a pageSize configuration on the Store, and pass the paging requirements to
  110769. * the server when the store is first loaded.
  110770. *
  110771. * store.load({
  110772. * params: {
  110773. * // specify params for the first page load if using paging
  110774. * start: 0,
  110775. * limit: myPageSize,
  110776. * // other params
  110777. * foo: 'bar'
  110778. * }
  110779. * });
  110780. *
  110781. * If using {@link Ext.data.Store#autoLoad store's autoLoad} configuration:
  110782. *
  110783. * var myStore = Ext.create('Ext.data.Store', {
  110784. * {@link Ext.data.Store#autoLoad autoLoad}: {start: 0, limit: 25},
  110785. * ...
  110786. * });
  110787. *
  110788. * The packet sent back from the server would have this form:
  110789. *
  110790. * {
  110791. * "success": true,
  110792. * "results": 2000,
  110793. * "rows": [ // ***Note:** this must be an Array
  110794. * { "id": 1, "name": "Bill", "occupation": "Gardener" },
  110795. * { "id": 2, "name": "Ben", "occupation": "Horticulturalist" },
  110796. * ...
  110797. * { "id": 25, "name": "Sue", "occupation": "Botanist" }
  110798. * ]
  110799. * }
  110800. *
  110801. * ## Paging with Local Data
  110802. *
  110803. * Paging can also be accomplished with local data using extensions:
  110804. *
  110805. * - [Ext.ux.data.PagingStore][1]
  110806. * - Paging Memory Proxy (examples/ux/PagingMemoryProxy.js)
  110807. *
  110808. * [1]: http://sencha.com/forum/showthread.php?t=71532
  110809. */
  110810. Ext.define('Ext.toolbar.Paging', {
  110811. extend: 'Ext.toolbar.Toolbar',
  110812. alias: 'widget.pagingtoolbar',
  110813. alternateClassName: 'Ext.PagingToolbar',
  110814. requires: ['Ext.toolbar.TextItem', 'Ext.form.field.Number'],
  110815. mixins: {
  110816. bindable: 'Ext.util.Bindable'
  110817. },
  110818. /**
  110819. * @cfg {Ext.data.Store} store (required)
  110820. * The {@link Ext.data.Store} the paging toolbar should use as its data source.
  110821. */
  110822. /**
  110823. * @cfg {Boolean} displayInfo
  110824. * true to display the displayMsg
  110825. */
  110826. displayInfo: false,
  110827. /**
  110828. * @cfg {Boolean} prependButtons
  110829. * true to insert any configured items _before_ the paging buttons.
  110830. */
  110831. prependButtons: false,
  110832. //<locale>
  110833. /**
  110834. * @cfg {String} displayMsg
  110835. * The paging status message to display. Note that this string is
  110836. * formatted using the braced numbers {0}-{2} as tokens that are replaced by the values for start, end and total
  110837. * respectively. These tokens should be preserved when overriding this string if showing those values is desired.
  110838. */
  110839. displayMsg : 'Displaying {0} - {1} of {2}',
  110840. //</locale>
  110841. //<locale>
  110842. /**
  110843. * @cfg {String} emptyMsg
  110844. * The message to display when no records are found.
  110845. */
  110846. emptyMsg : 'No data to display',
  110847. //</locale>
  110848. //<locale>
  110849. /**
  110850. * @cfg {String} beforePageText
  110851. * The text displayed before the input item.
  110852. */
  110853. beforePageText : 'Page',
  110854. //</locale>
  110855. //<locale>
  110856. /**
  110857. * @cfg {String} afterPageText
  110858. * Customizable piece of the default paging text. Note that this string is formatted using
  110859. * {0} as a token that is replaced by the number of total pages. This token should be preserved when overriding this
  110860. * string if showing the total page count is desired.
  110861. */
  110862. afterPageText : 'of {0}',
  110863. //</locale>
  110864. //<locale>
  110865. /**
  110866. * @cfg {String} firstText
  110867. * The quicktip text displayed for the first page button.
  110868. * **Note**: quick tips must be initialized for the quicktip to show.
  110869. */
  110870. firstText : 'First Page',
  110871. //</locale>
  110872. //<locale>
  110873. /**
  110874. * @cfg {String} prevText
  110875. * The quicktip text displayed for the previous page button.
  110876. * **Note**: quick tips must be initialized for the quicktip to show.
  110877. */
  110878. prevText : 'Previous Page',
  110879. //</locale>
  110880. //<locale>
  110881. /**
  110882. * @cfg {String} nextText
  110883. * The quicktip text displayed for the next page button.
  110884. * **Note**: quick tips must be initialized for the quicktip to show.
  110885. */
  110886. nextText : 'Next Page',
  110887. //</locale>
  110888. //<locale>
  110889. /**
  110890. * @cfg {String} lastText
  110891. * The quicktip text displayed for the last page button.
  110892. * **Note**: quick tips must be initialized for the quicktip to show.
  110893. */
  110894. lastText : 'Last Page',
  110895. //</locale>
  110896. //<locale>
  110897. /**
  110898. * @cfg {String} refreshText
  110899. * The quicktip text displayed for the Refresh button.
  110900. * **Note**: quick tips must be initialized for the quicktip to show.
  110901. */
  110902. refreshText : 'Refresh',
  110903. //</locale>
  110904. /**
  110905. * @cfg {Number} inputItemWidth
  110906. * The width in pixels of the input field used to display and change the current page number.
  110907. */
  110908. inputItemWidth : 30,
  110909. /**
  110910. * Gets the standard paging items in the toolbar
  110911. * @private
  110912. */
  110913. getPagingItems: function() {
  110914. var me = this;
  110915. return [{
  110916. itemId: 'first',
  110917. tooltip: me.firstText,
  110918. overflowText: me.firstText,
  110919. iconCls: Ext.baseCSSPrefix + 'tbar-page-first',
  110920. disabled: true,
  110921. handler: me.moveFirst,
  110922. scope: me
  110923. },{
  110924. itemId: 'prev',
  110925. tooltip: me.prevText,
  110926. overflowText: me.prevText,
  110927. iconCls: Ext.baseCSSPrefix + 'tbar-page-prev',
  110928. disabled: true,
  110929. handler: me.movePrevious,
  110930. scope: me
  110931. },
  110932. '-',
  110933. me.beforePageText,
  110934. {
  110935. xtype: 'numberfield',
  110936. itemId: 'inputItem',
  110937. name: 'inputItem',
  110938. cls: Ext.baseCSSPrefix + 'tbar-page-number',
  110939. allowDecimals: false,
  110940. minValue: 1,
  110941. hideTrigger: true,
  110942. enableKeyEvents: true,
  110943. keyNavEnabled: false,
  110944. selectOnFocus: true,
  110945. submitValue: false,
  110946. // mark it as not a field so the form will not catch it when getting fields
  110947. isFormField: false,
  110948. width: me.inputItemWidth,
  110949. margins: '-1 2 3 2',
  110950. listeners: {
  110951. scope: me,
  110952. keydown: me.onPagingKeyDown,
  110953. blur: me.onPagingBlur
  110954. }
  110955. },{
  110956. xtype: 'tbtext',
  110957. itemId: 'afterTextItem',
  110958. text: Ext.String.format(me.afterPageText, 1)
  110959. },
  110960. '-',
  110961. {
  110962. itemId: 'next',
  110963. tooltip: me.nextText,
  110964. overflowText: me.nextText,
  110965. iconCls: Ext.baseCSSPrefix + 'tbar-page-next',
  110966. disabled: true,
  110967. handler: me.moveNext,
  110968. scope: me
  110969. },{
  110970. itemId: 'last',
  110971. tooltip: me.lastText,
  110972. overflowText: me.lastText,
  110973. iconCls: Ext.baseCSSPrefix + 'tbar-page-last',
  110974. disabled: true,
  110975. handler: me.moveLast,
  110976. scope: me
  110977. },
  110978. '-',
  110979. {
  110980. itemId: 'refresh',
  110981. tooltip: me.refreshText,
  110982. overflowText: me.refreshText,
  110983. iconCls: Ext.baseCSSPrefix + 'tbar-loading',
  110984. handler: me.doRefresh,
  110985. scope: me
  110986. }];
  110987. },
  110988. initComponent : function(){
  110989. var me = this,
  110990. pagingItems = me.getPagingItems(),
  110991. userItems = me.items || me.buttons || [];
  110992. if (me.prependButtons) {
  110993. me.items = userItems.concat(pagingItems);
  110994. } else {
  110995. me.items = pagingItems.concat(userItems);
  110996. }
  110997. delete me.buttons;
  110998. if (me.displayInfo) {
  110999. me.items.push('->');
  111000. me.items.push({xtype: 'tbtext', itemId: 'displayItem'});
  111001. }
  111002. me.callParent();
  111003. me.addEvents(
  111004. /**
  111005. * @event change
  111006. * Fires after the active page has been changed.
  111007. * @param {Ext.toolbar.Paging} this
  111008. * @param {Object} pageData An object that has these properties:
  111009. *
  111010. * - `total` : Number
  111011. *
  111012. * The total number of records in the dataset as returned by the server
  111013. *
  111014. * - `currentPage` : Number
  111015. *
  111016. * The current page number
  111017. *
  111018. * - `pageCount` : Number
  111019. *
  111020. * The total number of pages (calculated from the total number of records in the dataset as returned by the
  111021. * server and the current {@link Ext.data.Store#pageSize pageSize})
  111022. *
  111023. * - `toRecord` : Number
  111024. *
  111025. * The starting record index for the current page
  111026. *
  111027. * - `fromRecord` : Number
  111028. *
  111029. * The ending record index for the current page
  111030. */
  111031. 'change',
  111032. /**
  111033. * @event beforechange
  111034. * Fires just before the active page is changed. Return false to prevent the active page from being changed.
  111035. * @param {Ext.toolbar.Paging} this
  111036. * @param {Number} page The page number that will be loaded on change
  111037. */
  111038. 'beforechange'
  111039. );
  111040. me.on('beforerender', me.onLoad, me, {single: true});
  111041. me.bindStore(me.store || 'ext-empty-store', true);
  111042. },
  111043. // private
  111044. updateInfo : function(){
  111045. var me = this,
  111046. displayItem = me.child('#displayItem'),
  111047. store = me.store,
  111048. pageData = me.getPageData(),
  111049. count, msg;
  111050. if (displayItem) {
  111051. count = store.getCount();
  111052. if (count === 0) {
  111053. msg = me.emptyMsg;
  111054. } else {
  111055. msg = Ext.String.format(
  111056. me.displayMsg,
  111057. pageData.fromRecord,
  111058. pageData.toRecord,
  111059. pageData.total
  111060. );
  111061. }
  111062. displayItem.setText(msg);
  111063. }
  111064. },
  111065. // private
  111066. onLoad : function(){
  111067. var me = this,
  111068. pageData,
  111069. currPage,
  111070. pageCount,
  111071. afterText,
  111072. count,
  111073. isEmpty;
  111074. count = me.store.getCount();
  111075. isEmpty = count === 0;
  111076. if (!isEmpty) {
  111077. pageData = me.getPageData();
  111078. currPage = pageData.currentPage;
  111079. pageCount = pageData.pageCount;
  111080. afterText = Ext.String.format(me.afterPageText, isNaN(pageCount) ? 1 : pageCount);
  111081. } else {
  111082. currPage = 0;
  111083. pageCount = 0;
  111084. afterText = Ext.String.format(me.afterPageText, 0);
  111085. }
  111086. Ext.suspendLayouts();
  111087. me.child('#afterTextItem').setText(afterText);
  111088. me.child('#inputItem').setDisabled(isEmpty).setValue(currPage);
  111089. me.child('#first').setDisabled(currPage === 1 || isEmpty);
  111090. me.child('#prev').setDisabled(currPage === 1 || isEmpty);
  111091. me.child('#next').setDisabled(currPage === pageCount || isEmpty);
  111092. me.child('#last').setDisabled(currPage === pageCount || isEmpty);
  111093. me.child('#refresh').enable();
  111094. me.updateInfo();
  111095. Ext.resumeLayouts(true);
  111096. if (me.rendered) {
  111097. me.fireEvent('change', me, pageData);
  111098. }
  111099. },
  111100. // private
  111101. getPageData : function(){
  111102. var store = this.store,
  111103. totalCount = store.getTotalCount();
  111104. return {
  111105. total : totalCount,
  111106. currentPage : store.currentPage,
  111107. pageCount: Math.ceil(totalCount / store.pageSize),
  111108. fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
  111109. toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
  111110. };
  111111. },
  111112. // private
  111113. onLoadError : function(){
  111114. if (!this.rendered) {
  111115. return;
  111116. }
  111117. this.child('#refresh').enable();
  111118. },
  111119. // private
  111120. readPageFromInput : function(pageData){
  111121. var v = this.child('#inputItem').getValue(),
  111122. pageNum = parseInt(v, 10);
  111123. if (!v || isNaN(pageNum)) {
  111124. this.child('#inputItem').setValue(pageData.currentPage);
  111125. return false;
  111126. }
  111127. return pageNum;
  111128. },
  111129. onPagingFocus : function(){
  111130. this.child('#inputItem').select();
  111131. },
  111132. //private
  111133. onPagingBlur : function(e){
  111134. var curPage = this.getPageData().currentPage;
  111135. this.child('#inputItem').setValue(curPage);
  111136. },
  111137. // private
  111138. onPagingKeyDown : function(field, e){
  111139. var me = this,
  111140. k = e.getKey(),
  111141. pageData = me.getPageData(),
  111142. increment = e.shiftKey ? 10 : 1,
  111143. pageNum;
  111144. if (k == e.RETURN) {
  111145. e.stopEvent();
  111146. pageNum = me.readPageFromInput(pageData);
  111147. if (pageNum !== false) {
  111148. pageNum = Math.min(Math.max(1, pageNum), pageData.pageCount);
  111149. if(me.fireEvent('beforechange', me, pageNum) !== false){
  111150. me.store.loadPage(pageNum);
  111151. }
  111152. }
  111153. } else if (k == e.HOME || k == e.END) {
  111154. e.stopEvent();
  111155. pageNum = k == e.HOME ? 1 : pageData.pageCount;
  111156. field.setValue(pageNum);
  111157. } else if (k == e.UP || k == e.PAGE_UP || k == e.DOWN || k == e.PAGE_DOWN) {
  111158. e.stopEvent();
  111159. pageNum = me.readPageFromInput(pageData);
  111160. if (pageNum) {
  111161. if (k == e.DOWN || k == e.PAGE_DOWN) {
  111162. increment *= -1;
  111163. }
  111164. pageNum += increment;
  111165. if (pageNum >= 1 && pageNum <= pageData.pageCount) {
  111166. field.setValue(pageNum);
  111167. }
  111168. }
  111169. }
  111170. },
  111171. // private
  111172. beforeLoad : function(){
  111173. if(this.rendered && this.refresh){
  111174. this.refresh.disable();
  111175. }
  111176. },
  111177. /**
  111178. * Move to the first page, has the same effect as clicking the 'first' button.
  111179. */
  111180. moveFirst : function(){
  111181. if (this.fireEvent('beforechange', this, 1) !== false){
  111182. this.store.loadPage(1);
  111183. }
  111184. },
  111185. /**
  111186. * Move to the previous page, has the same effect as clicking the 'previous' button.
  111187. */
  111188. movePrevious : function(){
  111189. var me = this,
  111190. prev = me.store.currentPage - 1;
  111191. if (prev > 0) {
  111192. if (me.fireEvent('beforechange', me, prev) !== false) {
  111193. me.store.previousPage();
  111194. }
  111195. }
  111196. },
  111197. /**
  111198. * Move to the next page, has the same effect as clicking the 'next' button.
  111199. */
  111200. moveNext : function(){
  111201. var me = this,
  111202. total = me.getPageData().pageCount,
  111203. next = me.store.currentPage + 1;
  111204. if (next <= total) {
  111205. if (me.fireEvent('beforechange', me, next) !== false) {
  111206. me.store.nextPage();
  111207. }
  111208. }
  111209. },
  111210. /**
  111211. * Move to the last page, has the same effect as clicking the 'last' button.
  111212. */
  111213. moveLast : function(){
  111214. var me = this,
  111215. last = me.getPageData().pageCount;
  111216. if (me.fireEvent('beforechange', me, last) !== false) {
  111217. me.store.loadPage(last);
  111218. }
  111219. },
  111220. /**
  111221. * Refresh the current page, has the same effect as clicking the 'refresh' button.
  111222. */
  111223. doRefresh : function(){
  111224. var me = this,
  111225. current = me.store.currentPage;
  111226. if (me.fireEvent('beforechange', me, current) !== false) {
  111227. me.store.loadPage(current);
  111228. }
  111229. },
  111230. getStoreListeners: function() {
  111231. return {
  111232. beforeload: this.beforeLoad,
  111233. load: this.onLoad,
  111234. exception: this.onLoadError
  111235. };
  111236. },
  111237. /**
  111238. * Unbinds the paging toolbar from the specified {@link Ext.data.Store} **(deprecated)**
  111239. * @param {Ext.data.Store} store The data store to unbind
  111240. */
  111241. unbind : function(store){
  111242. this.bindStore(null);
  111243. },
  111244. /**
  111245. * Binds the paging toolbar to the specified {@link Ext.data.Store} **(deprecated)**
  111246. * @param {Ext.data.Store} store The data store to bind
  111247. */
  111248. bind : function(store){
  111249. this.bindStore(store);
  111250. },
  111251. // private
  111252. onDestroy : function(){
  111253. this.unbind();
  111254. this.callParent();
  111255. }
  111256. });
  111257. /**
  111258. * An internally used DataView for {@link Ext.form.field.ComboBox ComboBox}.
  111259. */
  111260. Ext.define('Ext.view.BoundList', {
  111261. extend: 'Ext.view.View',
  111262. alias: 'widget.boundlist',
  111263. alternateClassName: 'Ext.BoundList',
  111264. requires: ['Ext.layout.component.BoundList', 'Ext.toolbar.Paging'],
  111265. /**
  111266. * @cfg {Number} [pageSize=0]
  111267. * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed at the bottom of the list and store
  111268. * queries will execute with page {@link Ext.data.Operation#start start} and
  111269. * {@link Ext.data.Operation#limit limit} parameters.
  111270. */
  111271. pageSize: 0,
  111272. /**
  111273. * @cfg {String} [displayField=""]
  111274. * The field from the store to show in the view.
  111275. */
  111276. /**
  111277. * @property {Ext.toolbar.Paging} pagingToolbar
  111278. * A reference to the PagingToolbar instance in this view. Only populated if {@link #pageSize} is greater
  111279. * than zero and the BoundList has been rendered.
  111280. */
  111281. // private overrides
  111282. baseCls: Ext.baseCSSPrefix + 'boundlist',
  111283. itemCls: Ext.baseCSSPrefix + 'boundlist-item',
  111284. listItemCls: '',
  111285. shadow: false,
  111286. trackOver: true,
  111287. refreshed: 0,
  111288. // This Component is used as a popup, not part of a complex layout. Display data immediately.
  111289. deferInitialRefresh: false,
  111290. componentLayout: 'boundlist',
  111291. childEls: [
  111292. 'listEl'
  111293. ],
  111294. renderTpl: [
  111295. '<div id="{id}-listEl" class="{baseCls}-list-ct" style="overflow:auto"></div>',
  111296. '{%',
  111297. 'var me=values.$comp, pagingToolbar=me.pagingToolbar;',
  111298. 'if (pagingToolbar) {',
  111299. 'pagingToolbar.ownerLayout = me.componentLayout;',
  111300. 'Ext.DomHelper.generateMarkup(pagingToolbar.getRenderTree(), out);',
  111301. '}',
  111302. '%}',
  111303. {
  111304. disableFormats: true
  111305. }
  111306. ],
  111307. /**
  111308. * @cfg {String/Ext.XTemplate} tpl
  111309. * A String or Ext.XTemplate instance to apply to inner template.
  111310. *
  111311. * {@link Ext.view.BoundList} is used for the dropdown list of {@link Ext.form.field.ComboBox}.
  111312. * To customize the template you can do this:
  111313. *
  111314. * Ext.create('Ext.form.field.ComboBox', {
  111315. * fieldLabel : 'State',
  111316. * queryMode : 'local',
  111317. * displayField : 'text',
  111318. * valueField : 'abbr',
  111319. * store : Ext.create('StateStore', {
  111320. * fields : ['abbr', 'text'],
  111321. * data : [
  111322. * {"abbr":"AL", "name":"Alabama"},
  111323. * {"abbr":"AK", "name":"Alaska"},
  111324. * {"abbr":"AZ", "name":"Arizona"}
  111325. * //...
  111326. * ]
  111327. * }),
  111328. * listConfig : {
  111329. * tpl : '<tpl for="."><div class="x-boundlist-item">{abbr}</div></tpl>'
  111330. * }
  111331. * });
  111332. *
  111333. * Defaults to:
  111334. *
  111335. * Ext.create('Ext.XTemplate',
  111336. * '<ul><tpl for=".">',
  111337. * '<li role="option" class="' + itemCls + '">' + me.getInnerTpl(me.displayField) + '</li>',
  111338. * '</tpl></ul>'
  111339. * );
  111340. *
  111341. */
  111342. initComponent: function() {
  111343. var me = this,
  111344. baseCls = me.baseCls,
  111345. itemCls = me.itemCls;
  111346. me.selectedItemCls = baseCls + '-selected';
  111347. me.overItemCls = baseCls + '-item-over';
  111348. me.itemSelector = "." + itemCls;
  111349. if (me.floating) {
  111350. me.addCls(baseCls + '-floating');
  111351. }
  111352. if (!me.tpl) {
  111353. // should be setting aria-posinset based on entire set of data
  111354. // not filtered set
  111355. me.tpl = new Ext.XTemplate(
  111356. '<ul><tpl for=".">',
  111357. '<li role="option" class="' + itemCls + '">' + me.getInnerTpl(me.displayField) + '</li>',
  111358. '</tpl></ul>'
  111359. );
  111360. } else if (Ext.isString(me.tpl)) {
  111361. me.tpl = new Ext.XTemplate(me.tpl);
  111362. }
  111363. if (me.pageSize) {
  111364. me.pagingToolbar = me.createPagingToolbar();
  111365. }
  111366. me.callParent();
  111367. },
  111368. beforeRender: function() {
  111369. var me = this;
  111370. me.callParent(arguments);
  111371. // If there's a Menu among our ancestors, then add the menu class.
  111372. // This is so that the MenuManager does not see a mousedown in this Component as a document mousedown, outside the Menu
  111373. if (me.up('menu')) {
  111374. me.addCls(Ext.baseCSSPrefix + 'menu');
  111375. }
  111376. },
  111377. /**
  111378. * @private
  111379. * Boundlist-specific implementation of the getBubbleTarget used by {@link Ext.AbstractComponent#up} method.
  111380. * This links to the owning input field so that the FocusManager, when receiving notification of a hide event,
  111381. * can find a focusable parent.
  111382. */
  111383. getBubbleTarget: function() {
  111384. return this.pickerField;
  111385. },
  111386. getRefItems: function() {
  111387. return this.pagingToolbar ? [ this.pagingToolbar ] : [];
  111388. },
  111389. createPagingToolbar: function() {
  111390. return Ext.widget('pagingtoolbar', {
  111391. id: this.id + '-paging-toolbar',
  111392. pageSize: this.pageSize,
  111393. store: this.store,
  111394. border: false,
  111395. ownerCt: this,
  111396. ownerLayout: this.getComponentLayout()
  111397. });
  111398. },
  111399. // Do the job of a container layout at this point even though we are not a Container.
  111400. // TODO: Refactor as a Container.
  111401. finishRenderChildren: function () {
  111402. var toolbar = this.pagingToolbar;
  111403. this.callParent(arguments);
  111404. if (toolbar) {
  111405. toolbar.finishRender();
  111406. }
  111407. },
  111408. refresh: function(){
  111409. var me = this,
  111410. toolbar = me.pagingToolbar;
  111411. me.callParent();
  111412. // The view removes the targetEl from the DOM before updating the template
  111413. // Ensure the toolbar goes to the end
  111414. if (me.rendered && toolbar && toolbar.rendered && !me.preserveScrollOnRefresh) {
  111415. me.el.appendChild(toolbar.el);
  111416. }
  111417. },
  111418. bindStore : function(store, initial) {
  111419. var toolbar = this.pagingToolbar;
  111420. this.callParent(arguments);
  111421. if (toolbar) {
  111422. toolbar.bindStore(this.store, initial);
  111423. }
  111424. },
  111425. getTargetEl: function() {
  111426. return this.listEl || this.el;
  111427. },
  111428. /**
  111429. * A method that returns the inner template for displaying items in the list.
  111430. * This method is useful to override when using a more complex display value, for example
  111431. * inserting an icon along with the text.
  111432. * @param {String} displayField The {@link #displayField} for the BoundList.
  111433. * @return {String} The inner template
  111434. */
  111435. getInnerTpl: function(displayField) {
  111436. return '{' + displayField + '}';
  111437. },
  111438. onDestroy: function() {
  111439. Ext.destroyMembers(this, 'pagingToolbar', 'listEl');
  111440. this.callParent();
  111441. }
  111442. });
  111443. /**
  111444. * A specialized {@link Ext.util.KeyNav} implementation for navigating a {@link Ext.view.BoundList} using
  111445. * the keyboard. The up, down, pageup, pagedown, home, and end keys move the active highlight
  111446. * through the list. The enter key invokes the selection model's select action using the highlighted item.
  111447. */
  111448. Ext.define('Ext.view.BoundListKeyNav', {
  111449. extend: 'Ext.util.KeyNav',
  111450. requires: 'Ext.view.BoundList',
  111451. /**
  111452. * @cfg {Ext.view.BoundList} boundList (required)
  111453. * The {@link Ext.view.BoundList} instance for which key navigation will be managed.
  111454. */
  111455. constructor: function(el, config) {
  111456. var me = this;
  111457. me.boundList = config.boundList;
  111458. me.callParent([el, Ext.apply({}, config, me.defaultHandlers)]);
  111459. },
  111460. defaultHandlers: {
  111461. up: function() {
  111462. var me = this,
  111463. boundList = me.boundList,
  111464. allItems = boundList.all,
  111465. oldItem = boundList.highlightedItem,
  111466. oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
  111467. newItemIdx = oldItemIdx > 0 ? oldItemIdx - 1 : allItems.getCount() - 1; //wraps around
  111468. me.highlightAt(newItemIdx);
  111469. },
  111470. down: function() {
  111471. var me = this,
  111472. boundList = me.boundList,
  111473. allItems = boundList.all,
  111474. oldItem = boundList.highlightedItem,
  111475. oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
  111476. newItemIdx = oldItemIdx < allItems.getCount() - 1 ? oldItemIdx + 1 : 0; //wraps around
  111477. me.highlightAt(newItemIdx);
  111478. },
  111479. pageup: function() {
  111480. //TODO
  111481. },
  111482. pagedown: function() {
  111483. //TODO
  111484. },
  111485. home: function() {
  111486. this.highlightAt(0);
  111487. },
  111488. end: function() {
  111489. var me = this;
  111490. me.highlightAt(me.boundList.all.getCount() - 1);
  111491. },
  111492. enter: function(e) {
  111493. this.selectHighlighted(e);
  111494. }
  111495. },
  111496. /**
  111497. * Highlights the item at the given index.
  111498. * @param {Number} index
  111499. */
  111500. highlightAt: function(index) {
  111501. var boundList = this.boundList,
  111502. item = boundList.all.item(index);
  111503. if (item) {
  111504. item = item.dom;
  111505. boundList.highlightItem(item);
  111506. boundList.getTargetEl().scrollChildIntoView(item, false);
  111507. }
  111508. },
  111509. /**
  111510. * Triggers selection of the currently highlighted item according to the behavior of
  111511. * the configured SelectionModel.
  111512. */
  111513. selectHighlighted: function(e) {
  111514. var me = this,
  111515. boundList = me.boundList,
  111516. highlighted = boundList.highlightedItem,
  111517. selModel = boundList.getSelectionModel();
  111518. if (highlighted) {
  111519. selModel.selectWithEvent(boundList.getRecord(highlighted), e);
  111520. }
  111521. }
  111522. });
  111523. /**
  111524. * @docauthor Jason Johnston <jason@sencha.com>
  111525. *
  111526. * A combobox control with support for autocomplete, remote loading, and many other features.
  111527. *
  111528. * A ComboBox is like a combination of a traditional HTML text `<input>` field and a `<select>`
  111529. * field; the user is able to type freely into the field, and/or pick values from a dropdown selection
  111530. * list. The user can input any value by default, even if it does not appear in the selection list;
  111531. * to prevent free-form values and restrict them to items in the list, set {@link #forceSelection} to `true`.
  111532. *
  111533. * The selection list's options are populated from any {@link Ext.data.Store}, including remote
  111534. * stores. The data items in the store are mapped to each option's displayed text and backing value via
  111535. * the {@link #valueField} and {@link #displayField} configurations, respectively.
  111536. *
  111537. * If your store is not remote, i.e. it depends only on local data and is loaded up front, you should be
  111538. * sure to set the {@link #queryMode} to `'local'`, as this will improve responsiveness for the user.
  111539. *
  111540. * # Example usage:
  111541. *
  111542. * @example
  111543. * // The data store containing the list of states
  111544. * var states = Ext.create('Ext.data.Store', {
  111545. * fields: ['abbr', 'name'],
  111546. * data : [
  111547. * {"abbr":"AL", "name":"Alabama"},
  111548. * {"abbr":"AK", "name":"Alaska"},
  111549. * {"abbr":"AZ", "name":"Arizona"}
  111550. * //...
  111551. * ]
  111552. * });
  111553. *
  111554. * // Create the combo box, attached to the states data store
  111555. * Ext.create('Ext.form.ComboBox', {
  111556. * fieldLabel: 'Choose State',
  111557. * store: states,
  111558. * queryMode: 'local',
  111559. * displayField: 'name',
  111560. * valueField: 'abbr',
  111561. * renderTo: Ext.getBody()
  111562. * });
  111563. *
  111564. * # Events
  111565. *
  111566. * To do something when something in ComboBox is selected, configure the select event:
  111567. *
  111568. * var cb = Ext.create('Ext.form.ComboBox', {
  111569. * // all of your config options
  111570. * listeners:{
  111571. * scope: yourScope,
  111572. * 'select': yourFunction
  111573. * }
  111574. * });
  111575. *
  111576. * // Alternatively, you can assign events after the object is created:
  111577. * var cb = new Ext.form.field.ComboBox(yourOptions);
  111578. * cb.on('select', yourFunction, yourScope);
  111579. *
  111580. * # Multiple Selection
  111581. *
  111582. * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
  111583. * {@link #multiSelect} config to `true`.
  111584. *
  111585. * # Filtered Stores
  111586. *
  111587. * If you have a local store that is already filtered, you can use the {@link #lastQuery} config option
  111588. * to prevent the store from having the filter being cleared on first expand.
  111589. *
  111590. * ## Customized combobox
  111591. *
  111592. * Both the text shown in dropdown menu and text field can be easily customized:
  111593. *
  111594. * @example
  111595. * var states = Ext.create('Ext.data.Store', {
  111596. * fields: ['abbr', 'name'],
  111597. * data : [
  111598. * {"abbr":"AL", "name":"Alabama"},
  111599. * {"abbr":"AK", "name":"Alaska"},
  111600. * {"abbr":"AZ", "name":"Arizona"}
  111601. * ]
  111602. * });
  111603. *
  111604. * Ext.create('Ext.form.ComboBox', {
  111605. * fieldLabel: 'Choose State',
  111606. * store: states,
  111607. * queryMode: 'local',
  111608. * valueField: 'abbr',
  111609. * renderTo: Ext.getBody(),
  111610. * // Template for the dropdown menu.
  111611. * // Note the use of "x-boundlist-item" class,
  111612. * // this is required to make the items selectable.
  111613. * tpl: Ext.create('Ext.XTemplate',
  111614. * '<tpl for=".">',
  111615. * '<div class="x-boundlist-item">{abbr} - {name}</div>',
  111616. * '</tpl>'
  111617. * ),
  111618. * // template for the content inside text field
  111619. * displayTpl: Ext.create('Ext.XTemplate',
  111620. * '<tpl for=".">',
  111621. * '{abbr} - {name}',
  111622. * '</tpl>'
  111623. * )
  111624. * });
  111625. *
  111626. * See also the {@link #listConfig} option for additional configuration of the dropdown.
  111627. *
  111628. */
  111629. Ext.define('Ext.form.field.ComboBox', {
  111630. extend:'Ext.form.field.Picker',
  111631. requires: ['Ext.util.DelayedTask', 'Ext.EventObject', 'Ext.view.BoundList', 'Ext.view.BoundListKeyNav', 'Ext.data.StoreManager', 'Ext.layout.component.field.ComboBox'],
  111632. alternateClassName: 'Ext.form.ComboBox',
  111633. alias: ['widget.combobox', 'widget.combo'],
  111634. mixins: {
  111635. bindable: 'Ext.util.Bindable'
  111636. },
  111637. componentLayout: 'combobox',
  111638. /**
  111639. * @cfg {String} [triggerCls='x-form-arrow-trigger']
  111640. * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
  111641. * by default and `triggerCls` will be **appended** if specified.
  111642. */
  111643. triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
  111644. /**
  111645. * @cfg {String} [hiddenName=""]
  111646. * The name of an underlying hidden field which will be synchronized with the underlying value of the combo.
  111647. * This option is useful if the combo is part of a form element doing a regular form post. The hidden field
  111648. * will not be created unless a hiddenName is specified.
  111649. */
  111650. hiddenName: '',
  111651. /**
  111652. * @property {Ext.Element} hiddenDataEl
  111653. * @private
  111654. */
  111655. /**
  111656. * @private
  111657. * @cfg {String}
  111658. * CSS class used to find the {@link #hiddenDataEl}
  111659. */
  111660. hiddenDataCls: Ext.baseCSSPrefix + 'hide-display ' + Ext.baseCSSPrefix + 'form-data-hidden',
  111661. /**
  111662. * @cfg
  111663. * @inheritdoc
  111664. */
  111665. fieldSubTpl: [
  111666. '<div class="{hiddenDataCls}" role="presentation"></div>',
  111667. '<input id="{id}" type="{type}" {inputAttrTpl} class="{fieldCls} {typeCls}" autocomplete="off"',
  111668. '<tpl if="value"> value="{[Ext.util.Format.htmlEncode(values.value)]}"</tpl>',
  111669. '<tpl if="name"> name="{name}"</tpl>',
  111670. '<tpl if="placeholder"> placeholder="{placeholder}"</tpl>',
  111671. '<tpl if="size"> size="{size}"</tpl>',
  111672. '<tpl if="maxLength !== undefined"> maxlength="{maxLength}"</tpl>',
  111673. '<tpl if="readOnly"> readonly="readonly"</tpl>',
  111674. '<tpl if="disabled"> disabled="disabled"</tpl>',
  111675. '<tpl if="tabIdx"> tabIndex="{tabIdx}"</tpl>',
  111676. '<tpl if="fieldStyle"> style="{fieldStyle}"</tpl>',
  111677. '/>',
  111678. {
  111679. compiled: true,
  111680. disableFormats: true
  111681. }
  111682. ],
  111683. getSubTplData: function(){
  111684. var me = this;
  111685. Ext.applyIf(me.subTplData, {
  111686. hiddenDataCls: me.hiddenDataCls
  111687. });
  111688. return me.callParent(arguments);
  111689. },
  111690. afterRender: function(){
  111691. var me = this;
  111692. me.callParent(arguments);
  111693. me.setHiddenValue(me.value);
  111694. },
  111695. /**
  111696. * @cfg {Ext.data.Store/Array} store
  111697. * The data source to which this combo is bound. Acceptable values for this property are:
  111698. *
  111699. * - **any {@link Ext.data.Store Store} subclass**
  111700. * - **an Array** : Arrays will be converted to a {@link Ext.data.Store} internally, automatically generating
  111701. * {@link Ext.data.Field#name field names} to work with all data components.
  111702. *
  111703. * - **1-dimensional array** : (e.g., `['Foo','Bar']`)
  111704. *
  111705. * A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
  111706. * {@link #valueField} and {@link #displayField})
  111707. *
  111708. * - **2-dimensional array** : (e.g., `[['f','Foo'],['b','Bar']]`)
  111709. *
  111710. * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
  111711. * {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
  111712. *
  111713. * See also {@link #queryMode}.
  111714. */
  111715. /**
  111716. * @cfg {Boolean} multiSelect
  111717. * If set to `true`, allows the combo field to hold more than one value at a time, and allows selecting multiple
  111718. * items from the dropdown list. The combo's text field will show all selected values separated by the
  111719. * {@link #delimiter}.
  111720. */
  111721. multiSelect: false,
  111722. //<locale>
  111723. /**
  111724. * @cfg {String} delimiter
  111725. * The character(s) used to separate the {@link #displayField display values} of multiple selected items when
  111726. * `{@link #multiSelect} = true`.
  111727. */
  111728. delimiter: ', ',
  111729. //</locale>
  111730. /**
  111731. * @cfg {String} displayField
  111732. * The underlying {@link Ext.data.Field#name data field name} to bind to this ComboBox.
  111733. *
  111734. * See also `{@link #valueField}`.
  111735. */
  111736. displayField: 'text',
  111737. /**
  111738. * @cfg {String} valueField (required)
  111739. * The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox.
  111740. *
  111741. * **Note**: use of a `valueField` requires the user to make a selection in order for a value to be mapped. See also
  111742. * `{@link #displayField}`.
  111743. *
  111744. * Defaults to match the value of the {@link #displayField} config.
  111745. */
  111746. /**
  111747. * @cfg {String} triggerAction
  111748. * The action to execute when the trigger is clicked.
  111749. *
  111750. * - **`'all'`** :
  111751. *
  111752. * {@link #doQuery run the query} specified by the `{@link #allQuery}` config option
  111753. *
  111754. * - **`'query'`** :
  111755. *
  111756. * {@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.
  111757. *
  111758. * See also `{@link #queryParam}`.
  111759. */
  111760. triggerAction: 'all',
  111761. /**
  111762. * @cfg {String} allQuery
  111763. * The text query to send to the server to return all records for the list with no filtering
  111764. */
  111765. allQuery: '',
  111766. /**
  111767. * @cfg {String} queryParam
  111768. * Name of the parameter used by the Store to pass the typed string when the ComboBox is configured with
  111769. * `{@link #queryMode}: 'remote'`. If explicitly set to a falsy value it will not be sent.
  111770. */
  111771. queryParam: 'query',
  111772. /**
  111773. * @cfg {String} queryMode
  111774. * The mode in which the ComboBox uses the configured Store. Acceptable values are:
  111775. *
  111776. * - **`'remote'`** :
  111777. *
  111778. * In `queryMode: 'remote'`, the ComboBox loads its Store dynamically based upon user interaction.
  111779. *
  111780. * This is typically used for "autocomplete" type inputs, and after the user finishes typing, the Store is {@link
  111781. * Ext.data.Store#method-load load}ed.
  111782. *
  111783. * A parameter containing the typed string is sent in the load request. The default parameter name for the input
  111784. * string is `query`, but this can be configured using the {@link #queryParam} config.
  111785. *
  111786. * In `queryMode: 'remote'`, the Store may be configured with `{@link Ext.data.Store#remoteFilter remoteFilter}:
  111787. * true`, and further filters may be _programatically_ added to the Store which are then passed with every load
  111788. * request which allows the server to further refine the returned dataset.
  111789. *
  111790. * Typically, in an autocomplete situation, {@link #hideTrigger} is configured `true` because it has no meaning for
  111791. * autocomplete.
  111792. *
  111793. * - **`'local'`** :
  111794. *
  111795. * ComboBox loads local data
  111796. *
  111797. * var combo = new Ext.form.field.ComboBox({
  111798. * renderTo: document.body,
  111799. * queryMode: 'local',
  111800. * store: new Ext.data.ArrayStore({
  111801. * id: 0,
  111802. * fields: [
  111803. * 'myId', // numeric value is the key
  111804. * 'displayText'
  111805. * ],
  111806. * data: [[1, 'item1'], [2, 'item2']] // data is local
  111807. * }),
  111808. * valueField: 'myId',
  111809. * displayField: 'displayText',
  111810. * triggerAction: 'all'
  111811. * });
  111812. */
  111813. queryMode: 'remote',
  111814. /**
  111815. * @cfg {Boolean} [queryCaching=true]
  111816. * When true, this prevents the combo from re-querying (either locally or remotely) when the current query
  111817. * is the same as the previous query.
  111818. */
  111819. queryCaching: true,
  111820. /**
  111821. * @cfg {Number} pageSize
  111822. * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed in the footer of the dropdown list and the
  111823. * {@link #doQuery filter queries} will execute with page start and {@link Ext.view.BoundList#pageSize limit}
  111824. * parameters. Only applies when `{@link #queryMode} = 'remote'`.
  111825. */
  111826. pageSize: 0,
  111827. /**
  111828. * @cfg {Number} queryDelay
  111829. * The length of time in milliseconds to delay between the start of typing and sending the query to filter the
  111830. * dropdown list.
  111831. *
  111832. * Defaults to `500` if `{@link #queryMode} = 'remote'` or `10` if `{@link #queryMode} = 'local'`
  111833. */
  111834. /**
  111835. * @cfg {Number} minChars
  111836. * The minimum number of characters the user must type before autocomplete and {@link #typeAhead} activate.
  111837. *
  111838. * Defaults to `4` if `{@link #queryMode} = 'remote'` or `0` if `{@link #queryMode} = 'local'`,
  111839. * does not apply if `{@link Ext.form.field.Trigger#editable editable} = false`.
  111840. */
  111841. /**
  111842. * @cfg {Boolean} autoSelect
  111843. * `true` to automatically highlight the first result gathered by the data store in the dropdown list when it is
  111844. * opened. A false value would cause nothing in the list to be highlighted automatically, so
  111845. * the user would have to manually highlight an item before pressing the enter or {@link #selectOnTab tab} key to
  111846. * select it (unless the value of ({@link #typeAhead}) were true), or use the mouse to select a value.
  111847. */
  111848. autoSelect: true,
  111849. /**
  111850. * @cfg {Boolean} typeAhead
  111851. * `true` to populate and autoselect the remainder of the text being typed after a configurable delay
  111852. * ({@link #typeAheadDelay}) if it matches a known value.
  111853. */
  111854. typeAhead: false,
  111855. /**
  111856. * @cfg {Number} typeAheadDelay
  111857. * The length of time in milliseconds to wait until the typeahead text is displayed if `{@link #typeAhead} = true`
  111858. */
  111859. typeAheadDelay: 250,
  111860. /**
  111861. * @cfg {Boolean} selectOnTab
  111862. * Whether the Tab key should select the currently highlighted item.
  111863. */
  111864. selectOnTab: true,
  111865. /**
  111866. * @cfg {Boolean} forceSelection
  111867. * `true` to restrict the selected value to one of the values in the list, `false` to allow the user to set
  111868. * arbitrary text into the field.
  111869. */
  111870. forceSelection: false,
  111871. /**
  111872. * @cfg {Boolean} growToLongestValue
  111873. * `false` to not allow the component to resize itself when its data changes
  111874. * (and its {@link #grow} property is `true`)
  111875. */
  111876. growToLongestValue: true,
  111877. /**
  111878. * @cfg {String} valueNotFoundText
  111879. * When using a name/value combo, if the value passed to setValue is not found in the store, valueNotFoundText will
  111880. * be displayed as the field text if defined. If this default text is used, it means there
  111881. * is no value set and no validation will occur on this field.
  111882. */
  111883. /**
  111884. * @property {String} lastQuery
  111885. * The value of the match string used to filter the store. Delete this property to force a requery. Example use:
  111886. *
  111887. * var combo = new Ext.form.field.ComboBox({
  111888. * ...
  111889. * queryMode: 'remote',
  111890. * listeners: {
  111891. * // delete the previous query in the beforequery event or set
  111892. * // combo.lastQuery = null (this will reload the store the next time it expands)
  111893. * beforequery: function(qe){
  111894. * delete qe.combo.lastQuery;
  111895. * }
  111896. * }
  111897. * });
  111898. *
  111899. * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used configure the
  111900. * combo with `lastQuery=''`. Example use:
  111901. *
  111902. * var combo = new Ext.form.field.ComboBox({
  111903. * ...
  111904. * queryMode: 'local',
  111905. * triggerAction: 'all',
  111906. * lastQuery: ''
  111907. * });
  111908. */
  111909. /**
  111910. * @cfg {Object} defaultListConfig
  111911. * Set of options that will be used as defaults for the user-configured {@link #listConfig} object.
  111912. */
  111913. defaultListConfig: {
  111914. loadingHeight: 70,
  111915. minWidth: 70,
  111916. maxHeight: 300,
  111917. shadow: 'sides'
  111918. },
  111919. /**
  111920. * @cfg {String/HTMLElement/Ext.Element} transform
  111921. * The id, DOM node or {@link Ext.Element} of an existing HTML `<select>` element to convert into a ComboBox. The
  111922. * target select's options will be used to build the options in the ComboBox dropdown; a configured {@link #store}
  111923. * will take precedence over this.
  111924. */
  111925. /**
  111926. * @cfg {Object} listConfig
  111927. * An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s constructor.
  111928. * Any configuration that is valid for BoundList can be included. Some of the more useful ones are:
  111929. *
  111930. * - {@link Ext.view.BoundList#cls cls} - defaults to empty
  111931. * - {@link Ext.view.BoundList#emptyText emptyText} - defaults to empty string
  111932. * - {@link Ext.view.BoundList#itemSelector itemSelector} - defaults to the value defined in BoundList
  111933. * - {@link Ext.view.BoundList#loadingText loadingText} - defaults to `'Loading...'`
  111934. * - {@link Ext.view.BoundList#minWidth minWidth} - defaults to `70`
  111935. * - {@link Ext.view.BoundList#maxWidth maxWidth} - defaults to `undefined`
  111936. * - {@link Ext.view.BoundList#maxHeight maxHeight} - defaults to `300`
  111937. * - {@link Ext.view.BoundList#resizable resizable} - defaults to `false`
  111938. * - {@link Ext.view.BoundList#shadow shadow} - defaults to `'sides'`
  111939. * - {@link Ext.view.BoundList#width width} - defaults to `undefined` (automatically set to the width of the ComboBox
  111940. * field if {@link #matchFieldWidth} is true)
  111941. */
  111942. //private
  111943. ignoreSelection: 0,
  111944. //private, tells the layout to recalculate its startingWidth when a record is removed from its bound store
  111945. removingRecords: null,
  111946. //private helper
  111947. resizeComboToGrow: function () {
  111948. var me = this;
  111949. return me.grow && me.growToLongestValue;
  111950. },
  111951. initComponent: function() {
  111952. var me = this,
  111953. isDefined = Ext.isDefined,
  111954. store = me.store,
  111955. transform = me.transform,
  111956. transformSelect, isLocalMode;
  111957. Ext.applyIf(me.renderSelectors, {
  111958. hiddenDataEl: '.' + me.hiddenDataCls.split(' ').join('.')
  111959. });
  111960. if (me.typeAhead && me.multiSelect) {
  111961. Ext.Error.raise('typeAhead and multiSelect are mutually exclusive options -- please remove one of them.');
  111962. }
  111963. if (me.typeAhead && !me.editable) {
  111964. Ext.Error.raise('If typeAhead is enabled the combo must be editable: true -- please change one of those settings.');
  111965. }
  111966. if (me.selectOnFocus && !me.editable) {
  111967. Ext.Error.raise('If selectOnFocus is enabled the combo must be editable: true -- please change one of those settings.');
  111968. }
  111969. this.addEvents(
  111970. /**
  111971. * @event beforequery
  111972. * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's cancel
  111973. * property to true.
  111974. *
  111975. * @param {Object} queryEvent An object that has these properties:
  111976. *
  111977. * - `combo` : Ext.form.field.ComboBox
  111978. *
  111979. * This combo box
  111980. *
  111981. * - `query` : String
  111982. *
  111983. * The query string
  111984. *
  111985. * - `forceAll` : Boolean
  111986. *
  111987. * True to force "all" query
  111988. *
  111989. * - `cancel` : Boolean
  111990. *
  111991. * Set to true to cancel the query
  111992. */
  111993. 'beforequery',
  111994. /**
  111995. * @event select
  111996. * Fires when at least one list item is selected.
  111997. * @param {Ext.form.field.ComboBox} combo This combo box
  111998. * @param {Array} records The selected records
  111999. */
  112000. 'select',
  112001. /**
  112002. * @event beforeselect
  112003. * Fires before the selected item is added to the collection
  112004. * @param {Ext.form.field.ComboBox} combo This combo box
  112005. * @param {Ext.data.Record} record The selected record
  112006. * @param {Number} index The index of the selected record
  112007. */
  112008. 'beforeselect',
  112009. /**
  112010. * @event beforedeselect
  112011. * Fires before the deselected item is removed from the collection
  112012. * @param {Ext.form.field.ComboBox} combo This combo box
  112013. * @param {Ext.data.Record} record The deselected record
  112014. * @param {Number} index The index of the deselected record
  112015. */
  112016. 'beforedeselect'
  112017. );
  112018. // Build store from 'transform' HTML select element's options
  112019. if (transform) {
  112020. transformSelect = Ext.getDom(transform);
  112021. if (transformSelect) {
  112022. if (!me.store) {
  112023. store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option){
  112024. return [option.value, option.text];
  112025. });
  112026. }
  112027. if (!me.name) {
  112028. me.name = transformSelect.name;
  112029. }
  112030. if (!('value' in me)) {
  112031. me.value = transformSelect.value;
  112032. }
  112033. }
  112034. }
  112035. me.bindStore(store || 'ext-empty-store', true);
  112036. store = me.store;
  112037. if (store.autoCreated) {
  112038. me.queryMode = 'local';
  112039. me.valueField = me.displayField = 'field1';
  112040. if (!store.expanded) {
  112041. me.displayField = 'field2';
  112042. }
  112043. }
  112044. if (!isDefined(me.valueField)) {
  112045. me.valueField = me.displayField;
  112046. }
  112047. isLocalMode = me.queryMode === 'local';
  112048. if (!isDefined(me.queryDelay)) {
  112049. me.queryDelay = isLocalMode ? 10 : 500;
  112050. }
  112051. if (!isDefined(me.minChars)) {
  112052. me.minChars = isLocalMode ? 0 : 4;
  112053. }
  112054. if (!me.displayTpl) {
  112055. me.displayTpl = new Ext.XTemplate(
  112056. '<tpl for=".">' +
  112057. '{[typeof values === "string" ? values : values["' + me.displayField + '"]]}' +
  112058. '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
  112059. '</tpl>'
  112060. );
  112061. } else if (Ext.isString(me.displayTpl)) {
  112062. me.displayTpl = new Ext.XTemplate(me.displayTpl);
  112063. }
  112064. me.callParent();
  112065. me.doQueryTask = new Ext.util.DelayedTask(me.doRawQuery, me);
  112066. // store has already been loaded, setValue
  112067. if (me.store.getCount() > 0) {
  112068. me.setValue(me.value);
  112069. }
  112070. // render in place of 'transform' select
  112071. if (transformSelect) {
  112072. me.render(transformSelect.parentNode, transformSelect);
  112073. Ext.removeNode(transformSelect);
  112074. delete me.renderTo;
  112075. }
  112076. },
  112077. /**
  112078. * Returns the store associated with this ComboBox.
  112079. * @return {Ext.data.Store} The store
  112080. */
  112081. getStore : function(){
  112082. return this.store;
  112083. },
  112084. beforeBlur: function() {
  112085. this.doQueryTask.cancel();
  112086. this.assertValue();
  112087. },
  112088. // private
  112089. assertValue: function() {
  112090. var me = this,
  112091. value = me.getRawValue(),
  112092. rec;
  112093. if (me.forceSelection) {
  112094. if (me.multiSelect) {
  112095. // For multiselect, check that the current displayed value matches the current
  112096. // selection, if it does not then revert to the most recent selection.
  112097. if (value !== me.getDisplayValue()) {
  112098. me.setValue(me.lastSelection);
  112099. }
  112100. } else {
  112101. // For single-select, match the displayed value to a record and select it,
  112102. // if it does not match a record then revert to the most recent selection.
  112103. rec = me.findRecordByDisplay(value);
  112104. if (rec) {
  112105. me.select(rec);
  112106. } else {
  112107. me.setValue(me.lastSelection);
  112108. }
  112109. }
  112110. }
  112111. me.collapse();
  112112. },
  112113. onTypeAhead: function() {
  112114. var me = this,
  112115. displayField = me.displayField,
  112116. record = me.store.findRecord(displayField, me.getRawValue()),
  112117. boundList = me.getPicker(),
  112118. newValue, len, selStart;
  112119. if (record) {
  112120. newValue = record.get(displayField);
  112121. len = newValue.length;
  112122. selStart = me.getRawValue().length;
  112123. boundList.highlightItem(boundList.getNode(record));
  112124. if (selStart !== 0 && selStart !== len) {
  112125. me.setRawValue(newValue);
  112126. me.selectText(selStart, newValue.length);
  112127. }
  112128. }
  112129. },
  112130. // invoked when a different store is bound to this combo
  112131. // than the original
  112132. resetToDefault: Ext.emptyFn,
  112133. beforeReset: function() {
  112134. this.callParent();
  112135. this.clearFilter();
  112136. },
  112137. onUnbindStore: function(store) {
  112138. var picker = this.picker;
  112139. if (!store && picker) {
  112140. picker.bindStore(null);
  112141. }
  112142. this.clearFilter();
  112143. },
  112144. onBindStore: function(store, initial) {
  112145. var picker = this.picker;
  112146. if (!initial) {
  112147. this.resetToDefault();
  112148. }
  112149. if (picker) {
  112150. picker.bindStore(store);
  112151. }
  112152. },
  112153. getStoreListeners: function() {
  112154. var me = this;
  112155. return {
  112156. beforeload: me.onBeforeLoad,
  112157. clear: me.onClear,
  112158. datachanged: me.onDataChanged,
  112159. load: me.onLoad,
  112160. exception: me.onException,
  112161. remove: me.onRemove
  112162. };
  112163. },
  112164. onBeforeLoad: function(){
  112165. // If we're remote loading, the load mask will show which will trigger a deslectAll.
  112166. // This selection change will trigger the collapse in onListSelectionChange. As such
  112167. // we'll veto it for now and restore selection listeners when we've loaded.
  112168. ++this.ignoreSelection;
  112169. },
  112170. onDataChanged: function() {
  112171. var me = this;
  112172. if (me.resizeComboToGrow()) {
  112173. me.updateLayout();
  112174. }
  112175. },
  112176. onClear: function() {
  112177. var me = this;
  112178. if (me.resizeComboToGrow()) {
  112179. me.removingRecords = true;
  112180. me.onDataChanged();
  112181. }
  112182. },
  112183. onRemove: function() {
  112184. var me = this;
  112185. if (me.resizeComboToGrow()) {
  112186. me.removingRecords = true;
  112187. }
  112188. },
  112189. onException: function(){
  112190. if (this.ignoreSelection > 0) {
  112191. --this.ignoreSelection;
  112192. }
  112193. this.collapse();
  112194. },
  112195. onLoad: function() {
  112196. var me = this,
  112197. value = me.value;
  112198. if (me.ignoreSelection > 0) {
  112199. --me.ignoreSelection;
  112200. }
  112201. // If performing a remote query upon the raw value...
  112202. if (me.rawQuery) {
  112203. me.rawQuery = false;
  112204. me.syncSelection();
  112205. if (me.picker && !me.picker.getSelectionModel().hasSelection()) {
  112206. me.doAutoSelect();
  112207. }
  112208. }
  112209. // If store initial load or triggerAction: 'all' trigger click.
  112210. else {
  112211. // Set the value on load
  112212. if (me.value || me.value === 0) {
  112213. me.setValue(me.value);
  112214. } else {
  112215. // There's no value.
  112216. // Highlight the first item in the list if autoSelect: true
  112217. if (me.store.getCount()) {
  112218. me.doAutoSelect();
  112219. } else {
  112220. // assign whatever empty value we have to prevent change from firing
  112221. me.setValue(me.value);
  112222. }
  112223. }
  112224. }
  112225. },
  112226. /**
  112227. * @private
  112228. * Execute the query with the raw contents within the textfield.
  112229. */
  112230. doRawQuery: function() {
  112231. this.doQuery(this.getRawValue(), false, true);
  112232. },
  112233. /**
  112234. * Executes a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the query
  112235. * allowing the query action to be canceled if needed.
  112236. *
  112237. * @param {String} queryString The SQL query to execute
  112238. * @param {Boolean} [forceAll=false] `true` to force the query to execute even if there are currently fewer characters in
  112239. * the field than the minimum specified by the `{@link #minChars}` config option. It also clears any filter
  112240. * previously saved in the current store.
  112241. * @param {Boolean} [rawQuery=false] Pass as true if the raw typed value is being used as the query string. This causes the
  112242. * resulting store load to leave the raw value undisturbed.
  112243. * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery}
  112244. * handler.
  112245. */
  112246. doQuery: function(queryString, forceAll, rawQuery) {
  112247. queryString = queryString || '';
  112248. // store in object and pass by reference in 'beforequery'
  112249. // so that client code can modify values.
  112250. var me = this,
  112251. qe = {
  112252. query: queryString,
  112253. forceAll: forceAll,
  112254. combo: me,
  112255. cancel: false
  112256. },
  112257. store = me.store,
  112258. isLocalMode = me.queryMode === 'local',
  112259. needsRefresh;
  112260. if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
  112261. return false;
  112262. }
  112263. // get back out possibly modified values
  112264. queryString = qe.query;
  112265. forceAll = qe.forceAll;
  112266. // query permitted to run
  112267. if (forceAll || (queryString.length >= me.minChars)) {
  112268. // expand before starting query so LoadMask can position itself correctly
  112269. me.expand();
  112270. // make sure they aren't querying the same thing
  112271. if (!me.queryCaching || me.lastQuery !== queryString) {
  112272. me.lastQuery = queryString;
  112273. if (isLocalMode) {
  112274. // forceAll means no filtering - show whole dataset.
  112275. store.suspendEvents();
  112276. needsRefresh = me.clearFilter();
  112277. if (queryString || !forceAll) {
  112278. me.activeFilter = new Ext.util.Filter({
  112279. root: 'data',
  112280. property: me.displayField,
  112281. value: queryString
  112282. });
  112283. store.filter(me.activeFilter);
  112284. needsRefresh = true;
  112285. } else {
  112286. delete me.activeFilter;
  112287. }
  112288. store.resumeEvents();
  112289. if (me.rendered && needsRefresh) {
  112290. me.getPicker().refresh();
  112291. }
  112292. } else {
  112293. // Set flag for onLoad handling to know how the Store was loaded
  112294. me.rawQuery = rawQuery;
  112295. // In queryMode: 'remote', we assume Store filters are added by the developer as remote filters,
  112296. // and these are automatically passed as params with every load call, so we do *not* call clearFilter.
  112297. if (me.pageSize) {
  112298. // if we're paging, we've changed the query so start at page 1.
  112299. me.loadPage(1);
  112300. } else {
  112301. store.load({
  112302. params: me.getParams(queryString)
  112303. });
  112304. }
  112305. }
  112306. }
  112307. // Clear current selection if it does not match the current value in the field
  112308. if (me.getRawValue() !== me.getDisplayValue()) {
  112309. me.ignoreSelection++;
  112310. me.picker.getSelectionModel().deselectAll();
  112311. me.ignoreSelection--;
  112312. }
  112313. if (isLocalMode) {
  112314. me.doAutoSelect();
  112315. }
  112316. if (me.typeAhead) {
  112317. me.doTypeAhead();
  112318. }
  112319. }
  112320. return true;
  112321. },
  112322. /**
  112323. * Clears any previous filters applied by the combo to the store
  112324. * @private
  112325. * @return {Boolean} True if a filter was removed
  112326. */
  112327. clearFilter: function() {
  112328. var store = this.store,
  112329. filter = this.activeFilter,
  112330. filters = store.filters,
  112331. remaining;
  112332. if (filter) {
  112333. if (filters.getCount() > 1) {
  112334. // More than 1 existing filter
  112335. filters.remove(filter);
  112336. remaining = filters.getRange();
  112337. }
  112338. store.clearFilter(true);
  112339. if (remaining) {
  112340. store.filter(remaining);
  112341. }
  112342. }
  112343. return !!filter;
  112344. },
  112345. loadPage: function(pageNum){
  112346. this.store.loadPage(pageNum, {
  112347. params: this.getParams(this.lastQuery)
  112348. });
  112349. },
  112350. onPageChange: function(toolbar, newPage){
  112351. /*
  112352. * Return false here so we can call load ourselves and inject the query param.
  112353. * We don't want to do this for every store load since the developer may load
  112354. * the store through some other means so we won't add the query param.
  112355. */
  112356. this.loadPage(newPage);
  112357. return false;
  112358. },
  112359. // private
  112360. getParams: function(queryString) {
  112361. var params = {},
  112362. param = this.queryParam;
  112363. if (param) {
  112364. params[param] = queryString;
  112365. }
  112366. return params;
  112367. },
  112368. /**
  112369. * @private
  112370. * If the autoSelect config is true, and the picker is open, highlights the first item.
  112371. */
  112372. doAutoSelect: function() {
  112373. var me = this,
  112374. picker = me.picker,
  112375. lastSelected, itemNode;
  112376. if (picker && me.autoSelect && me.store.getCount() > 0) {
  112377. // Highlight the last selected item and scroll it into view
  112378. lastSelected = picker.getSelectionModel().lastSelected;
  112379. itemNode = picker.getNode(lastSelected || 0);
  112380. if (itemNode) {
  112381. picker.highlightItem(itemNode);
  112382. picker.listEl.scrollChildIntoView(itemNode, false);
  112383. }
  112384. }
  112385. },
  112386. doTypeAhead: function() {
  112387. if (!this.typeAheadTask) {
  112388. this.typeAheadTask = new Ext.util.DelayedTask(this.onTypeAhead, this);
  112389. }
  112390. if (this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE) {
  112391. this.typeAheadTask.delay(this.typeAheadDelay);
  112392. }
  112393. },
  112394. onTriggerClick: function() {
  112395. var me = this;
  112396. if (!me.readOnly && !me.disabled) {
  112397. if (me.isExpanded) {
  112398. me.collapse();
  112399. } else {
  112400. me.onFocus({});
  112401. if (me.triggerAction === 'all') {
  112402. me.doQuery(me.allQuery, true);
  112403. } else {
  112404. me.doQuery(me.getRawValue(), false, true);
  112405. }
  112406. }
  112407. me.inputEl.focus();
  112408. }
  112409. },
  112410. // store the last key and doQuery if relevant
  112411. onKeyUp: function(e, t) {
  112412. var me = this,
  112413. key = e.getKey();
  112414. if (!me.readOnly && !me.disabled && me.editable) {
  112415. me.lastKey = key;
  112416. // we put this in a task so that we can cancel it if a user is
  112417. // in and out before the queryDelay elapses
  112418. // perform query w/ any normal key or backspace or delete
  112419. if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {
  112420. me.doQueryTask.delay(me.queryDelay);
  112421. }
  112422. }
  112423. if (me.enableKeyEvents) {
  112424. me.callParent(arguments);
  112425. }
  112426. },
  112427. initEvents: function() {
  112428. var me = this;
  112429. me.callParent();
  112430. /*
  112431. * Setup keyboard handling. If enableKeyEvents is true, we already have
  112432. * a listener on the inputEl for keyup, so don't create a second.
  112433. */
  112434. if (!me.enableKeyEvents) {
  112435. me.mon(me.inputEl, 'keyup', me.onKeyUp, me);
  112436. }
  112437. },
  112438. onDestroy: function() {
  112439. this.bindStore(null);
  112440. this.callParent();
  112441. },
  112442. // The picker (the dropdown) must have its zIndex managed by the same ZIndexManager which is
  112443. // providing the zIndex of our Container.
  112444. onAdded: function() {
  112445. var me = this;
  112446. me.callParent(arguments);
  112447. if (me.picker) {
  112448. me.picker.ownerCt = me.up('[floating]');
  112449. me.picker.registerWithOwnerCt();
  112450. }
  112451. },
  112452. createPicker: function() {
  112453. var me = this,
  112454. picker,
  112455. pickerCfg = Ext.apply({
  112456. xtype: 'boundlist',
  112457. pickerField: me,
  112458. selModel: {
  112459. mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
  112460. },
  112461. floating: true,
  112462. hidden: true,
  112463. store: me.store,
  112464. displayField: me.displayField,
  112465. focusOnToFront: false,
  112466. pageSize: me.pageSize,
  112467. tpl: me.tpl
  112468. }, me.listConfig, me.defaultListConfig);
  112469. picker = me.picker = Ext.widget(pickerCfg);
  112470. if (me.pageSize) {
  112471. picker.pagingToolbar.on('beforechange', me.onPageChange, me);
  112472. }
  112473. me.mon(picker, {
  112474. itemclick: me.onItemClick,
  112475. refresh: me.onListRefresh,
  112476. scope: me
  112477. });
  112478. me.mon(picker.getSelectionModel(), {
  112479. beforeselect: me.onBeforeSelect,
  112480. beforedeselect: me.onBeforeDeselect,
  112481. selectionchange: me.onListSelectionChange,
  112482. scope: me
  112483. });
  112484. return picker;
  112485. },
  112486. alignPicker: function(){
  112487. var me = this,
  112488. picker = me.getPicker(),
  112489. heightAbove = me.getPosition()[1] - Ext.getBody().getScroll().top,
  112490. heightBelow = Ext.Element.getViewHeight() - heightAbove - me.getHeight(),
  112491. space = Math.max(heightAbove, heightBelow);
  112492. // Allow the picker to height itself naturally.
  112493. if (picker.height) {
  112494. delete picker.height;
  112495. picker.updateLayout();
  112496. }
  112497. // Then ensure that vertically, the dropdown will fit into the space either above or below the inputEl.
  112498. if (picker.getHeight() > space - 5) {
  112499. picker.setHeight(space - 5); // have some leeway so we aren't flush against
  112500. }
  112501. me.callParent();
  112502. },
  112503. onListRefresh: function() {
  112504. this.alignPicker();
  112505. this.syncSelection();
  112506. },
  112507. onItemClick: function(picker, record){
  112508. /*
  112509. * If we're doing single selection, the selection change events won't fire when
  112510. * clicking on the selected element. Detect it here.
  112511. */
  112512. var me = this,
  112513. selection = me.picker.getSelectionModel().getSelection(),
  112514. valueField = me.valueField;
  112515. if (!me.multiSelect && selection.length) {
  112516. if (record.get(valueField) === selection[0].get(valueField)) {
  112517. // Make sure we also update the display value if it's only partial
  112518. me.displayTplData = [record.data];
  112519. me.setRawValue(me.getDisplayValue());
  112520. me.collapse();
  112521. }
  112522. }
  112523. },
  112524. onBeforeSelect: function(list, record) {
  112525. return this.fireEvent('beforeselect', this, record, record.index);
  112526. },
  112527. onBeforeDeselect: function(list, record) {
  112528. return this.fireEvent('beforedeselect', this, record, record.index);
  112529. },
  112530. onListSelectionChange: function(list, selectedRecords) {
  112531. var me = this,
  112532. isMulti = me.multiSelect,
  112533. hasRecords = selectedRecords.length > 0;
  112534. // Only react to selection if it is not called from setValue, and if our list is
  112535. // expanded (ignores changes to the selection model triggered elsewhere)
  112536. if (!me.ignoreSelection && me.isExpanded) {
  112537. if (!isMulti) {
  112538. Ext.defer(me.collapse, 1, me);
  112539. }
  112540. /*
  112541. * Only set the value here if we're in multi selection mode or we have
  112542. * a selection. Otherwise setValue will be called with an empty value
  112543. * which will cause the change event to fire twice.
  112544. */
  112545. if (isMulti || hasRecords) {
  112546. me.setValue(selectedRecords, false);
  112547. }
  112548. if (hasRecords) {
  112549. me.fireEvent('select', me, selectedRecords);
  112550. }
  112551. me.inputEl.focus();
  112552. }
  112553. },
  112554. /**
  112555. * @private
  112556. * Enables the key nav for the BoundList when it is expanded.
  112557. */
  112558. onExpand: function() {
  112559. var me = this,
  112560. keyNav = me.listKeyNav,
  112561. selectOnTab = me.selectOnTab,
  112562. picker = me.getPicker();
  112563. // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.
  112564. if (keyNav) {
  112565. keyNav.enable();
  112566. } else {
  112567. keyNav = me.listKeyNav = new Ext.view.BoundListKeyNav(this.inputEl, {
  112568. boundList: picker,
  112569. forceKeyDown: true,
  112570. tab: function(e) {
  112571. if (selectOnTab) {
  112572. this.selectHighlighted(e);
  112573. me.triggerBlur();
  112574. }
  112575. // Tab key event is allowed to propagate to field
  112576. return true;
  112577. }
  112578. });
  112579. }
  112580. // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
  112581. if (selectOnTab) {
  112582. me.ignoreMonitorTab = true;
  112583. }
  112584. Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
  112585. me.inputEl.focus();
  112586. },
  112587. /**
  112588. * @private
  112589. * Disables the key nav for the BoundList when it is collapsed.
  112590. */
  112591. onCollapse: function() {
  112592. var me = this,
  112593. keyNav = me.listKeyNav;
  112594. if (keyNav) {
  112595. keyNav.disable();
  112596. me.ignoreMonitorTab = false;
  112597. }
  112598. },
  112599. /**
  112600. * Selects an item by a {@link Ext.data.Model Model}, or by a key value.
  112601. * @param {Object} r
  112602. */
  112603. select: function(r) {
  112604. this.setValue(r, true);
  112605. },
  112606. /**
  112607. * Finds the record by searching for a specific field/value combination.
  112608. * @param {String} field The name of the field to test.
  112609. * @param {Object} value The value to match the field against.
  112610. * @return {Ext.data.Model} The matched record or false.
  112611. */
  112612. findRecord: function(field, value) {
  112613. var ds = this.store,
  112614. idx = ds.findExact(field, value);
  112615. return idx !== -1 ? ds.getAt(idx) : false;
  112616. },
  112617. /**
  112618. * Finds the record by searching values in the {@link #valueField}.
  112619. * @param {Object} value The value to match the field against.
  112620. * @return {Ext.data.Model} The matched record or false.
  112621. */
  112622. findRecordByValue: function(value) {
  112623. return this.findRecord(this.valueField, value);
  112624. },
  112625. /**
  112626. * Finds the record by searching values in the {@link #displayField}.
  112627. * @param {Object} value The value to match the field against.
  112628. * @return {Ext.data.Model} The matched record or false.
  112629. */
  112630. findRecordByDisplay: function(value) {
  112631. return this.findRecord(this.displayField, value);
  112632. },
  112633. /**
  112634. * Sets the specified value(s) into the field. For each value, if a record is found in the {@link #store} that
  112635. * matches based on the {@link #valueField}, then that record's {@link #displayField} will be displayed in the
  112636. * field. If no match is found, and the {@link #valueNotFoundText} config option is defined, then that will be
  112637. * displayed as the default field text. Otherwise a blank value will be shown, although the value will still be set.
  112638. * @param {String/String[]} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},
  112639. * or an Array of Strings or Models.
  112640. * @return {Ext.form.field.Field} this
  112641. */
  112642. setValue: function(value, doSelect) {
  112643. var me = this,
  112644. valueNotFoundText = me.valueNotFoundText,
  112645. inputEl = me.inputEl,
  112646. i, len, record,
  112647. dataObj,
  112648. matchedRecords = [],
  112649. displayTplData = [],
  112650. processedValue = [];
  112651. if (me.store.loading) {
  112652. // Called while the Store is loading. Ensure it is processed by the onLoad method.
  112653. me.value = value;
  112654. me.setHiddenValue(me.value);
  112655. return me;
  112656. }
  112657. // This method processes multi-values, so ensure value is an array.
  112658. value = Ext.Array.from(value);
  112659. // Loop through values, matching each from the Store, and collecting matched records
  112660. for (i = 0, len = value.length; i < len; i++) {
  112661. record = value[i];
  112662. if (!record || !record.isModel) {
  112663. record = me.findRecordByValue(record);
  112664. }
  112665. // record found, select it.
  112666. if (record) {
  112667. matchedRecords.push(record);
  112668. displayTplData.push(record.data);
  112669. processedValue.push(record.get(me.valueField));
  112670. }
  112671. // record was not found, this could happen because
  112672. // store is not loaded or they set a value not in the store
  112673. else {
  112674. // If we are allowing insertion of values not represented in the Store, then push the value and
  112675. // create a fake record data object to push as a display value for use by the displayTpl
  112676. if (!me.forceSelection) {
  112677. processedValue.push(value[i]);
  112678. dataObj = {};
  112679. dataObj[me.displayField] = value[i];
  112680. displayTplData.push(dataObj);
  112681. // TODO: Add config to create new records on selection of a value that has no match in the Store
  112682. }
  112683. // Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
  112684. else if (Ext.isDefined(valueNotFoundText)) {
  112685. displayTplData.push(valueNotFoundText);
  112686. }
  112687. }
  112688. }
  112689. // Set the value of this field. If we are multiselecting, then that is an array.
  112690. me.setHiddenValue(processedValue);
  112691. me.value = me.multiSelect ? processedValue : processedValue[0];
  112692. if (!Ext.isDefined(me.value)) {
  112693. me.value = null;
  112694. }
  112695. me.displayTplData = displayTplData; //store for getDisplayValue method
  112696. me.lastSelection = me.valueModels = matchedRecords;
  112697. if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
  112698. inputEl.removeCls(me.emptyCls);
  112699. }
  112700. // Calculate raw value from the collection of Model data
  112701. me.setRawValue(me.getDisplayValue());
  112702. me.checkChange();
  112703. if (doSelect !== false) {
  112704. me.syncSelection();
  112705. }
  112706. me.applyEmptyText();
  112707. return me;
  112708. },
  112709. /**
  112710. * @private
  112711. * Set the value of {@link #hiddenDataEl}
  112712. * Dynamically adds and removes input[type=hidden] elements
  112713. */
  112714. setHiddenValue: function(values){
  112715. var me = this,
  112716. name = me.hiddenName,
  112717. i,
  112718. dom, childNodes, input, valueCount, childrenCount;
  112719. if (!me.hiddenDataEl || !name) {
  112720. return;
  112721. }
  112722. values = Ext.Array.from(values);
  112723. dom = me.hiddenDataEl.dom;
  112724. childNodes = dom.childNodes;
  112725. input = childNodes[0];
  112726. valueCount = values.length;
  112727. childrenCount = childNodes.length;
  112728. if (!input && valueCount > 0) {
  112729. me.hiddenDataEl.update(Ext.DomHelper.markup({
  112730. tag: 'input',
  112731. type: 'hidden',
  112732. name: name
  112733. }));
  112734. childrenCount = 1;
  112735. input = dom.firstChild;
  112736. }
  112737. while (childrenCount > valueCount) {
  112738. dom.removeChild(childNodes[0]);
  112739. -- childrenCount;
  112740. }
  112741. while (childrenCount < valueCount) {
  112742. dom.appendChild(input.cloneNode(true));
  112743. ++ childrenCount;
  112744. }
  112745. for (i = 0; i < valueCount; i++) {
  112746. childNodes[i].value = values[i];
  112747. }
  112748. },
  112749. /**
  112750. * @private Generates the string value to be displayed in the text field for the currently stored value
  112751. */
  112752. getDisplayValue: function() {
  112753. return this.displayTpl.apply(this.displayTplData);
  112754. },
  112755. getValue: function() {
  112756. // If the user has not changed the raw field value since a value was selected from the list,
  112757. // then return the structured value from the selection. If the raw field value is different
  112758. // than what would be displayed due to selection, return that raw value.
  112759. var me = this,
  112760. picker = me.picker,
  112761. rawValue = me.getRawValue(), //current value of text field
  112762. value = me.value; //stored value from last selection or setValue() call
  112763. if (me.getDisplayValue() !== rawValue) {
  112764. value = rawValue;
  112765. me.value = me.displayTplData = me.valueModels = null;
  112766. if (picker) {
  112767. me.ignoreSelection++;
  112768. picker.getSelectionModel().deselectAll();
  112769. me.ignoreSelection--;
  112770. }
  112771. }
  112772. return value;
  112773. },
  112774. getSubmitValue: function() {
  112775. return this.getValue();
  112776. },
  112777. isEqual: function(v1, v2) {
  112778. var fromArray = Ext.Array.from,
  112779. i, len;
  112780. v1 = fromArray(v1);
  112781. v2 = fromArray(v2);
  112782. len = v1.length;
  112783. if (len !== v2.length) {
  112784. return false;
  112785. }
  112786. for(i = 0; i < len; i++) {
  112787. if (v2[i] !== v1[i]) {
  112788. return false;
  112789. }
  112790. }
  112791. return true;
  112792. },
  112793. /**
  112794. * Clears any value currently set in the ComboBox.
  112795. */
  112796. clearValue: function() {
  112797. this.setValue([]);
  112798. },
  112799. /**
  112800. * @private Synchronizes the selection in the picker to match the current value of the combobox.
  112801. */
  112802. syncSelection: function() {
  112803. var me = this,
  112804. picker = me.picker,
  112805. selection, selModel,
  112806. values = me.valueModels || [],
  112807. vLen = values.length, v, value;
  112808. if (picker) {
  112809. // From the value, find the Models that are in the store's current data
  112810. selection = [];
  112811. for (v = 0; v < vLen; v++) {
  112812. value = values[v];
  112813. if (value && value.isModel && me.store.indexOf(value) >= 0) {
  112814. selection.push(value);
  112815. }
  112816. }
  112817. // Update the selection to match
  112818. me.ignoreSelection++;
  112819. selModel = picker.getSelectionModel();
  112820. selModel.deselectAll();
  112821. if (selection.length) {
  112822. selModel.select(selection);
  112823. }
  112824. me.ignoreSelection--;
  112825. }
  112826. },
  112827. onEditorTab: function(e){
  112828. var keyNav = this.listKeyNav;
  112829. if (this.selectOnTab && keyNav) {
  112830. keyNav.selectHighlighted(e);
  112831. }
  112832. }
  112833. });
  112834. /**
  112835. * @private
  112836. * A month picker component. This class is used by the {@link Ext.picker.Date Date picker} class
  112837. * to allow browsing and selection of year/months combinations.
  112838. */
  112839. Ext.define('Ext.picker.Month', {
  112840. extend: 'Ext.Component',
  112841. requires: [
  112842. 'Ext.XTemplate',
  112843. 'Ext.util.ClickRepeater',
  112844. 'Ext.Date',
  112845. 'Ext.button.Button'
  112846. ],
  112847. alias: 'widget.monthpicker',
  112848. alternateClassName: 'Ext.MonthPicker',
  112849. childEls: [
  112850. 'bodyEl', 'prevEl', 'nextEl', 'buttonsEl', 'monthEl', 'yearEl'
  112851. ],
  112852. renderTpl: [
  112853. '<div id="{id}-bodyEl" class="{baseCls}-body">',
  112854. '<div id="{id}-monthEl" class="{baseCls}-months">',
  112855. '<tpl for="months">',
  112856. '<div class="{parent.baseCls}-item {parent.baseCls}-month"><a style="{parent.monthStyle}" href="#" hidefocus="on">{.}</a></div>',
  112857. '</tpl>',
  112858. '</div>',
  112859. '<div id="{id}-yearEl" class="{baseCls}-years">',
  112860. '<div class="{baseCls}-yearnav">',
  112861. '<button id="{id}-prevEl" class="{baseCls}-yearnav-prev"></button>',
  112862. '<button id="{id}-nextEl" class="{baseCls}-yearnav-next"></button>',
  112863. '</div>',
  112864. '<tpl for="years">',
  112865. '<div class="{parent.baseCls}-item {parent.baseCls}-year"><a href="#" hidefocus="on">{.}</a></div>',
  112866. '</tpl>',
  112867. '</div>',
  112868. '<div class="' + Ext.baseCSSPrefix + 'clear"></div>',
  112869. '</div>',
  112870. '<tpl if="showButtons">',
  112871. '<div id="{id}-buttonsEl" class="{baseCls}-buttons">{%',
  112872. 'var me=values.$comp, okBtn=me.okBtn, cancelBtn=me.cancelBtn;',
  112873. 'okBtn.ownerLayout = cancelBtn.ownerLayout = me.componentLayout;',
  112874. 'okBtn.ownerCt = cancelBtn.ownerCt = me;',
  112875. 'Ext.DomHelper.generateMarkup(okBtn.getRenderTree(), out);',
  112876. 'Ext.DomHelper.generateMarkup(cancelBtn.getRenderTree(), out);',
  112877. '%}</div>',
  112878. '</tpl>'
  112879. ],
  112880. //<locale>
  112881. /**
  112882. * @cfg {String} okText The text to display on the ok button.
  112883. */
  112884. okText: 'OK',
  112885. //</locale>
  112886. //<locale>
  112887. /**
  112888. * @cfg {String} cancelText The text to display on the cancel button.
  112889. */
  112890. cancelText: 'Cancel',
  112891. //</locale>
  112892. /**
  112893. * @cfg {String} [baseCls='x-monthpicker']
  112894. * The base CSS class to apply to the picker element.
  112895. */
  112896. baseCls: Ext.baseCSSPrefix + 'monthpicker',
  112897. /**
  112898. * @cfg {Boolean} showButtons True to show ok and cancel buttons below the picker.
  112899. */
  112900. showButtons: true,
  112901. /**
  112902. * @cfg {String} [selectedCls='x-monthpicker-selected'] The class to be added to selected items in the picker.
  112903. */
  112904. /**
  112905. * @cfg {Date/Number[]} value The default value to set. See {@link #setValue}
  112906. */
  112907. width: 178,
  112908. measureWidth: 35,
  112909. measureMaxHeight: 20,
  112910. // used when attached to date picker which isnt showing buttons
  112911. smallCls: Ext.baseCSSPrefix + 'monthpicker-small',
  112912. // private
  112913. totalYears: 10,
  112914. yearOffset: 5, // 10 years in total, 2 per row
  112915. monthOffset: 6, // 12 months, 2 per row
  112916. // private, inherit docs
  112917. initComponent: function(){
  112918. var me = this;
  112919. me.selectedCls = me.baseCls + '-selected';
  112920. me.addEvents(
  112921. /**
  112922. * @event cancelclick
  112923. * Fires when the cancel button is pressed.
  112924. * @param {Ext.picker.Month} this
  112925. */
  112926. 'cancelclick',
  112927. /**
  112928. * @event monthclick
  112929. * Fires when a month is clicked.
  112930. * @param {Ext.picker.Month} this
  112931. * @param {Array} value The current value
  112932. */
  112933. 'monthclick',
  112934. /**
  112935. * @event monthdblclick
  112936. * Fires when a month is clicked.
  112937. * @param {Ext.picker.Month} this
  112938. * @param {Array} value The current value
  112939. */
  112940. 'monthdblclick',
  112941. /**
  112942. * @event okclick
  112943. * Fires when the ok button is pressed.
  112944. * @param {Ext.picker.Month} this
  112945. * @param {Array} value The current value
  112946. */
  112947. 'okclick',
  112948. /**
  112949. * @event select
  112950. * Fires when a month/year is selected.
  112951. * @param {Ext.picker.Month} this
  112952. * @param {Array} value The current value
  112953. */
  112954. 'select',
  112955. /**
  112956. * @event yearclick
  112957. * Fires when a year is clicked.
  112958. * @param {Ext.picker.Month} this
  112959. * @param {Array} value The current value
  112960. */
  112961. 'yearclick',
  112962. /**
  112963. * @event yeardblclick
  112964. * Fires when a year is clicked.
  112965. * @param {Ext.picker.Month} this
  112966. * @param {Array} value The current value
  112967. */
  112968. 'yeardblclick'
  112969. );
  112970. if (me.small) {
  112971. me.addCls(me.smallCls);
  112972. }
  112973. me.setValue(me.value);
  112974. me.activeYear = me.getYear(new Date().getFullYear() - 4, -4);
  112975. if (me.showButtons) {
  112976. me.okBtn = new Ext.button.Button({
  112977. text: me.okText,
  112978. handler: me.onOkClick,
  112979. scope: me
  112980. });
  112981. me.cancelBtn = new Ext.button.Button({
  112982. text: me.cancelText,
  112983. handler: me.onCancelClick,
  112984. scope: me
  112985. });
  112986. }
  112987. this.callParent();
  112988. },
  112989. // private, inherit docs
  112990. beforeRender: function(){
  112991. var me = this,
  112992. i = 0,
  112993. months = [],
  112994. shortName = Ext.Date.getShortMonthName,
  112995. monthLen = me.monthOffset,
  112996. margin = me.monthMargin,
  112997. style = '';
  112998. me.callParent();
  112999. for (; i < monthLen; ++i) {
  113000. months.push(shortName(i), shortName(i + monthLen));
  113001. }
  113002. if (Ext.isDefined(margin)) {
  113003. style = 'margin: 0 ' + margin + 'px;';
  113004. }
  113005. Ext.apply(me.renderData, {
  113006. months: months,
  113007. years: me.getYears(),
  113008. showButtons: me.showButtons,
  113009. monthStyle: style
  113010. });
  113011. },
  113012. // private, inherit docs
  113013. afterRender: function(){
  113014. var me = this,
  113015. body = me.bodyEl,
  113016. buttonsEl = me.buttonsEl;
  113017. me.callParent();
  113018. me.mon(body, 'click', me.onBodyClick, me);
  113019. me.mon(body, 'dblclick', me.onBodyClick, me);
  113020. // keep a reference to the year/month elements since we'll be re-using them
  113021. me.years = body.select('.' + me.baseCls + '-year a');
  113022. me.months = body.select('.' + me.baseCls + '-month a');
  113023. me.backRepeater = new Ext.util.ClickRepeater(me.prevEl, {
  113024. handler: Ext.Function.bind(me.adjustYear, me, [-me.totalYears])
  113025. });
  113026. me.prevEl.addClsOnOver(me.baseCls + '-yearnav-prev-over');
  113027. me.nextRepeater = new Ext.util.ClickRepeater(me.nextEl, {
  113028. handler: Ext.Function.bind(me.adjustYear, me, [me.totalYears])
  113029. });
  113030. me.nextEl.addClsOnOver(me.baseCls + '-yearnav-next-over');
  113031. me.updateBody();
  113032. if (!Ext.isDefined(me.monthMargin)) {
  113033. Ext.picker.Month.prototype.monthMargin = me.calculateMonthMargin();
  113034. }
  113035. },
  113036. calculateMonthMargin: function(){
  113037. // We use this method for locales where the short month name
  113038. // may be longer than we see in English. For example in the
  113039. // zh_TW locale the month ends up spanning lines, so we loosen
  113040. // the margins to get some extra space
  113041. var me = this,
  113042. monthEl = me.monthEl,
  113043. months = me.months,
  113044. first = months.first(),
  113045. itemMargin = first.getMargin('l');
  113046. while (itemMargin && me.getLargest() > me.measureMaxHeight) {
  113047. --itemMargin;
  113048. months.setStyle('margin', '0 ' + itemMargin + 'px');
  113049. }
  113050. return itemMargin;
  113051. },
  113052. getLargest: function(months){
  113053. var largest = 0;
  113054. this.months.each(function(item){
  113055. var h = item.getHeight();
  113056. if (h > largest) {
  113057. largest = h;
  113058. }
  113059. });
  113060. return largest;
  113061. },
  113062. /**
  113063. * Set the value for the picker.
  113064. * @param {Date/Number[]} value The value to set. It can be a Date object, where the month/year will be extracted, or
  113065. * it can be an array, with the month as the first index and the year as the second.
  113066. * @return {Ext.picker.Month} this
  113067. */
  113068. setValue: function(value){
  113069. var me = this,
  113070. active = me.activeYear,
  113071. offset = me.monthOffset,
  113072. year,
  113073. index;
  113074. if (!value) {
  113075. me.value = [null, null];
  113076. } else if (Ext.isDate(value)) {
  113077. me.value = [value.getMonth(), value.getFullYear()];
  113078. } else {
  113079. me.value = [value[0], value[1]];
  113080. }
  113081. if (me.rendered) {
  113082. year = me.value[1];
  113083. if (year !== null) {
  113084. if ((year < active || year > active + me.yearOffset)) {
  113085. me.activeYear = year - me.yearOffset + 1;
  113086. }
  113087. }
  113088. me.updateBody();
  113089. }
  113090. return me;
  113091. },
  113092. /**
  113093. * Gets the selected value. It is returned as an array [month, year]. It may
  113094. * be a partial value, for example [null, 2010]. The month is returned as
  113095. * 0 based.
  113096. * @return {Number[]} The selected value
  113097. */
  113098. getValue: function(){
  113099. return this.value;
  113100. },
  113101. /**
  113102. * Checks whether the picker has a selection
  113103. * @return {Boolean} Returns true if both a month and year have been selected
  113104. */
  113105. hasSelection: function(){
  113106. var value = this.value;
  113107. return value[0] !== null && value[1] !== null;
  113108. },
  113109. /**
  113110. * Get an array of years to be pushed in the template. It is not in strict
  113111. * numerical order because we want to show them in columns.
  113112. * @private
  113113. * @return {Number[]} An array of years
  113114. */
  113115. getYears: function(){
  113116. var me = this,
  113117. offset = me.yearOffset,
  113118. start = me.activeYear, // put the "active" year on the left
  113119. end = start + offset,
  113120. i = start,
  113121. years = [];
  113122. for (; i < end; ++i) {
  113123. years.push(i, i + offset);
  113124. }
  113125. return years;
  113126. },
  113127. /**
  113128. * Update the years in the body based on any change
  113129. * @private
  113130. */
  113131. updateBody: function(){
  113132. var me = this,
  113133. years = me.years,
  113134. months = me.months,
  113135. yearNumbers = me.getYears(),
  113136. cls = me.selectedCls,
  113137. value = me.getYear(null),
  113138. month = me.value[0],
  113139. monthOffset = me.monthOffset,
  113140. year,
  113141. yearItems, y, yLen, el;
  113142. if (me.rendered) {
  113143. years.removeCls(cls);
  113144. months.removeCls(cls);
  113145. yearItems = years.elements;
  113146. yLen = yearItems.length;
  113147. for (y = 0; y < yLen; y++) {
  113148. el = Ext.fly(yearItems[y]);
  113149. year = yearNumbers[y];
  113150. el.dom.innerHTML = year;
  113151. if (year == value) {
  113152. el.dom.className = cls;
  113153. }
  113154. }
  113155. if (month !== null) {
  113156. if (month < monthOffset) {
  113157. month = month * 2;
  113158. } else {
  113159. month = (month - monthOffset) * 2 + 1;
  113160. }
  113161. months.item(month).addCls(cls);
  113162. }
  113163. }
  113164. },
  113165. /**
  113166. * Gets the current year value, or the default.
  113167. * @private
  113168. * @param {Number} defaultValue The default value to use if the year is not defined.
  113169. * @param {Number} offset A number to offset the value by
  113170. * @return {Number} The year value
  113171. */
  113172. getYear: function(defaultValue, offset) {
  113173. var year = this.value[1];
  113174. offset = offset || 0;
  113175. return year === null ? defaultValue : year + offset;
  113176. },
  113177. /**
  113178. * React to clicks on the body
  113179. * @private
  113180. */
  113181. onBodyClick: function(e, t) {
  113182. var me = this,
  113183. isDouble = e.type == 'dblclick';
  113184. if (e.getTarget('.' + me.baseCls + '-month')) {
  113185. e.stopEvent();
  113186. me.onMonthClick(t, isDouble);
  113187. } else if (e.getTarget('.' + me.baseCls + '-year')) {
  113188. e.stopEvent();
  113189. me.onYearClick(t, isDouble);
  113190. }
  113191. },
  113192. /**
  113193. * Modify the year display by passing an offset.
  113194. * @param {Number} [offset=10] The offset to move by.
  113195. */
  113196. adjustYear: function(offset){
  113197. if (typeof offset != 'number') {
  113198. offset = this.totalYears;
  113199. }
  113200. this.activeYear += offset;
  113201. this.updateBody();
  113202. },
  113203. /**
  113204. * React to the ok button being pressed
  113205. * @private
  113206. */
  113207. onOkClick: function(){
  113208. this.fireEvent('okclick', this, this.value);
  113209. },
  113210. /**
  113211. * React to the cancel button being pressed
  113212. * @private
  113213. */
  113214. onCancelClick: function(){
  113215. this.fireEvent('cancelclick', this);
  113216. },
  113217. /**
  113218. * React to a month being clicked
  113219. * @private
  113220. * @param {HTMLElement} target The element that was clicked
  113221. * @param {Boolean} isDouble True if the event was a doubleclick
  113222. */
  113223. onMonthClick: function(target, isDouble){
  113224. var me = this;
  113225. me.value[0] = me.resolveOffset(me.months.indexOf(target), me.monthOffset);
  113226. me.updateBody();
  113227. me.fireEvent('month' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
  113228. me.fireEvent('select', me, me.value);
  113229. },
  113230. /**
  113231. * React to a year being clicked
  113232. * @private
  113233. * @param {HTMLElement} target The element that was clicked
  113234. * @param {Boolean} isDouble True if the event was a doubleclick
  113235. */
  113236. onYearClick: function(target, isDouble){
  113237. var me = this;
  113238. me.value[1] = me.activeYear + me.resolveOffset(me.years.indexOf(target), me.yearOffset);
  113239. me.updateBody();
  113240. me.fireEvent('year' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
  113241. me.fireEvent('select', me, me.value);
  113242. },
  113243. /**
  113244. * Returns an offsetted number based on the position in the collection. Since our collections aren't
  113245. * numerically ordered, this function helps to normalize those differences.
  113246. * @private
  113247. * @param {Object} index
  113248. * @param {Object} offset
  113249. * @return {Number} The correctly offsetted number
  113250. */
  113251. resolveOffset: function(index, offset){
  113252. if (index % 2 === 0) {
  113253. return (index / 2);
  113254. } else {
  113255. return offset + Math.floor(index / 2);
  113256. }
  113257. },
  113258. // private, inherit docs
  113259. beforeDestroy: function(){
  113260. var me = this;
  113261. me.years = me.months = null;
  113262. Ext.destroyMembers(me, 'backRepeater', 'nextRepeater', 'okBtn', 'cancelBtn');
  113263. me.callParent();
  113264. },
  113265. // Do the job of a container layout at this point even though we are not a Container.
  113266. // TODO: Refactor as a Container.
  113267. finishRenderChildren: function () {
  113268. var me = this;
  113269. this.callParent(arguments);
  113270. if (this.showButtons) {
  113271. me.okBtn.finishRender();
  113272. me.cancelBtn.finishRender();
  113273. }
  113274. },
  113275. onDestroy: function() {
  113276. Ext.destroyMembers(this, 'okBtn', 'cancelBtn');
  113277. this.callParent();
  113278. }
  113279. });
  113280. /**
  113281. * A date picker. This class is used by the Ext.form.field.Date field to allow browsing and selection of valid
  113282. * dates in a popup next to the field, but may also be used with other components.
  113283. *
  113284. * Typically you will need to implement a handler function to be notified when the user chooses a date from the picker;
  113285. * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method.
  113286. *
  113287. * By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate},
  113288. * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs.
  113289. *
  113290. * All the string values documented below may be overridden by including an Ext locale file in your page.
  113291. *
  113292. * @example
  113293. * Ext.create('Ext.panel.Panel', {
  113294. * title: 'Choose a future date:',
  113295. * width: 200,
  113296. * bodyPadding: 10,
  113297. * renderTo: Ext.getBody(),
  113298. * items: [{
  113299. * xtype: 'datepicker',
  113300. * minDate: new Date(),
  113301. * handler: function(picker, date) {
  113302. * // do something with the selected date
  113303. * }
  113304. * }]
  113305. * });
  113306. */
  113307. Ext.define('Ext.picker.Date', {
  113308. extend: 'Ext.Component',
  113309. requires: [
  113310. 'Ext.XTemplate',
  113311. 'Ext.button.Button',
  113312. 'Ext.button.Split',
  113313. 'Ext.util.ClickRepeater',
  113314. 'Ext.util.KeyNav',
  113315. 'Ext.EventObject',
  113316. 'Ext.fx.Manager',
  113317. 'Ext.picker.Month'
  113318. ],
  113319. alias: 'widget.datepicker',
  113320. alternateClassName: 'Ext.DatePicker',
  113321. childEls: [
  113322. 'innerEl', 'eventEl', 'prevEl', 'nextEl', 'middleBtnEl', 'footerEl'
  113323. ],
  113324. border: true,
  113325. renderTpl: [
  113326. '<div id="{id}-innerEl" role="grid">',
  113327. '<div role="presentation" class="{baseCls}-header">',
  113328. '<div class="{baseCls}-prev"><a id="{id}-prevEl" href="#" role="button" title="{prevText}"></a></div>',
  113329. '<div class="{baseCls}-month" id="{id}-middleBtnEl">{%this.renderMonthBtn(values, out)%}</div>',
  113330. '<div class="{baseCls}-next"><a id="{id}-nextEl" href="#" role="button" title="{nextText}"></a></div>',
  113331. '</div>',
  113332. '<table id="{id}-eventEl" class="{baseCls}-inner" cellspacing="0" role="presentation">',
  113333. '<thead role="presentation"><tr role="presentation">',
  113334. '<tpl for="dayNames">',
  113335. '<th role="columnheader" title="{.}"><span>{.:this.firstInitial}</span></th>',
  113336. '</tpl>',
  113337. '</tr></thead>',
  113338. '<tbody role="presentation"><tr role="presentation">',
  113339. '<tpl for="days">',
  113340. '{#:this.isEndOfWeek}',
  113341. '<td role="gridcell" id="{[Ext.id()]}">',
  113342. '<a role="presentation" href="#" hidefocus="on" class="{parent.baseCls}-date" tabIndex="1">',
  113343. '<em role="presentation"><span role="presentation"></span></em>',
  113344. '</a>',
  113345. '</td>',
  113346. '</tpl>',
  113347. '</tr></tbody>',
  113348. '</table>',
  113349. '<tpl if="showToday">',
  113350. '<div id="{id}-footerEl" role="presentation" class="{baseCls}-footer">{%this.renderTodayBtn(values, out)%}</div>',
  113351. '</tpl>',
  113352. '</div>',
  113353. {
  113354. firstInitial: function(value) {
  113355. return Ext.picker.Date.prototype.getDayInitial(value);
  113356. },
  113357. isEndOfWeek: function(value) {
  113358. // convert from 1 based index to 0 based
  113359. // by decrementing value once.
  113360. value--;
  113361. var end = value % 7 === 0 && value !== 0;
  113362. return end ? '</tr><tr role="row">' : '';
  113363. },
  113364. renderTodayBtn: function(values, out) {
  113365. Ext.DomHelper.generateMarkup(values.$comp.todayBtn.getRenderTree(), out);
  113366. },
  113367. renderMonthBtn: function(values, out) {
  113368. Ext.DomHelper.generateMarkup(values.$comp.monthBtn.getRenderTree(), out);
  113369. }
  113370. }
  113371. ],
  113372. //<locale>
  113373. /**
  113374. * @cfg {String} todayText
  113375. * The text to display on the button that selects the current date
  113376. */
  113377. todayText : 'Today',
  113378. //</locale>
  113379. //<locale>
  113380. /**
  113381. * @cfg {String} ariaTitle
  113382. * The text to display for the aria title
  113383. */
  113384. ariaTitle: 'Date Picker: {0}',
  113385. //</locale>
  113386. //<locale>
  113387. /**
  113388. * @cfg {String} ariaTitleDateFormat
  113389. * The date format to display for the current value in the {@link #ariaTitle}
  113390. */
  113391. ariaTitleDateFormat: 'F d, Y',
  113392. //</locale>
  113393. /**
  113394. * @cfg {Function} handler
  113395. * Optional. A function that will handle the select event of this picker. The handler is passed the following
  113396. * parameters:
  113397. *
  113398. * - `picker` : Ext.picker.Date
  113399. *
  113400. * This Date picker.
  113401. *
  113402. * - `date` : Date
  113403. *
  113404. * The selected date.
  113405. */
  113406. /**
  113407. * @cfg {Object} scope
  113408. * The scope (`this` reference) in which the `{@link #handler}` function will be called.
  113409. *
  113410. * Defaults to this DatePicker instance.
  113411. */
  113412. //<locale>
  113413. /**
  113414. * @cfg {String} todayTip
  113415. * A string used to format the message for displaying in a tooltip over the button that selects the current date.
  113416. * The `{0}` token in string is replaced by today's date.
  113417. */
  113418. todayTip : '{0} (Spacebar)',
  113419. //</locale>
  113420. //<locale>
  113421. /**
  113422. * @cfg {String} minText
  113423. * The error text to display if the minDate validation fails.
  113424. */
  113425. minText : 'This date is before the minimum date',
  113426. //</locale>
  113427. //<locale>
  113428. /**
  113429. * @cfg {String} maxText
  113430. * The error text to display if the maxDate validation fails.
  113431. */
  113432. maxText : 'This date is after the maximum date',
  113433. //</locale>
  113434. /**
  113435. * @cfg {String} format
  113436. * The default date format string which can be overriden for localization support. The format must be valid
  113437. * according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}).
  113438. */
  113439. //<locale>
  113440. /**
  113441. * @cfg {String} disabledDaysText
  113442. * The tooltip to display when the date falls on a disabled day.
  113443. */
  113444. disabledDaysText : 'Disabled',
  113445. //</locale>
  113446. //<locale>
  113447. /**
  113448. * @cfg {String} disabledDatesText
  113449. * The tooltip text to display when the date falls on a disabled date.
  113450. */
  113451. disabledDatesText : 'Disabled',
  113452. //</locale>
  113453. /**
  113454. * @cfg {String[]} monthNames
  113455. * An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames)
  113456. */
  113457. /**
  113458. * @cfg {String[]} dayNames
  113459. * An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames)
  113460. */
  113461. //<locale>
  113462. /**
  113463. * @cfg {String} nextText
  113464. * The next month navigation button tooltip
  113465. */
  113466. nextText : 'Next Month (Control+Right)',
  113467. //</locale>
  113468. //<locale>
  113469. /**
  113470. * @cfg {String} prevText
  113471. * The previous month navigation button tooltip
  113472. */
  113473. prevText : 'Previous Month (Control+Left)',
  113474. //</locale>
  113475. //<locale>
  113476. /**
  113477. * @cfg {String} monthYearText
  113478. * The header month selector tooltip
  113479. */
  113480. monthYearText : 'Choose a month (Control+Up/Down to move years)',
  113481. //</locale>
  113482. //<locale>
  113483. /**
  113484. * @cfg {String} monthYearFormat
  113485. * The date format for the header month
  113486. */
  113487. monthYearFormat: 'F Y',
  113488. //</locale>
  113489. //<locale>
  113490. /**
  113491. * @cfg {Number} [startDay=undefined]
  113492. * Day index at which the week should begin, 0-based.
  113493. *
  113494. * Defaults to `0` (Sunday).
  113495. */
  113496. startDay : 0,
  113497. //</locale>
  113498. //<locale>
  113499. /**
  113500. * @cfg {Boolean} showToday
  113501. * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar that
  113502. * selects the current date.
  113503. */
  113504. showToday : true,
  113505. //</locale>
  113506. /**
  113507. * @cfg {Date} [minDate=null]
  113508. * Minimum allowable date (JavaScript date object)
  113509. */
  113510. /**
  113511. * @cfg {Date} [maxDate=null]
  113512. * Maximum allowable date (JavaScript date object)
  113513. */
  113514. /**
  113515. * @cfg {Number[]} [disabledDays=null]
  113516. * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday.
  113517. */
  113518. /**
  113519. * @cfg {RegExp} [disabledDatesRE=null]
  113520. * JavaScript regular expression used to disable a pattern of dates. The {@link #disabledDates}
  113521. * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
  113522. * disabledDates value.
  113523. */
  113524. /**
  113525. * @cfg {String[]} disabledDates
  113526. * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular expression so
  113527. * they are very powerful. Some examples:
  113528. *
  113529. * - ['03/08/2003', '09/16/2003'] would disable those exact dates
  113530. * - ['03/08', '09/16'] would disable those days for every year
  113531. * - ['^03/08'] would only match the beginning (useful if you are using short years)
  113532. * - ['03/../2006'] would disable every day in March 2006
  113533. * - ['^03'] would disable every day in every March
  113534. *
  113535. * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
  113536. * to support regular expressions, if you are using a date format that has '.' in it, you will have to escape the
  113537. * dot when restricting dates. For example: ['03\\.08\\.03'].
  113538. */
  113539. /**
  113540. * @cfg {Boolean} disableAnim
  113541. * True to disable animations when showing the month picker.
  113542. */
  113543. disableAnim: false,
  113544. /**
  113545. * @cfg {String} [baseCls='x-datepicker']
  113546. * The base CSS class to apply to this components element.
  113547. */
  113548. baseCls: Ext.baseCSSPrefix + 'datepicker',
  113549. /**
  113550. * @cfg {String} [selectedCls='x-datepicker-selected']
  113551. * The class to apply to the selected cell.
  113552. */
  113553. /**
  113554. * @cfg {String} [disabledCellCls='x-datepicker-disabled']
  113555. * The class to apply to disabled cells.
  113556. */
  113557. //<locale>
  113558. /**
  113559. * @cfg {String} longDayFormat
  113560. * The format for displaying a date in a longer format.
  113561. */
  113562. longDayFormat: 'F d, Y',
  113563. //</locale>
  113564. /**
  113565. * @cfg {Object} keyNavConfig
  113566. * Specifies optional custom key event handlers for the {@link Ext.util.KeyNav} attached to this date picker. Must
  113567. * conform to the config format recognized by the {@link Ext.util.KeyNav} constructor. Handlers specified in this
  113568. * object will replace default handlers of the same name.
  113569. */
  113570. /**
  113571. * @cfg {Boolean} focusOnShow
  113572. * True to automatically focus the picker on show.
  113573. */
  113574. focusOnShow: false,
  113575. // private
  113576. // Set by other components to stop the picker focus being updated when the value changes.
  113577. focusOnSelect: true,
  113578. width: 178,
  113579. // default value used to initialise each date in the DatePicker
  113580. // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
  113581. initHour: 12, // 24-hour format
  113582. numDays: 42,
  113583. // private, inherit docs
  113584. initComponent : function() {
  113585. var me = this,
  113586. clearTime = Ext.Date.clearTime;
  113587. me.selectedCls = me.baseCls + '-selected';
  113588. me.disabledCellCls = me.baseCls + '-disabled';
  113589. me.prevCls = me.baseCls + '-prevday';
  113590. me.activeCls = me.baseCls + '-active';
  113591. me.nextCls = me.baseCls + '-prevday';
  113592. me.todayCls = me.baseCls + '-today';
  113593. me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay));
  113594. me.listeners = Ext.apply(me.listeners||{}, {
  113595. mousewheel: {
  113596. element: 'eventEl',
  113597. fn: me.handleMouseWheel,
  113598. scope: me
  113599. },
  113600. click: {
  113601. element: 'eventEl',
  113602. fn: me.handleDateClick,
  113603. scope: me,
  113604. delegate: 'a.' + me.baseCls + '-date'
  113605. }
  113606. });
  113607. this.callParent();
  113608. me.value = me.value ?
  113609. clearTime(me.value, true) : clearTime(new Date());
  113610. me.addEvents(
  113611. /**
  113612. * @event select
  113613. * Fires when a date is selected
  113614. * @param {Ext.picker.Date} this DatePicker
  113615. * @param {Date} date The selected date
  113616. */
  113617. 'select'
  113618. );
  113619. me.initDisabledDays();
  113620. },
  113621. beforeRender: function () {
  113622. /*
  113623. * days array for looping through 6 full weeks (6 weeks * 7 days)
  113624. * Note that we explicitly force the size here so the template creates
  113625. * all the appropriate cells.
  113626. */
  113627. var me = this,
  113628. days = new Array(me.numDays),
  113629. today = Ext.Date.format(new Date(), me.format);
  113630. // If there's a Menu among our ancestors, then add the menu class.
  113631. // This is so that the MenuManager does not see a mousedown in this Component as a document mousedown, outside the Menu
  113632. if (me.up('menu')) {
  113633. me.addCls(Ext.baseCSSPrefix + 'menu');
  113634. }
  113635. me.monthBtn = new Ext.button.Split({
  113636. ownerCt: me,
  113637. ownerLayout: me.getComponentLayout(),
  113638. text: '',
  113639. tooltip: me.monthYearText,
  113640. listeners: {
  113641. click: me.showMonthPicker,
  113642. arrowclick: me.showMonthPicker,
  113643. scope: me
  113644. }
  113645. });
  113646. if (this.showToday) {
  113647. me.todayBtn = new Ext.button.Button({
  113648. ownerCt: me,
  113649. ownerLayout: me.getComponentLayout(),
  113650. text: Ext.String.format(me.todayText, today),
  113651. tooltip: Ext.String.format(me.todayTip, today),
  113652. tooltipType: 'title',
  113653. handler: me.selectToday,
  113654. scope: me
  113655. });
  113656. }
  113657. me.callParent();
  113658. Ext.applyIf(me, {
  113659. renderData: {}
  113660. });
  113661. Ext.apply(me.renderData, {
  113662. dayNames: me.dayNames,
  113663. showToday: me.showToday,
  113664. prevText: me.prevText,
  113665. nextText: me.nextText,
  113666. days: days
  113667. });
  113668. },
  113669. // Do the job of a container layout at this point even though we are not a Container.
  113670. // TODO: Refactor as a Container.
  113671. finishRenderChildren: function () {
  113672. var me = this;
  113673. me.callParent();
  113674. me.monthBtn.finishRender();
  113675. if (me.showToday) {
  113676. me.todayBtn.finishRender();
  113677. }
  113678. },
  113679. // private, inherit docs
  113680. onRender : function(container, position){
  113681. var me = this;
  113682. me.callParent(arguments);
  113683. me.el.unselectable();
  113684. me.cells = me.eventEl.select('tbody td');
  113685. me.textNodes = me.eventEl.query('tbody td span');
  113686. },
  113687. // private, inherit docs
  113688. initEvents: function(){
  113689. var me = this,
  113690. eDate = Ext.Date,
  113691. day = eDate.DAY;
  113692. me.callParent();
  113693. me.prevRepeater = new Ext.util.ClickRepeater(me.prevEl, {
  113694. handler: me.showPrevMonth,
  113695. scope: me,
  113696. preventDefault: true,
  113697. stopDefault: true
  113698. });
  113699. me.nextRepeater = new Ext.util.ClickRepeater(me.nextEl, {
  113700. handler: me.showNextMonth,
  113701. scope: me,
  113702. preventDefault:true,
  113703. stopDefault:true
  113704. });
  113705. me.keyNav = new Ext.util.KeyNav(me.eventEl, Ext.apply({
  113706. scope: me,
  113707. left : function(e){
  113708. if(e.ctrlKey){
  113709. me.showPrevMonth();
  113710. }else{
  113711. me.update(eDate.add(me.activeDate, day, -1));
  113712. }
  113713. },
  113714. right : function(e){
  113715. if(e.ctrlKey){
  113716. me.showNextMonth();
  113717. }else{
  113718. me.update(eDate.add(me.activeDate, day, 1));
  113719. }
  113720. },
  113721. up : function(e){
  113722. if(e.ctrlKey){
  113723. me.showNextYear();
  113724. }else{
  113725. me.update(eDate.add(me.activeDate, day, -7));
  113726. }
  113727. },
  113728. down : function(e){
  113729. if(e.ctrlKey){
  113730. me.showPrevYear();
  113731. }else{
  113732. me.update(eDate.add(me.activeDate, day, 7));
  113733. }
  113734. },
  113735. pageUp : me.showNextMonth,
  113736. pageDown : me.showPrevMonth,
  113737. enter : function(e){
  113738. e.stopPropagation();
  113739. return true;
  113740. }
  113741. }, me.keyNavConfig));
  113742. if (me.showToday) {
  113743. me.todayKeyListener = me.eventEl.addKeyListener(Ext.EventObject.SPACE, me.selectToday, me);
  113744. }
  113745. me.update(me.value);
  113746. },
  113747. /**
  113748. * Setup the disabled dates regex based on config options
  113749. * @private
  113750. */
  113751. initDisabledDays : function(){
  113752. var me = this,
  113753. dd = me.disabledDates,
  113754. re = '(?:',
  113755. len,
  113756. d, dLen, dI;
  113757. if(!me.disabledDatesRE && dd){
  113758. len = dd.length - 1;
  113759. dLen = dd.length;
  113760. for (d = 0; d < dLen; d++) {
  113761. dI = dd[d];
  113762. re += Ext.isDate(dI) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(dI, me.format)) + '$' : dI;
  113763. if (d != len) {
  113764. re += '|';
  113765. }
  113766. }
  113767. me.disabledDatesRE = new RegExp(re + ')');
  113768. }
  113769. },
  113770. /**
  113771. * Replaces any existing disabled dates with new values and refreshes the DatePicker.
  113772. * @param {String[]/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config for
  113773. * details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
  113774. * @return {Ext.picker.Date} this
  113775. */
  113776. setDisabledDates : function(dd){
  113777. var me = this;
  113778. if(Ext.isArray(dd)){
  113779. me.disabledDates = dd;
  113780. me.disabledDatesRE = null;
  113781. }else{
  113782. me.disabledDatesRE = dd;
  113783. }
  113784. me.initDisabledDays();
  113785. me.update(me.value, true);
  113786. return me;
  113787. },
  113788. /**
  113789. * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
  113790. * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details
  113791. * on supported values.
  113792. * @return {Ext.picker.Date} this
  113793. */
  113794. setDisabledDays : function(dd){
  113795. this.disabledDays = dd;
  113796. return this.update(this.value, true);
  113797. },
  113798. /**
  113799. * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
  113800. * @param {Date} value The minimum date that can be selected
  113801. * @return {Ext.picker.Date} this
  113802. */
  113803. setMinDate : function(dt){
  113804. this.minDate = dt;
  113805. return this.update(this.value, true);
  113806. },
  113807. /**
  113808. * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
  113809. * @param {Date} value The maximum date that can be selected
  113810. * @return {Ext.picker.Date} this
  113811. */
  113812. setMaxDate : function(dt){
  113813. this.maxDate = dt;
  113814. return this.update(this.value, true);
  113815. },
  113816. /**
  113817. * Sets the value of the date field
  113818. * @param {Date} value The date to set
  113819. * @return {Ext.picker.Date} this
  113820. */
  113821. setValue : function(value){
  113822. this.value = Ext.Date.clearTime(value, true);
  113823. return this.update(this.value);
  113824. },
  113825. /**
  113826. * Gets the current selected value of the date field
  113827. * @return {Date} The selected date
  113828. */
  113829. getValue : function(){
  113830. return this.value;
  113831. },
  113832. //<locale type="function">
  113833. /**
  113834. * Gets a single character to represent the day of the week
  113835. * @return {String} The character
  113836. */
  113837. getDayInitial: function(value){
  113838. return value.substr(0,1);
  113839. },
  113840. //</locale>
  113841. // private
  113842. focus : function(){
  113843. this.update(this.activeDate);
  113844. },
  113845. // private, inherit docs
  113846. onEnable: function(){
  113847. this.callParent();
  113848. this.setDisabledStatus(false);
  113849. this.update(this.activeDate);
  113850. },
  113851. // private, inherit docs
  113852. onDisable : function(){
  113853. this.callParent();
  113854. this.setDisabledStatus(true);
  113855. },
  113856. /**
  113857. * Set the disabled state of various internal components
  113858. * @private
  113859. * @param {Boolean} disabled
  113860. */
  113861. setDisabledStatus : function(disabled){
  113862. var me = this;
  113863. me.keyNav.setDisabled(disabled);
  113864. me.prevRepeater.setDisabled(disabled);
  113865. me.nextRepeater.setDisabled(disabled);
  113866. if (me.showToday) {
  113867. me.todayKeyListener.setDisabled(disabled);
  113868. me.todayBtn.setDisabled(disabled);
  113869. }
  113870. },
  113871. /**
  113872. * Get the current active date.
  113873. * @private
  113874. * @return {Date} The active date
  113875. */
  113876. getActive: function(){
  113877. return this.activeDate || this.value;
  113878. },
  113879. /**
  113880. * Run any animation required to hide/show the month picker.
  113881. * @private
  113882. * @param {Boolean} isHide True if it's a hide operation
  113883. */
  113884. runAnimation: function(isHide){
  113885. var picker = this.monthPicker,
  113886. options = {
  113887. duration: 200,
  113888. callback: function(){
  113889. if (isHide) {
  113890. picker.hide();
  113891. } else {
  113892. picker.show();
  113893. }
  113894. }
  113895. };
  113896. if (isHide) {
  113897. picker.el.slideOut('t', options);
  113898. } else {
  113899. picker.el.slideIn('t', options);
  113900. }
  113901. },
  113902. /**
  113903. * Hides the month picker, if it's visible.
  113904. * @param {Boolean} [animate] Indicates whether to animate this action. If the animate
  113905. * parameter is not specified, the behavior will use {@link #disableAnim} to determine
  113906. * whether to animate or not.
  113907. * @return {Ext.picker.Date} this
  113908. */
  113909. hideMonthPicker : function(animate){
  113910. var me = this,
  113911. picker = me.monthPicker;
  113912. if (picker) {
  113913. if (me.shouldAnimate(animate)) {
  113914. me.runAnimation(true);
  113915. } else {
  113916. picker.hide();
  113917. }
  113918. }
  113919. return me;
  113920. },
  113921. /**
  113922. * Show the month picker
  113923. * @param {Boolean} [animate] Indicates whether to animate this action. If the animate
  113924. * parameter is not specified, the behavior will use {@link #disableAnim} to determine
  113925. * whether to animate or not.
  113926. * @return {Ext.picker.Date} this
  113927. */
  113928. showMonthPicker : function(animate){
  113929. var me = this,
  113930. picker;
  113931. if (me.rendered && !me.disabled) {
  113932. picker = me.createMonthPicker();
  113933. picker.setValue(me.getActive());
  113934. picker.setSize(me.getSize());
  113935. picker.setPosition(-1, -1);
  113936. if (me.shouldAnimate(animate)) {
  113937. me.runAnimation(false);
  113938. } else {
  113939. picker.show();
  113940. }
  113941. }
  113942. return me;
  113943. },
  113944. /**
  113945. * Checks whether a hide/show action should animate
  113946. * @private
  113947. * @param {Boolean} [animate] A possible animation value
  113948. * @return {Boolean} Whether to animate the action
  113949. */
  113950. shouldAnimate: function(animate){
  113951. return Ext.isDefined(animate) ? animate : !this.disableAnim;
  113952. },
  113953. /**
  113954. * Create the month picker instance
  113955. * @private
  113956. * @return {Ext.picker.Month} picker
  113957. */
  113958. createMonthPicker: function(){
  113959. var me = this,
  113960. picker = me.monthPicker;
  113961. if (!picker) {
  113962. me.monthPicker = picker = new Ext.picker.Month({
  113963. renderTo: me.el,
  113964. floating: true,
  113965. shadow: false,
  113966. small: me.showToday === false,
  113967. listeners: {
  113968. scope: me,
  113969. cancelclick: me.onCancelClick,
  113970. okclick: me.onOkClick,
  113971. yeardblclick: me.onOkClick,
  113972. monthdblclick: me.onOkClick
  113973. }
  113974. });
  113975. if (!me.disableAnim) {
  113976. // hide the element if we're animating to prevent an initial flicker
  113977. picker.el.setStyle('display', 'none');
  113978. }
  113979. me.on('beforehide', Ext.Function.bind(me.hideMonthPicker, me, [false]));
  113980. }
  113981. return picker;
  113982. },
  113983. /**
  113984. * Respond to an ok click on the month picker
  113985. * @private
  113986. */
  113987. onOkClick: function(picker, value){
  113988. var me = this,
  113989. month = value[0],
  113990. year = value[1],
  113991. date = new Date(year, month, me.getActive().getDate());
  113992. if (date.getMonth() !== month) {
  113993. // 'fix' the JS rolling date conversion if needed
  113994. date = Ext.Date.getLastDateOfMonth(new Date(year, month, 1));
  113995. }
  113996. me.update(date);
  113997. me.hideMonthPicker();
  113998. },
  113999. /**
  114000. * Respond to a cancel click on the month picker
  114001. * @private
  114002. */
  114003. onCancelClick: function(){
  114004. // update the selected value, also triggers a focus
  114005. this.selectedUpdate(this.activeDate);
  114006. this.hideMonthPicker();
  114007. },
  114008. /**
  114009. * Show the previous month.
  114010. * @param {Object} e
  114011. * @return {Ext.picker.Date} this
  114012. */
  114013. showPrevMonth : function(e){
  114014. return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1));
  114015. },
  114016. /**
  114017. * Show the next month.
  114018. * @param {Object} e
  114019. * @return {Ext.picker.Date} this
  114020. */
  114021. showNextMonth : function(e){
  114022. return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1));
  114023. },
  114024. /**
  114025. * Show the previous year.
  114026. * @return {Ext.picker.Date} this
  114027. */
  114028. showPrevYear : function(){
  114029. this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1));
  114030. },
  114031. /**
  114032. * Show the next year.
  114033. * @return {Ext.picker.Date} this
  114034. */
  114035. showNextYear : function(){
  114036. this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1));
  114037. },
  114038. /**
  114039. * Respond to the mouse wheel event
  114040. * @private
  114041. * @param {Ext.EventObject} e
  114042. */
  114043. handleMouseWheel : function(e){
  114044. e.stopEvent();
  114045. if(!this.disabled){
  114046. var delta = e.getWheelDelta();
  114047. if(delta > 0){
  114048. this.showPrevMonth();
  114049. } else if(delta < 0){
  114050. this.showNextMonth();
  114051. }
  114052. }
  114053. },
  114054. /**
  114055. * Respond to a date being clicked in the picker
  114056. * @private
  114057. * @param {Ext.EventObject} e
  114058. * @param {HTMLElement} t
  114059. */
  114060. handleDateClick : function(e, t){
  114061. var me = this,
  114062. handler = me.handler;
  114063. e.stopEvent();
  114064. if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){
  114065. me.doCancelFocus = me.focusOnSelect === false;
  114066. me.setValue(new Date(t.dateValue));
  114067. delete me.doCancelFocus;
  114068. me.fireEvent('select', me, me.value);
  114069. if (handler) {
  114070. handler.call(me.scope || me, me, me.value);
  114071. }
  114072. // event handling is turned off on hide
  114073. // when we are using the picker in a field
  114074. // therefore onSelect comes AFTER the select
  114075. // event.
  114076. me.onSelect();
  114077. }
  114078. },
  114079. /**
  114080. * Perform any post-select actions
  114081. * @private
  114082. */
  114083. onSelect: function() {
  114084. if (this.hideOnSelect) {
  114085. this.hide();
  114086. }
  114087. },
  114088. /**
  114089. * Sets the current value to today.
  114090. * @return {Ext.picker.Date} this
  114091. */
  114092. selectToday : function(){
  114093. var me = this,
  114094. btn = me.todayBtn,
  114095. handler = me.handler;
  114096. if(btn && !btn.disabled){
  114097. me.setValue(Ext.Date.clearTime(new Date()));
  114098. me.fireEvent('select', me, me.value);
  114099. if (handler) {
  114100. handler.call(me.scope || me, me, me.value);
  114101. }
  114102. me.onSelect();
  114103. }
  114104. return me;
  114105. },
  114106. /**
  114107. * Update the selected cell
  114108. * @private
  114109. * @param {Date} date The new date
  114110. */
  114111. selectedUpdate: function(date){
  114112. var me = this,
  114113. t = date.getTime(),
  114114. cells = me.cells,
  114115. cls = me.selectedCls,
  114116. cellItems = cells.elements,
  114117. c,
  114118. cLen = cellItems.length,
  114119. cell;
  114120. cells.removeCls(cls);
  114121. for (c = 0; c < cLen; c++) {
  114122. cell = Ext.fly(cellItems[c]);
  114123. if (cell.dom.firstChild.dateValue == t) {
  114124. me.fireEvent('highlightitem', me, cell);
  114125. cell.addCls(cls);
  114126. if(me.isVisible() && !me.doCancelFocus){
  114127. Ext.fly(cell.dom.firstChild).focus(50);
  114128. }
  114129. break;
  114130. }
  114131. }
  114132. },
  114133. /**
  114134. * Update the contents of the picker for a new month
  114135. * @private
  114136. * @param {Date} date The new date
  114137. */
  114138. fullUpdate: function(date){
  114139. var me = this,
  114140. cells = me.cells.elements,
  114141. textNodes = me.textNodes,
  114142. disabledCls = me.disabledCellCls,
  114143. eDate = Ext.Date,
  114144. i = 0,
  114145. extraDays = 0,
  114146. visible = me.isVisible(),
  114147. sel = +eDate.clearTime(date, true),
  114148. today = +eDate.clearTime(new Date()),
  114149. min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY,
  114150. max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY,
  114151. ddMatch = me.disabledDatesRE,
  114152. ddText = me.disabledDatesText,
  114153. ddays = me.disabledDays ? me.disabledDays.join('') : false,
  114154. ddaysText = me.disabledDaysText,
  114155. format = me.format,
  114156. days = eDate.getDaysInMonth(date),
  114157. firstOfMonth = eDate.getFirstDateOfMonth(date),
  114158. startingPos = firstOfMonth.getDay() - me.startDay,
  114159. previousMonth = eDate.add(date, eDate.MONTH, -1),
  114160. longDayFormat = me.longDayFormat,
  114161. prevStart,
  114162. current,
  114163. disableToday,
  114164. tempDate,
  114165. setCellClass,
  114166. html,
  114167. cls,
  114168. formatValue,
  114169. value;
  114170. if (startingPos < 0) {
  114171. startingPos += 7;
  114172. }
  114173. days += startingPos;
  114174. prevStart = eDate.getDaysInMonth(previousMonth) - startingPos;
  114175. current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour);
  114176. if (me.showToday) {
  114177. tempDate = eDate.clearTime(new Date());
  114178. disableToday = (tempDate < min || tempDate > max ||
  114179. (ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) ||
  114180. (ddays && ddays.indexOf(tempDate.getDay()) != -1));
  114181. if (!me.disabled) {
  114182. me.todayBtn.setDisabled(disableToday);
  114183. me.todayKeyListener.setDisabled(disableToday);
  114184. }
  114185. }
  114186. setCellClass = function(cell){
  114187. value = +eDate.clearTime(current, true);
  114188. cell.title = eDate.format(current, longDayFormat);
  114189. // store dateValue number as an expando
  114190. cell.firstChild.dateValue = value;
  114191. if(value == today){
  114192. cell.className += ' ' + me.todayCls;
  114193. cell.title = me.todayText;
  114194. }
  114195. if(value == sel){
  114196. cell.className += ' ' + me.selectedCls;
  114197. me.fireEvent('highlightitem', me, cell);
  114198. if (visible && me.floating) {
  114199. Ext.fly(cell.firstChild).focus(50);
  114200. }
  114201. }
  114202. // disabling
  114203. if(value < min) {
  114204. cell.className = disabledCls;
  114205. cell.title = me.minText;
  114206. return;
  114207. }
  114208. if(value > max) {
  114209. cell.className = disabledCls;
  114210. cell.title = me.maxText;
  114211. return;
  114212. }
  114213. if(ddays){
  114214. if(ddays.indexOf(current.getDay()) != -1){
  114215. cell.title = ddaysText;
  114216. cell.className = disabledCls;
  114217. }
  114218. }
  114219. if(ddMatch && format){
  114220. formatValue = eDate.dateFormat(current, format);
  114221. if(ddMatch.test(formatValue)){
  114222. cell.title = ddText.replace('%0', formatValue);
  114223. cell.className = disabledCls;
  114224. }
  114225. }
  114226. };
  114227. for(; i < me.numDays; ++i) {
  114228. if (i < startingPos) {
  114229. html = (++prevStart);
  114230. cls = me.prevCls;
  114231. } else if (i >= days) {
  114232. html = (++extraDays);
  114233. cls = me.nextCls;
  114234. } else {
  114235. html = i - startingPos + 1;
  114236. cls = me.activeCls;
  114237. }
  114238. textNodes[i].innerHTML = html;
  114239. cells[i].className = cls;
  114240. current.setDate(current.getDate() + 1);
  114241. setCellClass(cells[i]);
  114242. }
  114243. me.monthBtn.setText(Ext.Date.format(date, me.monthYearFormat));
  114244. },
  114245. /**
  114246. * Update the contents of the picker
  114247. * @private
  114248. * @param {Date} date The new date
  114249. * @param {Boolean} forceRefresh True to force a full refresh
  114250. */
  114251. update : function(date, forceRefresh){
  114252. var me = this,
  114253. active = me.activeDate;
  114254. if (me.rendered) {
  114255. me.activeDate = date;
  114256. if(!forceRefresh && active && me.el && active.getMonth() == date.getMonth() && active.getFullYear() == date.getFullYear()){
  114257. me.selectedUpdate(date, active);
  114258. } else {
  114259. me.fullUpdate(date, active);
  114260. }
  114261. me.innerEl.dom.title = Ext.String.format(me.ariaTitle, Ext.Date.format(me.activeDate, me.ariaTitleDateFormat));
  114262. }
  114263. return me;
  114264. },
  114265. // private, inherit docs
  114266. beforeDestroy : function() {
  114267. var me = this;
  114268. if (me.rendered) {
  114269. Ext.destroy(
  114270. me.todayKeyListener,
  114271. me.keyNav,
  114272. me.monthPicker,
  114273. me.monthBtn,
  114274. me.nextRepeater,
  114275. me.prevRepeater,
  114276. me.todayBtn
  114277. );
  114278. delete me.textNodes;
  114279. delete me.cells.elements;
  114280. }
  114281. me.callParent();
  114282. },
  114283. // private, inherit docs
  114284. onShow: function() {
  114285. this.callParent(arguments);
  114286. if (this.focusOnShow) {
  114287. this.focus();
  114288. }
  114289. }
  114290. },
  114291. // After dependencies have loaded:
  114292. function() {
  114293. var proto = this.prototype,
  114294. date = Ext.Date;
  114295. proto.monthNames = date.monthNames;
  114296. proto.dayNames = date.dayNames;
  114297. proto.format = date.defaultFormat;
  114298. });
  114299. /**
  114300. * @docauthor Jason Johnston <jason@sencha.com>
  114301. *
  114302. * Provides a date input field with a {@link Ext.picker.Date date picker} dropdown and automatic date
  114303. * validation.
  114304. *
  114305. * This field recognizes and uses the JavaScript Date object as its main {@link #value} type. In addition,
  114306. * it recognizes string values which are parsed according to the {@link #format} and/or {@link #altFormats}
  114307. * configs. These may be reconfigured to use date formats appropriate for the user's locale.
  114308. *
  114309. * The field may be limited to a certain range of dates by using the {@link #minValue}, {@link #maxValue},
  114310. * {@link #disabledDays}, and {@link #disabledDates} config parameters. These configurations will be used both
  114311. * in the field's validation, and in the date picker dropdown by preventing invalid dates from being selected.
  114312. *
  114313. * # Example usage
  114314. *
  114315. * @example
  114316. * Ext.create('Ext.form.Panel', {
  114317. * renderTo: Ext.getBody(),
  114318. * width: 300,
  114319. * bodyPadding: 10,
  114320. * title: 'Dates',
  114321. * items: [{
  114322. * xtype: 'datefield',
  114323. * anchor: '100%',
  114324. * fieldLabel: 'From',
  114325. * name: 'from_date',
  114326. * maxValue: new Date() // limited to the current date or prior
  114327. * }, {
  114328. * xtype: 'datefield',
  114329. * anchor: '100%',
  114330. * fieldLabel: 'To',
  114331. * name: 'to_date',
  114332. * value: new Date() // defaults to today
  114333. * }]
  114334. * });
  114335. *
  114336. * # Date Formats Examples
  114337. *
  114338. * This example shows a couple of different date format parsing scenarios. Both use custom date format
  114339. * configurations; the first one matches the configured `format` while the second matches the `altFormats`.
  114340. *
  114341. * @example
  114342. * Ext.create('Ext.form.Panel', {
  114343. * renderTo: Ext.getBody(),
  114344. * width: 300,
  114345. * bodyPadding: 10,
  114346. * title: 'Dates',
  114347. * items: [{
  114348. * xtype: 'datefield',
  114349. * anchor: '100%',
  114350. * fieldLabel: 'Date',
  114351. * name: 'date',
  114352. * // The value matches the format; will be parsed and displayed using that format.
  114353. * format: 'm d Y',
  114354. * value: '2 4 1978'
  114355. * }, {
  114356. * xtype: 'datefield',
  114357. * anchor: '100%',
  114358. * fieldLabel: 'Date',
  114359. * name: 'date',
  114360. * // The value does not match the format, but does match an altFormat; will be parsed
  114361. * // using the altFormat and displayed using the format.
  114362. * format: 'm d Y',
  114363. * altFormats: 'm,d,Y|m.d.Y',
  114364. * value: '2.4.1978'
  114365. * }]
  114366. * });
  114367. */
  114368. Ext.define('Ext.form.field.Date', {
  114369. extend:'Ext.form.field.Picker',
  114370. alias: 'widget.datefield',
  114371. requires: ['Ext.picker.Date'],
  114372. alternateClassName: ['Ext.form.DateField', 'Ext.form.Date'],
  114373. //<locale>
  114374. /**
  114375. * @cfg {String} format
  114376. * The default date format string which can be overriden for localization support. The format must be valid
  114377. * according to {@link Ext.Date#parse}.
  114378. */
  114379. format : "m/d/Y",
  114380. //</locale>
  114381. //<locale>
  114382. /**
  114383. * @cfg {String} altFormats
  114384. * Multiple date formats separated by "|" to try when parsing a user input value and it does not match the defined
  114385. * format.
  114386. */
  114387. altFormats : "m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d|n-j|n/j",
  114388. //</locale>
  114389. //<locale>
  114390. /**
  114391. * @cfg {String} disabledDaysText
  114392. * The tooltip to display when the date falls on a disabled day.
  114393. */
  114394. disabledDaysText : "Disabled",
  114395. //</locale>
  114396. //<locale>
  114397. /**
  114398. * @cfg {String} disabledDatesText
  114399. * The tooltip text to display when the date falls on a disabled date.
  114400. */
  114401. disabledDatesText : "Disabled",
  114402. //</locale>
  114403. //<locale>
  114404. /**
  114405. * @cfg {String} minText
  114406. * The error text to display when the date in the cell is before {@link #minValue}.
  114407. */
  114408. minText : "The date in this field must be equal to or after {0}",
  114409. //</locale>
  114410. //<locale>
  114411. /**
  114412. * @cfg {String} maxText
  114413. * The error text to display when the date in the cell is after {@link #maxValue}.
  114414. */
  114415. maxText : "The date in this field must be equal to or before {0}",
  114416. //</locale>
  114417. //<locale>
  114418. /**
  114419. * @cfg {String} invalidText
  114420. * The error text to display when the date in the field is invalid.
  114421. */
  114422. invalidText : "{0} is not a valid date - it must be in the format {1}",
  114423. //</locale>
  114424. /**
  114425. * @cfg {String} [triggerCls='x-form-date-trigger']
  114426. * An additional CSS class used to style the trigger button. The trigger will always get the class 'x-form-trigger'
  114427. * and triggerCls will be **appended** if specified (default class displays a calendar icon).
  114428. */
  114429. triggerCls : Ext.baseCSSPrefix + 'form-date-trigger',
  114430. /**
  114431. * @cfg {Boolean} showToday
  114432. * false to hide the footer area of the Date picker containing the Today button and disable the keyboard handler for
  114433. * spacebar that selects the current date.
  114434. */
  114435. showToday : true,
  114436. /**
  114437. * @cfg {Date/String} minValue
  114438. * The minimum allowed date. Can be either a Javascript date object or a string date in a valid format.
  114439. */
  114440. /**
  114441. * @cfg {Date/String} maxValue
  114442. * The maximum allowed date. Can be either a Javascript date object or a string date in a valid format.
  114443. */
  114444. /**
  114445. * @cfg {Number[]} disabledDays
  114446. * An array of days to disable, 0 based. Some examples:
  114447. *
  114448. * // disable Sunday and Saturday:
  114449. * disabledDays: [0, 6]
  114450. * // disable weekdays:
  114451. * disabledDays: [1,2,3,4,5]
  114452. */
  114453. /**
  114454. * @cfg {String[]} disabledDates
  114455. * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular expression so
  114456. * they are very powerful. Some examples:
  114457. *
  114458. * // disable these exact dates:
  114459. * disabledDates: ["03/08/2003", "09/16/2003"]
  114460. * // disable these days for every year:
  114461. * disabledDates: ["03/08", "09/16"]
  114462. * // only match the beginning (useful if you are using short years):
  114463. * disabledDates: ["^03/08"]
  114464. * // disable every day in March 2006:
  114465. * disabledDates: ["03/../2006"]
  114466. * // disable every day in every March:
  114467. * disabledDates: ["^03"]
  114468. *
  114469. * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
  114470. * to support regular expressions, if you are using a {@link #format date format} that has "." in it, you will have
  114471. * to escape the dot when restricting dates. For example: `["03\\.08\\.03"]`.
  114472. */
  114473. /**
  114474. * @cfg {String} submitFormat
  114475. * The date format string which will be submitted to the server. The format must be valid according to
  114476. * {@link Ext.Date#parse}.
  114477. *
  114478. * Defaults to {@link #format}.
  114479. */
  114480. /**
  114481. * @cfg {Boolean} useStrict
  114482. * True to enforce strict date parsing to prevent the default Javascript "date rollover".
  114483. * Defaults to the useStrict parameter set on Ext.Date
  114484. * See {@link Ext.Date#parse}.
  114485. */
  114486. useStrict: undefined,
  114487. // in the absence of a time value, a default value of 12 noon will be used
  114488. // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
  114489. initTime: '12', // 24 hour format
  114490. initTimeFormat: 'H',
  114491. matchFieldWidth: false,
  114492. //<locale>
  114493. /**
  114494. * @cfg {Number} [startDay=undefined]
  114495. * Day index at which the week should begin, 0-based.
  114496. *
  114497. * Defaults to `0` (Sunday).
  114498. */
  114499. startDay: 0,
  114500. //</locale>
  114501. initComponent : function(){
  114502. var me = this,
  114503. isString = Ext.isString,
  114504. min, max;
  114505. min = me.minValue;
  114506. max = me.maxValue;
  114507. if(isString(min)){
  114508. me.minValue = me.parseDate(min);
  114509. }
  114510. if(isString(max)){
  114511. me.maxValue = me.parseDate(max);
  114512. }
  114513. me.disabledDatesRE = null;
  114514. me.initDisabledDays();
  114515. me.callParent();
  114516. },
  114517. initValue: function() {
  114518. var me = this,
  114519. value = me.value;
  114520. // If a String value was supplied, try to convert it to a proper Date
  114521. if (Ext.isString(value)) {
  114522. me.value = me.rawToValue(value);
  114523. }
  114524. me.callParent();
  114525. },
  114526. // private
  114527. initDisabledDays : function(){
  114528. if(this.disabledDates){
  114529. var dd = this.disabledDates,
  114530. len = dd.length - 1,
  114531. re = "(?:",
  114532. d,
  114533. dLen = dd.length,
  114534. date;
  114535. for (d = 0; d < dLen; d++) {
  114536. date = dd[d];
  114537. re += Ext.isDate(date) ? '^' + Ext.String.escapeRegex(date.dateFormat(this.format)) + '$' : date;
  114538. if (d !== len) {
  114539. re += '|';
  114540. }
  114541. }
  114542. this.disabledDatesRE = new RegExp(re + ')');
  114543. }
  114544. },
  114545. /**
  114546. * Replaces any existing disabled dates with new values and refreshes the Date picker.
  114547. * @param {String[]} disabledDates An array of date strings (see the {@link #disabledDates} config for details on
  114548. * supported values) used to disable a pattern of dates.
  114549. */
  114550. setDisabledDates : function(dd){
  114551. var me = this,
  114552. picker = me.picker;
  114553. me.disabledDates = dd;
  114554. me.initDisabledDays();
  114555. if (picker) {
  114556. picker.setDisabledDates(me.disabledDatesRE);
  114557. }
  114558. },
  114559. /**
  114560. * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the Date picker.
  114561. * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details on
  114562. * supported values.
  114563. */
  114564. setDisabledDays : function(dd){
  114565. var picker = this.picker;
  114566. this.disabledDays = dd;
  114567. if (picker) {
  114568. picker.setDisabledDays(dd);
  114569. }
  114570. },
  114571. /**
  114572. * Replaces any existing {@link #minValue} with the new value and refreshes the Date picker.
  114573. * @param {Date} value The minimum date that can be selected
  114574. */
  114575. setMinValue : function(dt){
  114576. var me = this,
  114577. picker = me.picker,
  114578. minValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
  114579. me.minValue = minValue;
  114580. if (picker) {
  114581. picker.minText = Ext.String.format(me.minText, me.formatDate(me.minValue));
  114582. picker.setMinDate(minValue);
  114583. }
  114584. },
  114585. /**
  114586. * Replaces any existing {@link #maxValue} with the new value and refreshes the Date picker.
  114587. * @param {Date} value The maximum date that can be selected
  114588. */
  114589. setMaxValue : function(dt){
  114590. var me = this,
  114591. picker = me.picker,
  114592. maxValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
  114593. me.maxValue = maxValue;
  114594. if (picker) {
  114595. picker.maxText = Ext.String.format(me.maxText, me.formatDate(me.maxValue));
  114596. picker.setMaxDate(maxValue);
  114597. }
  114598. },
  114599. /**
  114600. * Runs all of Date's validations and returns an array of any errors. Note that this first runs Text's validations,
  114601. * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
  114602. * the date format is valid, that the chosen date is within the min and max date constraints set, that the date
  114603. * chosen is not in the disabledDates regex and that the day chosed is not one of the disabledDays.
  114604. * @param {Object} [value] The value to get errors for (defaults to the current field value)
  114605. * @return {String[]} All validation errors for this field
  114606. */
  114607. getErrors: function(value) {
  114608. var me = this,
  114609. format = Ext.String.format,
  114610. clearTime = Ext.Date.clearTime,
  114611. errors = me.callParent(arguments),
  114612. disabledDays = me.disabledDays,
  114613. disabledDatesRE = me.disabledDatesRE,
  114614. minValue = me.minValue,
  114615. maxValue = me.maxValue,
  114616. len = disabledDays ? disabledDays.length : 0,
  114617. i = 0,
  114618. svalue,
  114619. fvalue,
  114620. day,
  114621. time;
  114622. value = me.formatDate(value || me.processRawValue(me.getRawValue()));
  114623. if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
  114624. return errors;
  114625. }
  114626. svalue = value;
  114627. value = me.parseDate(value);
  114628. if (!value) {
  114629. errors.push(format(me.invalidText, svalue, Ext.Date.unescapeFormat(me.format)));
  114630. return errors;
  114631. }
  114632. time = value.getTime();
  114633. if (minValue && time < clearTime(minValue).getTime()) {
  114634. errors.push(format(me.minText, me.formatDate(minValue)));
  114635. }
  114636. if (maxValue && time > clearTime(maxValue).getTime()) {
  114637. errors.push(format(me.maxText, me.formatDate(maxValue)));
  114638. }
  114639. if (disabledDays) {
  114640. day = value.getDay();
  114641. for(; i < len; i++) {
  114642. if (day === disabledDays[i]) {
  114643. errors.push(me.disabledDaysText);
  114644. break;
  114645. }
  114646. }
  114647. }
  114648. fvalue = me.formatDate(value);
  114649. if (disabledDatesRE && disabledDatesRE.test(fvalue)) {
  114650. errors.push(format(me.disabledDatesText, fvalue));
  114651. }
  114652. return errors;
  114653. },
  114654. rawToValue: function(rawValue) {
  114655. return this.parseDate(rawValue) || rawValue || null;
  114656. },
  114657. valueToRaw: function(value) {
  114658. return this.formatDate(this.parseDate(value));
  114659. },
  114660. /**
  114661. * @method setValue
  114662. * Sets the value of the date field. You can pass a date object or any string that can be parsed into a valid date,
  114663. * using {@link #format} as the date format, according to the same rules as {@link Ext.Date#parse} (the default
  114664. * format used is "m/d/Y").
  114665. *
  114666. * Usage:
  114667. *
  114668. * //All of these calls set the same date value (May 4, 2006)
  114669. *
  114670. * //Pass a date object:
  114671. * var dt = new Date('5/4/2006');
  114672. * dateField.setValue(dt);
  114673. *
  114674. * //Pass a date string (default format):
  114675. * dateField.setValue('05/04/2006');
  114676. *
  114677. * //Pass a date string (custom format):
  114678. * dateField.format = 'Y-m-d';
  114679. * dateField.setValue('2006-05-04');
  114680. *
  114681. * @param {String/Date} date The date or valid date string
  114682. * @return {Ext.form.field.Date} this
  114683. */
  114684. /**
  114685. * Attempts to parse a given string value using a given {@link Ext.Date#parse date format}.
  114686. * @param {String} value The value to attempt to parse
  114687. * @param {String} format A valid date format (see {@link Ext.Date#parse})
  114688. * @return {Date} The parsed Date object, or null if the value could not be successfully parsed.
  114689. */
  114690. safeParse : function(value, format) {
  114691. var me = this,
  114692. utilDate = Ext.Date,
  114693. result = null,
  114694. strict = me.useStrict,
  114695. parsedDate;
  114696. if (utilDate.formatContainsHourInfo(format)) {
  114697. // if parse format contains hour information, no DST adjustment is necessary
  114698. result = utilDate.parse(value, format, strict);
  114699. } else {
  114700. // set time to 12 noon, then clear the time
  114701. parsedDate = utilDate.parse(value + ' ' + me.initTime, format + ' ' + me.initTimeFormat, strict);
  114702. if (parsedDate) {
  114703. result = utilDate.clearTime(parsedDate);
  114704. }
  114705. }
  114706. return result;
  114707. },
  114708. // @private
  114709. getSubmitValue: function() {
  114710. var format = this.submitFormat || this.format,
  114711. value = this.getValue();
  114712. return value ? Ext.Date.format(value, format) : '';
  114713. },
  114714. /**
  114715. * @private
  114716. */
  114717. parseDate : function(value) {
  114718. if(!value || Ext.isDate(value)){
  114719. return value;
  114720. }
  114721. var me = this,
  114722. val = me.safeParse(value, me.format),
  114723. altFormats = me.altFormats,
  114724. altFormatsArray = me.altFormatsArray,
  114725. i = 0,
  114726. len;
  114727. if (!val && altFormats) {
  114728. altFormatsArray = altFormatsArray || altFormats.split('|');
  114729. len = altFormatsArray.length;
  114730. for (; i < len && !val; ++i) {
  114731. val = me.safeParse(value, altFormatsArray[i]);
  114732. }
  114733. }
  114734. return val;
  114735. },
  114736. // private
  114737. formatDate : function(date){
  114738. return Ext.isDate(date) ? Ext.Date.dateFormat(date, this.format) : date;
  114739. },
  114740. createPicker: function() {
  114741. var me = this,
  114742. format = Ext.String.format;
  114743. return new Ext.picker.Date({
  114744. pickerField: me,
  114745. ownerCt: me.ownerCt,
  114746. renderTo: document.body,
  114747. floating: true,
  114748. hidden: true,
  114749. focusOnShow: true,
  114750. minDate: me.minValue,
  114751. maxDate: me.maxValue,
  114752. disabledDatesRE: me.disabledDatesRE,
  114753. disabledDatesText: me.disabledDatesText,
  114754. disabledDays: me.disabledDays,
  114755. disabledDaysText: me.disabledDaysText,
  114756. format: me.format,
  114757. showToday: me.showToday,
  114758. startDay: me.startDay,
  114759. minText: format(me.minText, me.formatDate(me.minValue)),
  114760. maxText: format(me.maxText, me.formatDate(me.maxValue)),
  114761. listeners: {
  114762. scope: me,
  114763. select: me.onSelect
  114764. },
  114765. keyNavConfig: {
  114766. esc: function() {
  114767. me.collapse();
  114768. }
  114769. }
  114770. });
  114771. },
  114772. onSelect: function(m, d) {
  114773. var me = this;
  114774. me.setValue(d);
  114775. me.fireEvent('select', me, d);
  114776. me.collapse();
  114777. },
  114778. /**
  114779. * @private
  114780. * Sets the Date picker's value to match the current field value when expanding.
  114781. */
  114782. onExpand: function() {
  114783. var value = this.getValue();
  114784. this.picker.setValue(Ext.isDate(value) ? value : new Date());
  114785. },
  114786. /**
  114787. * @private
  114788. * Focuses the field when collapsing the Date picker.
  114789. */
  114790. onCollapse: function() {
  114791. this.focus(false, 60);
  114792. },
  114793. // private
  114794. beforeBlur : function(){
  114795. var me = this,
  114796. v = me.parseDate(me.getRawValue()),
  114797. focusTask = me.focusTask;
  114798. if (focusTask) {
  114799. focusTask.cancel();
  114800. }
  114801. if (v) {
  114802. me.setValue(v);
  114803. }
  114804. }
  114805. /**
  114806. * @cfg {Boolean} grow
  114807. * @private
  114808. */
  114809. /**
  114810. * @cfg {Number} growMin
  114811. * @private
  114812. */
  114813. /**
  114814. * @cfg {Number} growMax
  114815. * @private
  114816. */
  114817. /**
  114818. * @method autoSize
  114819. * @private
  114820. */
  114821. });
  114822. /**
  114823. * @docauthor Jason Johnston <jason@sencha.com>
  114824. *
  114825. * A file upload field which has custom styling and allows control over the button text and other
  114826. * features of {@link Ext.form.field.Text text fields} like {@link Ext.form.field.Text#emptyText empty text}.
  114827. * It uses a hidden file input element behind the scenes to allow user selection of a file and to
  114828. * perform the actual upload during {@link Ext.form.Basic#submit form submit}.
  114829. *
  114830. * Because there is no secure cross-browser way to programmatically set the value of a file input,
  114831. * the standard Field `setValue` method is not implemented. The `{@link #getValue}` method will return
  114832. * a value that is browser-dependent; some have just the file name, some have a full path, some use
  114833. * a fake path.
  114834. *
  114835. * **IMPORTANT:** File uploads are not performed using normal 'Ajax' techniques; see the description for
  114836. * {@link Ext.form.Basic#hasUpload} for details.
  114837. *
  114838. * # Example Usage
  114839. *
  114840. * @example
  114841. * Ext.create('Ext.form.Panel', {
  114842. * title: 'Upload a Photo',
  114843. * width: 400,
  114844. * bodyPadding: 10,
  114845. * frame: true,
  114846. * renderTo: Ext.getBody(),
  114847. * items: [{
  114848. * xtype: 'filefield',
  114849. * name: 'photo',
  114850. * fieldLabel: 'Photo',
  114851. * labelWidth: 50,
  114852. * msgTarget: 'side',
  114853. * allowBlank: false,
  114854. * anchor: '100%',
  114855. * buttonText: 'Select Photo...'
  114856. * }],
  114857. *
  114858. * buttons: [{
  114859. * text: 'Upload',
  114860. * handler: function() {
  114861. * var form = this.up('form').getForm();
  114862. * if(form.isValid()){
  114863. * form.submit({
  114864. * url: 'photo-upload.php',
  114865. * waitMsg: 'Uploading your photo...',
  114866. * success: function(fp, o) {
  114867. * Ext.Msg.alert('Success', 'Your photo "' + o.result.file + '" has been uploaded.');
  114868. * }
  114869. * });
  114870. * }
  114871. * }
  114872. * }]
  114873. * });
  114874. */
  114875. Ext.define("Ext.form.field.File", {
  114876. extend: 'Ext.form.field.Trigger',
  114877. alias: ['widget.filefield', 'widget.fileuploadfield'],
  114878. alternateClassName: ['Ext.form.FileUploadField', 'Ext.ux.form.FileUploadField', 'Ext.form.File'],
  114879. uses: ['Ext.button.Button', 'Ext.layout.component.field.Field'],
  114880. //<locale>
  114881. /**
  114882. * @cfg {String} buttonText
  114883. * The button text to display on the upload button. Note that if you supply a value for
  114884. * {@link #buttonConfig}, the buttonConfig.text value will be used instead if available.
  114885. */
  114886. buttonText: 'Browse...',
  114887. //</locale>
  114888. /**
  114889. * @cfg {Boolean} buttonOnly
  114890. * True to display the file upload field as a button with no visible text field. If true, all
  114891. * inherited Text members will still be available.
  114892. */
  114893. buttonOnly: false,
  114894. /**
  114895. * @cfg {Number} buttonMargin
  114896. * The number of pixels of space reserved between the button and the text field. Note that this only
  114897. * applies if {@link #buttonOnly} = false.
  114898. */
  114899. buttonMargin: 3,
  114900. /**
  114901. * @cfg {Object} buttonConfig
  114902. * A standard {@link Ext.button.Button} config object.
  114903. */
  114904. /**
  114905. * @event change
  114906. * Fires when the underlying file input field's value has changed from the user selecting a new file from the system
  114907. * file selection dialog.
  114908. * @param {Ext.ux.form.FileUploadField} this
  114909. * @param {String} value The file value returned by the underlying file input field
  114910. */
  114911. /**
  114912. * @property {Ext.Element} fileInputEl
  114913. * A reference to the invisible file input element created for this upload field. Only populated after this
  114914. * component is rendered.
  114915. */
  114916. /**
  114917. * @property {Ext.button.Button} button
  114918. * A reference to the trigger Button component created for this upload field. Only populated after this component is
  114919. * rendered.
  114920. */
  114921. /**
  114922. * @cfg {String} [fieldBodyCls='x-form-file-wrap']
  114923. * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
  114924. */
  114925. fieldBodyCls: Ext.baseCSSPrefix + 'form-file-wrap',
  114926. /**
  114927. * @cfg {Boolean} readOnly
  114928. * Unlike with other form fields, the readOnly config defaults to true in File field.
  114929. */
  114930. readOnly: true,
  114931. /**
  114932. * Do not show hand pointer over text field since file choose dialog is only shown when clicking in the button
  114933. * @private
  114934. */
  114935. triggerNoEditCls: '',
  114936. // private
  114937. componentLayout: 'triggerfield',
  114938. // private. Extract the file element, button outer element, and button active element.
  114939. childEls: ['fileInputEl', 'buttonEl', 'buttonEl-btnEl', 'browseButtonWrap'],
  114940. // private
  114941. onRender: function() {
  114942. var me = this,
  114943. inputEl;
  114944. me.callParent(arguments);
  114945. inputEl = me.inputEl;
  114946. inputEl.dom.name = ''; //name goes on the fileInput, not the text input
  114947. me.fileInputEl.dom.name = me.getName();
  114948. me.fileInputEl.on({
  114949. scope: me,
  114950. change: me.onFileChange
  114951. });
  114952. if (me.buttonOnly) {
  114953. me.inputCell.setDisplayed(false);
  114954. }
  114955. // Ensure the trigger cell is sized correctly upon render
  114956. me.browseButtonWrap.dom.style.width = (me.browseButtonWrap.dom.lastChild.offsetWidth + me.buttonEl.getMargin('lr')) + 'px';
  114957. if (Ext.isIE) {
  114958. me.buttonEl.repaint();
  114959. }
  114960. },
  114961. /**
  114962. * Gets the markup to be inserted into the subTplMarkup.
  114963. */
  114964. getTriggerMarkup: function() {
  114965. var me = this,
  114966. result,
  114967. btn = Ext.widget('button', Ext.apply({
  114968. id: me.id + '-buttonEl',
  114969. ui: me.ui,
  114970. disabled: me.disabled,
  114971. text: me.buttonText,
  114972. cls: Ext.baseCSSPrefix + 'form-file-btn',
  114973. preventDefault: false,
  114974. style: me.buttonOnly ? '' : 'margin-left:' + me.buttonMargin + 'px'
  114975. }, me.buttonConfig)),
  114976. btnCfg = btn.getRenderTree(),
  114977. inputElCfg = {
  114978. id: me.id + '-fileInputEl',
  114979. cls: Ext.baseCSSPrefix + 'form-file-input',
  114980. tag: 'input',
  114981. type: 'file',
  114982. size: 1
  114983. };
  114984. if (me.disabled) {
  114985. inputElCfg.disabled = true;
  114986. }
  114987. btnCfg.cn = inputElCfg;
  114988. result = '<td id="' + me.id + '-browseButtonWrap">' + Ext.DomHelper.markup(btnCfg) + '</td>';
  114989. btn.destroy();
  114990. return result;
  114991. },
  114992. /**
  114993. * @private
  114994. * Creates the file input element. It is inserted into the trigger button component, made
  114995. * invisible, and floated on top of the button's other content so that it will receive the
  114996. * button's clicks.
  114997. */
  114998. createFileInput : function() {
  114999. var me = this;
  115000. me.fileInputEl = me.buttonEl.createChild({
  115001. name: me.getName(),
  115002. id: me.id + '-fileInputEl',
  115003. cls: Ext.baseCSSPrefix + 'form-file-input',
  115004. tag: 'input',
  115005. type: 'file',
  115006. size: 1
  115007. });
  115008. me.fileInputEl.on({
  115009. scope: me,
  115010. change: me.onFileChange
  115011. });
  115012. },
  115013. /**
  115014. * @private Event handler fired when the user selects a file.
  115015. */
  115016. onFileChange: function() {
  115017. this.lastValue = null; // force change event to get fired even if the user selects a file with the same name
  115018. Ext.form.field.File.superclass.setValue.call(this, this.fileInputEl.dom.value);
  115019. },
  115020. /**
  115021. * Overridden to do nothing
  115022. * @method
  115023. */
  115024. setValue: Ext.emptyFn,
  115025. reset : function(){
  115026. var me = this;
  115027. if (me.rendered) {
  115028. me.fileInputEl.remove();
  115029. me.createFileInput();
  115030. me.inputEl.dom.value = '';
  115031. }
  115032. me.callParent();
  115033. },
  115034. onDisable: function(){
  115035. this.callParent();
  115036. this.disableItems();
  115037. },
  115038. disableItems: function(){
  115039. var file = this.fileInputEl;
  115040. if (file) {
  115041. file.dom.disabled = true;
  115042. }
  115043. this['buttonEl-btnEl'].dom.disabled = true;
  115044. },
  115045. onEnable: function(){
  115046. var me = this;
  115047. me.callParent();
  115048. me.fileInputEl.dom.disabled = false;
  115049. this['buttonEl-btnEl'].dom.disabled = false;
  115050. },
  115051. isFileUpload: function() {
  115052. return true;
  115053. },
  115054. extractFileInput: function() {
  115055. var fileInput = this.fileInputEl.dom;
  115056. this.reset();
  115057. return fileInput;
  115058. },
  115059. onDestroy: function(){
  115060. Ext.destroyMembers(this, 'fileInputEl', 'buttonEl');
  115061. this.callParent();
  115062. }
  115063. });
  115064. /**
  115065. * A basic hidden field for storing hidden values in forms that need to be passed in the form submit.
  115066. *
  115067. * This creates an actual input element with type="submit" in the DOM. While its label is
  115068. * {@link #hideLabel not rendered} by default, it is still a real component and may be sized according
  115069. * to its owner container's layout.
  115070. *
  115071. * Because of this, in most cases it is more convenient and less problematic to simply
  115072. * {@link Ext.form.action.Action#params pass hidden parameters} directly when
  115073. * {@link Ext.form.Basic#submit submitting the form}.
  115074. *
  115075. * Example:
  115076. *
  115077. * new Ext.form.Panel({
  115078. * title: 'My Form',
  115079. * items: [{
  115080. * xtype: 'textfield',
  115081. * fieldLabel: 'Text Field',
  115082. * name: 'text_field',
  115083. * value: 'value from text field'
  115084. * }, {
  115085. * xtype: 'hiddenfield',
  115086. * name: 'hidden_field_1',
  115087. * value: 'value from hidden field'
  115088. * }],
  115089. *
  115090. * buttons: [{
  115091. * text: 'Submit',
  115092. * handler: function() {
  115093. * this.up('form').getForm().submit({
  115094. * params: {
  115095. * hidden_field_2: 'value from submit call'
  115096. * }
  115097. * });
  115098. * }
  115099. * }]
  115100. * });
  115101. *
  115102. * Submitting the above form will result in three values sent to the server:
  115103. *
  115104. * text_field=value+from+text+field&hidden;_field_1=value+from+hidden+field&hidden_field_2=value+from+submit+call
  115105. *
  115106. */
  115107. Ext.define('Ext.form.field.Hidden', {
  115108. extend:'Ext.form.field.Base',
  115109. alias: ['widget.hiddenfield', 'widget.hidden'],
  115110. alternateClassName: 'Ext.form.Hidden',
  115111. // private
  115112. inputType : 'hidden',
  115113. hideLabel: true,
  115114. initComponent: function(){
  115115. this.formItemCls += '-hidden';
  115116. this.callParent();
  115117. },
  115118. /**
  115119. * @private
  115120. * Override. Treat undefined and null values as equal to an empty string value.
  115121. */
  115122. isEqual: function(value1, value2) {
  115123. return this.isEqualAsString(value1, value2);
  115124. },
  115125. // These are all private overrides
  115126. initEvents: Ext.emptyFn,
  115127. setSize : Ext.emptyFn,
  115128. setWidth : Ext.emptyFn,
  115129. setHeight : Ext.emptyFn,
  115130. setPosition : Ext.emptyFn,
  115131. setPagePosition : Ext.emptyFn,
  115132. markInvalid : Ext.emptyFn,
  115133. clearInvalid : Ext.emptyFn
  115134. });
  115135. /**
  115136. * Layout class for {@link Ext.form.field.HtmlEditor} fields. Sizes the toolbar, textarea, and iframe elements.
  115137. * @private
  115138. */
  115139. Ext.define('Ext.layout.component.field.HtmlEditor', {
  115140. extend: 'Ext.layout.component.field.Field',
  115141. alias: ['layout.htmleditor'],
  115142. type: 'htmleditor',
  115143. // Flags to say that the item is autosizing itself.
  115144. toolbarSizePolicy: {
  115145. setsWidth: 0,
  115146. setsHeight: 0
  115147. },
  115148. beginLayout: function(ownerContext) {
  115149. this.callParent(arguments);
  115150. ownerContext.textAreaContext = ownerContext.getEl('textareaEl');
  115151. ownerContext.iframeContext = ownerContext.getEl('iframeEl');
  115152. ownerContext.toolbarContext = ownerContext.context.getCmp(this.owner.getToolbar());
  115153. },
  115154. // It's not a container, can never add/remove dynamically
  115155. renderItems: Ext.emptyFn,
  115156. getItemSizePolicy: function (item) {
  115157. // we are only ever called by the toolbar
  115158. return this.toolbarSizePolicy;
  115159. },
  115160. getLayoutItems: function () {
  115161. var toolbar = this.owner.getToolbar();
  115162. // The toolbar may not exist if we're destroying
  115163. return toolbar ? [toolbar] : [];
  115164. },
  115165. getRenderTarget: function() {
  115166. return this.owner.bodyEl;
  115167. },
  115168. publishInnerHeight: function (ownerContext, height) {
  115169. var me = this,
  115170. innerHeight = height - me.measureLabelErrorHeight(ownerContext) -
  115171. ownerContext.toolbarContext.getProp('height') -
  115172. ownerContext.bodyCellContext.getPaddingInfo().height;
  115173. // If the Toolbar has not acheieved a height yet, we are not done laying out.
  115174. if (Ext.isNumber(innerHeight)) {
  115175. ownerContext.textAreaContext.setHeight(innerHeight);
  115176. ownerContext.iframeContext.setHeight(innerHeight);
  115177. } else {
  115178. me.done = false;
  115179. }
  115180. }
  115181. });
  115182. /**
  115183. * Color picker provides a simple color palette for choosing colors. The picker can be rendered to any container. The
  115184. * available default to a standard 40-color palette; this can be customized with the {@link #colors} config.
  115185. *
  115186. * Typically you will need to implement a handler function to be notified when the user chooses a color from the picker;
  115187. * you can register the handler using the {@link #event-select} event, or by implementing the {@link #handler} method.
  115188. *
  115189. * @example
  115190. * Ext.create('Ext.picker.Color', {
  115191. * value: '993300', // initial selected color
  115192. * renderTo: Ext.getBody(),
  115193. * listeners: {
  115194. * select: function(picker, selColor) {
  115195. * alert(selColor);
  115196. * }
  115197. * }
  115198. * });
  115199. */
  115200. Ext.define('Ext.picker.Color', {
  115201. extend: 'Ext.Component',
  115202. requires: 'Ext.XTemplate',
  115203. alias: 'widget.colorpicker',
  115204. alternateClassName: 'Ext.ColorPalette',
  115205. /**
  115206. * @cfg {String} [componentCls='x-color-picker']
  115207. * The CSS class to apply to the containing element.
  115208. */
  115209. componentCls : Ext.baseCSSPrefix + 'color-picker',
  115210. /**
  115211. * @cfg {String} [selectedCls='x-color-picker-selected']
  115212. * The CSS class to apply to the selected element
  115213. */
  115214. selectedCls: Ext.baseCSSPrefix + 'color-picker-selected',
  115215. /**
  115216. * @cfg {String} value
  115217. * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol). Note that the hex
  115218. * codes are case-sensitive.
  115219. */
  115220. value : null,
  115221. /**
  115222. * @cfg {String} clickEvent
  115223. * The DOM event that will cause a color to be selected. This can be any valid event name (dblclick, contextmenu).
  115224. */
  115225. clickEvent :'click',
  115226. /**
  115227. * @cfg {Boolean} allowReselect
  115228. * If set to true then reselecting a color that is already selected fires the {@link #event-select} event
  115229. */
  115230. allowReselect : false,
  115231. /**
  115232. * @property {String[]} colors
  115233. * An array of 6-digit color hex code strings (without the # symbol). This array can contain any number of colors,
  115234. * and each hex code should be unique. The width of the picker is controlled via CSS by adjusting the width property
  115235. * of the 'x-color-picker' class (or assigning a custom class), so you can balance the number of colors with the
  115236. * width setting until the box is symmetrical.
  115237. *
  115238. * You can override individual colors if needed:
  115239. *
  115240. * var cp = new Ext.picker.Color();
  115241. * cp.colors[0] = 'FF0000'; // change the first box to red
  115242. *
  115243. * Or you can provide a custom array of your own for complete control:
  115244. *
  115245. * var cp = new Ext.picker.Color();
  115246. * cp.colors = ['000000', '993300', '333300'];
  115247. */
  115248. colors : [
  115249. '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333',
  115250. '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080',
  115251. 'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696',
  115252. 'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0',
  115253. 'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF'
  115254. ],
  115255. /**
  115256. * @cfg {Function} handler
  115257. * A function that will handle the select event of this picker. The handler is passed the following parameters:
  115258. *
  115259. * - `picker` : ColorPicker
  115260. *
  115261. * The {@link Ext.picker.Color picker}.
  115262. *
  115263. * - `color` : String
  115264. *
  115265. * The 6-digit color hex code (without the # symbol).
  115266. */
  115267. /**
  115268. * @cfg {Object} scope
  115269. * The scope (`this` reference) in which the `{@link #handler}` function will be called.
  115270. *
  115271. * Defaults to this Color picker instance.
  115272. */
  115273. colorRe: /(?:^|\s)color-(.{6})(?:\s|$)/,
  115274. renderTpl: [
  115275. '<tpl for="colors">',
  115276. '<a href="#" class="color-{.}" hidefocus="on">',
  115277. '<em><span style="background:#{.}" unselectable="on">&#160;</span></em>',
  115278. '</a>',
  115279. '</tpl>'
  115280. ],
  115281. // private
  115282. initComponent : function(){
  115283. var me = this;
  115284. me.callParent(arguments);
  115285. me.addEvents(
  115286. /**
  115287. * @event select
  115288. * Fires when a color is selected
  115289. * @param {Ext.picker.Color} this
  115290. * @param {String} color The 6-digit color hex code (without the # symbol)
  115291. */
  115292. 'select'
  115293. );
  115294. if (me.handler) {
  115295. me.on('select', me.handler, me.scope, true);
  115296. }
  115297. },
  115298. // private
  115299. initRenderData : function(){
  115300. var me = this;
  115301. return Ext.apply(me.callParent(), {
  115302. itemCls: me.itemCls,
  115303. colors: me.colors
  115304. });
  115305. },
  115306. onRender : function(){
  115307. var me = this,
  115308. clickEvent = me.clickEvent;
  115309. me.callParent(arguments);
  115310. me.mon(me.el, clickEvent, me.handleClick, me, {delegate: 'a'});
  115311. // always stop following the anchors
  115312. if(clickEvent != 'click'){
  115313. me.mon(me.el, 'click', Ext.emptyFn, me, {delegate: 'a', stopEvent: true});
  115314. }
  115315. },
  115316. // private
  115317. afterRender : function(){
  115318. var me = this,
  115319. value;
  115320. me.callParent(arguments);
  115321. if (me.value) {
  115322. value = me.value;
  115323. me.value = null;
  115324. me.select(value, true);
  115325. }
  115326. },
  115327. // private
  115328. handleClick : function(event, target){
  115329. var me = this,
  115330. color;
  115331. event.stopEvent();
  115332. if (!me.disabled) {
  115333. color = target.className.match(me.colorRe)[1];
  115334. me.select(color.toUpperCase());
  115335. }
  115336. },
  115337. /**
  115338. * Selects the specified color in the picker (fires the {@link #event-select} event)
  115339. * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
  115340. * @param {Boolean} [suppressEvent=false] True to stop the select event from firing.
  115341. */
  115342. select : function(color, suppressEvent){
  115343. var me = this,
  115344. selectedCls = me.selectedCls,
  115345. value = me.value,
  115346. el;
  115347. color = color.replace('#', '');
  115348. if (!me.rendered) {
  115349. me.value = color;
  115350. return;
  115351. }
  115352. if (color != value || me.allowReselect) {
  115353. el = me.el;
  115354. if (me.value) {
  115355. el.down('a.color-' + value).removeCls(selectedCls);
  115356. }
  115357. el.down('a.color-' + color).addCls(selectedCls);
  115358. me.value = color;
  115359. if (suppressEvent !== true) {
  115360. me.fireEvent('select', me, color);
  115361. }
  115362. }
  115363. },
  115364. /**
  115365. * Get the currently selected color value.
  115366. * @return {String} value The selected value. Null if nothing is selected.
  115367. */
  115368. getValue: function(){
  115369. return this.value || null;
  115370. }
  115371. });
  115372. /**
  115373. * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
  115374. * automatically hidden when needed. These are noted in the config options where appropriate.
  115375. *
  115376. * The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
  115377. * enabled by default unless the global {@link Ext.tip.QuickTipManager} singleton is
  115378. * {@link Ext.tip.QuickTipManager#init initialized}.
  115379. *
  115380. * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an
  115381. * Editor within any element that has display set to 'none' can cause problems in Safari and Firefox due to their
  115382. * default iframe reloading bugs.
  115383. *
  115384. * # Example usage
  115385. *
  115386. * Simple example rendered with default options:
  115387. *
  115388. * @example
  115389. * Ext.tip.QuickTipManager.init(); // enable tooltips
  115390. * Ext.create('Ext.form.HtmlEditor', {
  115391. * width: 580,
  115392. * height: 250,
  115393. * renderTo: Ext.getBody()
  115394. * });
  115395. *
  115396. * Passed via xtype into a container and with custom options:
  115397. *
  115398. * @example
  115399. * Ext.tip.QuickTipManager.init(); // enable tooltips
  115400. * new Ext.panel.Panel({
  115401. * title: 'HTML Editor',
  115402. * renderTo: Ext.getBody(),
  115403. * width: 550,
  115404. * height: 250,
  115405. * frame: true,
  115406. * layout: 'fit',
  115407. * items: {
  115408. * xtype: 'htmleditor',
  115409. * enableColors: false,
  115410. * enableAlignments: false
  115411. * }
  115412. * });
  115413. *
  115414. * # Reflow issues
  115415. *
  115416. * In some browsers, a layout reflow will cause the underlying editor iframe to be reset. This
  115417. * is most commonly seen when using the editor in collapsed panels with animation. In these cases
  115418. * it is best to avoid animation. More information can be found here: https://bugzilla.mozilla.org/show_bug.cgi?id=90268
  115419. */
  115420. Ext.define('Ext.form.field.HtmlEditor', {
  115421. extend:'Ext.Component',
  115422. mixins: {
  115423. labelable: 'Ext.form.Labelable',
  115424. field: 'Ext.form.field.Field'
  115425. },
  115426. alias: 'widget.htmleditor',
  115427. alternateClassName: 'Ext.form.HtmlEditor',
  115428. requires: [
  115429. 'Ext.tip.QuickTipManager',
  115430. 'Ext.picker.Color',
  115431. 'Ext.toolbar.Item',
  115432. 'Ext.toolbar.Toolbar',
  115433. 'Ext.util.Format',
  115434. 'Ext.layout.component.field.HtmlEditor'
  115435. ],
  115436. childEls: [
  115437. 'iframeEl', 'textareaEl'
  115438. ],
  115439. fieldSubTpl: [
  115440. '{beforeTextAreaTpl}',
  115441. '<textarea id="{cmpId}-textareaEl" name="{name}" tabIndex="-1" {inputAttrTpl}',
  115442. ' class="{textareaCls}" style="{size}" autocomplete="off">',
  115443. '{[Ext.util.Format.htmlEncode(values.value)]}',
  115444. '</textarea>',
  115445. '{afterTextAreaTpl}',
  115446. '{beforeIFrameTpl}',
  115447. '<iframe id="{cmpId}-iframeEl" name="{iframeName}" frameBorder="0" {iframeAttrTpl}',
  115448. ' style="overflow:auto;{size}" src="{iframeSrc}"></iframe>',
  115449. '{afterIFrameTpl}',
  115450. {
  115451. disableFormats: true
  115452. }
  115453. ],
  115454. subTplInsertions: [
  115455. /**
  115456. * @cfg {String/Array/Ext.XTemplate} beforeTextAreaTpl
  115457. * An optional string or `XTemplate` configuration to insert in the field markup
  115458. * before the textarea element. If an `XTemplate` is used, the component's
  115459. * {@link Ext.form.field.Base#getSubTplData subTpl data} serves as the context.
  115460. */
  115461. 'beforeTextAreaTpl',
  115462. /**
  115463. * @cfg {String/Array/Ext.XTemplate} afterTextAreaTpl
  115464. * An optional string or `XTemplate` configuration to insert in the field markup
  115465. * after the textarea element. If an `XTemplate` is used, the component's
  115466. * {@link Ext.form.field.Base#getSubTplData subTpl data} serves as the context.
  115467. */
  115468. 'afterTextAreaTpl',
  115469. /**
  115470. * @cfg {String/Array/Ext.XTemplate} beforeIFrameTpl
  115471. * An optional string or `XTemplate` configuration to insert in the field markup
  115472. * before the iframe element. If an `XTemplate` is used, the component's
  115473. * {@link Ext.form.field.Base#getSubTplData subTpl data} serves as the context.
  115474. */
  115475. 'beforeIFrameTpl',
  115476. /**
  115477. * @cfg {String/Array/Ext.XTemplate} afterIFrameTpl
  115478. * An optional string or `XTemplate` configuration to insert in the field markup
  115479. * after the iframe element. If an `XTemplate` is used, the component's
  115480. * {@link Ext.form.field.Base#getSubTplData subTpl data} serves as the context.
  115481. */
  115482. 'afterIFrameTpl',
  115483. /**
  115484. * @cfg {String/Array/Ext.XTemplate} iframeAttrTpl
  115485. * An optional string or `XTemplate` configuration to insert in the field markup
  115486. * inside the iframe element (as attributes). If an `XTemplate` is used, the component's
  115487. * {@link Ext.form.field.Base#getSubTplData subTpl data} serves as the context.
  115488. */
  115489. 'iframeAttrTpl',
  115490. // inherited
  115491. 'inputAttrTpl'
  115492. ],
  115493. /**
  115494. * @cfg {Boolean} enableFormat
  115495. * Enable the bold, italic and underline buttons
  115496. */
  115497. enableFormat : true,
  115498. /**
  115499. * @cfg {Boolean} enableFontSize
  115500. * Enable the increase/decrease font size buttons
  115501. */
  115502. enableFontSize : true,
  115503. /**
  115504. * @cfg {Boolean} enableColors
  115505. * Enable the fore/highlight color buttons
  115506. */
  115507. enableColors : true,
  115508. /**
  115509. * @cfg {Boolean} enableAlignments
  115510. * Enable the left, center, right alignment buttons
  115511. */
  115512. enableAlignments : true,
  115513. /**
  115514. * @cfg {Boolean} enableLists
  115515. * Enable the bullet and numbered list buttons. Not available in Safari.
  115516. */
  115517. enableLists : true,
  115518. /**
  115519. * @cfg {Boolean} enableSourceEdit
  115520. * Enable the switch to source edit button. Not available in Safari.
  115521. */
  115522. enableSourceEdit : true,
  115523. /**
  115524. * @cfg {Boolean} enableLinks
  115525. * Enable the create link button. Not available in Safari.
  115526. */
  115527. enableLinks : true,
  115528. /**
  115529. * @cfg {Boolean} enableFont
  115530. * Enable font selection. Not available in Safari.
  115531. */
  115532. enableFont : true,
  115533. //<locale>
  115534. /**
  115535. * @cfg {String} createLinkText
  115536. * The default text for the create link prompt
  115537. */
  115538. createLinkText : 'Please enter the URL for the link:',
  115539. //</locale>
  115540. /**
  115541. * @cfg {String} [defaultLinkValue='http://']
  115542. * The default value for the create link prompt
  115543. */
  115544. defaultLinkValue : 'http:/'+'/',
  115545. /**
  115546. * @cfg {String[]} fontFamilies
  115547. * An array of available font families
  115548. */
  115549. fontFamilies : [
  115550. 'Arial',
  115551. 'Courier New',
  115552. 'Tahoma',
  115553. 'Times New Roman',
  115554. 'Verdana'
  115555. ],
  115556. defaultFont: 'tahoma',
  115557. /**
  115558. * @cfg {String} defaultValue
  115559. * A default value to be put into the editor to resolve focus issues.
  115560. *
  115561. * Defaults to (Non-breaking space) in Opera and IE6,
  115562. * (Zero-width space) in all other browsers.
  115563. */
  115564. defaultValue: (Ext.isOpera || Ext.isIE6) ? '&#160;' : '&#8203;',
  115565. editorWrapCls: Ext.baseCSSPrefix + 'html-editor-wrap',
  115566. componentLayout: 'htmleditor',
  115567. // private properties
  115568. initialized : false,
  115569. activated : false,
  115570. sourceEditMode : false,
  115571. iframePad:3,
  115572. hideMode:'offsets',
  115573. afterBodyEl: '</div>',
  115574. maskOnDisable: true,
  115575. // private
  115576. initComponent : function(){
  115577. var me = this;
  115578. me.addEvents(
  115579. /**
  115580. * @event initialize
  115581. * Fires when the editor is fully initialized (including the iframe)
  115582. * @param {Ext.form.field.HtmlEditor} this
  115583. */
  115584. 'initialize',
  115585. /**
  115586. * @event activate
  115587. * Fires when the editor is first receives the focus. Any insertion must wait until after this event.
  115588. * @param {Ext.form.field.HtmlEditor} this
  115589. */
  115590. 'activate',
  115591. /**
  115592. * @event beforesync
  115593. * Fires before the textarea is updated with content from the editor iframe. Return false to cancel the
  115594. * sync.
  115595. * @param {Ext.form.field.HtmlEditor} this
  115596. * @param {String} html
  115597. */
  115598. 'beforesync',
  115599. /**
  115600. * @event beforepush
  115601. * Fires before the iframe editor is updated with content from the textarea. Return false to cancel the
  115602. * push.
  115603. * @param {Ext.form.field.HtmlEditor} this
  115604. * @param {String} html
  115605. */
  115606. 'beforepush',
  115607. /**
  115608. * @event sync
  115609. * Fires when the textarea is updated with content from the editor iframe.
  115610. * @param {Ext.form.field.HtmlEditor} this
  115611. * @param {String} html
  115612. */
  115613. 'sync',
  115614. /**
  115615. * @event push
  115616. * Fires when the iframe editor is updated with content from the textarea.
  115617. * @param {Ext.form.field.HtmlEditor} this
  115618. * @param {String} html
  115619. */
  115620. 'push',
  115621. /**
  115622. * @event editmodechange
  115623. * Fires when the editor switches edit modes
  115624. * @param {Ext.form.field.HtmlEditor} this
  115625. * @param {Boolean} sourceEdit True if source edit, false if standard editing.
  115626. */
  115627. 'editmodechange'
  115628. );
  115629. me.callParent(arguments);
  115630. me.createToolbar(me);
  115631. // Init mixins
  115632. me.initLabelable();
  115633. me.initField();
  115634. },
  115635. /**
  115636. * @private
  115637. * Must define this function to allow the Layout base class to collect all descendant layouts to be run.
  115638. */
  115639. getRefItems: function() {
  115640. return [ this.toolbar ];
  115641. },
  115642. /*
  115643. * Called when the editor creates its toolbar. Override this method if you need to
  115644. * add custom toolbar buttons.
  115645. * @param {Ext.form.field.HtmlEditor} editor
  115646. * @protected
  115647. */
  115648. createToolbar : function(editor){
  115649. var me = this,
  115650. items = [], i,
  115651. tipsEnabled = Ext.tip.QuickTipManager && Ext.tip.QuickTipManager.isEnabled(),
  115652. baseCSSPrefix = Ext.baseCSSPrefix,
  115653. fontSelectItem, toolbar, undef;
  115654. function btn(id, toggle, handler){
  115655. return {
  115656. itemId : id,
  115657. cls : baseCSSPrefix + 'btn-icon',
  115658. iconCls: baseCSSPrefix + 'edit-'+id,
  115659. enableToggle:toggle !== false,
  115660. scope: editor,
  115661. handler:handler||editor.relayBtnCmd,
  115662. clickEvent: 'mousedown',
  115663. tooltip: tipsEnabled ? editor.buttonTips[id] || undef : undef,
  115664. overflowText: editor.buttonTips[id].title || undef,
  115665. tabIndex: -1
  115666. };
  115667. }
  115668. if (me.enableFont && !Ext.isSafari2) {
  115669. fontSelectItem = Ext.widget('component', {
  115670. renderTpl: [
  115671. '<select id="{id}-selectEl" class="{cls}">',
  115672. '<tpl for="fonts">',
  115673. '<option value="{[values.toLowerCase()]}" style="font-family:{.}"<tpl if="values.toLowerCase()==parent.defaultFont"> selected</tpl>>{.}</option>',
  115674. '</tpl>',
  115675. '</select>'
  115676. ],
  115677. renderData: {
  115678. cls: baseCSSPrefix + 'font-select',
  115679. fonts: me.fontFamilies,
  115680. defaultFont: me.defaultFont
  115681. },
  115682. childEls: ['selectEl'],
  115683. afterRender: function() {
  115684. me.fontSelect = this.selectEl;
  115685. Ext.Component.prototype.afterRender.apply(this, arguments);
  115686. },
  115687. onDisable: function() {
  115688. var selectEl = this.selectEl;
  115689. if (selectEl) {
  115690. selectEl.dom.disabled = true;
  115691. }
  115692. Ext.Component.prototype.onDisable.apply(this, arguments);
  115693. },
  115694. onEnable: function() {
  115695. var selectEl = this.selectEl;
  115696. if (selectEl) {
  115697. selectEl.dom.disabled = false;
  115698. }
  115699. Ext.Component.prototype.onEnable.apply(this, arguments);
  115700. },
  115701. listeners: {
  115702. change: function() {
  115703. me.relayCmd('fontname', me.fontSelect.dom.value);
  115704. me.deferFocus();
  115705. },
  115706. element: 'selectEl'
  115707. }
  115708. });
  115709. items.push(
  115710. fontSelectItem,
  115711. '-'
  115712. );
  115713. }
  115714. if (me.enableFormat) {
  115715. items.push(
  115716. btn('bold'),
  115717. btn('italic'),
  115718. btn('underline')
  115719. );
  115720. }
  115721. if (me.enableFontSize) {
  115722. items.push(
  115723. '-',
  115724. btn('increasefontsize', false, me.adjustFont),
  115725. btn('decreasefontsize', false, me.adjustFont)
  115726. );
  115727. }
  115728. if (me.enableColors) {
  115729. items.push(
  115730. '-', {
  115731. itemId: 'forecolor',
  115732. cls: baseCSSPrefix + 'btn-icon',
  115733. iconCls: baseCSSPrefix + 'edit-forecolor',
  115734. overflowText: editor.buttonTips.forecolor.title,
  115735. tooltip: tipsEnabled ? editor.buttonTips.forecolor || undef : undef,
  115736. tabIndex:-1,
  115737. menu : Ext.widget('menu', {
  115738. plain: true,
  115739. items: [{
  115740. xtype: 'colorpicker',
  115741. allowReselect: true,
  115742. focus: Ext.emptyFn,
  115743. value: '000000',
  115744. plain: true,
  115745. clickEvent: 'mousedown',
  115746. handler: function(cp, color) {
  115747. me.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
  115748. me.deferFocus();
  115749. this.up('menu').hide();
  115750. }
  115751. }]
  115752. })
  115753. }, {
  115754. itemId: 'backcolor',
  115755. cls: baseCSSPrefix + 'btn-icon',
  115756. iconCls: baseCSSPrefix + 'edit-backcolor',
  115757. overflowText: editor.buttonTips.backcolor.title,
  115758. tooltip: tipsEnabled ? editor.buttonTips.backcolor || undef : undef,
  115759. tabIndex:-1,
  115760. menu : Ext.widget('menu', {
  115761. plain: true,
  115762. items: [{
  115763. xtype: 'colorpicker',
  115764. focus: Ext.emptyFn,
  115765. value: 'FFFFFF',
  115766. plain: true,
  115767. allowReselect: true,
  115768. clickEvent: 'mousedown',
  115769. handler: function(cp, color) {
  115770. if (Ext.isGecko) {
  115771. me.execCmd('useCSS', false);
  115772. me.execCmd('hilitecolor', color);
  115773. me.execCmd('useCSS', true);
  115774. me.deferFocus();
  115775. } else {
  115776. me.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
  115777. me.deferFocus();
  115778. }
  115779. this.up('menu').hide();
  115780. }
  115781. }]
  115782. })
  115783. }
  115784. );
  115785. }
  115786. if (me.enableAlignments) {
  115787. items.push(
  115788. '-',
  115789. btn('justifyleft'),
  115790. btn('justifycenter'),
  115791. btn('justifyright')
  115792. );
  115793. }
  115794. if (!Ext.isSafari2) {
  115795. if (me.enableLinks) {
  115796. items.push(
  115797. '-',
  115798. btn('createlink', false, me.createLink)
  115799. );
  115800. }
  115801. if (me.enableLists) {
  115802. items.push(
  115803. '-',
  115804. btn('insertorderedlist'),
  115805. btn('insertunorderedlist')
  115806. );
  115807. }
  115808. if (me.enableSourceEdit) {
  115809. items.push(
  115810. '-',
  115811. btn('sourceedit', true, function(btn){
  115812. me.toggleSourceEdit(!me.sourceEditMode);
  115813. })
  115814. );
  115815. }
  115816. }
  115817. // Everything starts disabled.
  115818. for (i = 0; i < items.length; i++) {
  115819. if (items[i].itemId !== 'sourceedit') {
  115820. items[i].disabled = true;
  115821. }
  115822. }
  115823. // build the toolbar
  115824. // Automatically rendered in AbstractComponent.afterRender's renderChildren call
  115825. toolbar = Ext.widget('toolbar', {
  115826. id: me.id + '-toolbar',
  115827. ownerCt: me,
  115828. cls: Ext.baseCSSPrefix + 'html-editor-tb',
  115829. enableOverflow: true,
  115830. items: items,
  115831. ownerLayout: me.getComponentLayout(),
  115832. // stop form submits
  115833. listeners: {
  115834. click: function(e){
  115835. e.preventDefault();
  115836. },
  115837. element: 'el'
  115838. }
  115839. });
  115840. me.toolbar = toolbar;
  115841. },
  115842. getMaskTarget: function(){
  115843. return this.bodyEl;
  115844. },
  115845. /**
  115846. * Sets the read only state of this field.
  115847. * @param {Boolean} readOnly Whether the field should be read only.
  115848. */
  115849. setReadOnly: function(readOnly) {
  115850. var me = this,
  115851. textareaEl = me.textareaEl,
  115852. iframeEl = me.iframeEl,
  115853. body;
  115854. me.readOnly = readOnly;
  115855. if (textareaEl) {
  115856. textareaEl.dom.readOnly = readOnly;
  115857. }
  115858. if (me.initialized) {
  115859. body = me.getEditorBody();
  115860. if (Ext.isIE) {
  115861. // Hide the iframe while setting contentEditable so it doesn't grab focus
  115862. iframeEl.setDisplayed(false);
  115863. body.contentEditable = !readOnly;
  115864. iframeEl.setDisplayed(true);
  115865. } else {
  115866. me.setDesignMode(!readOnly);
  115867. }
  115868. if (body) {
  115869. body.style.cursor = readOnly ? 'default' : 'text';
  115870. }
  115871. me.disableItems(readOnly);
  115872. }
  115873. },
  115874. /**
  115875. * Called when the editor initializes the iframe with HTML contents. Override this method if you
  115876. * want to change the initialization markup of the iframe (e.g. to add stylesheets).
  115877. *
  115878. * **Note:** IE8-Standards has unwanted scroller behavior, so the default meta tag forces IE7 compatibility.
  115879. * Also note that forcing IE7 mode works when the page is loaded normally, but if you are using IE's Web
  115880. * Developer Tools to manually set the document mode, that will take precedence and override what this
  115881. * code sets by default. This can be confusing when developing, but is not a user-facing issue.
  115882. * @protected
  115883. */
  115884. getDocMarkup: function() {
  115885. var me = this,
  115886. h = me.iframeEl.getHeight() - me.iframePad * 2;
  115887. return Ext.String.format('<html><head><style type="text/css">body{border:0;margin:0;padding:{0}px;height:{1}px;box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box;cursor:text}</style></head><body></body></html>', me.iframePad, h);
  115888. },
  115889. // private
  115890. getEditorBody: function() {
  115891. var doc = this.getDoc();
  115892. return doc.body || doc.documentElement;
  115893. },
  115894. // private
  115895. getDoc: function() {
  115896. return (!Ext.isIE && this.iframeEl.dom.contentDocument) || this.getWin().document;
  115897. },
  115898. // private
  115899. getWin: function() {
  115900. return Ext.isIE ? this.iframeEl.dom.contentWindow : window.frames[this.iframeEl.dom.name];
  115901. },
  115902. // Do the job of a container layout at this point even though we are not a Container.
  115903. // TODO: Refactor as a Container.
  115904. finishRenderChildren: function () {
  115905. this.callParent();
  115906. this.toolbar.finishRender();
  115907. },
  115908. // private
  115909. onRender: function() {
  115910. var me = this;
  115911. me.callParent(arguments);
  115912. // The input element is interrogated by the layout to extract height when labelAlign is 'top'
  115913. // It must be set, and then switched between the iframe and the textarea
  115914. me.inputEl = me.iframeEl;
  115915. // Start polling for when the iframe document is ready to be manipulated
  115916. me.monitorTask = Ext.TaskManager.start({
  115917. run: me.checkDesignMode,
  115918. scope: me,
  115919. interval: 100
  115920. });
  115921. },
  115922. initRenderTpl: function() {
  115923. var me = this;
  115924. if (!me.hasOwnProperty('renderTpl')) {
  115925. me.renderTpl = me.getTpl('labelableRenderTpl');
  115926. }
  115927. return me.callParent();
  115928. },
  115929. initRenderData: function() {
  115930. this.beforeSubTpl = '<div class="' + this.editorWrapCls + '">' + Ext.DomHelper.markup(this.toolbar.getRenderTree());
  115931. return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
  115932. },
  115933. getSubTplData: function() {
  115934. return {
  115935. $comp : this,
  115936. cmpId : this.id,
  115937. id : this.getInputId(),
  115938. textareaCls : Ext.baseCSSPrefix + 'hidden',
  115939. value : this.value,
  115940. iframeName : Ext.id(),
  115941. iframeSrc : Ext.SSL_SECURE_URL,
  115942. size : 'height:100px;width:100%'
  115943. };
  115944. },
  115945. getSubTplMarkup: function() {
  115946. return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
  115947. },
  115948. initFrameDoc: function() {
  115949. var me = this,
  115950. doc, task;
  115951. Ext.TaskManager.stop(me.monitorTask);
  115952. doc = me.getDoc();
  115953. me.win = me.getWin();
  115954. doc.open();
  115955. doc.write(me.getDocMarkup());
  115956. doc.close();
  115957. task = { // must defer to wait for browser to be ready
  115958. run: function() {
  115959. var doc = me.getDoc();
  115960. if (doc.body || doc.readyState === 'complete') {
  115961. Ext.TaskManager.stop(task);
  115962. me.setDesignMode(true);
  115963. Ext.defer(me.initEditor, 10, me);
  115964. }
  115965. },
  115966. interval : 10,
  115967. duration:10000,
  115968. scope: me
  115969. };
  115970. Ext.TaskManager.start(task);
  115971. },
  115972. checkDesignMode: function() {
  115973. var me = this,
  115974. doc = me.getDoc();
  115975. if (doc && (!doc.editorInitialized || me.getDesignMode() !== 'on')) {
  115976. me.initFrameDoc();
  115977. }
  115978. },
  115979. /**
  115980. * @private
  115981. * Sets current design mode. To enable, mode can be true or 'on', off otherwise
  115982. */
  115983. setDesignMode: function(mode) {
  115984. var me = this,
  115985. doc = me.getDoc();
  115986. if (doc) {
  115987. if (me.readOnly) {
  115988. mode = false;
  115989. }
  115990. doc.designMode = (/on|true/i).test(String(mode).toLowerCase()) ?'on':'off';
  115991. }
  115992. },
  115993. // private
  115994. getDesignMode: function() {
  115995. var doc = this.getDoc();
  115996. return !doc ? '' : String(doc.designMode).toLowerCase();
  115997. },
  115998. disableItems: function(disabled) {
  115999. var items = this.getToolbar().items.items,
  116000. i,
  116001. iLen = items.length,
  116002. item;
  116003. for (i = 0; i < iLen; i++) {
  116004. item = items[i];
  116005. if (item.getItemId() !== 'sourceedit') {
  116006. item.setDisabled(disabled);
  116007. }
  116008. }
  116009. },
  116010. /**
  116011. * Toggles the editor between standard and source edit mode.
  116012. * @param {Boolean} [sourceEditMode] True for source edit, false for standard
  116013. */
  116014. toggleSourceEdit: function(sourceEditMode) {
  116015. var me = this,
  116016. iframe = me.iframeEl,
  116017. textarea = me.textareaEl,
  116018. hiddenCls = Ext.baseCSSPrefix + 'hidden',
  116019. btn = me.getToolbar().getComponent('sourceedit');
  116020. if (!Ext.isBoolean(sourceEditMode)) {
  116021. sourceEditMode = !me.sourceEditMode;
  116022. }
  116023. me.sourceEditMode = sourceEditMode;
  116024. if (btn.pressed !== sourceEditMode) {
  116025. btn.toggle(sourceEditMode);
  116026. }
  116027. if (sourceEditMode) {
  116028. me.disableItems(true);
  116029. me.syncValue();
  116030. iframe.addCls(hiddenCls);
  116031. textarea.removeCls(hiddenCls);
  116032. textarea.dom.removeAttribute('tabIndex');
  116033. textarea.focus();
  116034. me.inputEl = textarea;
  116035. }
  116036. else {
  116037. if (me.initialized) {
  116038. me.disableItems(me.readOnly);
  116039. }
  116040. me.pushValue();
  116041. iframe.removeCls(hiddenCls);
  116042. textarea.addCls(hiddenCls);
  116043. textarea.dom.setAttribute('tabIndex', -1);
  116044. me.deferFocus();
  116045. me.inputEl = iframe;
  116046. }
  116047. me.fireEvent('editmodechange', me, sourceEditMode);
  116048. me.updateLayout();
  116049. },
  116050. // private used internally
  116051. createLink : function() {
  116052. var url = prompt(this.createLinkText, this.defaultLinkValue);
  116053. if (url && url !== 'http:/'+'/') {
  116054. this.relayCmd('createlink', url);
  116055. }
  116056. },
  116057. clearInvalid: Ext.emptyFn,
  116058. // docs inherit from Field
  116059. setValue: function(value) {
  116060. var me = this,
  116061. textarea = me.textareaEl;
  116062. me.mixins.field.setValue.call(me, value);
  116063. if (value === null || value === undefined) {
  116064. value = '';
  116065. }
  116066. if (textarea) {
  116067. textarea.dom.value = value;
  116068. }
  116069. me.pushValue();
  116070. return this;
  116071. },
  116072. /**
  116073. * If you need/want custom HTML cleanup, this is the method you should override.
  116074. * @param {String} html The HTML to be cleaned
  116075. * @return {String} The cleaned HTML
  116076. * @protected
  116077. */
  116078. cleanHtml: function(html) {
  116079. html = String(html);
  116080. if (Ext.isWebKit) { // strip safari nonsense
  116081. html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
  116082. }
  116083. /*
  116084. * Neat little hack. Strips out all the non-digit characters from the default
  116085. * value and compares it to the character code of the first character in the string
  116086. * because it can cause encoding issues when posted to the server. We need the
  116087. * parseInt here because charCodeAt will return a number.
  116088. */
  116089. if (html.charCodeAt(0) === parseInt(this.defaultValue.replace(/\D/g, ''), 10)) {
  116090. html = html.substring(1);
  116091. }
  116092. return html;
  116093. },
  116094. /**
  116095. * Syncs the contents of the editor iframe with the textarea.
  116096. * @protected
  116097. */
  116098. syncValue : function(){
  116099. var me = this,
  116100. body, changed, html, bodyStyle, match;
  116101. if (me.initialized) {
  116102. body = me.getEditorBody();
  116103. html = body.innerHTML;
  116104. if (Ext.isWebKit) {
  116105. bodyStyle = body.getAttribute('style'); // Safari puts text-align styles on the body element!
  116106. match = bodyStyle.match(/text-align:(.*?);/i);
  116107. if (match && match[1]) {
  116108. html = '<div style="' + match[0] + '">' + html + '</div>';
  116109. }
  116110. }
  116111. html = me.cleanHtml(html);
  116112. if (me.fireEvent('beforesync', me, html) !== false) {
  116113. if (me.textareaEl.dom.value != html) {
  116114. me.textareaEl.dom.value = html;
  116115. changed = true;
  116116. }
  116117. me.fireEvent('sync', me, html);
  116118. if (changed) {
  116119. // we have to guard this to avoid infinite recursion because getValue
  116120. // calls this method...
  116121. me.checkChange();
  116122. }
  116123. }
  116124. }
  116125. },
  116126. //docs inherit from Field
  116127. getValue : function() {
  116128. var me = this,
  116129. value;
  116130. if (!me.sourceEditMode) {
  116131. me.syncValue();
  116132. }
  116133. value = me.rendered ? me.textareaEl.dom.value : me.value;
  116134. me.value = value;
  116135. return value;
  116136. },
  116137. /**
  116138. * Pushes the value of the textarea into the iframe editor.
  116139. * @protected
  116140. */
  116141. pushValue: function() {
  116142. var me = this,
  116143. v;
  116144. if(me.initialized){
  116145. v = me.textareaEl.dom.value || '';
  116146. if (!me.activated && v.length < 1) {
  116147. v = me.defaultValue;
  116148. }
  116149. if (me.fireEvent('beforepush', me, v) !== false) {
  116150. me.getEditorBody().innerHTML = v;
  116151. if (Ext.isGecko) {
  116152. // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8
  116153. me.setDesignMode(false); //toggle off first
  116154. me.setDesignMode(true);
  116155. }
  116156. me.fireEvent('push', me, v);
  116157. }
  116158. }
  116159. },
  116160. // private
  116161. deferFocus : function(){
  116162. this.focus(false, true);
  116163. },
  116164. getFocusEl: function() {
  116165. var me = this,
  116166. win = me.win;
  116167. return win && !me.sourceEditMode ? win : me.textareaEl;
  116168. },
  116169. // private
  116170. initEditor : function(){
  116171. //Destroying the component during/before initEditor can cause issues.
  116172. try {
  116173. var me = this,
  116174. dbody = me.getEditorBody(),
  116175. ss = me.textareaEl.getStyles('font-size', 'font-family', 'background-image', 'background-repeat', 'background-color', 'color'),
  116176. doc,
  116177. fn;
  116178. ss['background-attachment'] = 'fixed'; // w3c
  116179. dbody.bgProperties = 'fixed'; // ie
  116180. Ext.DomHelper.applyStyles(dbody, ss);
  116181. doc = me.getDoc();
  116182. if (doc) {
  116183. try {
  116184. Ext.EventManager.removeAll(doc);
  116185. } catch(e) {}
  116186. }
  116187. /*
  116188. * We need to use createDelegate here, because when using buffer, the delayed task is added
  116189. * as a property to the function. When the listener is removed, the task is deleted from the function.
  116190. * Since onEditorEvent is shared on the prototype, if we have multiple html editors, the first time one of the editors
  116191. * is destroyed, it causes the fn to be deleted from the prototype, which causes errors. Essentially, we're just anonymizing the function.
  116192. */
  116193. fn = Ext.Function.bind(me.onEditorEvent, me);
  116194. Ext.EventManager.on(doc, {
  116195. mousedown: fn,
  116196. dblclick: fn,
  116197. click: fn,
  116198. keyup: fn,
  116199. buffer:100
  116200. });
  116201. // These events need to be relayed from the inner document (where they stop
  116202. // bubbling) up to the outer document. This has to be done at the DOM level so
  116203. // the event reaches listeners on elements like the document body. The effected
  116204. // mechanisms that depend on this bubbling behavior are listed to the right
  116205. // of the event.
  116206. fn = me.onRelayedEvent;
  116207. Ext.EventManager.on(doc, {
  116208. mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
  116209. mousemove: fn, // window resize drag detection
  116210. mouseup: fn, // window resize termination
  116211. click: fn, // not sure, but just to be safe
  116212. dblclick: fn, // not sure again
  116213. scope: me
  116214. });
  116215. if (Ext.isGecko) {
  116216. Ext.EventManager.on(doc, 'keypress', me.applyCommand, me);
  116217. }
  116218. if (me.fixKeys) {
  116219. Ext.EventManager.on(doc, 'keydown', me.fixKeys, me);
  116220. }
  116221. // We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
  116222. Ext.EventManager.on(window, 'unload', me.beforeDestroy, me);
  116223. doc.editorInitialized = true;
  116224. me.initialized = true;
  116225. me.pushValue();
  116226. me.setReadOnly(me.readOnly);
  116227. me.fireEvent('initialize', me);
  116228. } catch(ex) {
  116229. // ignore (why?)
  116230. }
  116231. },
  116232. // private
  116233. beforeDestroy : function(){
  116234. var me = this,
  116235. monitorTask = me.monitorTask,
  116236. doc, prop;
  116237. if (monitorTask) {
  116238. Ext.TaskManager.stop(monitorTask);
  116239. }
  116240. if (me.rendered) {
  116241. try {
  116242. doc = me.getDoc();
  116243. if (doc) {
  116244. // removeAll() doesn't currently know how to handle iframe document,
  116245. // so for now we have to wrap it in an Ext.Element using Ext.fly,
  116246. // or else IE6/7 will leak big time when the page is refreshed.
  116247. // TODO: this may not be needed once we find a more permanent fix.
  116248. // see EXTJSIV-5891.
  116249. Ext.EventManager.removeAll(Ext.fly(doc));
  116250. for (prop in doc) {
  116251. if (doc.hasOwnProperty && doc.hasOwnProperty(prop)) {
  116252. delete doc[prop];
  116253. }
  116254. }
  116255. }
  116256. } catch(e) {
  116257. // ignore (why?)
  116258. }
  116259. Ext.destroyMembers(me, 'toolbar', 'iframeEl', 'textareaEl');
  116260. }
  116261. me.callParent();
  116262. },
  116263. // private
  116264. onRelayedEvent: function (event) {
  116265. // relay event from the iframe's document to the document that owns the iframe...
  116266. var iframeEl = this.iframeEl,
  116267. iframeXY = iframeEl.getXY(),
  116268. eventXY = event.getXY();
  116269. // the event from the inner document has XY relative to that document's origin,
  116270. // so adjust it to use the origin of the iframe in the outer document:
  116271. event.xy = [iframeXY[0] + eventXY[0], iframeXY[1] + eventXY[1]];
  116272. event.injectEvent(iframeEl); // blame the iframe for the event...
  116273. event.xy = eventXY; // restore the original XY (just for safety)
  116274. },
  116275. // private
  116276. onFirstFocus : function(){
  116277. var me = this,
  116278. selection, range;
  116279. me.activated = true;
  116280. me.disableItems(me.readOnly);
  116281. if (Ext.isGecko) { // prevent silly gecko errors
  116282. me.win.focus();
  116283. selection = me.win.getSelection();
  116284. if (!selection.focusNode || selection.focusNode.nodeType !== 3) {
  116285. range = selection.getRangeAt(0);
  116286. range.selectNodeContents(me.getEditorBody());
  116287. range.collapse(true);
  116288. me.deferFocus();
  116289. }
  116290. try {
  116291. me.execCmd('useCSS', true);
  116292. me.execCmd('styleWithCSS', false);
  116293. } catch(e) {
  116294. // ignore (why?)
  116295. }
  116296. }
  116297. me.fireEvent('activate', me);
  116298. },
  116299. // private
  116300. adjustFont: function(btn) {
  116301. var adjust = btn.getItemId() === 'increasefontsize' ? 1 : -1,
  116302. size = this.getDoc().queryCommandValue('FontSize') || '2',
  116303. isPxSize = Ext.isString(size) && size.indexOf('px') !== -1,
  116304. isSafari;
  116305. size = parseInt(size, 10);
  116306. if (isPxSize) {
  116307. // Safari 3 values
  116308. // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px
  116309. if (size <= 10) {
  116310. size = 1 + adjust;
  116311. }
  116312. else if (size <= 13) {
  116313. size = 2 + adjust;
  116314. }
  116315. else if (size <= 16) {
  116316. size = 3 + adjust;
  116317. }
  116318. else if (size <= 18) {
  116319. size = 4 + adjust;
  116320. }
  116321. else if (size <= 24) {
  116322. size = 5 + adjust;
  116323. }
  116324. else {
  116325. size = 6 + adjust;
  116326. }
  116327. size = Ext.Number.constrain(size, 1, 6);
  116328. } else {
  116329. isSafari = Ext.isSafari;
  116330. if (isSafari) { // safari
  116331. adjust *= 2;
  116332. }
  116333. size = Math.max(1, size + adjust) + (isSafari ? 'px' : 0);
  116334. }
  116335. this.execCmd('FontSize', size);
  116336. },
  116337. // private
  116338. onEditorEvent: function(e) {
  116339. this.updateToolbar();
  116340. },
  116341. /**
  116342. * Triggers a toolbar update by reading the markup state of the current selection in the editor.
  116343. * @protected
  116344. */
  116345. updateToolbar: function() {
  116346. var me = this,
  116347. btns, doc, name, fontSelect;
  116348. if (me.readOnly) {
  116349. return;
  116350. }
  116351. if (!me.activated) {
  116352. me.onFirstFocus();
  116353. return;
  116354. }
  116355. btns = me.getToolbar().items.map;
  116356. doc = me.getDoc();
  116357. if (me.enableFont && !Ext.isSafari2) {
  116358. name = (doc.queryCommandValue('FontName') || me.defaultFont).toLowerCase();
  116359. fontSelect = me.fontSelect.dom;
  116360. if (name !== fontSelect.value) {
  116361. fontSelect.value = name;
  116362. }
  116363. }
  116364. function updateButtons() {
  116365. for (var i = 0, l = arguments.length, name; i < l; i++) {
  116366. name = arguments[i];
  116367. btns[name].toggle(doc.queryCommandState(name));
  116368. }
  116369. }
  116370. if(me.enableFormat){
  116371. updateButtons('bold', 'italic', 'underline');
  116372. }
  116373. if(me.enableAlignments){
  116374. updateButtons('justifyleft', 'justifycenter', 'justifyright');
  116375. }
  116376. if(!Ext.isSafari2 && me.enableLists){
  116377. updateButtons('insertorderedlist', 'insertunorderedlist');
  116378. }
  116379. Ext.menu.Manager.hideAll();
  116380. me.syncValue();
  116381. },
  116382. // private
  116383. relayBtnCmd: function(btn) {
  116384. this.relayCmd(btn.getItemId());
  116385. },
  116386. /**
  116387. * Executes a Midas editor command on the editor document and performs necessary focus and toolbar updates.
  116388. * **This should only be called after the editor is initialized.**
  116389. * @param {String} cmd The Midas command
  116390. * @param {String/Boolean} [value=null] The value to pass to the command
  116391. */
  116392. relayCmd: function(cmd, value) {
  116393. Ext.defer(function() {
  116394. var me = this;
  116395. me.focus();
  116396. me.execCmd(cmd, value);
  116397. me.updateToolbar();
  116398. }, 10, this);
  116399. },
  116400. /**
  116401. * Executes a Midas editor command directly on the editor document. For visual commands, you should use
  116402. * {@link #relayCmd} instead. **This should only be called after the editor is initialized.**
  116403. * @param {String} cmd The Midas command
  116404. * @param {String/Boolean} [value=null] The value to pass to the command
  116405. */
  116406. execCmd : function(cmd, value){
  116407. var me = this,
  116408. doc = me.getDoc(),
  116409. undef;
  116410. doc.execCommand(cmd, false, value === undef ? null : value);
  116411. me.syncValue();
  116412. },
  116413. // private
  116414. applyCommand : function(e){
  116415. if (e.ctrlKey) {
  116416. var me = this,
  116417. c = e.getCharCode(), cmd;
  116418. if (c > 0) {
  116419. c = String.fromCharCode(c);
  116420. switch (c) {
  116421. case 'b':
  116422. cmd = 'bold';
  116423. break;
  116424. case 'i':
  116425. cmd = 'italic';
  116426. break;
  116427. case 'u':
  116428. cmd = 'underline';
  116429. break;
  116430. }
  116431. if (cmd) {
  116432. me.win.focus();
  116433. me.execCmd(cmd);
  116434. me.deferFocus();
  116435. e.preventDefault();
  116436. }
  116437. }
  116438. }
  116439. },
  116440. /**
  116441. * Inserts the passed text at the current cursor position.
  116442. * Note: the editor must be initialized and activated to insert text.
  116443. * @param {String} text
  116444. */
  116445. insertAtCursor : function(text){
  116446. var me = this,
  116447. range;
  116448. if (me.activated) {
  116449. me.win.focus();
  116450. if (Ext.isIE) {
  116451. range = me.getDoc().selection.createRange();
  116452. if (range) {
  116453. range.pasteHTML(text);
  116454. me.syncValue();
  116455. me.deferFocus();
  116456. }
  116457. }else{
  116458. me.execCmd('InsertHTML', text);
  116459. me.deferFocus();
  116460. }
  116461. }
  116462. },
  116463. // private
  116464. fixKeys: (function() { // load time branching for fastest keydown performance
  116465. if (Ext.isIE) {
  116466. return function(e){
  116467. var me = this,
  116468. k = e.getKey(),
  116469. doc = me.getDoc(),
  116470. readOnly = me.readOnly,
  116471. range, target;
  116472. if (k === e.TAB) {
  116473. e.stopEvent();
  116474. if (!readOnly) {
  116475. range = doc.selection.createRange();
  116476. if(range){
  116477. range.collapse(true);
  116478. range.pasteHTML('&#160;&#160;&#160;&#160;');
  116479. me.deferFocus();
  116480. }
  116481. }
  116482. }
  116483. else if (k === e.ENTER) {
  116484. if (!readOnly) {
  116485. range = doc.selection.createRange();
  116486. if (range) {
  116487. target = range.parentElement();
  116488. if(!target || target.tagName.toLowerCase() !== 'li'){
  116489. e.stopEvent();
  116490. range.pasteHTML('<br />');
  116491. range.collapse(false);
  116492. range.select();
  116493. }
  116494. }
  116495. }
  116496. }
  116497. };
  116498. }
  116499. if (Ext.isOpera) {
  116500. return function(e){
  116501. var me = this;
  116502. if (e.getKey() === e.TAB) {
  116503. e.stopEvent();
  116504. if (!me.readOnly) {
  116505. me.win.focus();
  116506. me.execCmd('InsertHTML','&#160;&#160;&#160;&#160;');
  116507. me.deferFocus();
  116508. }
  116509. }
  116510. };
  116511. }
  116512. if (Ext.isWebKit) {
  116513. return function(e){
  116514. var me = this,
  116515. k = e.getKey(),
  116516. readOnly = me.readOnly;
  116517. if (k === e.TAB) {
  116518. e.stopEvent();
  116519. if (!readOnly) {
  116520. me.execCmd('InsertText','\t');
  116521. me.deferFocus();
  116522. }
  116523. }
  116524. else if (k === e.ENTER) {
  116525. e.stopEvent();
  116526. if (!readOnly) {
  116527. me.execCmd('InsertHtml','<br /><br />');
  116528. me.deferFocus();
  116529. }
  116530. }
  116531. };
  116532. }
  116533. return null; // not needed, so null
  116534. }()),
  116535. /**
  116536. * Returns the editor's toolbar. **This is only available after the editor has been rendered.**
  116537. * @return {Ext.toolbar.Toolbar}
  116538. */
  116539. getToolbar : function(){
  116540. return this.toolbar;
  116541. },
  116542. //<locale>
  116543. /**
  116544. * @property {Object} buttonTips
  116545. * Object collection of toolbar tooltips for the buttons in the editor. The key is the command id associated with
  116546. * that button and the value is a valid QuickTips object. For example:
  116547. *
  116548. * {
  116549. * bold : {
  116550. * title: 'Bold (Ctrl+B)',
  116551. * text: 'Make the selected text bold.',
  116552. * cls: 'x-html-editor-tip'
  116553. * },
  116554. * italic : {
  116555. * title: 'Italic (Ctrl+I)',
  116556. * text: 'Make the selected text italic.',
  116557. * cls: 'x-html-editor-tip'
  116558. * },
  116559. * ...
  116560. */
  116561. buttonTips : {
  116562. bold : {
  116563. title: 'Bold (Ctrl+B)',
  116564. text: 'Make the selected text bold.',
  116565. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116566. },
  116567. italic : {
  116568. title: 'Italic (Ctrl+I)',
  116569. text: 'Make the selected text italic.',
  116570. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116571. },
  116572. underline : {
  116573. title: 'Underline (Ctrl+U)',
  116574. text: 'Underline the selected text.',
  116575. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116576. },
  116577. increasefontsize : {
  116578. title: 'Grow Text',
  116579. text: 'Increase the font size.',
  116580. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116581. },
  116582. decreasefontsize : {
  116583. title: 'Shrink Text',
  116584. text: 'Decrease the font size.',
  116585. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116586. },
  116587. backcolor : {
  116588. title: 'Text Highlight Color',
  116589. text: 'Change the background color of the selected text.',
  116590. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116591. },
  116592. forecolor : {
  116593. title: 'Font Color',
  116594. text: 'Change the color of the selected text.',
  116595. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116596. },
  116597. justifyleft : {
  116598. title: 'Align Text Left',
  116599. text: 'Align text to the left.',
  116600. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116601. },
  116602. justifycenter : {
  116603. title: 'Center Text',
  116604. text: 'Center text in the editor.',
  116605. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116606. },
  116607. justifyright : {
  116608. title: 'Align Text Right',
  116609. text: 'Align text to the right.',
  116610. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116611. },
  116612. insertunorderedlist : {
  116613. title: 'Bullet List',
  116614. text: 'Start a bulleted list.',
  116615. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116616. },
  116617. insertorderedlist : {
  116618. title: 'Numbered List',
  116619. text: 'Start a numbered list.',
  116620. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116621. },
  116622. createlink : {
  116623. title: 'Hyperlink',
  116624. text: 'Make the selected text a hyperlink.',
  116625. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116626. },
  116627. sourceedit : {
  116628. title: 'Source Edit',
  116629. text: 'Switch to source editing mode.',
  116630. cls: Ext.baseCSSPrefix + 'html-editor-tip'
  116631. }
  116632. }
  116633. //</locale>
  116634. // hide stuff that is not compatible
  116635. /**
  116636. * @event blur
  116637. * @private
  116638. */
  116639. /**
  116640. * @event focus
  116641. * @private
  116642. */
  116643. /**
  116644. * @event specialkey
  116645. * @private
  116646. */
  116647. /**
  116648. * @cfg {String} fieldCls
  116649. * @private
  116650. */
  116651. /**
  116652. * @cfg {String} focusCls
  116653. * @private
  116654. */
  116655. /**
  116656. * @cfg {String} autoCreate
  116657. * @private
  116658. */
  116659. /**
  116660. * @cfg {String} inputType
  116661. * @private
  116662. */
  116663. /**
  116664. * @cfg {String} invalidCls
  116665. * @private
  116666. */
  116667. /**
  116668. * @cfg {String} invalidText
  116669. * @private
  116670. */
  116671. /**
  116672. * @cfg {String} msgFx
  116673. * @private
  116674. */
  116675. /**
  116676. * @cfg {Boolean} allowDomMove
  116677. * @private
  116678. */
  116679. /**
  116680. * @cfg {String} applyTo
  116681. * @private
  116682. */
  116683. /**
  116684. * @cfg {String} readOnly
  116685. * @private
  116686. */
  116687. /**
  116688. * @cfg {String} tabIndex
  116689. * @private
  116690. */
  116691. /**
  116692. * @method validate
  116693. * @private
  116694. */
  116695. });
  116696. /**
  116697. * @docauthor Robert Dougan <rob@sencha.com>
  116698. *
  116699. * Single radio field. Similar to checkbox, but automatically handles making sure only one radio is checked
  116700. * at a time within a group of radios with the same name.
  116701. *
  116702. * # Labeling
  116703. *
  116704. * In addition to the {@link Ext.form.Labelable standard field labeling options}, radio buttons
  116705. * may be given an optional {@link #boxLabel} which will be displayed immediately to the right of the input. Also
  116706. * see {@link Ext.form.RadioGroup} for a convenient method of grouping related radio buttons.
  116707. *
  116708. * # Values
  116709. *
  116710. * The main value of a Radio field is a boolean, indicating whether or not the radio is checked.
  116711. *
  116712. * The following values will check the radio:
  116713. *
  116714. * - `true`
  116715. * - `'true'`
  116716. * - `'1'`
  116717. * - `'on'`
  116718. *
  116719. * Any other value will uncheck it.
  116720. *
  116721. * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be sent
  116722. * as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set this
  116723. * value if you have multiple radio buttons with the same {@link #name}, as is almost always the case.
  116724. *
  116725. * # Example usage
  116726. *
  116727. * @example
  116728. * Ext.create('Ext.form.Panel', {
  116729. * title : 'Order Form',
  116730. * width : 300,
  116731. * bodyPadding: 10,
  116732. * renderTo : Ext.getBody(),
  116733. * items: [
  116734. * {
  116735. * xtype : 'fieldcontainer',
  116736. * fieldLabel : 'Size',
  116737. * defaultType: 'radiofield',
  116738. * defaults: {
  116739. * flex: 1
  116740. * },
  116741. * layout: 'hbox',
  116742. * items: [
  116743. * {
  116744. * boxLabel : 'M',
  116745. * name : 'size',
  116746. * inputValue: 'm',
  116747. * id : 'radio1'
  116748. * }, {
  116749. * boxLabel : 'L',
  116750. * name : 'size',
  116751. * inputValue: 'l',
  116752. * id : 'radio2'
  116753. * }, {
  116754. * boxLabel : 'XL',
  116755. * name : 'size',
  116756. * inputValue: 'xl',
  116757. * id : 'radio3'
  116758. * }
  116759. * ]
  116760. * },
  116761. * {
  116762. * xtype : 'fieldcontainer',
  116763. * fieldLabel : 'Color',
  116764. * defaultType: 'radiofield',
  116765. * defaults: {
  116766. * flex: 1
  116767. * },
  116768. * layout: 'hbox',
  116769. * items: [
  116770. * {
  116771. * boxLabel : 'Blue',
  116772. * name : 'color',
  116773. * inputValue: 'blue',
  116774. * id : 'radio4'
  116775. * }, {
  116776. * boxLabel : 'Grey',
  116777. * name : 'color',
  116778. * inputValue: 'grey',
  116779. * id : 'radio5'
  116780. * }, {
  116781. * boxLabel : 'Black',
  116782. * name : 'color',
  116783. * inputValue: 'black',
  116784. * id : 'radio6'
  116785. * }
  116786. * ]
  116787. * }
  116788. * ],
  116789. * bbar: [
  116790. * {
  116791. * text: 'Smaller Size',
  116792. * handler: function() {
  116793. * var radio1 = Ext.getCmp('radio1'),
  116794. * radio2 = Ext.getCmp('radio2'),
  116795. * radio3 = Ext.getCmp('radio3');
  116796. *
  116797. * //if L is selected, change to M
  116798. * if (radio2.getValue()) {
  116799. * radio1.setValue(true);
  116800. * return;
  116801. * }
  116802. *
  116803. * //if XL is selected, change to L
  116804. * if (radio3.getValue()) {
  116805. * radio2.setValue(true);
  116806. * return;
  116807. * }
  116808. *
  116809. * //if nothing is set, set size to S
  116810. * radio1.setValue(true);
  116811. * }
  116812. * },
  116813. * {
  116814. * text: 'Larger Size',
  116815. * handler: function() {
  116816. * var radio1 = Ext.getCmp('radio1'),
  116817. * radio2 = Ext.getCmp('radio2'),
  116818. * radio3 = Ext.getCmp('radio3');
  116819. *
  116820. * //if M is selected, change to L
  116821. * if (radio1.getValue()) {
  116822. * radio2.setValue(true);
  116823. * return;
  116824. * }
  116825. *
  116826. * //if L is selected, change to XL
  116827. * if (radio2.getValue()) {
  116828. * radio3.setValue(true);
  116829. * return;
  116830. * }
  116831. *
  116832. * //if nothing is set, set size to XL
  116833. * radio3.setValue(true);
  116834. * }
  116835. * },
  116836. * '-',
  116837. * {
  116838. * text: 'Select color',
  116839. * menu: {
  116840. * indent: false,
  116841. * items: [
  116842. * {
  116843. * text: 'Blue',
  116844. * handler: function() {
  116845. * var radio = Ext.getCmp('radio4');
  116846. * radio.setValue(true);
  116847. * }
  116848. * },
  116849. * {
  116850. * text: 'Grey',
  116851. * handler: function() {
  116852. * var radio = Ext.getCmp('radio5');
  116853. * radio.setValue(true);
  116854. * }
  116855. * },
  116856. * {
  116857. * text: 'Black',
  116858. * handler: function() {
  116859. * var radio = Ext.getCmp('radio6');
  116860. * radio.setValue(true);
  116861. * }
  116862. * }
  116863. * ]
  116864. * }
  116865. * }
  116866. * ]
  116867. * });
  116868. */
  116869. Ext.define('Ext.form.field.Radio', {
  116870. extend:'Ext.form.field.Checkbox',
  116871. alias: ['widget.radiofield', 'widget.radio'],
  116872. alternateClassName: 'Ext.form.Radio',
  116873. requires: ['Ext.form.RadioManager'],
  116874. /**
  116875. * @property {Boolean} isRadio
  116876. * `true` in this class to identify an object as an instantiated Radio, or subclass thereof.
  116877. */
  116878. isRadio: true,
  116879. /**
  116880. * @cfg {String} uncheckedValue
  116881. * @private
  116882. */
  116883. // private
  116884. inputType: 'radio',
  116885. ariaRole: 'radio',
  116886. formId: null,
  116887. /**
  116888. * If this radio is part of a group, it will return the selected value
  116889. * @return {String}
  116890. */
  116891. getGroupValue: function() {
  116892. var selected = this.getManager().getChecked(this.name, this.getFormId());
  116893. return selected ? selected.inputValue : null;
  116894. },
  116895. /**
  116896. * @private Handle click on the radio button
  116897. */
  116898. onBoxClick: function(e) {
  116899. var me = this;
  116900. if (!me.disabled && !me.readOnly) {
  116901. this.setValue(true);
  116902. }
  116903. },
  116904. onRemoved: function(){
  116905. this.callParent(arguments);
  116906. this.formId = null;
  116907. },
  116908. /**
  116909. * Sets either the checked/unchecked status of this Radio, or, if a string value is passed, checks a sibling Radio
  116910. * of the same name whose value is the value specified.
  116911. * @param {String/Boolean} value Checked value, or the value of the sibling radio button to check.
  116912. * @return {Ext.form.field.Radio} this
  116913. */
  116914. setValue: function(v) {
  116915. var me = this,
  116916. active;
  116917. if (Ext.isBoolean(v)) {
  116918. me.callParent(arguments);
  116919. } else {
  116920. active = me.getManager().getWithValue(me.name, v, me.getFormId()).getAt(0);
  116921. if (active) {
  116922. active.setValue(true);
  116923. }
  116924. }
  116925. return me;
  116926. },
  116927. /**
  116928. * Returns the submit value for the checkbox which can be used when submitting forms.
  116929. * @return {Boolean/Object} True if checked, null if not.
  116930. */
  116931. getSubmitValue: function() {
  116932. return this.checked ? this.inputValue : null;
  116933. },
  116934. getModelData: function() {
  116935. return this.getSubmitData();
  116936. },
  116937. // inherit docs
  116938. onChange: function(newVal, oldVal) {
  116939. var me = this,
  116940. r, rLen, radio, radios;
  116941. me.callParent(arguments);
  116942. if (newVal) {
  116943. radios = me.getManager().getByName(me.name, me.getFormId()).items;
  116944. rLen = radios.length;
  116945. for (r = 0; r < rLen; r++) {
  116946. radio = radios[r];
  116947. if (radio !== me) {
  116948. radio.setValue(false);
  116949. }
  116950. }
  116951. }
  116952. },
  116953. // inherit docs
  116954. getManager: function() {
  116955. return Ext.form.RadioManager;
  116956. }
  116957. });
  116958. /**
  116959. * A time picker which provides a list of times from which to choose. This is used by the Ext.form.field.Time
  116960. * class to allow browsing and selection of valid times, but could also be used with other components.
  116961. *
  116962. * By default, all times starting at midnight and incrementing every 15 minutes will be presented. This list of
  116963. * available times can be controlled using the {@link #minValue}, {@link #maxValue}, and {@link #increment}
  116964. * configuration properties. The format of the times presented in the list can be customized with the {@link #format}
  116965. * config.
  116966. *
  116967. * To handle when the user selects a time from the list, you can subscribe to the {@link #selectionchange} event.
  116968. *
  116969. * @example
  116970. * Ext.create('Ext.picker.Time', {
  116971. * width: 60,
  116972. * minValue: Ext.Date.parse('04:30:00 AM', 'h:i:s A'),
  116973. * maxValue: Ext.Date.parse('08:00:00 AM', 'h:i:s A'),
  116974. * renderTo: Ext.getBody()
  116975. * });
  116976. */
  116977. Ext.define('Ext.picker.Time', {
  116978. extend: 'Ext.view.BoundList',
  116979. alias: 'widget.timepicker',
  116980. requires: ['Ext.data.Store', 'Ext.Date'],
  116981. /**
  116982. * @cfg {Date} minValue
  116983. * The minimum time to be shown in the list of times. This must be a Date object (only the time fields will be
  116984. * used); no parsing of String values will be done.
  116985. */
  116986. /**
  116987. * @cfg {Date} maxValue
  116988. * The maximum time to be shown in the list of times. This must be a Date object (only the time fields will be
  116989. * used); no parsing of String values will be done.
  116990. */
  116991. /**
  116992. * @cfg {Number} increment
  116993. * The number of minutes between each time value in the list.
  116994. */
  116995. increment: 15,
  116996. //<locale>
  116997. /**
  116998. * @cfg {String} [format=undefined]
  116999. * The default time format string which can be overriden for localization support. The format must be valid
  117000. * according to {@link Ext.Date#parse}.
  117001. *
  117002. * Defaults to `'g:i A'`, e.g., `'3:15 PM'`. For 24-hour time format try `'H:i'` instead.
  117003. */
  117004. format : "g:i A",
  117005. //</locale>
  117006. /**
  117007. * @private
  117008. * The field in the implicitly-generated Model objects that gets displayed in the list. This is
  117009. * an internal field name only and is not useful to change via config.
  117010. */
  117011. displayField: 'disp',
  117012. /**
  117013. * @private
  117014. * Year, month, and day that all times will be normalized into internally.
  117015. */
  117016. initDate: [2008,0,1],
  117017. componentCls: Ext.baseCSSPrefix + 'timepicker',
  117018. /**
  117019. * @cfg
  117020. * @private
  117021. */
  117022. loadMask: false,
  117023. initComponent: function() {
  117024. var me = this,
  117025. dateUtil = Ext.Date,
  117026. clearTime = dateUtil.clearTime,
  117027. initDate = me.initDate;
  117028. // Set up absolute min and max for the entire day
  117029. me.absMin = clearTime(new Date(initDate[0], initDate[1], initDate[2]));
  117030. me.absMax = dateUtil.add(clearTime(new Date(initDate[0], initDate[1], initDate[2])), 'mi', (24 * 60) - 1);
  117031. me.store = me.createStore();
  117032. me.updateList();
  117033. me.callParent();
  117034. },
  117035. /**
  117036. * Set the {@link #minValue} and update the list of available times. This must be a Date object (only the time
  117037. * fields will be used); no parsing of String values will be done.
  117038. * @param {Date} value
  117039. */
  117040. setMinValue: function(value) {
  117041. this.minValue = value;
  117042. this.updateList();
  117043. },
  117044. /**
  117045. * Set the {@link #maxValue} and update the list of available times. This must be a Date object (only the time
  117046. * fields will be used); no parsing of String values will be done.
  117047. * @param {Date} value
  117048. */
  117049. setMaxValue: function(value) {
  117050. this.maxValue = value;
  117051. this.updateList();
  117052. },
  117053. /**
  117054. * @private
  117055. * Sets the year/month/day of the given Date object to the {@link #initDate}, so that only
  117056. * the time fields are significant. This makes values suitable for time comparison.
  117057. * @param {Date} date
  117058. */
  117059. normalizeDate: function(date) {
  117060. var initDate = this.initDate;
  117061. date.setFullYear(initDate[0], initDate[1], initDate[2]);
  117062. return date;
  117063. },
  117064. /**
  117065. * Update the list of available times in the list to be constrained within the {@link #minValue}
  117066. * and {@link #maxValue}.
  117067. */
  117068. updateList: function() {
  117069. var me = this,
  117070. min = me.normalizeDate(me.minValue || me.absMin),
  117071. max = me.normalizeDate(me.maxValue || me.absMax);
  117072. me.store.filterBy(function(record) {
  117073. var date = record.get('date');
  117074. return date >= min && date <= max;
  117075. });
  117076. },
  117077. /**
  117078. * @private
  117079. * Creates the internal {@link Ext.data.Store} that contains the available times. The store
  117080. * is loaded with all possible times, and it is later filtered to hide those times outside
  117081. * the minValue/maxValue.
  117082. */
  117083. createStore: function() {
  117084. var me = this,
  117085. utilDate = Ext.Date,
  117086. times = [],
  117087. min = me.absMin,
  117088. max = me.absMax;
  117089. while(min <= max){
  117090. times.push({
  117091. disp: utilDate.dateFormat(min, me.format),
  117092. date: min
  117093. });
  117094. min = utilDate.add(min, 'mi', me.increment);
  117095. }
  117096. return new Ext.data.Store({
  117097. fields: ['disp', 'date'],
  117098. data: times
  117099. });
  117100. }
  117101. });
  117102. /**
  117103. * Provides a time input field with a time dropdown and automatic time validation.
  117104. *
  117105. * This field recognizes and uses JavaScript Date objects as its main {@link #value} type (only the time portion of the
  117106. * date is used; the month/day/year are ignored). In addition, it recognizes string values which are parsed according to
  117107. * the {@link #format} and/or {@link #altFormats} configs. These may be reconfigured to use time formats appropriate for
  117108. * the user's locale.
  117109. *
  117110. * The field may be limited to a certain range of times by using the {@link #minValue} and {@link #maxValue} configs,
  117111. * and the interval between time options in the dropdown can be changed with the {@link #increment} config.
  117112. *
  117113. * Example usage:
  117114. *
  117115. * @example
  117116. * Ext.create('Ext.form.Panel', {
  117117. * title: 'Time Card',
  117118. * width: 300,
  117119. * bodyPadding: 10,
  117120. * renderTo: Ext.getBody(),
  117121. * items: [{
  117122. * xtype: 'timefield',
  117123. * name: 'in',
  117124. * fieldLabel: 'Time In',
  117125. * minValue: '6:00 AM',
  117126. * maxValue: '8:00 PM',
  117127. * increment: 30,
  117128. * anchor: '100%'
  117129. * }, {
  117130. * xtype: 'timefield',
  117131. * name: 'out',
  117132. * fieldLabel: 'Time Out',
  117133. * minValue: '6:00 AM',
  117134. * maxValue: '8:00 PM',
  117135. * increment: 30,
  117136. * anchor: '100%'
  117137. * }]
  117138. * });
  117139. */
  117140. Ext.define('Ext.form.field.Time', {
  117141. extend:'Ext.form.field.ComboBox',
  117142. alias: 'widget.timefield',
  117143. requires: ['Ext.form.field.Date', 'Ext.picker.Time', 'Ext.view.BoundListKeyNav', 'Ext.Date'],
  117144. alternateClassName: ['Ext.form.TimeField', 'Ext.form.Time'],
  117145. /**
  117146. * @cfg {String} [triggerCls='x-form-time-trigger']
  117147. * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
  117148. * by default and triggerCls will be **appended** if specified.
  117149. */
  117150. triggerCls: Ext.baseCSSPrefix + 'form-time-trigger',
  117151. /**
  117152. * @cfg {Date/String} minValue
  117153. * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
  117154. * valid format -- see {@link #format} and {@link #altFormats}.
  117155. */
  117156. /**
  117157. * @cfg {Date/String} maxValue
  117158. * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
  117159. * valid format -- see {@link #format} and {@link #altFormats}.
  117160. */
  117161. //<locale>
  117162. /**
  117163. * @cfg {String} minText
  117164. * The error text to display when the entered time is before {@link #minValue}.
  117165. */
  117166. minText : "The time in this field must be equal to or after {0}",
  117167. //</locale>
  117168. //<locale>
  117169. /**
  117170. * @cfg {String} maxText
  117171. * The error text to display when the entered time is after {@link #maxValue}.
  117172. */
  117173. maxText : "The time in this field must be equal to or before {0}",
  117174. //</locale>
  117175. //<locale>
  117176. /**
  117177. * @cfg {String} invalidText
  117178. * The error text to display when the time in the field is invalid.
  117179. */
  117180. invalidText : "{0} is not a valid time",
  117181. //</locale>
  117182. //<locale>
  117183. /**
  117184. * @cfg {String} [format=undefined]
  117185. * The default time format string which can be overriden for localization support. The format must be valid
  117186. * according to {@link Ext.Date#parse}.
  117187. *
  117188. * Defaults to `'g:i A'`, e.g., `'3:15 PM'`. For 24-hour time format try `'H:i'` instead.
  117189. */
  117190. format : "g:i A",
  117191. //</locale>
  117192. //<locale>
  117193. /**
  117194. * @cfg {String} [submitFormat=undefined]
  117195. * The date format string which will be submitted to the server. The format must be valid according to
  117196. * {@link Ext.Date#parse}.
  117197. *
  117198. * Defaults to {@link #format}.
  117199. */
  117200. //</locale>
  117201. //<locale>
  117202. /**
  117203. * @cfg {String} altFormats
  117204. * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
  117205. * format.
  117206. */
  117207. altFormats : "g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H|gi a|hi a|giA|hiA|gi A|hi A",
  117208. //</locale>
  117209. /**
  117210. * @cfg {Number} increment
  117211. * The number of minutes between each time value in the list.
  117212. */
  117213. increment: 15,
  117214. /**
  117215. * @cfg {Number} pickerMaxHeight
  117216. * The maximum height of the {@link Ext.picker.Time} dropdown.
  117217. */
  117218. pickerMaxHeight: 300,
  117219. /**
  117220. * @cfg {Boolean} selectOnTab
  117221. * Whether the Tab key should select the currently highlighted item.
  117222. */
  117223. selectOnTab: true,
  117224. /**
  117225. * @cfg {Boolean} [snapToIncrement=false]
  117226. * Specify as `true` to enforce that only values on the {@link #increment} boundary are accepted.
  117227. */
  117228. snapToIncrement: false,
  117229. /**
  117230. * @private
  117231. * This is the date to use when generating time values in the absence of either minValue
  117232. * or maxValue. Using the current date causes DST issues on DST boundary dates, so this is an
  117233. * arbitrary "safe" date that can be any date aside from DST boundary dates.
  117234. */
  117235. initDate: '1/1/2008',
  117236. initDateFormat: 'j/n/Y',
  117237. ignoreSelection: 0,
  117238. queryMode: 'local',
  117239. displayField: 'disp',
  117240. valueField: 'date',
  117241. initComponent: function() {
  117242. var me = this,
  117243. min = me.minValue,
  117244. max = me.maxValue;
  117245. if (min) {
  117246. me.setMinValue(min);
  117247. }
  117248. if (max) {
  117249. me.setMaxValue(max);
  117250. }
  117251. me.displayTpl = new Ext.XTemplate(
  117252. '<tpl for=".">' +
  117253. '{[typeof values === "string" ? values : this.formatDate(values["' + me.displayField + '"])]}' +
  117254. '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
  117255. '</tpl>', {
  117256. formatDate: Ext.Function.bind(me.formatDate, me)
  117257. });
  117258. this.callParent();
  117259. },
  117260. /**
  117261. * @private
  117262. */
  117263. transformOriginalValue: function(value) {
  117264. if (Ext.isString(value)) {
  117265. return this.rawToValue(value);
  117266. }
  117267. return value;
  117268. },
  117269. /**
  117270. * @private
  117271. */
  117272. isEqual: function(v1, v2) {
  117273. return Ext.Date.isEqual(v1, v2);
  117274. },
  117275. /**
  117276. * Replaces any existing {@link #minValue} with the new time and refreshes the picker's range.
  117277. * @param {Date/String} value The minimum time that can be selected
  117278. */
  117279. setMinValue: function(value) {
  117280. var me = this,
  117281. picker = me.picker;
  117282. me.setLimit(value, true);
  117283. if (picker) {
  117284. picker.setMinValue(me.minValue);
  117285. }
  117286. },
  117287. /**
  117288. * Replaces any existing {@link #maxValue} with the new time and refreshes the picker's range.
  117289. * @param {Date/String} value The maximum time that can be selected
  117290. */
  117291. setMaxValue: function(value) {
  117292. var me = this,
  117293. picker = me.picker;
  117294. me.setLimit(value, false);
  117295. if (picker) {
  117296. picker.setMaxValue(me.maxValue);
  117297. }
  117298. },
  117299. /**
  117300. * @private
  117301. * Updates either the min or max value. Converts the user's value into a Date object whose
  117302. * year/month/day is set to the {@link #initDate} so that only the time fields are significant.
  117303. */
  117304. setLimit: function(value, isMin) {
  117305. var me = this,
  117306. d, val;
  117307. if (Ext.isString(value)) {
  117308. d = me.parseDate(value);
  117309. }
  117310. else if (Ext.isDate(value)) {
  117311. d = value;
  117312. }
  117313. if (d) {
  117314. val = Ext.Date.clearTime(new Date(me.initDate));
  117315. val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
  117316. }
  117317. // Invalid min/maxValue config should result in a null so that defaulting takes over
  117318. else {
  117319. val = null;
  117320. }
  117321. me[isMin ? 'minValue' : 'maxValue'] = val;
  117322. },
  117323. rawToValue: function(rawValue) {
  117324. return this.parseDate(rawValue) || rawValue || null;
  117325. },
  117326. valueToRaw: function(value) {
  117327. return this.formatDate(this.parseDate(value));
  117328. },
  117329. /**
  117330. * Runs all of Time's validations and returns an array of any errors. Note that this first runs Text's validations,
  117331. * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
  117332. * the time format is valid, that the chosen time is within the {@link #minValue} and {@link #maxValue} constraints
  117333. * set.
  117334. * @param {Object} [value] The value to get errors for (defaults to the current field value)
  117335. * @return {String[]} All validation errors for this field
  117336. */
  117337. getErrors: function(value) {
  117338. var me = this,
  117339. format = Ext.String.format,
  117340. errors = me.callParent(arguments),
  117341. minValue = me.minValue,
  117342. maxValue = me.maxValue,
  117343. date;
  117344. value = me.formatDate(value || me.processRawValue(me.getRawValue()));
  117345. if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
  117346. return errors;
  117347. }
  117348. date = me.parseDate(value);
  117349. if (!date) {
  117350. errors.push(format(me.invalidText, value, Ext.Date.unescapeFormat(me.format)));
  117351. return errors;
  117352. }
  117353. if (minValue && date < minValue) {
  117354. errors.push(format(me.minText, me.formatDate(minValue)));
  117355. }
  117356. if (maxValue && date > maxValue) {
  117357. errors.push(format(me.maxText, me.formatDate(maxValue)));
  117358. }
  117359. return errors;
  117360. },
  117361. formatDate: function() {
  117362. return Ext.form.field.Date.prototype.formatDate.apply(this, arguments);
  117363. },
  117364. /**
  117365. * @private
  117366. * Parses an input value into a valid Date object.
  117367. * @param {String/Date} value
  117368. */
  117369. parseDate: function(value) {
  117370. var me = this,
  117371. val = value,
  117372. altFormats = me.altFormats,
  117373. altFormatsArray = me.altFormatsArray,
  117374. i = 0,
  117375. len;
  117376. if (value && !Ext.isDate(value)) {
  117377. val = me.safeParse(value, me.format);
  117378. if (!val && altFormats) {
  117379. altFormatsArray = altFormatsArray || altFormats.split('|');
  117380. len = altFormatsArray.length;
  117381. for (; i < len && !val; ++i) {
  117382. val = me.safeParse(value, altFormatsArray[i]);
  117383. }
  117384. }
  117385. }
  117386. // If configured to snap, snap resulting parsed Date to the closest increment.
  117387. if (val && me.snapToIncrement) {
  117388. val = new Date(Ext.Number.snap(val.getTime(), me.increment * 60 * 1000));
  117389. }
  117390. return val;
  117391. },
  117392. safeParse: function(value, format){
  117393. var me = this,
  117394. utilDate = Ext.Date,
  117395. parsedDate,
  117396. result = null;
  117397. if (utilDate.formatContainsDateInfo(format)) {
  117398. // assume we've been given a full date
  117399. result = utilDate.parse(value, format);
  117400. } else {
  117401. // Use our initial safe date
  117402. parsedDate = utilDate.parse(me.initDate + ' ' + value, me.initDateFormat + ' ' + format);
  117403. if (parsedDate) {
  117404. result = parsedDate;
  117405. }
  117406. }
  117407. return result;
  117408. },
  117409. // @private
  117410. getSubmitValue: function() {
  117411. var me = this,
  117412. format = me.submitFormat || me.format,
  117413. value = me.getValue();
  117414. return value ? Ext.Date.format(value, format) : null;
  117415. },
  117416. /**
  117417. * @private
  117418. * Creates the {@link Ext.picker.Time}
  117419. */
  117420. createPicker: function() {
  117421. var me = this,
  117422. picker;
  117423. me.listConfig = Ext.apply({
  117424. xtype: 'timepicker',
  117425. selModel: {
  117426. mode: 'SINGLE'
  117427. },
  117428. cls: undefined,
  117429. minValue: me.minValue,
  117430. maxValue: me.maxValue,
  117431. increment: me.increment,
  117432. format: me.format,
  117433. maxHeight: me.pickerMaxHeight
  117434. }, me.listConfig);
  117435. picker = me.callParent();
  117436. me.store = picker.store;
  117437. return picker;
  117438. },
  117439. onItemClick: function(picker, record){
  117440. // The selection change events won't fire when clicking on the selected element. Detect it here.
  117441. var me = this,
  117442. selected = picker.getSelectionModel().getSelection();
  117443. if (selected.length > 0) {
  117444. selected = selected[0];
  117445. if (selected && Ext.Date.isEqual(record.get('date'), selected.get('date'))) {
  117446. me.collapse();
  117447. }
  117448. }
  117449. },
  117450. /**
  117451. * @private
  117452. * Handles a time being selected from the Time picker.
  117453. */
  117454. onListSelectionChange: function(list, recordArray) {
  117455. var me = this,
  117456. record = recordArray[0],
  117457. val = record ? record.get('date') : null;
  117458. if (!me.ignoreSelection) {
  117459. me.skipSync = true;
  117460. me.setValue(val);
  117461. me.skipSync = false;
  117462. me.fireEvent('select', me, val);
  117463. me.picker.clearHighlight();
  117464. me.collapse();
  117465. me.inputEl.focus();
  117466. }
  117467. },
  117468. /**
  117469. * @private
  117470. * Synchronizes the selection in the picker to match the current value
  117471. */
  117472. syncSelection: function() {
  117473. var me = this,
  117474. picker = me.picker,
  117475. toSelect,
  117476. selModel,
  117477. value,
  117478. data, d, dLen, rec;
  117479. if (picker && !me.skipSync) {
  117480. picker.clearHighlight();
  117481. value = me.getValue();
  117482. selModel = picker.getSelectionModel();
  117483. // Update the selection to match
  117484. me.ignoreSelection++;
  117485. if (value === null) {
  117486. selModel.deselectAll();
  117487. } else if(Ext.isDate(value)) {
  117488. // find value, select it
  117489. data = picker.store.data.items;
  117490. dLen = data.length;
  117491. for (d = 0; d < dLen; d++) {
  117492. rec = data[d];
  117493. if (Ext.Date.isEqual(rec.get('date'), value)) {
  117494. toSelect = rec;
  117495. break;
  117496. }
  117497. }
  117498. selModel.select(toSelect);
  117499. }
  117500. me.ignoreSelection--;
  117501. }
  117502. },
  117503. postBlur: function() {
  117504. var me = this;
  117505. me.callParent(arguments);
  117506. me.setRawValue(me.formatDate(me.getValue()));
  117507. },
  117508. setValue: function() {
  117509. // Store MUST be created for parent setValue to function
  117510. this.getPicker();
  117511. this.callParent(arguments);
  117512. },
  117513. getValue: function() {
  117514. return this.parseDate(this.callParent(arguments));
  117515. }
  117516. });
  117517. /**
  117518. * Internal utility class that provides default configuration for cell editing.
  117519. * @private
  117520. */
  117521. Ext.define('Ext.grid.CellEditor', {
  117522. extend: 'Ext.Editor',
  117523. constructor: function(config) {
  117524. config = Ext.apply({}, config);
  117525. if (config.field) {
  117526. config.field.monitorTab = false;
  117527. }
  117528. this.callParent([config]);
  117529. },
  117530. /**
  117531. * @private
  117532. * Hide the grid cell text when editor is shown.
  117533. *
  117534. * There are 2 reasons this needs to happen:
  117535. *
  117536. * 1. checkbox editor does not take up enough space to hide the underlying text.
  117537. *
  117538. * 2. When columnLines are turned off in browsers that don't support text-overflow:
  117539. * ellipsis (firefox 6 and below and IE quirks), the text extends to the last pixel
  117540. * in the cell, however the right border of the cell editor is always positioned 1px
  117541. * offset from the edge of the cell (to give it the appearance of being "inside" the
  117542. * cell. This results in 1px of the underlying cell text being visible to the right
  117543. * of the cell editor if the text is not hidden.
  117544. *
  117545. * We can't just hide the entire cell, because then treecolumn's icons would be hidden
  117546. * as well. We also can't just set "color: transparent" to hide the text because it is
  117547. * not supported by IE8 and below. The only remaining solution is to remove the text
  117548. * from the text node and then add it back when the editor is hidden.
  117549. */
  117550. onShow: function() {
  117551. var me = this,
  117552. innerCell = me.boundEl.first(),
  117553. lastChild,
  117554. textNode;
  117555. if (innerCell) {
  117556. lastChild = innerCell.dom.lastChild;
  117557. if(lastChild && lastChild.nodeType === 3) {
  117558. // if the cell has a text node, save a reference to it
  117559. textNode = me.cellTextNode = innerCell.dom.lastChild;
  117560. // save the cell text so we can add it back when we're done editing
  117561. me.cellTextValue = textNode.nodeValue;
  117562. // The text node has to have at least one character in it, or the cell borders
  117563. // in IE quirks mode will not show correctly, so let's use a non-breaking space.
  117564. textNode.nodeValue = '\u00a0';
  117565. }
  117566. }
  117567. me.callParent(arguments);
  117568. },
  117569. /**
  117570. * @private
  117571. * Show the grid cell text when the editor is hidden by adding the text back to the text node
  117572. */
  117573. onHide: function() {
  117574. var me = this,
  117575. innerCell = me.boundEl.first();
  117576. if (innerCell && me.cellTextNode) {
  117577. me.cellTextNode.nodeValue = me.cellTextValue;
  117578. delete me.cellTextNode;
  117579. delete me.cellTextValue;
  117580. }
  117581. me.callParent(arguments);
  117582. },
  117583. /**
  117584. * @private
  117585. * Fix checkbox blur when it is clicked.
  117586. */
  117587. afterRender: function() {
  117588. var me = this,
  117589. field = me.field;
  117590. me.callParent(arguments);
  117591. if (field.isXType('checkboxfield')) {
  117592. field.mon(field.inputEl, {
  117593. mousedown: me.onCheckBoxMouseDown,
  117594. click: me.onCheckBoxClick,
  117595. scope: me
  117596. });
  117597. }
  117598. },
  117599. /**
  117600. * @private
  117601. * Because when checkbox is clicked it loses focus completeEdit is bypassed.
  117602. */
  117603. onCheckBoxMouseDown: function() {
  117604. this.completeEdit = Ext.emptyFn;
  117605. },
  117606. /**
  117607. * @private
  117608. * Restore checkbox focus and completeEdit method.
  117609. */
  117610. onCheckBoxClick: function() {
  117611. delete this.completeEdit;
  117612. this.field.focus(false, 10);
  117613. },
  117614. /**
  117615. * @private
  117616. * Realigns the Editor to the grid cell, or to the text node in the grid inner cell
  117617. * if the inner cell contains multiple child nodes.
  117618. */
  117619. realign: function(autoSize) {
  117620. var me = this,
  117621. boundEl = me.boundEl,
  117622. innerCell = boundEl.first(),
  117623. children = innerCell.dom.childNodes,
  117624. childCount = children.length,
  117625. offsets = Ext.Array.clone(me.offsets),
  117626. inputEl = me.field.inputEl,
  117627. lastChild, leftBound, rightBound, width;
  117628. // If the inner cell has more than one child, or the first child node is not a text node,
  117629. // let's assume this cell contains additional elements before the text node.
  117630. // This is the case for tree cells, but could also be used to accomodate grid cells that
  117631. // have a custom renderer that render, say, an icon followed by some text for example
  117632. // For now however, this support will only be used for trees.
  117633. if(me.isForTree && (childCount > 1 || (childCount === 1 && children[0].nodeType !== 3))) {
  117634. // get the inner cell's last child
  117635. lastChild = innerCell.last();
  117636. // calculate the left bound of the text node
  117637. leftBound = lastChild.getOffsetsTo(innerCell)[0] + lastChild.getWidth();
  117638. // calculate the right bound of the text node (this is assumed to be the right edge of
  117639. // the inner cell, since we are assuming the text node is always the last node in the
  117640. // inner cell)
  117641. rightBound = innerCell.getWidth();
  117642. // difference between right and left bound is the text node's allowed "width",
  117643. // this will be used as the width for the editor.
  117644. width = rightBound - leftBound;
  117645. // adjust width for column lines - this ensures the editor will be the same width
  117646. // regardless of columLines config
  117647. if(!me.editingPlugin.grid.columnLines) {
  117648. width --;
  117649. }
  117650. // set the editor's x offset to the left bound position
  117651. offsets[0] += leftBound;
  117652. me.addCls(Ext.baseCSSPrefix + 'grid-editor-on-text-node');
  117653. } else {
  117654. width = boundEl.getWidth() - 1;
  117655. }
  117656. if (autoSize === true) {
  117657. me.field.setWidth(width);
  117658. }
  117659. me.alignTo(boundEl, me.alignment, offsets);
  117660. },
  117661. onEditorTab: function(e){
  117662. var field = this.field;
  117663. if (field.onEditorTab) {
  117664. field.onEditorTab(e);
  117665. }
  117666. },
  117667. alignment: "tl-tl",
  117668. hideEl : false,
  117669. cls: Ext.baseCSSPrefix + "small-editor " + Ext.baseCSSPrefix + "grid-editor",
  117670. shim: false,
  117671. shadow: false
  117672. });
  117673. /**
  117674. * Component layout for grid column headers which have a title element at the top followed by content.
  117675. * @private
  117676. */
  117677. Ext.define('Ext.grid.ColumnComponentLayout', {
  117678. extend: 'Ext.layout.component.Auto',
  117679. alias: 'layout.columncomponent',
  117680. type: 'columncomponent',
  117681. setWidthInDom: true,
  117682. getContentHeight : function(ownerContext) {
  117683. // If we are a group header return container layout's contentHeight, else default to AutoComponent's answer
  117684. return this.owner.isGroupHeader ? ownerContext.getProp('contentHeight') : this.callParent(arguments);
  117685. },
  117686. calculateOwnerHeightFromContentHeight: function (ownerContext, contentHeight) {
  117687. var result = this.callParent(arguments);
  117688. if (this.owner.isGroupHeader) {
  117689. result += this.owner.titleEl.dom.offsetHeight;
  117690. }
  117691. return result;
  117692. },
  117693. getContentWidth : function(ownerContext) {
  117694. // If we are a group header return container layout's contentHeight, else default to AutoComponent's answer
  117695. return this.owner.isGroupHeader ? ownerContext.getProp('contentWidth') : this.callParent(arguments);
  117696. },
  117697. calculateOwnerWidthFromContentWidth: function (ownerContext, contentWidth) {
  117698. return contentWidth + ownerContext.getPaddingInfo().width;
  117699. }
  117700. });
  117701. /**
  117702. * @private
  117703. *
  117704. * This class is used only by the grid's HeaderContainer docked child.
  117705. *
  117706. * It adds the ability to shrink the vertical size of the inner container element back if a grouped
  117707. * column header has all its child columns dragged out, and the whole HeaderContainer needs to shrink back down.
  117708. *
  117709. * Also, after every layout, after all headers have attained their 'stretchmax' height, it goes through and calls
  117710. * `setPadding` on the columns so that they lay out correctly.
  117711. */
  117712. Ext.define('Ext.grid.ColumnLayout', {
  117713. extend: 'Ext.layout.container.HBox',
  117714. alias: 'layout.gridcolumn',
  117715. type : 'gridcolumn',
  117716. reserveOffset: false,
  117717. firstHeaderCls: Ext.baseCSSPrefix + 'column-header-first',
  117718. lastHeaderCls: Ext.baseCSSPrefix + 'column-header-last',
  117719. initLayout: function() {
  117720. this.grid = this.owner.up('[scrollerOwner]');
  117721. this.callParent();
  117722. },
  117723. // Collect the height of the table of data upon layout begin
  117724. beginLayout: function (ownerContext) {
  117725. var me = this,
  117726. grid = me.grid,
  117727. view = grid.view,
  117728. i = 0,
  117729. items = me.getVisibleItems(),
  117730. len = items.length,
  117731. item;
  117732. ownerContext.gridContext = ownerContext.context.getCmp(me.grid);
  117733. // If we are one side of a locking grid, then if we are on the "normal" side, we have to grab the normal view
  117734. // for use in determining whether to subtract scrollbar width from available width.
  117735. // The locked side does not have scrollbars, so it should not look at the view.
  117736. if (grid.lockable) {
  117737. if (me.owner.up('tablepanel') === view.normalGrid) {
  117738. view = view.normalGrid.getView();
  117739. } else {
  117740. view = null;
  117741. }
  117742. }
  117743. me.callParent(arguments);
  117744. // Unstretch child items before the layout which stretches them.
  117745. for (; i < len; i++) {
  117746. item = items[i];
  117747. item.removeCls([me.firstHeaderCls, me.lastHeaderCls]);
  117748. item.el.setStyle({
  117749. height: 'auto'
  117750. });
  117751. item.titleEl.setStyle({
  117752. height: 'auto',
  117753. paddingTop: '' // reset back to default padding of the style
  117754. });
  117755. }
  117756. // Add special first/last classes
  117757. if (len > 0) {
  117758. items[0].addCls(me.firstHeaderCls);
  117759. items[len - 1].addCls(me.lastHeaderCls);
  117760. }
  117761. // If the owner is the grid's HeaderContainer, and the UI displays old fashioned scrollbars and there is a rendered View with data in it,
  117762. // AND we are scrolling vertically:
  117763. // collect the View context to interrogate it for overflow, and possibly invalidate it if there is overflow
  117764. if (!me.owner.isHeader && Ext.getScrollbarSize().width && !grid.collapsed && view &&
  117765. view.table.dom && (view.autoScroll || view.overflowY)) {
  117766. ownerContext.viewContext = ownerContext.context.getCmp(view);
  117767. }
  117768. },
  117769. roundFlex: function(width) {
  117770. return Math.floor(width);
  117771. },
  117772. calculate: function(ownerContext) {
  117773. var me = this,
  117774. viewContext = ownerContext.viewContext,
  117775. tableHeight,
  117776. viewHeight;
  117777. me.callParent(arguments);
  117778. if (ownerContext.state.parallelDone) {
  117779. ownerContext.setProp('columnWidthsDone', true);
  117780. }
  117781. // If we have a viewContext (Only created if there is an existing <table> within the view, AND we are scolling vertically AND scrollbars take up space)
  117782. // we are not already in the second pass, and we are not shrinkWrapping...
  117783. // Then we have to see if we know enough to determine whether there is vertical opverflow so that we can
  117784. // invalidate and loop back for the second pass with a narrower target width.
  117785. if (viewContext && !ownerContext.state.overflowAdjust.width && !ownerContext.gridContext.heightModel.shrinkWrap) {
  117786. tableHeight = viewContext.tableContext.getProp('height');
  117787. viewHeight = viewContext.getProp('height');
  117788. // Heights of both view and its table content have not both been published; we cannot complete
  117789. if (isNaN(tableHeight + viewHeight)) {
  117790. me.done = false;
  117791. }
  117792. // Heights have been published, and there is vertical overflow; invalidate with a width adjustment to allow for the scrollbar
  117793. else if (tableHeight >= viewHeight) {
  117794. ownerContext.gridContext.invalidate({
  117795. after: function() {
  117796. ownerContext.state.overflowAdjust = {
  117797. width: Ext.getScrollbarSize().width,
  117798. height: 0
  117799. };
  117800. }
  117801. });
  117802. }
  117803. }
  117804. },
  117805. completeLayout: function(ownerContext) {
  117806. var me = this,
  117807. owner = me.owner,
  117808. state = ownerContext.state,
  117809. needsInvalidate = false,
  117810. calculated = me.sizeModels.calculated,
  117811. childItems, len, i, childContext, item;
  117812. me.callParent(arguments);
  117813. // If we have not been through this already, and the owning Container is configured
  117814. // forceFit, is not a group column and and there is a valid width, then convert
  117815. // widths to flexes, and loop back.
  117816. if (!state.flexesCalculated && owner.forceFit && !owner.isHeader) {
  117817. childItems = ownerContext.childItems;
  117818. len = childItems.length;
  117819. for (i = 0; i < len; i++) {
  117820. childContext = childItems[i];
  117821. item = childContext.target;
  117822. // For forceFit, just use allocated width as the flex value, and the proportions
  117823. // will end up the same whatever HeaderContainer width they are being forced into.
  117824. if (item.width) {
  117825. item.flex = ownerContext.childItems[i].flex = item.width;
  117826. delete item.width;
  117827. childContext.widthModel = calculated;
  117828. needsInvalidate = true;
  117829. }
  117830. }
  117831. // Recalculate based upon all columns now being flexed instead of sized.
  117832. // Set flag, so that we do not do this infinitely
  117833. if (needsInvalidate) {
  117834. me.cacheFlexes(ownerContext);
  117835. ownerContext.invalidate({
  117836. state: {
  117837. flexesCalculated: true
  117838. }
  117839. });
  117840. }
  117841. }
  117842. },
  117843. finalizeLayout: function() {
  117844. var me = this,
  117845. i = 0,
  117846. items,
  117847. len,
  117848. itemsHeight,
  117849. owner = me.owner,
  117850. titleEl = owner.titleEl;
  117851. // Set up padding in items
  117852. items = me.getVisibleItems();
  117853. len = items.length;
  117854. // header container's items take up the whole height
  117855. itemsHeight = owner.el.getViewSize().height;
  117856. if (titleEl) {
  117857. // if owner is a grouped column with children, we need to subtract the titleEl's height
  117858. // to determine the remaining available height for the child items
  117859. itemsHeight -= titleEl.getHeight();
  117860. }
  117861. for (; i < len; i++) {
  117862. items[i].setPadding(itemsHeight);
  117863. }
  117864. },
  117865. // FIX: when flexing we actually don't have enough space as we would
  117866. // typically because of the scrollOffset on the GridView, must reserve this
  117867. publishInnerCtSize: function(ownerContext) {
  117868. var me = this,
  117869. size = ownerContext.state.boxPlan.targetSize,
  117870. cw = ownerContext.peek('contentWidth'),
  117871. view;
  117872. // InnerCt MUST stretch to accommodate all columns so that left/right scrolling is enabled in the header container.
  117873. if ((cw != null) && !me.owner.isHeader) {
  117874. size.width = cw;
  117875. // innerCt must also encompass any vertical scrollbar width if there may be one
  117876. view = me.owner.ownerCt.view;
  117877. if (view.autoScroll || view.overflowY) {
  117878. size.width += Ext.getScrollbarSize().width;
  117879. }
  117880. }
  117881. return me.callParent(arguments);
  117882. }
  117883. });
  117884. /**
  117885. * This class is used internally to provide a single interface when using
  117886. * a locking grid. Internally, the locking grid creates two separate grids,
  117887. * so this class is used to map calls appropriately.
  117888. * @private
  117889. */
  117890. Ext.define('Ext.grid.LockingView', {
  117891. mixins: {
  117892. observable: 'Ext.util.Observable'
  117893. },
  117894. eventRelayRe: /^(beforeitem|beforecontainer|item|container|cell)/,
  117895. constructor: function(config){
  117896. var me = this,
  117897. eventNames = [],
  117898. eventRe = me.eventRelayRe,
  117899. locked = config.locked.getView(),
  117900. normal = config.normal.getView(),
  117901. events,
  117902. event;
  117903. Ext.apply(me, {
  117904. lockedView: locked,
  117905. normalView: normal,
  117906. lockedGrid: config.locked,
  117907. normalGrid: config.normal,
  117908. panel: config.panel
  117909. });
  117910. me.mixins.observable.constructor.call(me, config);
  117911. // relay events
  117912. events = locked.events;
  117913. for (event in events) {
  117914. if (events.hasOwnProperty(event) && eventRe.test(event)) {
  117915. eventNames.push(event);
  117916. }
  117917. }
  117918. me.relayEvents(locked, eventNames);
  117919. me.relayEvents(normal, eventNames);
  117920. normal.on({
  117921. scope: me,
  117922. itemmouseleave: me.onItemMouseLeave,
  117923. itemmouseenter: me.onItemMouseEnter
  117924. });
  117925. locked.on({
  117926. scope: me,
  117927. itemmouseleave: me.onItemMouseLeave,
  117928. itemmouseenter: me.onItemMouseEnter
  117929. });
  117930. },
  117931. getGridColumns: function() {
  117932. var cols = this.lockedGrid.headerCt.getGridColumns();
  117933. return cols.concat(this.normalGrid.headerCt.getGridColumns());
  117934. },
  117935. getEl: function(column){
  117936. return this.getViewForColumn(column).getEl();
  117937. },
  117938. getViewForColumn: function(column) {
  117939. var view = this.lockedView,
  117940. inLocked;
  117941. view.headerCt.cascade(function(col){
  117942. if (col === column) {
  117943. inLocked = true;
  117944. return false;
  117945. }
  117946. });
  117947. return inLocked ? view : this.normalView;
  117948. },
  117949. onItemMouseEnter: function(view, record){
  117950. var me = this,
  117951. locked = me.lockedView,
  117952. other = me.normalView,
  117953. item;
  117954. if (view.trackOver) {
  117955. if (view !== locked) {
  117956. other = locked;
  117957. }
  117958. item = other.getNode(record);
  117959. other.highlightItem(item);
  117960. }
  117961. },
  117962. onItemMouseLeave: function(view, record){
  117963. var me = this,
  117964. locked = me.lockedView,
  117965. other = me.normalView;
  117966. if (view.trackOver) {
  117967. if (view !== locked) {
  117968. other = locked;
  117969. }
  117970. other.clearHighlight();
  117971. }
  117972. },
  117973. relayFn: function(name, args){
  117974. args = args || [];
  117975. var view = this.lockedView;
  117976. view[name].apply(view, args || []);
  117977. view = this.normalView;
  117978. view[name].apply(view, args || []);
  117979. },
  117980. getSelectionModel: function(){
  117981. return this.panel.getSelectionModel();
  117982. },
  117983. getStore: function(){
  117984. return this.panel.store;
  117985. },
  117986. getNode: function(nodeInfo){
  117987. // default to the normal view
  117988. return this.normalView.getNode(nodeInfo);
  117989. },
  117990. getCell: function(record, column){
  117991. var view = this.getViewForColumn(column),
  117992. row;
  117993. row = view.getNode(record);
  117994. return Ext.fly(row).down(column.getCellSelector());
  117995. },
  117996. getRecord: function(node){
  117997. var result = this.lockedView.getRecord(node);
  117998. if (!node) {
  117999. result = this.normalView.getRecord(node);
  118000. }
  118001. return result;
  118002. },
  118003. addElListener: function(eventName, fn, scope){
  118004. this.relayFn('addElListener', arguments);
  118005. },
  118006. refreshNode: function(){
  118007. this.relayFn('refreshNode', arguments);
  118008. },
  118009. refresh: function(){
  118010. this.relayFn('refresh', arguments);
  118011. },
  118012. bindStore: function(){
  118013. this.relayFn('bindStore', arguments);
  118014. },
  118015. addRowCls: function(){
  118016. this.relayFn('addRowCls', arguments);
  118017. },
  118018. removeRowCls: function(){
  118019. this.relayFn('removeRowCls', arguments);
  118020. }
  118021. });
  118022. /**
  118023. * Component layout for {@link Ext.view.Table}
  118024. * @private
  118025. *
  118026. */
  118027. Ext.define('Ext.view.TableLayout', {
  118028. extend: 'Ext.layout.component.Auto',
  118029. alias: ['layout.tableview'],
  118030. type: 'tableview',
  118031. beginLayout: function(ownerContext) {
  118032. var me = this;
  118033. me.callParent(arguments);
  118034. // Grab ContextItem for the driving HeaderContainer and the table only if their is a table to size
  118035. if (me.owner.table.dom) {
  118036. ownerContext.tableContext = ownerContext.getEl(me.owner.table);
  118037. // Grab a ContextItem for the header container
  118038. ownerContext.headerContext = ownerContext.context.getCmp(me.headerCt);
  118039. }
  118040. },
  118041. calculate: function(ownerContext) {
  118042. var me = this;
  118043. me.callParent(arguments);
  118044. if (ownerContext.tableContext) {
  118045. if (ownerContext.state.columnWidthsSynced) {
  118046. if (ownerContext.hasProp('columnWidthsFlushed')) {
  118047. ownerContext.tableContext.setHeight(ownerContext.tableContext.el.dom.offsetHeight, false);
  118048. } else {
  118049. me.done = false;
  118050. }
  118051. } else {
  118052. if (ownerContext.headerContext.hasProp('columnWidthsDone')) {
  118053. ownerContext.context.queueFlush(me);
  118054. ownerContext.state.columnWidthsSynced = true;
  118055. }
  118056. // Either our base class (Auto) needs to measureContentHeight
  118057. // if we are shrinkWrapHeight OR we need to measure the table
  118058. // element height if we are not shrinkWrapHeight
  118059. me.done = false;
  118060. }
  118061. }
  118062. },
  118063. measureContentHeight: function(ownerContext) {
  118064. // Only able to produce a valid contentHeight if we have flushed all column widths to the table (or there's no table at all).
  118065. if (!ownerContext.headerContext || ownerContext.hasProp('columnWidthsFlushed')) {
  118066. return this.callParent(arguments);
  118067. }
  118068. },
  118069. flush: function() {
  118070. var me = this,
  118071. context = me.ownerContext.context,
  118072. columns = me.headerCt.getGridColumns(),
  118073. i = 0, len = columns.length,
  118074. el = me.owner.el,
  118075. tableWidth = 0,
  118076. colWidth;
  118077. // So that the setProp can trigger this layout.
  118078. context.currentLayout = me;
  118079. // Set column width corresponding to each header
  118080. for (i = 0; i < len; i++) {
  118081. colWidth = columns[i].hidden ? 0 : context.getCmp(columns[i]).props.width;
  118082. tableWidth += colWidth;
  118083. // Grab the col and set the width.
  118084. // CSS class is generated in TableChunker.
  118085. // Select composites because there may be several chunks.
  118086. el.select(me.getColumnSelector(columns[i])).setWidth(colWidth);
  118087. }
  118088. el.select('table.' + Ext.baseCSSPrefix + 'grid-table-resizer').setWidth(tableWidth);
  118089. // Now we can measure contentHeight if necessary (if we are height shrinkwrapped)
  118090. me.ownerContext.setProp('columnWidthsFlushed', true);
  118091. },
  118092. finishedLayout: function(){
  118093. var me = this,
  118094. first;
  118095. me.callParent(arguments);
  118096. // In FF, in some cases during a resize or column hide/show, the <td> cells in
  118097. // the grid won't respond to the new width set in the <th> at the top. So we
  118098. // force a reflow of the table which seems to correct it. Related to EXTJSIV-6410
  118099. if (Ext.isGecko) {
  118100. first = me.headerCt.getGridColumns()[0];
  118101. if (first) {
  118102. first = me.owner.el.down(me.getColumnSelector(first));
  118103. if (first) {
  118104. first.setStyle('display', 'none');
  118105. first.dom.scrollWidth;
  118106. first.setStyle('display', '');
  118107. }
  118108. }
  118109. }
  118110. },
  118111. getColumnSelector: function(column) {
  118112. return 'th.' + Ext.baseCSSPrefix + 'grid-col-resizer-' + column.id;
  118113. }
  118114. });
  118115. /**
  118116. * This class encapsulates the user interface for a tabular data set.
  118117. * It acts as a centralized manager for controlling the various interface
  118118. * elements of the view. This includes handling events, such as row and cell
  118119. * level based DOM events. It also reacts to events from the underlying {@link Ext.selection.Model}
  118120. * to provide visual feedback to the user.
  118121. *
  118122. * This class does not provide ways to manipulate the underlying data of the configured
  118123. * {@link Ext.data.Store}.
  118124. *
  118125. * This is the base class for both {@link Ext.grid.View} and {@link Ext.tree.View} and is not
  118126. * to be used directly.
  118127. */
  118128. Ext.define('Ext.view.Table', {
  118129. extend: 'Ext.view.View',
  118130. alias: 'widget.tableview',
  118131. uses: [
  118132. 'Ext.view.TableLayout',
  118133. 'Ext.view.TableChunker',
  118134. 'Ext.util.DelayedTask',
  118135. 'Ext.util.MixedCollection'
  118136. ],
  118137. componentLayout: 'tableview',
  118138. baseCls: Ext.baseCSSPrefix + 'grid-view',
  118139. // row
  118140. itemSelector: 'tr.' + Ext.baseCSSPrefix + 'grid-row',
  118141. // cell
  118142. cellSelector: 'td.' + Ext.baseCSSPrefix + 'grid-cell',
  118143. // keep a separate rowSelector, since we may need to select the actual row elements
  118144. rowSelector: 'tr.' + Ext.baseCSSPrefix + 'grid-row',
  118145. /**
  118146. * @cfg {String} [firstCls='x-grid-cell-first']
  118147. * A CSS class to add to the *first* cell in every row to enable special styling for the first column.
  118148. * If no styling is needed on the first column, this may be configured as `null`.
  118149. */
  118150. firstCls: Ext.baseCSSPrefix + 'grid-cell-first',
  118151. /**
  118152. * @cfg {String} [lastCls='x-grid-cell-last']
  118153. * A CSS class to add to the *last* cell in every row to enable special styling for the last column.
  118154. * If no styling is needed on the last column, this may be configured as `null`.
  118155. */
  118156. lastCls: Ext.baseCSSPrefix + 'grid-cell-last',
  118157. headerRowSelector: 'tr.' + Ext.baseCSSPrefix + 'grid-header-row',
  118158. selectedItemCls: Ext.baseCSSPrefix + 'grid-row-selected',
  118159. selectedCellCls: Ext.baseCSSPrefix + 'grid-cell-selected',
  118160. focusedItemCls: Ext.baseCSSPrefix + 'grid-row-focused',
  118161. overItemCls: Ext.baseCSSPrefix + 'grid-row-over',
  118162. altRowCls: Ext.baseCSSPrefix + 'grid-row-alt',
  118163. rowClsRe: new RegExp('(?:^|\\s*)' + Ext.baseCSSPrefix + 'grid-row-(first|last|alt)(?:\\s+|$)', 'g'),
  118164. cellRe: new RegExp(Ext.baseCSSPrefix + 'grid-cell-([^\\s]+) ', ''),
  118165. // cfg docs inherited
  118166. trackOver: true,
  118167. /**
  118168. * Override this function to apply custom CSS classes to rows during rendering. This function should return the
  118169. * CSS class name (or empty string '' for none) that will be added to the row's wrapping div. To apply multiple
  118170. * class names, simply return them space-delimited within the string (e.g. 'my-class another-class').
  118171. * Example usage:
  118172. *
  118173. * viewConfig: {
  118174. * getRowClass: function(record, rowIndex, rowParams, store){
  118175. * return record.get("valid") ? "row-valid" : "row-error";
  118176. * }
  118177. * }
  118178. *
  118179. * @param {Ext.data.Model} record The record corresponding to the current row.
  118180. * @param {Number} index The row index.
  118181. * @param {Object} rowParams **DEPRECATED.** For row body use the
  118182. * {@link Ext.grid.feature.RowBody#getAdditionalData getAdditionalData} method of the rowbody feature.
  118183. * @param {Ext.data.Store} store The store this grid is bound to
  118184. * @return {String} a CSS class name to add to the row.
  118185. * @method
  118186. */
  118187. getRowClass: null,
  118188. /**
  118189. * @cfg {Boolean} stripeRows
  118190. * True to stripe the rows.
  118191. *
  118192. * This causes the CSS class **`x-grid-row-alt`** to be added to alternate rows of
  118193. * the grid. A default CSS rule is provided which sets a background color, but you can override this
  118194. * with a rule which either overrides the **background-color** style using the `!important`
  118195. * modifier, or which uses a CSS selector of higher specificity.
  118196. */
  118197. stripeRows: true,
  118198. /**
  118199. * @cfg {Boolean} markDirty
  118200. * True to show the dirty cell indicator when a cell has been modified.
  118201. */
  118202. markDirty : true,
  118203. /**
  118204. * @cfg {Boolean} enableTextSelection
  118205. * True to enable text selections.
  118206. */
  118207. /**
  118208. * @private
  118209. * Simple initial tpl for TableView just to satisfy the validation within AbstractView.initComponent.
  118210. */
  118211. initialTpl: '<div></div>',
  118212. initComponent: function() {
  118213. var me = this,
  118214. scroll = me.scroll;
  118215. /**
  118216. * @private
  118217. * @property {Ext.dom.AbstractElement.Fly} table
  118218. * A flyweight Ext.Element which encapsulates a reference to the transient `<table>` element within this View.
  118219. * *Note that the `dom` reference will not be present until the first data refresh*
  118220. */
  118221. me.table = new Ext.dom.Element.Fly();
  118222. me.table.id = me.id + 'gridTable';
  118223. // Scrolling within a TableView is controlled by the scroll config of its owning GridPanel
  118224. // It must see undefined in this property in order to leave the scroll styles alone at afterRender time
  118225. me.autoScroll = undefined;
  118226. // Convert grid scroll config to standard Component scrolling configurations.
  118227. if (scroll === true || scroll === 'both') {
  118228. me.autoScroll = true;
  118229. } else if (scroll === 'horizontal') {
  118230. me.overflowX = 'auto';
  118231. } else if (scroll === 'vertical') {
  118232. me.overflowY = 'auto';
  118233. }
  118234. me.selModel.view = me;
  118235. me.headerCt.view = me;
  118236. me.headerCt.markDirty = me.markDirty;
  118237. // Features need a reference to the grid.
  118238. me.initFeatures(me.grid);
  118239. delete me.grid;
  118240. // The real tpl is generated, but AbstractView.initComponent insists upon the presence of a fully instantiated XTemplate at construction time.
  118241. me.tpl = me.getTpl('initialTpl');
  118242. me.callParent();
  118243. },
  118244. /**
  118245. * @private
  118246. * Move a grid column from one position to another
  118247. * @param {Number} fromIdx The index from which to move columns
  118248. * @param {Number} toIdx The index at which to insert columns.
  118249. * @param {Number} [colsToMove=1] The number of columns to move beginning at the `fromIdx`
  118250. */
  118251. moveColumn: function(fromIdx, toIdx, colsToMove) {
  118252. var me = this,
  118253. fragment = (colsToMove > 1) ? document.createDocumentFragment() : undefined,
  118254. destinationCellIdx = toIdx,
  118255. colCount = me.getGridColumns().length,
  118256. lastIdx = colCount - 1,
  118257. doFirstLastClasses = (me.firstCls || me.lastCls) && (toIdx === 0 || toIdx == colCount || fromIdx === 0 || fromIdx == lastIdx),
  118258. i,
  118259. j,
  118260. rows, len, tr, headerRows;
  118261. if (me.rendered) {
  118262. // Use select here. In most cases there will only be one row. In
  118263. // the case of a grouping grid, each group also has a header.
  118264. headerRows = me.el.query(me.headerRowSelector);
  118265. rows = me.el.query(me.rowSelector);
  118266. if (toIdx > fromIdx && fragment) {
  118267. destinationCellIdx -= colsToMove;
  118268. }
  118269. // Move the column sizing header to match
  118270. for (i = 0, len = headerRows.length; i < len; ++i) {
  118271. tr = headerRows[i];
  118272. if (fragment) {
  118273. for (j = 0; j < colsToMove; j++) {
  118274. fragment.appendChild(tr.cells[fromIdx]);
  118275. }
  118276. tr.insertBefore(fragment, tr.cells[destinationCellIdx] || null);
  118277. } else {
  118278. tr.insertBefore(tr.cells[fromIdx], tr.cells[destinationCellIdx] || null);
  118279. }
  118280. }
  118281. for (i = 0, len = rows.length; i < len; i++) {
  118282. tr = rows[i];
  118283. // Keep first cell class and last cell class correct *only if needed*
  118284. if (doFirstLastClasses) {
  118285. if (fromIdx === 0) {
  118286. Ext.fly(tr.cells[0]).removeCls(me.firstCls);
  118287. Ext.fly(tr.cells[1]).addCls(me.firstCls);
  118288. } else if (fromIdx === lastIdx) {
  118289. Ext.fly(tr.cells[lastIdx]).removeCls(me.lastCls);
  118290. Ext.fly(tr.cells[lastIdx - 1]).addCls(me.lastCls);
  118291. }
  118292. if (toIdx === 0) {
  118293. Ext.fly(tr.cells[0]).removeCls(me.firstCls);
  118294. Ext.fly(tr.cells[fromIdx]).addCls(me.firstCls);
  118295. } else if (toIdx === colCount) {
  118296. Ext.fly(tr.cells[lastIdx]).removeCls(me.lastCls);
  118297. Ext.fly(tr.cells[fromIdx]).addCls(me.lastCls);
  118298. }
  118299. }
  118300. if (fragment) {
  118301. for (j = 0; j < colsToMove; j++) {
  118302. fragment.appendChild(tr.cells[fromIdx]);
  118303. }
  118304. tr.insertBefore(fragment, tr.cells[destinationCellIdx] || null);
  118305. } else {
  118306. tr.insertBefore(tr.cells[fromIdx], tr.cells[destinationCellIdx] || null);
  118307. }
  118308. }
  118309. me.setNewTemplate();
  118310. }
  118311. },
  118312. // scroll the view to the top
  118313. scrollToTop: Ext.emptyFn,
  118314. /**
  118315. * Add a listener to the main view element. It will be destroyed with the view.
  118316. * @private
  118317. */
  118318. addElListener: function(eventName, fn, scope){
  118319. this.mon(this, eventName, fn, scope, {
  118320. element: 'el'
  118321. });
  118322. },
  118323. /**
  118324. * Get the columns used for generating a template via TableChunker.
  118325. * See {@link Ext.grid.header.Container#getGridColumns}.
  118326. * @private
  118327. */
  118328. getGridColumns: function() {
  118329. return this.headerCt.getGridColumns();
  118330. },
  118331. /**
  118332. * Get a leaf level header by index regardless of what the nesting
  118333. * structure is.
  118334. * @private
  118335. * @param {Number} index The index
  118336. */
  118337. getHeaderAtIndex: function(index) {
  118338. return this.headerCt.getHeaderAtIndex(index);
  118339. },
  118340. /**
  118341. * Get the cell (td) for a particular record and column.
  118342. * @param {Ext.data.Model} record
  118343. * @param {Ext.grid.column.Column} column
  118344. * @private
  118345. */
  118346. getCell: function(record, column) {
  118347. var row = this.getNode(record);
  118348. return Ext.fly(row).down(column.getCellSelector());
  118349. },
  118350. /**
  118351. * Get a reference to a feature
  118352. * @param {String} id The id of the feature
  118353. * @return {Ext.grid.feature.Feature} The feature. Undefined if not found
  118354. */
  118355. getFeature: function(id) {
  118356. var features = this.featuresMC;
  118357. if (features) {
  118358. return features.get(id);
  118359. }
  118360. },
  118361. /**
  118362. * Initializes each feature and bind it to this view.
  118363. * @private
  118364. */
  118365. initFeatures: function(grid) {
  118366. var me = this,
  118367. i,
  118368. features,
  118369. feature,
  118370. len;
  118371. me.featuresMC = new Ext.util.MixedCollection();
  118372. features = me.features = me.constructFeatures();
  118373. len = features ? features.length : 0;
  118374. for (i = 0; i < len; i++) {
  118375. feature = features[i];
  118376. // inject a reference to view and grid - Features need both
  118377. feature.view = me;
  118378. feature.grid = grid;
  118379. me.featuresMC.add(feature);
  118380. feature.init();
  118381. }
  118382. },
  118383. /**
  118384. * @private
  118385. * Converts the features array as configured, into an array of instantiated Feature objects.
  118386. *
  118387. * This is borrowed by Lockable which clones and distributes Features to both child grids of a locking grid.
  118388. *
  118389. * Must have no side effects other than Feature instantiation.
  118390. *
  118391. * MUST NOT update the this.features property, and MUST NOT update the instantiated Features.
  118392. */
  118393. constructFeatures: function() {
  118394. var me = this,
  118395. features = me.features,
  118396. feature,
  118397. result,
  118398. i = 0, len;
  118399. if (features) {
  118400. result = [];
  118401. len = features.length;
  118402. for (; i < len; i++) {
  118403. feature = features[i];
  118404. if (!feature.isFeature) {
  118405. feature = Ext.create('feature.' + feature.ftype, feature);
  118406. }
  118407. result[i] = feature;
  118408. }
  118409. }
  118410. return result;
  118411. },
  118412. /**
  118413. * Gives features an injection point to attach events to the markup that
  118414. * has been created for this view.
  118415. * @private
  118416. */
  118417. attachEventsForFeatures: function() {
  118418. var features = this.features,
  118419. ln = features.length,
  118420. i = 0;
  118421. for (; i < ln; i++) {
  118422. if (features[i].isFeature) {
  118423. features[i].attachEvents();
  118424. }
  118425. }
  118426. },
  118427. afterRender: function() {
  118428. var me = this;
  118429. me.callParent();
  118430. if (!me.enableTextSelection) {
  118431. me.el.unselectable();
  118432. }
  118433. me.attachEventsForFeatures();
  118434. },
  118435. // Private template method implemented starting at the AbstractView class.
  118436. onViewScroll: function(e, t) {
  118437. this.callParent(arguments);
  118438. this.fireEvent('bodyscroll', e, t);
  118439. },
  118440. /**
  118441. * Uses the headerCt (Which is the repository of all information relating to Column definitions)
  118442. * to transform data from dataIndex keys in a record to headerId keys in each header and then run
  118443. * them through each feature to get additional data for variables they have injected into the view template.
  118444. * @private
  118445. */
  118446. prepareData: function(data, idx, record) {
  118447. var me = this,
  118448. result = me.headerCt.prepareData(data, idx, record, me, me.ownerCt),
  118449. features = me.features,
  118450. ln = features.length,
  118451. i = 0,
  118452. feature;
  118453. for (; i < ln; i++) {
  118454. feature = features[i];
  118455. if (feature.isFeature) {
  118456. Ext.apply(result, feature.getAdditionalData(data, idx, record, result, me));
  118457. }
  118458. }
  118459. return result;
  118460. },
  118461. // TODO: Refactor headerCt dependency here to colModel
  118462. collectData: function(records, startIndex) {
  118463. var me = this,
  118464. preppedRecords = me.callParent(arguments),
  118465. headerCt = me.headerCt,
  118466. fullWidth = headerCt.getFullWidth(),
  118467. features = me.features,
  118468. ln = features.length,
  118469. o = {
  118470. rows: preppedRecords,
  118471. fullWidth: fullWidth
  118472. },
  118473. i = 0,
  118474. feature,
  118475. j = 0,
  118476. jln,
  118477. rowParams,
  118478. rec,
  118479. cls;
  118480. jln = preppedRecords.length;
  118481. // process row classes, rowParams has been deprecated and has been moved
  118482. // to the individual features that implement the behavior.
  118483. if (me.getRowClass) {
  118484. for (; j < jln; j++) {
  118485. rowParams = {};
  118486. rec = preppedRecords[j];
  118487. cls = rec.rowCls || '';
  118488. rec.rowCls = this.getRowClass(records[j], j, rowParams, me.store) + ' ' + cls;
  118489. if (rowParams.alt) {
  118490. Ext.Error.raise("The getRowClass alt property is no longer supported.");
  118491. }
  118492. if (rowParams.tstyle) {
  118493. Ext.Error.raise("The getRowClass tstyle property is no longer supported.");
  118494. }
  118495. if (rowParams.cells) {
  118496. Ext.Error.raise("The getRowClass cells property is no longer supported.");
  118497. }
  118498. if (rowParams.body) {
  118499. Ext.Error.raise("The getRowClass body property is no longer supported. Use the getAdditionalData method of the rowbody feature.");
  118500. }
  118501. if (rowParams.bodyStyle) {
  118502. Ext.Error.raise("The getRowClass bodyStyle property is no longer supported.");
  118503. }
  118504. if (rowParams.cols) {
  118505. Ext.Error.raise("The getRowClass cols property is no longer supported.");
  118506. }
  118507. }
  118508. }
  118509. // currently only one feature may implement collectData. This is to modify
  118510. // what's returned to the view before its rendered
  118511. for (; i < ln; i++) {
  118512. feature = features[i];
  118513. if (feature.isFeature && feature.collectData && !feature.disabled) {
  118514. o = feature.collectData(records, preppedRecords, startIndex, fullWidth, o);
  118515. break;
  118516. }
  118517. }
  118518. return o;
  118519. },
  118520. // Private. Called when the table changes height.
  118521. // For example, see examples/grid/group-summary-grid.html
  118522. // If we have flexed column headers, we need to update the header layout
  118523. // because it may have to accommodate (or cease to accommodate) a vertical scrollbar.
  118524. // Only do this on platforms which have a space-consuming scrollbar.
  118525. // Only do it when vertical scrolling is enabled.
  118526. refreshSize: function() {
  118527. var me = this,
  118528. cmp;
  118529. // On every update of the layout system due to data update, capture the table's DOM in our private flyweight
  118530. me.table.attach(me.el.child('table', true));
  118531. if (!me.hasLoadingHeight) {
  118532. cmp = me.up('tablepanel');
  118533. // Suspend layouts in case the superclass requests a layout. We might too, so they
  118534. // must be coalescsed.
  118535. Ext.suspendLayouts();
  118536. me.callParent();
  118537. // If the OS displays scrollbars, and we are overflowing vertically, ensure the
  118538. // HeaderContainer accounts for the scrollbar.
  118539. if (cmp && Ext.getScrollbarSize().width && (me.autoScroll || me.overflowY)) {
  118540. cmp.updateLayout();
  118541. }
  118542. Ext.resumeLayouts(true);
  118543. }
  118544. },
  118545. /**
  118546. * Set a new template based on the current columns displayed in the grid.
  118547. * @private
  118548. */
  118549. setNewTemplate: function() {
  118550. var me = this,
  118551. columns = me.headerCt.getColumnsForTpl(true);
  118552. // Template generation requires the rowCount as well as the column definitions and features.
  118553. me.tpl = me.getTableChunker().getTableTpl({
  118554. rowCount: me.store.getCount(),
  118555. columns: columns,
  118556. features: me.features,
  118557. enableTextSelection: me.enableTextSelection
  118558. });
  118559. },
  118560. /**
  118561. * Returns the configured chunker or default of Ext.view.TableChunker
  118562. */
  118563. getTableChunker: function() {
  118564. return this.chunker || Ext.view.TableChunker;
  118565. },
  118566. /**
  118567. * Adds a CSS Class to a specific row.
  118568. * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
  118569. * representing this row
  118570. * @param {String} cls
  118571. */
  118572. addRowCls: function(rowInfo, cls) {
  118573. var row = this.getNode(rowInfo);
  118574. if (row) {
  118575. Ext.fly(row).addCls(cls);
  118576. }
  118577. },
  118578. /**
  118579. * Removes a CSS Class from a specific row.
  118580. * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
  118581. * representing this row
  118582. * @param {String} cls
  118583. */
  118584. removeRowCls: function(rowInfo, cls) {
  118585. var row = this.getNode(rowInfo);
  118586. if (row) {
  118587. Ext.fly(row).removeCls(cls);
  118588. }
  118589. },
  118590. // GridSelectionModel invokes onRowSelect as selection changes
  118591. onRowSelect : function(rowIdx) {
  118592. this.addRowCls(rowIdx, this.selectedItemCls);
  118593. },
  118594. // GridSelectionModel invokes onRowDeselect as selection changes
  118595. onRowDeselect : function(rowIdx) {
  118596. var me = this;
  118597. me.removeRowCls(rowIdx, me.selectedItemCls);
  118598. me.removeRowCls(rowIdx, me.focusedItemCls);
  118599. },
  118600. onCellSelect: function(position) {
  118601. var cell = this.getCellByPosition(position, true);
  118602. if (cell) {
  118603. Ext.fly(cell).addCls(this.selectedCellCls);
  118604. }
  118605. },
  118606. onCellDeselect: function(position) {
  118607. var cell = this.getCellByPosition(position, true);
  118608. if (cell) {
  118609. Ext.fly(cell).removeCls(this.selectedCellCls);
  118610. }
  118611. },
  118612. onCellFocus: function(position) {
  118613. this.focusCell(position);
  118614. },
  118615. getCellByPosition: function(position, returnDom) {
  118616. if (position) {
  118617. var node = this.getNode(position.row),
  118618. header = this.headerCt.getHeaderAtIndex(position.column);
  118619. if (header && node) {
  118620. return Ext.fly(node).down(header.getCellSelector(), returnDom);
  118621. }
  118622. }
  118623. return false;
  118624. },
  118625. // GridSelectionModel invokes onRowFocus to 'highlight'
  118626. // the last row focused
  118627. onRowFocus: function(rowIdx, highlight, supressFocus) {
  118628. var me = this;
  118629. if (highlight) {
  118630. me.addRowCls(rowIdx, me.focusedItemCls);
  118631. if (!supressFocus) {
  118632. me.focusRow(rowIdx);
  118633. }
  118634. //this.el.dom.setAttribute('aria-activedescendant', row.id);
  118635. } else {
  118636. me.removeRowCls(rowIdx, me.focusedItemCls);
  118637. }
  118638. },
  118639. /**
  118640. * Focuses a particular row and brings it into view. Will fire the rowfocus event.
  118641. * @param {HTMLElement/String/Number/Ext.data.Model} rowIdx
  118642. * An HTMLElement template node, index of a template node, the id of a template node or the
  118643. * record associated with the node.
  118644. */
  118645. focusRow: function(rowIdx) {
  118646. var me = this,
  118647. row = me.getNode(rowIdx),
  118648. el = me.el,
  118649. adjustment = 0,
  118650. panel = me.ownerCt,
  118651. rowRegion,
  118652. elTop,
  118653. elBottom,
  118654. record;
  118655. if (row && el) {
  118656. // Viewable region must not include scrollbars, so use
  118657. // DOM clientHeight to determine height
  118658. elTop = el.getY();
  118659. elBottom = elTop + el.dom.clientHeight;
  118660. rowRegion = Ext.fly(row).getRegion();
  118661. // row is above
  118662. if (rowRegion.top < elTop) {
  118663. adjustment = rowRegion.top - elTop;
  118664. // row is below
  118665. } else if (rowRegion.bottom > elBottom) {
  118666. adjustment = rowRegion.bottom - elBottom;
  118667. }
  118668. record = me.getRecord(row);
  118669. rowIdx = me.store.indexOf(record);
  118670. if (adjustment) {
  118671. panel.scrollByDeltaY(adjustment);
  118672. }
  118673. me.fireEvent('rowfocus', record, row, rowIdx);
  118674. }
  118675. },
  118676. focusCell: function(position) {
  118677. var me = this,
  118678. cell = me.getCellByPosition(position),
  118679. el = me.el,
  118680. adjustmentY = 0,
  118681. adjustmentX = 0,
  118682. elRegion = el.getRegion(),
  118683. panel = me.ownerCt,
  118684. cellRegion,
  118685. record;
  118686. // Viewable region must not include scrollbars, so use
  118687. // DOM client dimensions
  118688. elRegion.bottom = elRegion.top + el.dom.clientHeight;
  118689. elRegion.right = elRegion.left + el.dom.clientWidth;
  118690. if (cell) {
  118691. cellRegion = cell.getRegion();
  118692. // cell is above
  118693. if (cellRegion.top < elRegion.top) {
  118694. adjustmentY = cellRegion.top - elRegion.top;
  118695. // cell is below
  118696. } else if (cellRegion.bottom > elRegion.bottom) {
  118697. adjustmentY = cellRegion.bottom - elRegion.bottom;
  118698. }
  118699. // cell is left
  118700. if (cellRegion.left < elRegion.left) {
  118701. adjustmentX = cellRegion.left - elRegion.left;
  118702. // cell is right
  118703. } else if (cellRegion.right > elRegion.right) {
  118704. adjustmentX = cellRegion.right - elRegion.right;
  118705. }
  118706. if (adjustmentY) {
  118707. panel.scrollByDeltaY(adjustmentY);
  118708. }
  118709. if (adjustmentX) {
  118710. panel.scrollByDeltaX(adjustmentX);
  118711. }
  118712. el.focus();
  118713. me.fireEvent('cellfocus', record, cell, position);
  118714. }
  118715. },
  118716. /**
  118717. * Scrolls by delta. This affects this individual view ONLY and does not
  118718. * synchronize across views or scrollers.
  118719. * @param {Number} delta
  118720. * @param {String} [dir] Valid values are scrollTop and scrollLeft. Defaults to scrollTop.
  118721. * @private
  118722. */
  118723. scrollByDelta: function(delta, dir) {
  118724. dir = dir || 'scrollTop';
  118725. var elDom = this.el.dom;
  118726. elDom[dir] = (elDom[dir] += delta);
  118727. },
  118728. // private
  118729. onUpdate : function(store, record, operation, changedFieldNames) {
  118730. var me = this,
  118731. index,
  118732. newRow, newAttrs, attLen, i, attName, oldRow, oldRowDom,
  118733. oldCells, newCells, len, i,
  118734. columns, overItemCls,
  118735. isHovered, row,
  118736. // See if an editing plugin is active.
  118737. isEditing = me.editingPlugin && me.editingPlugin.editing;
  118738. if (me.viewReady) {
  118739. index = me.store.indexOf(record);
  118740. columns = me.headerCt.getGridColumns();
  118741. overItemCls = me.overItemCls;
  118742. // If we have columns which may *need* updating (think lockable grid child with all columns either locked or unlocked)
  118743. // and the changed record is within our view, then update the view
  118744. if (columns.length && index > -1) {
  118745. newRow = me.bufferRender([record], index)[0];
  118746. oldRow = me.all.item(index);
  118747. if (oldRow) {
  118748. oldRowDom = oldRow.dom;
  118749. isHovered = oldRow.hasCls(overItemCls);
  118750. // Copy new row attributes across. Use IE-specific method if possible.
  118751. if (oldRowDom.mergeAttributes) {
  118752. oldRowDom.mergeAttributes(newRow, true);
  118753. } else {
  118754. newAttrs = newRow.attributes;
  118755. attLen = newAttrs.length;
  118756. for (i = 0; i < attLen; i++) {
  118757. attName = newAttrs[i].name;
  118758. if (attName !== 'id') {
  118759. oldRowDom.setAttribute(attName, newAttrs[i].value);
  118760. }
  118761. }
  118762. }
  118763. if (isHovered) {
  118764. oldRow.addCls(overItemCls);
  118765. }
  118766. // Replace changed cells in the existing row structure with the new version from the rendered row.
  118767. oldCells = oldRow.query(me.cellSelector);
  118768. newCells = Ext.fly(newRow).query(me.cellSelector);
  118769. len = newCells.length;
  118770. // row is the element that contains the cells. This will be a different element from oldRow when using a rowwrap feature
  118771. row = oldCells[0].parentNode;
  118772. for (i = 0; i < len; i++) {
  118773. // If the field at this column index was changed, or column has a custom renderer
  118774. // (which means value could rely on any other changed field) the update the cell's content.
  118775. if (me.shouldUpdateCell(columns[i], changedFieldNames)) {
  118776. // If an editor plugin is active, we carefully replace just the *contents* of the cell.
  118777. if (isEditing) {
  118778. Ext.fly(oldCells[i]).syncContent(newCells[i]);
  118779. }
  118780. // Otherwise, we simply replace whole TDs with a new version
  118781. else {
  118782. row.insertBefore(newCells[i], oldCells[i]);
  118783. row.removeChild(oldCells[i]);
  118784. }
  118785. }
  118786. }
  118787. }
  118788. me.fireEvent('itemupdate', record, index, newRow);
  118789. }
  118790. }
  118791. },
  118792. shouldUpdateCell: function(column, changedFieldNames){
  118793. // Though this may not be the most efficient, a renderer could be dependent on any field in the
  118794. // store, so we must always update the cell
  118795. if (column.hasCustomRenderer) {
  118796. return true;
  118797. }
  118798. return !changedFieldNames || Ext.Array.contains(changedFieldNames, column.dataIndex);
  118799. },
  118800. /**
  118801. * Refreshes the grid view. Saves and restores the scroll state, generates a new template, stripes rows and
  118802. * invalidates the scrollers.
  118803. */
  118804. refresh: function() {
  118805. var me = this;
  118806. me.setNewTemplate();
  118807. me.callParent(arguments);
  118808. me.doStripeRows(0);
  118809. me.headerCt.setSortState();
  118810. },
  118811. clearViewEl: function() {
  118812. this.callParent();
  118813. delete this.table.dom;
  118814. },
  118815. processItemEvent: function(record, row, rowIndex, e) {
  118816. var me = this,
  118817. cell = e.getTarget(me.cellSelector, row),
  118818. cellIndex = cell ? cell.cellIndex : -1,
  118819. map = me.statics().EventMap,
  118820. selModel = me.getSelectionModel(),
  118821. type = e.type,
  118822. result;
  118823. if (type == 'keydown' && !cell && selModel.getCurrentPosition) {
  118824. // CellModel, otherwise we can't tell which cell to invoke
  118825. cell = me.getCellByPosition(selModel.getCurrentPosition());
  118826. if (cell) {
  118827. cell = cell.dom;
  118828. cellIndex = cell.cellIndex;
  118829. }
  118830. }
  118831. result = me.fireEvent('uievent', type, me, cell, rowIndex, cellIndex, e, record, row);
  118832. if (result === false || me.callParent(arguments) === false) {
  118833. return false;
  118834. }
  118835. // Don't handle cellmouseenter and cellmouseleave events for now
  118836. if (type == 'mouseover' || type == 'mouseout') {
  118837. return true;
  118838. }
  118839. if(!cell) {
  118840. // if the element whose event is being processed is not an actual cell (for example if using a rowbody
  118841. // feature and the rowbody element's event is being processed) then do not fire any "cell" events
  118842. return true;
  118843. }
  118844. return !(
  118845. // We are adding cell and feature events
  118846. (me['onBeforeCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
  118847. (me.fireEvent('beforecell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false) ||
  118848. (me['onCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
  118849. (me.fireEvent('cell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false)
  118850. );
  118851. },
  118852. processSpecialEvent: function(e) {
  118853. var me = this,
  118854. map = me.statics().EventMap,
  118855. features = me.features,
  118856. ln = features.length,
  118857. type = e.type,
  118858. i, feature, prefix, featureTarget,
  118859. beforeArgs, args,
  118860. panel = me.ownerCt;
  118861. me.callParent(arguments);
  118862. if (type == 'mouseover' || type == 'mouseout') {
  118863. return;
  118864. }
  118865. for (i = 0; i < ln; i++) {
  118866. feature = features[i];
  118867. if (feature.hasFeatureEvent) {
  118868. featureTarget = e.getTarget(feature.eventSelector, me.getTargetEl());
  118869. if (featureTarget) {
  118870. prefix = feature.eventPrefix;
  118871. // allows features to implement getFireEventArgs to change the
  118872. // fireEvent signature
  118873. beforeArgs = feature.getFireEventArgs('before' + prefix + type, me, featureTarget, e);
  118874. args = feature.getFireEventArgs(prefix + type, me, featureTarget, e);
  118875. if (
  118876. // before view event
  118877. (me.fireEvent.apply(me, beforeArgs) === false) ||
  118878. // panel grid event
  118879. (panel.fireEvent.apply(panel, beforeArgs) === false) ||
  118880. // view event
  118881. (me.fireEvent.apply(me, args) === false) ||
  118882. // panel event
  118883. (panel.fireEvent.apply(panel, args) === false)
  118884. ) {
  118885. return false;
  118886. }
  118887. }
  118888. }
  118889. }
  118890. return true;
  118891. },
  118892. onCellMouseDown: Ext.emptyFn,
  118893. onCellMouseUp: Ext.emptyFn,
  118894. onCellClick: Ext.emptyFn,
  118895. onCellDblClick: Ext.emptyFn,
  118896. onCellContextMenu: Ext.emptyFn,
  118897. onCellKeyDown: Ext.emptyFn,
  118898. onBeforeCellMouseDown: Ext.emptyFn,
  118899. onBeforeCellMouseUp: Ext.emptyFn,
  118900. onBeforeCellClick: Ext.emptyFn,
  118901. onBeforeCellDblClick: Ext.emptyFn,
  118902. onBeforeCellContextMenu: Ext.emptyFn,
  118903. onBeforeCellKeyDown: Ext.emptyFn,
  118904. /**
  118905. * Expands a particular header to fit the max content width.
  118906. * This will ONLY expand, not contract.
  118907. * @private
  118908. */
  118909. expandToFit: function(header) {
  118910. if (header) {
  118911. var maxWidth = this.getMaxContentWidth(header);
  118912. delete header.flex;
  118913. header.setWidth(maxWidth);
  118914. }
  118915. },
  118916. /**
  118917. * Returns the max contentWidth of the header's text and all cells
  118918. * in the grid under this header.
  118919. * @private
  118920. */
  118921. getMaxContentWidth: function(header) {
  118922. var cellSelector = header.getCellInnerSelector(),
  118923. cells = this.el.query(cellSelector),
  118924. i = 0,
  118925. ln = cells.length,
  118926. maxWidth = header.el.dom.scrollWidth,
  118927. scrollWidth;
  118928. for (; i < ln; i++) {
  118929. scrollWidth = cells[i].scrollWidth;
  118930. if (scrollWidth > maxWidth) {
  118931. maxWidth = scrollWidth;
  118932. }
  118933. }
  118934. return maxWidth;
  118935. },
  118936. getPositionByEvent: function(e) {
  118937. var me = this,
  118938. cellNode = e.getTarget(me.cellSelector),
  118939. rowNode = e.getTarget(me.itemSelector),
  118940. record = me.getRecord(rowNode),
  118941. header = me.getHeaderByCell(cellNode);
  118942. return me.getPosition(record, header);
  118943. },
  118944. getHeaderByCell: function(cell) {
  118945. if (cell) {
  118946. var m = cell.className.match(this.cellRe);
  118947. if (m && m[1]) {
  118948. return Ext.getCmp(m[1]);
  118949. }
  118950. }
  118951. return false;
  118952. },
  118953. /**
  118954. * @param {Object} position The current row and column: an object containing the following properties:
  118955. *
  118956. * - row - The row index
  118957. * - column - The column index
  118958. *
  118959. * @param {String} direction 'up', 'down', 'right' and 'left'
  118960. * @param {Ext.EventObject} e event
  118961. * @param {Boolean} preventWrap Set to true to prevent wrap around to the next or previous row.
  118962. * @param {Function} verifierFn A function to verify the validity of the calculated position.
  118963. * When using this function, you must return true to allow the newPosition to be returned.
  118964. * @param {Object} scope Scope to run the verifierFn in
  118965. * @returns {Object} newPosition An object containing the following properties:
  118966. *
  118967. * - row - The row index
  118968. * - column - The column index
  118969. *
  118970. * @private
  118971. */
  118972. walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
  118973. // Caller (probably CellModel) had no current position. This can happen
  118974. // if the main el is focused and any navigation key is presssed.
  118975. if (!pos) {
  118976. return;
  118977. }
  118978. var me = this,
  118979. row = pos.row,
  118980. column = pos.column,
  118981. rowCount = me.store.getCount(),
  118982. firstCol = me.getFirstVisibleColumnIndex(),
  118983. lastCol = me.getLastVisibleColumnIndex(),
  118984. newPos = {row: row, column: column},
  118985. activeHeader = me.headerCt.getHeaderAtIndex(column);
  118986. // no active header or its currently hidden
  118987. if (!activeHeader || activeHeader.hidden) {
  118988. return false;
  118989. }
  118990. e = e || {};
  118991. direction = direction.toLowerCase();
  118992. switch (direction) {
  118993. case 'right':
  118994. // has the potential to wrap if its last
  118995. if (column === lastCol) {
  118996. // if bottom row and last column, deny right
  118997. if (preventWrap || row === rowCount - 1) {
  118998. return false;
  118999. }
  119000. if (!e.ctrlKey) {
  119001. // otherwise wrap to nextRow and firstCol
  119002. newPos.row = row + 1;
  119003. newPos.column = firstCol;
  119004. }
  119005. // go right
  119006. } else {
  119007. if (!e.ctrlKey) {
  119008. newPos.column = column + me.getRightGap(activeHeader);
  119009. } else {
  119010. newPos.column = lastCol;
  119011. }
  119012. }
  119013. break;
  119014. case 'left':
  119015. // has the potential to wrap
  119016. if (column === firstCol) {
  119017. // if top row and first column, deny left
  119018. if (preventWrap || row === 0) {
  119019. return false;
  119020. }
  119021. if (!e.ctrlKey) {
  119022. // otherwise wrap to prevRow and lastCol
  119023. newPos.row = row - 1;
  119024. newPos.column = lastCol;
  119025. }
  119026. // go left
  119027. } else {
  119028. if (!e.ctrlKey) {
  119029. newPos.column = column + me.getLeftGap(activeHeader);
  119030. } else {
  119031. newPos.column = firstCol;
  119032. }
  119033. }
  119034. break;
  119035. case 'up':
  119036. // if top row, deny up
  119037. if (row === 0) {
  119038. return false;
  119039. // go up
  119040. } else {
  119041. if (!e.ctrlKey) {
  119042. newPos.row = row - 1;
  119043. } else {
  119044. newPos.row = 0;
  119045. }
  119046. }
  119047. break;
  119048. case 'down':
  119049. // if bottom row, deny down
  119050. if (row === rowCount - 1) {
  119051. return false;
  119052. // go down
  119053. } else {
  119054. if (!e.ctrlKey) {
  119055. newPos.row = row + 1;
  119056. } else {
  119057. newPos.row = rowCount - 1;
  119058. }
  119059. }
  119060. break;
  119061. }
  119062. if (verifierFn && verifierFn.call(scope || window, newPos) !== true) {
  119063. return false;
  119064. } else {
  119065. return newPos;
  119066. }
  119067. },
  119068. getFirstVisibleColumnIndex: function() {
  119069. var firstVisibleHeader = this.getHeaderCt().getVisibleGridColumns()[0];
  119070. return firstVisibleHeader ? firstVisibleHeader.getIndex() : -1;
  119071. },
  119072. getLastVisibleColumnIndex: function() {
  119073. var visHeaders = this.getHeaderCt().getVisibleGridColumns(),
  119074. lastHeader = visHeaders[visHeaders.length - 1];
  119075. return lastHeader.getIndex();
  119076. },
  119077. getHeaderCt: function() {
  119078. return this.headerCt;
  119079. },
  119080. // TODO: have this use the new Ext.grid.CellContext class
  119081. getPosition: function(record, header) {
  119082. var me = this,
  119083. store = me.store,
  119084. gridCols = me.headerCt.getGridColumns();
  119085. return {
  119086. row: store.indexOf(record),
  119087. column: Ext.Array.indexOf(gridCols, header)
  119088. };
  119089. },
  119090. /**
  119091. * Determines the 'gap' between the closest adjacent header to the right
  119092. * that is not hidden.
  119093. * @private
  119094. */
  119095. getRightGap: function(activeHeader) {
  119096. var headerCt = this.getHeaderCt(),
  119097. headers = headerCt.getGridColumns(),
  119098. activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
  119099. i = activeHeaderIdx + 1,
  119100. nextIdx;
  119101. for (; i <= headers.length; i++) {
  119102. if (!headers[i].hidden) {
  119103. nextIdx = i;
  119104. break;
  119105. }
  119106. }
  119107. return nextIdx - activeHeaderIdx;
  119108. },
  119109. beforeDestroy: function() {
  119110. if (this.rendered) {
  119111. this.el.removeAllListeners();
  119112. }
  119113. this.callParent(arguments);
  119114. },
  119115. /**
  119116. * Determines the 'gap' between the closest adjacent header to the left
  119117. * that is not hidden.
  119118. * @private
  119119. */
  119120. getLeftGap: function(activeHeader) {
  119121. var headerCt = this.getHeaderCt(),
  119122. headers = headerCt.getGridColumns(),
  119123. activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
  119124. i = activeHeaderIdx - 1,
  119125. prevIdx;
  119126. for (; i >= 0; i--) {
  119127. if (!headers[i].hidden) {
  119128. prevIdx = i;
  119129. break;
  119130. }
  119131. }
  119132. return prevIdx - activeHeaderIdx;
  119133. },
  119134. // after adding a row stripe rows from then on
  119135. onAdd: function(ds, records, index) {
  119136. this.callParent(arguments);
  119137. this.doStripeRows(index);
  119138. },
  119139. // after removing a row stripe rows from then on
  119140. onRemove: function(ds, records, index) {
  119141. this.callParent(arguments);
  119142. this.doStripeRows(index);
  119143. },
  119144. /**
  119145. * Stripes rows from a particular row index.
  119146. * @param {Number} startRow
  119147. * @param {Number} [endRow] argument specifying the last row to process.
  119148. * By default process up to the last row.
  119149. * @private
  119150. */
  119151. doStripeRows: function(startRow, endRow) {
  119152. var me = this,
  119153. rows,
  119154. rowsLn,
  119155. i,
  119156. row;
  119157. // ensure stripeRows configuration is turned on
  119158. if (me.rendered && me.stripeRows) {
  119159. rows = me.getNodes(startRow, endRow);
  119160. for (i = 0, rowsLn = rows.length; i < rowsLn; i++) {
  119161. row = rows[i];
  119162. // Remove prior applied row classes.
  119163. row.className = row.className.replace(me.rowClsRe, ' ');
  119164. startRow++;
  119165. // Every odd row will get an additional cls
  119166. if (startRow % 2 === 0) {
  119167. row.className += (' ' + me.altRowCls);
  119168. }
  119169. }
  119170. }
  119171. }
  119172. });
  119173. /**
  119174. * @private
  119175. *
  119176. * Lockable is a private mixin which injects lockable behavior into any
  119177. * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
  119178. * automatically inject the Ext.grid.Lockable mixin in when one of the
  119179. * these conditions are met:
  119180. *
  119181. * - The TablePanel has the lockable configuration set to true
  119182. * - One of the columns in the TablePanel has locked set to true/false
  119183. *
  119184. * Each TablePanel subclass must register an alias. It should have an array
  119185. * of configurations to copy to the 2 separate tablepanel's that will be generated
  119186. * to note what configurations should be copied. These are named normalCfgCopy and
  119187. * lockedCfgCopy respectively.
  119188. *
  119189. * Columns which are locked must specify a fixed width. They do NOT support a
  119190. * flex width.
  119191. *
  119192. * Configurations which are specified in this class will be available on any grid or
  119193. * tree which is using the lockable functionality.
  119194. */
  119195. Ext.define('Ext.grid.Lockable', {
  119196. requires: [
  119197. 'Ext.grid.LockingView',
  119198. 'Ext.view.Table'
  119199. ],
  119200. /**
  119201. * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
  119202. * locked grid view. This is turned on by default. If your grid is guaranteed
  119203. * to have rows of all the same height, you should set this to false to
  119204. * optimize performance.
  119205. */
  119206. syncRowHeight: true,
  119207. /**
  119208. * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
  119209. * not specified lockable will determine the subgrid xtype to create by the
  119210. * following rule. Use the superclasses xtype if the superclass is NOT
  119211. * tablepanel, otherwise use the xtype itself.
  119212. */
  119213. /**
  119214. * @cfg {Object} lockedViewConfig A view configuration to be applied to the
  119215. * locked side of the grid. Any conflicting configurations between lockedViewConfig
  119216. * and viewConfig will be overwritten by the lockedViewConfig.
  119217. */
  119218. /**
  119219. * @cfg {Object} normalViewConfig A view configuration to be applied to the
  119220. * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
  119221. * and viewConfig will be overwritten by the normalViewConfig.
  119222. */
  119223. headerCounter: 0,
  119224. /**
  119225. * @cfg {Number} scrollDelta
  119226. * Number of pixels to scroll when scrolling the locked section with mousewheel.
  119227. */
  119228. scrollDelta: 40,
  119229. /**
  119230. * @cfg {Object} lockedGridConfig
  119231. * Any special configuration options for the locked part of the grid
  119232. */
  119233. /**
  119234. * @cfg {Object} normalGridConfig
  119235. * Any special configuration options for the normal part of the grid
  119236. */
  119237. // i8n text
  119238. //<locale>
  119239. unlockText: 'Unlock',
  119240. //</locale>
  119241. //<locale>
  119242. lockText: 'Lock',
  119243. //</locale>
  119244. determineXTypeToCreate: function() {
  119245. var me = this,
  119246. typeToCreate,
  119247. xtypes, xtypesLn, xtype, superxtype;
  119248. if (me.subGridXType) {
  119249. typeToCreate = me.subGridXType;
  119250. } else {
  119251. xtypes = this.getXTypes().split('/');
  119252. xtypesLn = xtypes.length;
  119253. xtype = xtypes[xtypesLn - 1];
  119254. superxtype = xtypes[xtypesLn - 2];
  119255. if (superxtype !== 'tablepanel') {
  119256. typeToCreate = superxtype;
  119257. } else {
  119258. typeToCreate = xtype;
  119259. }
  119260. }
  119261. return typeToCreate;
  119262. },
  119263. // injectLockable will be invoked before initComponent's parent class implementation
  119264. // is called, so throughout this method this. are configurations
  119265. injectLockable: function() {
  119266. // ensure lockable is set to true in the TablePanel
  119267. this.lockable = true;
  119268. // Instruct the TablePanel it already has a view and not to create one.
  119269. // We are going to aggregate 2 copies of whatever TablePanel we are using
  119270. this.hasView = true;
  119271. var me = this,
  119272. // If the OS does not show a space-taking scrollbar, the locked view can be overflow:auto
  119273. scrollLocked = Ext.getScrollbarSize().width === 0,
  119274. store = me.store = Ext.StoreManager.lookup(me.store),
  119275. // xtype of this class, 'treepanel' or 'gridpanel'
  119276. // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
  119277. // alias.)
  119278. xtype = me.determineXTypeToCreate(),
  119279. // share the selection model
  119280. selModel = me.getSelectionModel(),
  119281. lockedFeatures,
  119282. normalFeatures,
  119283. lockedPlugins,
  119284. normalPlugins,
  119285. lockedGrid,
  119286. normalGrid,
  119287. i, len,
  119288. columns,
  119289. lockedHeaderCt,
  119290. normalHeaderCt,
  119291. lockedView,
  119292. normalView,
  119293. listeners;
  119294. lockedFeatures = me.constructFeatures();
  119295. // Clone any Features in the Array which are already instantiated
  119296. me.cloneFeatures();
  119297. normalFeatures = me.constructFeatures();
  119298. lockedPlugins = me.constructPlugins();
  119299. // Clone any Plugins in the Array which are already instantiated
  119300. me.clonePlugins();
  119301. normalPlugins = me.constructPlugins();
  119302. // The "shell" Panel which just acts as a Container for the two grids must not use the features and plugins
  119303. delete me.features;
  119304. delete me.plugins;
  119305. // Each Feature must have a reference to its counterpart on the opposite side of the locking view
  119306. for (i = 0, len = (lockedFeatures ? lockedFeatures.length : 0); i < len; i++) {
  119307. lockedFeatures[i].lockingPartner = normalFeatures[i];
  119308. normalFeatures[i].lockingPartner = lockedFeatures[i];
  119309. }
  119310. lockedGrid = Ext.apply({
  119311. xtype: xtype,
  119312. store: store,
  119313. scrollerOwner: false,
  119314. // Lockable does NOT support animations for Tree
  119315. enableAnimations: false,
  119316. scroll: scrollLocked ? 'vertical' : false,
  119317. selModel: selModel,
  119318. border: false,
  119319. cls: Ext.baseCSSPrefix + 'grid-inner-locked',
  119320. isLayoutRoot: function() {
  119321. return false;
  119322. },
  119323. features: lockedFeatures,
  119324. plugins: lockedPlugins
  119325. }, me.lockedGridConfig);
  119326. normalGrid = Ext.apply({
  119327. xtype: xtype,
  119328. store: store,
  119329. scrollerOwner: false,
  119330. enableAnimations: false,
  119331. selModel: selModel,
  119332. border: false,
  119333. isLayoutRoot: function() {
  119334. return false;
  119335. },
  119336. features: normalFeatures,
  119337. plugins: normalPlugins
  119338. }, me.normalGridConfig);
  119339. me.addCls(Ext.baseCSSPrefix + 'grid-locked');
  119340. // copy appropriate configurations to the respective
  119341. // aggregated tablepanel instances and then delete them
  119342. // from the master tablepanel.
  119343. Ext.copyTo(normalGrid, me, me.bothCfgCopy);
  119344. Ext.copyTo(lockedGrid, me, me.bothCfgCopy);
  119345. Ext.copyTo(normalGrid, me, me.normalCfgCopy);
  119346. Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
  119347. for (i = 0; i < me.normalCfgCopy.length; i++) {
  119348. delete me[me.normalCfgCopy[i]];
  119349. }
  119350. for (i = 0; i < me.lockedCfgCopy.length; i++) {
  119351. delete me[me.lockedCfgCopy[i]];
  119352. }
  119353. me.addEvents(
  119354. /**
  119355. * @event lockcolumn
  119356. * Fires when a column is locked.
  119357. * @param {Ext.grid.Panel} this The gridpanel.
  119358. * @param {Ext.grid.column.Column} column The column being locked.
  119359. */
  119360. 'lockcolumn',
  119361. /**
  119362. * @event unlockcolumn
  119363. * Fires when a column is unlocked.
  119364. * @param {Ext.grid.Panel} this The gridpanel.
  119365. * @param {Ext.grid.column.Column} column The column being unlocked.
  119366. */
  119367. 'unlockcolumn'
  119368. );
  119369. me.addStateEvents(['lockcolumn', 'unlockcolumn']);
  119370. me.lockedHeights = [];
  119371. me.normalHeights = [];
  119372. columns = me.processColumns(me.columns);
  119373. lockedGrid.width = columns.lockedWidth + Ext.num(selModel.headerWidth, 0);
  119374. lockedGrid.columns = columns.locked;
  119375. normalGrid.columns = columns.normal;
  119376. // normal grid should flex the rest of the width
  119377. normalGrid.flex = 1;
  119378. lockedGrid.viewConfig = me.lockedViewConfig || {};
  119379. lockedGrid.viewConfig.loadingUseMsg = false;
  119380. normalGrid.viewConfig = me.normalViewConfig || {};
  119381. Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
  119382. Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
  119383. me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
  119384. lockedView = me.lockedGrid.getView();
  119385. normalGrid.viewConfig.lockingPartner = lockedView;
  119386. me.normalGrid = Ext.ComponentManager.create(normalGrid);
  119387. normalView = me.normalGrid.getView();
  119388. me.view = new Ext.grid.LockingView({
  119389. locked: me.lockedGrid,
  119390. normal: me.normalGrid,
  119391. panel: me
  119392. });
  119393. // Set up listeners for the locked view. If its SelModel ever scrolls it, the normal view must sync
  119394. listeners = {
  119395. scroll: {
  119396. fn: me.onLockedViewScroll,
  119397. element: 'el',
  119398. scope: me
  119399. }
  119400. };
  119401. // If there are system scrollbars, we have to monitor the mousewheel and fake a scroll
  119402. if (!scrollLocked) {
  119403. listeners.mousewheel = {
  119404. fn: me.onLockedViewMouseWheel,
  119405. element: 'el',
  119406. scope: me
  119407. };
  119408. }
  119409. if (me.syncRowHeight) {
  119410. listeners.refresh = me.onLockedViewRefresh;
  119411. listeners.itemupdate = me.onLockedViewItemUpdate;
  119412. listeners.scope = me;
  119413. }
  119414. lockedView.on(listeners);
  119415. // Set up listeners for the normal view
  119416. listeners = {
  119417. scroll: {
  119418. fn: me.onNormalViewScroll,
  119419. element: 'el',
  119420. scope: me
  119421. },
  119422. refresh: me.syncRowHeight ? me.onNormalViewRefresh : me.updateSpacer,
  119423. scope: me
  119424. };
  119425. normalView.on(listeners);
  119426. lockedHeaderCt = me.lockedGrid.headerCt;
  119427. normalHeaderCt = me.normalGrid.headerCt;
  119428. lockedHeaderCt.lockedCt = true;
  119429. lockedHeaderCt.lockableInjected = true;
  119430. normalHeaderCt.lockableInjected = true;
  119431. lockedHeaderCt.on({
  119432. columnshow: me.onLockedHeaderShow,
  119433. columnhide: me.onLockedHeaderHide,
  119434. columnmove: me.onLockedHeaderMove,
  119435. sortchange: me.onLockedHeaderSortChange,
  119436. columnresize: me.onLockedHeaderResize,
  119437. scope: me
  119438. });
  119439. normalHeaderCt.on({
  119440. columnmove: me.onNormalHeaderMove,
  119441. sortchange: me.onNormalHeaderSortChange,
  119442. scope: me
  119443. });
  119444. me.modifyHeaderCt();
  119445. me.items = [me.lockedGrid, me.normalGrid];
  119446. me.relayHeaderCtEvents(lockedHeaderCt);
  119447. me.relayHeaderCtEvents(normalHeaderCt);
  119448. me.layout = {
  119449. type: 'hbox',
  119450. align: 'stretch'
  119451. };
  119452. },
  119453. processColumns: function(columns){
  119454. // split apart normal and lockedWidths
  119455. var i = 0,
  119456. len = columns.length,
  119457. lockedWidth = 0,
  119458. lockedHeaders = [],
  119459. normalHeaders = [],
  119460. column;
  119461. for (; i < len; ++i) {
  119462. column = columns[i];
  119463. // MUST clone the column config because we mutate it, and we must not mutate passed in config objects in case they are re-used
  119464. // eg, in an extend-to-configure scenario.
  119465. if (!column.isComponent) {
  119466. column = Ext.apply({}, columns[i]);
  119467. }
  119468. // mark the column as processed so that the locked attribute does not
  119469. // trigger trying to aggregate the columns again.
  119470. column.processed = true;
  119471. if (column.locked) {
  119472. if (column.flex) {
  119473. Ext.Error.raise("Columns which are locked do NOT support a flex width. You must set a width on the " + columns[i].text + "column.");
  119474. }
  119475. if (!column.hidden) {
  119476. lockedWidth += column.width || Ext.grid.header.Container.prototype.defaultWidth;
  119477. }
  119478. lockedHeaders.push(column);
  119479. } else {
  119480. normalHeaders.push(column);
  119481. }
  119482. if (!column.headerId) {
  119483. column.headerId = (column.initialConfig || column).id || ('L' + (++this.headerCounter));
  119484. }
  119485. }
  119486. return {
  119487. lockedWidth: lockedWidth,
  119488. locked: {
  119489. items: lockedHeaders,
  119490. itemId: 'lockedHeaderCt',
  119491. stretchMaxPartner: '^^>>#normalHeaderCt'
  119492. },
  119493. normal: {
  119494. items: normalHeaders,
  119495. itemId: 'normalHeaderCt',
  119496. stretchMaxPartner: '^^>>#lockedHeaderCt'
  119497. }
  119498. };
  119499. },
  119500. /**
  119501. * @private
  119502. * Listen for mousewheel events on the locked section which does not scroll.
  119503. * Scroll it in response, and the other section will automatically sync.
  119504. */
  119505. onLockedViewMouseWheel: function(e) {
  119506. var me = this,
  119507. scrollDelta = -me.scrollDelta,
  119508. deltaY = scrollDelta * e.getWheelDeltas().y,
  119509. vertScrollerEl = me.lockedGrid.getView().el.dom,
  119510. verticalCanScrollDown, verticalCanScrollUp;
  119511. if (vertScrollerEl) {
  119512. verticalCanScrollDown = vertScrollerEl.scrollTop !== vertScrollerEl.scrollHeight - vertScrollerEl.clientHeight;
  119513. verticalCanScrollUp = vertScrollerEl.scrollTop !== 0;
  119514. }
  119515. if ((deltaY < 0 && verticalCanScrollUp) || (deltaY > 0 && verticalCanScrollDown)) {
  119516. e.stopEvent();
  119517. // Inhibit processing of any scroll events we *may* cause here.
  119518. // Some OSs do not fire a scroll event when we set the scrollTop of an overflow:hidden element,
  119519. // so we invoke the scroll handler programatically below.
  119520. me.scrolling = true;
  119521. vertScrollerEl.scrollTop += deltaY;
  119522. me.normalGrid.getView().el.dom.scrollTop = vertScrollerEl.scrollTop;
  119523. me.scrolling = false;
  119524. // Invoke the scroll event handler programatically to sync everything.
  119525. me.onNormalViewScroll();
  119526. }
  119527. },
  119528. onLockedViewScroll: function() {
  119529. var me = this,
  119530. lockedView = me.lockedGrid.getView(),
  119531. normalView = me.normalGrid.getView(),
  119532. normalTable,
  119533. lockedTable;
  119534. // Set a flag so that the scroll even doesn't bounce back when we set the normal view's scroll position
  119535. if (!me.scrolling) {
  119536. me.scrolling = true;
  119537. normalView.el.dom.scrollTop = lockedView.el.dom.scrollTop;
  119538. // For buffered views, the absolute position is important as well as scrollTop
  119539. if (me.store.buffered) {
  119540. lockedTable = lockedView.el.child('table', true);
  119541. normalTable = normalView.el.child('table', true);
  119542. lockedTable.style.position = 'absolute';
  119543. }
  119544. me.scrolling = false;
  119545. }
  119546. },
  119547. onNormalViewScroll: function() {
  119548. var me = this,
  119549. lockedView = me.lockedGrid.getView(),
  119550. normalView = me.normalGrid.getView(),
  119551. normalTable,
  119552. lockedTable;
  119553. // Set a flag so that the scroll even doesn't bounce back when we set the locked view's scroll position
  119554. if (!me.scrolling) {
  119555. me.scrolling = true;
  119556. lockedView.el.dom.scrollTop = normalView.el.dom.scrollTop;
  119557. // For buffered views, the absolute position is important as well as scrollTop
  119558. if (me.store.buffered) {
  119559. lockedTable = lockedView.el.child('table', true);
  119560. normalTable = normalView.el.child('table', true);
  119561. lockedTable.style.position = 'absolute';
  119562. lockedTable.style.top = normalTable.style.top;
  119563. }
  119564. me.scrolling = false;
  119565. }
  119566. },
  119567. // trigger a pseudo refresh on the normal side
  119568. onLockedHeaderMove: function() {
  119569. if (this.syncRowHeight) {
  119570. this.onNormalViewRefresh();
  119571. }
  119572. },
  119573. // trigger a pseudo refresh on the locked side
  119574. onNormalHeaderMove: function() {
  119575. if (this.syncRowHeight) {
  119576. this.onLockedViewRefresh();
  119577. }
  119578. },
  119579. // Create a spacer in lockedsection and store a reference.
  119580. // This is to allow the locked section to scroll past the bottom to
  119581. // take the mormal section's horizontal scrollbar into account
  119582. // TODO: Should destroy before refreshing content
  119583. updateSpacer: function() {
  119584. var me = this,
  119585. // This affects scrolling all the way to the bottom of a locked grid
  119586. // additional test, sort a column and make sure it synchronizes
  119587. lockedViewEl = me.lockedGrid.getView().el,
  119588. normalViewEl = me.normalGrid.getView().el.dom,
  119589. spacerId = lockedViewEl.dom.id + '-spacer',
  119590. spacerHeight = (normalViewEl.offsetHeight - normalViewEl.clientHeight) + 'px';
  119591. me.spacerEl = Ext.getDom(spacerId);
  119592. if (me.spacerEl) {
  119593. me.spacerEl.style.height = spacerHeight;
  119594. } else {
  119595. Ext.core.DomHelper.append(lockedViewEl, {
  119596. id: spacerId,
  119597. style: 'height: ' + spacerHeight
  119598. });
  119599. }
  119600. },
  119601. // cache the heights of all locked rows and sync rowheights
  119602. onLockedViewRefresh: function() {
  119603. // Only bother if there are some columns in the normal grid to sync
  119604. if (this.normalGrid.headerCt.getGridColumns().length) {
  119605. var me = this,
  119606. view = me.lockedGrid.getView(),
  119607. el = view.el,
  119608. rowEls = el.query(view.getItemSelector()),
  119609. ln = rowEls.length,
  119610. i = 0;
  119611. // reset heights each time.
  119612. me.lockedHeights = [];
  119613. for (; i < ln; i++) {
  119614. me.lockedHeights[i] = rowEls[i].offsetHeight;
  119615. }
  119616. me.syncRowHeights();
  119617. me.updateSpacer();
  119618. }
  119619. },
  119620. // cache the heights of all normal rows and sync rowheights
  119621. onNormalViewRefresh: function() {
  119622. // Only bother if there are some columns in the locked grid to sync
  119623. if (this.lockedGrid.headerCt.getGridColumns().length) {
  119624. var me = this,
  119625. view = me.normalGrid.getView(),
  119626. el = view.el,
  119627. rowEls = el.query(view.getItemSelector()),
  119628. ln = rowEls.length,
  119629. i = 0;
  119630. // reset heights each time.
  119631. me.normalHeights = [];
  119632. for (; i < ln; i++) {
  119633. me.normalHeights[i] = rowEls[i].offsetHeight;
  119634. }
  119635. me.syncRowHeights();
  119636. me.updateSpacer();
  119637. }
  119638. },
  119639. // rows can get bigger/smaller
  119640. onLockedViewItemUpdate: function(record, index, node) {
  119641. // Only bother if there are some columns in the normal grid to sync
  119642. if (this.normalGrid.headerCt.getGridColumns().length) {
  119643. this.lockedHeights[index] = node.offsetHeight;
  119644. this.syncRowHeights();
  119645. }
  119646. },
  119647. // rows can get bigger/smaller
  119648. onNormalViewItemUpdate: function(record, index, node) {
  119649. // Only bother if there are some columns in the locked grid to sync
  119650. if (this.lockedGrid.headerCt.getGridColumns().length) {
  119651. this.normalHeights[index] = node.offsetHeight;
  119652. this.syncRowHeights();
  119653. }
  119654. },
  119655. /**
  119656. * Synchronizes the row heights between the locked and non locked portion of the grid for each
  119657. * row. If one row is smaller than the other, the height will be increased to match the larger one.
  119658. */
  119659. syncRowHeights: function() {
  119660. var me = this,
  119661. lockedHeights = me.lockedHeights,
  119662. normalHeights = me.normalHeights,
  119663. ln = lockedHeights.length,
  119664. i = 0,
  119665. lockedView, normalView,
  119666. lockedRowEls, normalRowEls,
  119667. scrollTop;
  119668. // ensure there are an equal num of locked and normal
  119669. // rows before synchronization
  119670. if (lockedHeights.length && normalHeights.length) {
  119671. lockedView = me.lockedGrid.getView();
  119672. normalView = me.normalGrid.getView();
  119673. lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
  119674. normalRowEls = normalView.el.query(normalView.getItemSelector());
  119675. // loop thru all of the heights and sync to the other side
  119676. for (; i < ln; i++) {
  119677. // ensure both are numbers
  119678. if (!isNaN(lockedHeights[i]) && !isNaN(normalHeights[i])) {
  119679. if (lockedHeights[i] > normalHeights[i]) {
  119680. Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
  119681. } else if (lockedHeights[i] < normalHeights[i]) {
  119682. Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
  119683. }
  119684. }
  119685. }
  119686. // Synchronize the scrollTop positions of the two views
  119687. scrollTop = normalView.el.dom.scrollTop;
  119688. normalView.el.dom.scrollTop = scrollTop;
  119689. lockedView.el.dom.scrollTop = scrollTop;
  119690. // reset the heights
  119691. me.lockedHeights = [];
  119692. me.normalHeights = [];
  119693. }
  119694. },
  119695. // inject Lock and Unlock text
  119696. modifyHeaderCt: function() {
  119697. var me = this;
  119698. me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(me.lockedGrid.headerCt.getMenuItems, true);
  119699. me.normalGrid.headerCt.getMenuItems = me.getMenuItems(me.normalGrid.headerCt.getMenuItems, false);
  119700. },
  119701. onUnlockMenuClick: function() {
  119702. this.unlock();
  119703. },
  119704. onLockMenuClick: function() {
  119705. this.lock();
  119706. },
  119707. getMenuItems: function(getMenuItems, locked) {
  119708. var me = this,
  119709. unlockText = me.unlockText,
  119710. lockText = me.lockText,
  119711. unlockCls = Ext.baseCSSPrefix + 'hmenu-unlock',
  119712. lockCls = Ext.baseCSSPrefix + 'hmenu-lock',
  119713. unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
  119714. lockHandler = Ext.Function.bind(me.onLockMenuClick, me);
  119715. // runs in the scope of headerCt
  119716. return function() {
  119717. // We cannot use the method from HeaderContainer's prototype here
  119718. // because other plugins or features may already have injected an implementation
  119719. var o = getMenuItems.call(this);
  119720. o.push('-', {
  119721. cls: unlockCls,
  119722. text: unlockText,
  119723. handler: unlockHandler,
  119724. disabled: !locked
  119725. });
  119726. o.push({
  119727. cls: lockCls,
  119728. text: lockText,
  119729. handler: lockHandler,
  119730. disabled: locked
  119731. });
  119732. return o;
  119733. };
  119734. },
  119735. // going from unlocked section to locked
  119736. /**
  119737. * Locks the activeHeader as determined by which menu is open OR a header
  119738. * as specified.
  119739. * @param {Ext.grid.column.Column} [header] Header to unlock from the locked section.
  119740. * Defaults to the header which has the menu open currently.
  119741. * @param {Number} [toIdx] The index to move the unlocked header to.
  119742. * Defaults to appending as the last item.
  119743. * @private
  119744. */
  119745. lock: function(activeHd, toIdx) {
  119746. var me = this,
  119747. normalGrid = me.normalGrid,
  119748. lockedGrid = me.lockedGrid,
  119749. normalHCt = normalGrid.headerCt,
  119750. lockedHCt = lockedGrid.headerCt;
  119751. activeHd = activeHd || normalHCt.getMenu().activeHeader;
  119752. // if column was previously flexed, get/set current width
  119753. // and remove the flex
  119754. if (activeHd.flex) {
  119755. activeHd.width = activeHd.getWidth();
  119756. delete activeHd.flex;
  119757. }
  119758. Ext.suspendLayouts();
  119759. activeHd.ownerCt.remove(activeHd, false);
  119760. activeHd.locked = true;
  119761. if (Ext.isDefined(toIdx)) {
  119762. lockedHCt.insert(toIdx, activeHd);
  119763. } else {
  119764. lockedHCt.add(activeHd);
  119765. }
  119766. me.syncLockedSection();
  119767. Ext.resumeLayouts(true);
  119768. me.updateSpacer();
  119769. me.fireEvent('lockcolumn', me, activeHd);
  119770. },
  119771. syncLockedSection: function() {
  119772. var me = this;
  119773. me.syncLockedWidth();
  119774. me.lockedGrid.getView().refresh();
  119775. me.normalGrid.getView().refresh();
  119776. },
  119777. // adjust the locked section to the width of its respective
  119778. // headerCt
  119779. syncLockedWidth: function() {
  119780. var me = this,
  119781. locked = me.lockedGrid,
  119782. width = locked.headerCt.getFullWidth(true);
  119783. Ext.suspendLayouts();
  119784. if (width > 0) {
  119785. locked.setWidth(width);
  119786. locked.show();
  119787. } else {
  119788. locked.hide();
  119789. }
  119790. Ext.resumeLayouts(true);
  119791. return width > 0;
  119792. },
  119793. onLockedHeaderResize: function() {
  119794. this.syncLockedWidth();
  119795. },
  119796. onLockedHeaderHide: function() {
  119797. this.syncLockedWidth();
  119798. },
  119799. onLockedHeaderShow: function() {
  119800. this.syncLockedWidth();
  119801. },
  119802. onLockedHeaderSortChange: function(headerCt, header, sortState) {
  119803. if (sortState) {
  119804. // no real header, and silence the event so we dont get into an
  119805. // infinite loop
  119806. this.normalGrid.headerCt.clearOtherSortStates(null, true);
  119807. }
  119808. },
  119809. onNormalHeaderSortChange: function(headerCt, header, sortState) {
  119810. if (sortState) {
  119811. // no real header, and silence the event so we dont get into an
  119812. // infinite loop
  119813. this.lockedGrid.headerCt.clearOtherSortStates(null, true);
  119814. }
  119815. },
  119816. // going from locked section to unlocked
  119817. /**
  119818. * Unlocks the activeHeader as determined by which menu is open OR a header
  119819. * as specified.
  119820. * @param {Ext.grid.column.Column} [header] Header to unlock from the locked section.
  119821. * Defaults to the header which has the menu open currently.
  119822. * @param {Number} [toIdx=0] The index to move the unlocked header to.
  119823. * @private
  119824. */
  119825. unlock: function(activeHd, toIdx) {
  119826. var me = this,
  119827. normalGrid = me.normalGrid,
  119828. lockedGrid = me.lockedGrid,
  119829. normalHCt = normalGrid.headerCt,
  119830. lockedHCt = lockedGrid.headerCt,
  119831. refreshLocked = false;
  119832. if (!Ext.isDefined(toIdx)) {
  119833. toIdx = 0;
  119834. }
  119835. activeHd = activeHd || lockedHCt.getMenu().activeHeader;
  119836. Ext.suspendLayouts();
  119837. activeHd.ownerCt.remove(activeHd, false);
  119838. if (me.syncLockedWidth()) {
  119839. refreshLocked = true;
  119840. }
  119841. activeHd.locked = false;
  119842. // Refresh the locked section first in case it was empty
  119843. normalHCt.insert(toIdx, activeHd);
  119844. me.normalGrid.getView().refresh();
  119845. if (refreshLocked) {
  119846. me.lockedGrid.getView().refresh();
  119847. }
  119848. Ext.resumeLayouts(true);
  119849. me.fireEvent('unlockcolumn', me, activeHd);
  119850. },
  119851. applyColumnsState: function (columns) {
  119852. var me = this,
  119853. lockedGrid = me.lockedGrid,
  119854. lockedHeaderCt = lockedGrid.headerCt,
  119855. normalHeaderCt = me.normalGrid.headerCt,
  119856. lockedCols = Ext.Array.toMap(lockedHeaderCt.items, 'headerId'),
  119857. normalCols = Ext.Array.toMap(normalHeaderCt.items, 'headerId'),
  119858. locked = [],
  119859. normal = [],
  119860. lockedWidth = 1,
  119861. length = columns.length,
  119862. i, existing,
  119863. lockedDefault,
  119864. col;
  119865. for (i = 0; i < length; i++) {
  119866. col = columns[i];
  119867. lockedDefault = lockedCols[col.id];
  119868. existing = lockedDefault || normalCols[col.id];
  119869. if (existing) {
  119870. if (existing.applyColumnState) {
  119871. existing.applyColumnState(col);
  119872. }
  119873. if (existing.locked === undefined) {
  119874. existing.locked = !!lockedDefault;
  119875. }
  119876. if (existing.locked) {
  119877. locked.push(existing);
  119878. if (!existing.hidden && typeof existing.width == 'number') {
  119879. lockedWidth += existing.width;
  119880. }
  119881. } else {
  119882. normal.push(existing);
  119883. }
  119884. }
  119885. }
  119886. // state and config must have the same columns (compare counts for now):
  119887. if (locked.length + normal.length == lockedHeaderCt.items.getCount() + normalHeaderCt.items.getCount()) {
  119888. lockedHeaderCt.removeAll(false);
  119889. normalHeaderCt.removeAll(false);
  119890. lockedHeaderCt.add(locked);
  119891. normalHeaderCt.add(normal);
  119892. lockedGrid.setWidth(lockedWidth);
  119893. }
  119894. },
  119895. getColumnsState: function () {
  119896. var me = this,
  119897. locked = me.lockedGrid.headerCt.getColumnsState(),
  119898. normal = me.normalGrid.headerCt.getColumnsState();
  119899. return locked.concat(normal);
  119900. },
  119901. // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
  119902. reconfigureLockable: function(store, columns) {
  119903. var me = this,
  119904. lockedGrid = me.lockedGrid,
  119905. normalGrid = me.normalGrid;
  119906. if (columns) {
  119907. Ext.suspendLayouts();
  119908. lockedGrid.headerCt.removeAll();
  119909. normalGrid.headerCt.removeAll();
  119910. columns = me.processColumns(columns);
  119911. lockedGrid.setWidth(columns.lockedWidth);
  119912. lockedGrid.headerCt.add(columns.locked.items);
  119913. normalGrid.headerCt.add(columns.normal.items);
  119914. Ext.resumeLayouts(true);
  119915. }
  119916. if (store) {
  119917. store = Ext.data.StoreManager.lookup(store);
  119918. me.store = store;
  119919. lockedGrid.bindStore(store);
  119920. normalGrid.bindStore(store);
  119921. } else {
  119922. lockedGrid.getView().refresh();
  119923. normalGrid.getView().refresh();
  119924. }
  119925. },
  119926. /**
  119927. * Clones items in the features array if they are instantiated Features. If an item
  119928. * is just a feature config, it leaves it alone.
  119929. *
  119930. * This is so that features can be replicated on both sides of the LockingView
  119931. *
  119932. */
  119933. cloneFeatures: function() {
  119934. var me = this,
  119935. features = me.features,
  119936. feature,
  119937. i = 0, len;
  119938. if (features) {
  119939. len = features.length;
  119940. for (; i < len; i++) {
  119941. feature = features[i];
  119942. if (feature.isFeature) {
  119943. features[i] = feature.clone();
  119944. }
  119945. }
  119946. }
  119947. },
  119948. /**
  119949. * Clones items in the plugins array if they are instantiated Plugins. If an item
  119950. * is just a plugin config, it leaves it alone.
  119951. *
  119952. * This is so that plugins can be replicated on both sides of the LockingView
  119953. *
  119954. */
  119955. clonePlugins: function() {
  119956. var me = this,
  119957. plugins = me.plugins,
  119958. plugin,
  119959. i = 0, len;
  119960. if (plugins) {
  119961. len = plugins.length;
  119962. for (; i < len; i++) {
  119963. plugin = plugins[i];
  119964. if (typeof plugin.init === 'function') {
  119965. plugins[i] = plugin.clone();
  119966. }
  119967. }
  119968. }
  119969. }
  119970. }, function() {
  119971. this.borrow(Ext.view.Table, ['constructFeatures']);
  119972. this.borrow(Ext.AbstractComponent, ['constructPlugins', 'constructPlugin']);
  119973. });
  119974. /**
  119975. * Implements infinite scrolling of a grid, allowing users can scroll
  119976. * through thousands of records without the performance penalties of
  119977. * renderering all the records on screen at once. The grid should be
  119978. * bound to a *buffered* store with a pageSize specified.
  119979. *
  119980. * The number of rows rendered outside the visible area, and the
  119981. * buffering of pages of data from the remote server for immediate
  119982. * rendering upon scroll can be controlled by configuring the
  119983. * {@link Ext.grid.PagingScroller #verticalScroller}.
  119984. *
  119985. * You can tell it to create a larger table to provide more scrolling
  119986. * before a refresh is needed, and also to keep more pages of records
  119987. * in memory for faster refreshing when scrolling.
  119988. *
  119989. * var myStore = Ext.create('Ext.data.Store', {
  119990. * // ...
  119991. * buffered: true,
  119992. * pageSize: 100,
  119993. * // ...
  119994. * });
  119995. *
  119996. * var grid = Ext.create('Ext.grid.Panel', {
  119997. * // ...
  119998. * autoLoad: true,
  119999. * verticalScroller: {
  120000. * trailingBufferZone: 200, // Keep 200 records buffered in memory behind scroll
  120001. * leadingBufferZone: 5000 // Keep 5000 records buffered in memory ahead of scroll
  120002. * },
  120003. * // ...
  120004. * });
  120005. *
  120006. * ## Implementation notes
  120007. *
  120008. * This class monitors scrolling of the {@link Ext.view.Table
  120009. * TableView} within a {@link Ext.grid.Panel GridPanel} which is using
  120010. * a buffered store to only cache and render a small section of a very
  120011. * large dataset.
  120012. *
  120013. * **NB!** The GridPanel will instantiate this to perform monitoring,
  120014. * this class should never be instantiated by user code. Always use the
  120015. * {@link Ext.panel.Table#verticalScroller verticalScroller} config.
  120016. *
  120017. */
  120018. Ext.define('Ext.grid.PagingScroller', {
  120019. /**
  120020. * @cfg
  120021. * @deprecated This config is now ignored.
  120022. */
  120023. percentageFromEdge: 0.35,
  120024. /**
  120025. * @cfg
  120026. * The zone which causes a refresh of the rendered viewport. As soon as the edge
  120027. * of the rendered grid is this number of rows from the edge of the viewport, the view is moved.
  120028. */
  120029. numFromEdge: 2,
  120030. /**
  120031. * @cfg
  120032. * The number of extra rows to render on the trailing side of scrolling
  120033. * **outside the {@link #numFromEdge}** buffer as scrolling proceeds.
  120034. */
  120035. trailingBufferZone: 5,
  120036. /**
  120037. * @cfg
  120038. * The number of extra rows to render on the leading side of scrolling
  120039. * **outside the {@link #numFromEdge}** buffer as scrolling proceeds.
  120040. */
  120041. leadingBufferZone: 15,
  120042. /**
  120043. * @cfg
  120044. * This is the time in milliseconds to buffer load requests when scrolling the PagingScrollbar.
  120045. */
  120046. scrollToLoadBuffer: 200,
  120047. // private. Initial value of zero.
  120048. viewSize: 0,
  120049. // private. Start at default value
  120050. rowHeight: 21,
  120051. // private. Table extent at startup time
  120052. tableStart: 0,
  120053. tableEnd: 0,
  120054. constructor: function(config) {
  120055. var me = this;
  120056. me.variableRowHeight = config.variableRowHeight;
  120057. me.bindView(config.view);
  120058. Ext.apply(me, config);
  120059. me.callParent(arguments);
  120060. },
  120061. bindView: function(view) {
  120062. var me = this,
  120063. viewListeners = {
  120064. scroll: {
  120065. fn: me.onViewScroll,
  120066. element: 'el',
  120067. scope: me
  120068. },
  120069. render: me.onViewRender,
  120070. resize: me.onViewResize,
  120071. boxready: {
  120072. fn: me.onViewResize,
  120073. scope: me,
  120074. single: true
  120075. },
  120076. // If there are variable row heights, then in beforeRefresh, we have to find a common
  120077. // row so that we can synchronize the table's top position after the refresh.
  120078. // Also flag whether the grid view has focus so that it can be refocused after refresh.
  120079. beforerefresh: me.beforeViewRefresh,
  120080. refresh: me.onViewRefresh,
  120081. scope: me
  120082. },
  120083. storeListeners = {
  120084. guaranteedrange: me.onGuaranteedRange,
  120085. scope: me
  120086. },
  120087. gridListeners = {
  120088. reconfigure: me.onGridReconfigure,
  120089. scope: me
  120090. }, partner;
  120091. // If we need unbinding...
  120092. if (me.view) {
  120093. if (me.view.el) {
  120094. me.view.el.un('scroll', me.onViewScroll, me); // un does not understand the element options
  120095. }
  120096. partner = view.lockingPartner;
  120097. if (partner) {
  120098. partner.un('refresh', me.onLockRefresh, me);
  120099. }
  120100. me.view.un(viewListeners);
  120101. me.store.un(storeListeners);
  120102. if (me.grid) {
  120103. me.grid.un(gridListeners);
  120104. }
  120105. delete me.view.refreshSize; // Remove the injected refreshSize implementation
  120106. }
  120107. me.view = view;
  120108. me.grid = me.view.up('tablepanel');
  120109. me.store = view.store;
  120110. if (view.rendered) {
  120111. me.viewSize = me.store.viewSize = Math.ceil(view.getHeight() / me.rowHeight) + me.trailingBufferZone + (me.numFromEdge * 2) + me.leadingBufferZone;
  120112. }
  120113. partner = view.lockingPartner;
  120114. if (partner) {
  120115. partner.on('refresh', me.onLockRefresh, me);
  120116. }
  120117. me.view.mon(me.store.pageMap, {
  120118. scope: me,
  120119. clear: me.onCacheClear
  120120. });
  120121. // During scrolling we do not need to refresh the height - the Grid height must be set by config or layout in order to create a scrollable
  120122. // table just larger than that, so removing the layout call improves efficiency and removes the flicker when the
  120123. // HeaderContainer is reset to scrollLeft:0, and then resynced on the very next "scroll" event.
  120124. me.view.refreshSize = Ext.Function.createInterceptor(me.view.refreshSize, me.beforeViewrefreshSize, me);
  120125. /**
  120126. * @property {Number} position
  120127. * Current pixel scroll position of the associated {@link Ext.view.Table View}.
  120128. */
  120129. me.position = 0;
  120130. // We are created in View constructor. There won't be an ownerCt at this time.
  120131. if (me.grid) {
  120132. me.grid.on(gridListeners);
  120133. } else {
  120134. me.view.on({
  120135. added: function() {
  120136. me.grid = me.view.up('tablepanel');
  120137. me.grid.on(gridListeners);
  120138. },
  120139. single: true
  120140. });
  120141. }
  120142. me.view.on(me.viewListeners = viewListeners);
  120143. me.store.on(storeListeners);
  120144. },
  120145. onCacheClear: function() {
  120146. var me = this;
  120147. // Do not do anything if view is not rendered, or if the reason for cache clearing is store destruction
  120148. if (me.view.rendered && !me.store.isDestroyed) {
  120149. // Temporarily disable scroll monitoring until the scroll event caused by any following *change* of scrollTop has fired.
  120150. // Otherwise it will attempt to process a scroll on a stale view
  120151. me.ignoreNextScrollEvent = me.view.el.dom.scrollTop !== 0;
  120152. me.view.el.dom.scrollTop = 0;
  120153. delete me.lastScrollDirection;
  120154. delete me.scrollOffset;
  120155. delete me.scrollProportion;
  120156. }
  120157. },
  120158. onGridReconfigure: function (grid) {
  120159. this.bindView(grid.view);
  120160. },
  120161. // Ensure that the stretcher element is inserted into the View as the first element.
  120162. onViewRender: function() {
  120163. var me = this,
  120164. view = me.view,
  120165. el = me.view.el,
  120166. stretcher;
  120167. me.stretcher = me.createStretcher(view);
  120168. view = view.lockingPartner;
  120169. if (view) {
  120170. stretcher = me.stretcher;
  120171. me.stretcher = new Ext.CompositeElement(stretcher);
  120172. me.stretcher.add(me.createStretcher(view));
  120173. }
  120174. },
  120175. createStretcher: function(view) {
  120176. var el = view.el;
  120177. el.setStyle('position', 'relative');
  120178. return el.createChild({
  120179. style:{
  120180. position: 'absolute',
  120181. width: '1px',
  120182. height: 0,
  120183. top: 0,
  120184. left: 0
  120185. }
  120186. }, el.dom.firstChild);
  120187. },
  120188. onViewResize: function(view, width, height) {
  120189. var me = this,
  120190. newViewSize;
  120191. newViewSize = Math.ceil(height / me.rowHeight) + me.trailingBufferZone + (me.numFromEdge * 2) + me.leadingBufferZone;
  120192. if (newViewSize > me.viewSize) {
  120193. me.viewSize = me.store.viewSize = newViewSize;
  120194. me.handleViewScroll(me.lastScrollDirection || 1);
  120195. }
  120196. },
  120197. // Used for variable row heights. Try to find the offset from scrollTop of a common row
  120198. beforeViewRefresh: function() {
  120199. var me = this,
  120200. view = me.view,
  120201. rows,
  120202. direction;
  120203. // Refreshing can cause loss of focus.
  120204. me.focusOnRefresh = Ext.Element.getActiveElement === view.el.dom;
  120205. // Only need all this is variableRowHeight
  120206. if (me.variableRowHeight) {
  120207. direction = me.lastScrollDirection;
  120208. me.commonRecordIndex = undefined;
  120209. // If we are refreshing in response to a scroll,
  120210. // And we know where the previous start was,
  120211. // and we're not teleporting out of visible range
  120212. // and the view is not empty
  120213. if (direction && (me.previousStart !== undefined) && (me.scrollProportion === undefined) && (rows = view.getNodes()).length) {
  120214. // We have scrolled downwards
  120215. if (direction === 1) {
  120216. // If the ranges overlap, we are going to be able to position the table exactly
  120217. if (me.tableStart <= me.previousEnd) {
  120218. me.commonRecordIndex = rows.length - 1;
  120219. }
  120220. }
  120221. // We have scrolled upwards
  120222. else if (direction === -1) {
  120223. // If the ranges overlap, we are going to be able to position the table exactly
  120224. if (me.tableEnd >= me.previousStart) {
  120225. me.commonRecordIndex = 0;
  120226. }
  120227. }
  120228. // Cache the old offset of the common row from the scrollTop
  120229. me.scrollOffset = -view.el.getOffsetsTo(rows[me.commonRecordIndex])[1];
  120230. // In the new table the common row is at a different index
  120231. me.commonRecordIndex -= (me.tableStart - me.previousStart);
  120232. } else {
  120233. me.scrollOffset = undefined;
  120234. }
  120235. }
  120236. },
  120237. onLockRefresh: function(view) {
  120238. view.table.dom.style.position = 'absolute';
  120239. },
  120240. // Used for variable row heights. Try to find the offset from scrollTop of a common row
  120241. // Ensure, upon each refresh, that the stretcher element is the correct height
  120242. onViewRefresh: function() {
  120243. var me = this,
  120244. store = me.store,
  120245. newScrollHeight,
  120246. view = me.view,
  120247. viewEl = view.el,
  120248. viewDom = viewEl.dom,
  120249. rows,
  120250. newScrollOffset,
  120251. scrollDelta,
  120252. table = view.table.dom,
  120253. tableTop,
  120254. scrollTop;
  120255. // Refresh causes loss of focus
  120256. if (me.focusOnRefresh) {
  120257. viewEl.focus();
  120258. me.focusOnRefresh = false;
  120259. }
  120260. // Scroll events caused by processing in here must be ignored, so disable for the duration
  120261. me.disabled = true;
  120262. // No scroll monitoring is needed if
  120263. // All data is in view OR
  120264. // Store is filtered locally.
  120265. // - scrolling a locally filtered page is obv a local operation within the context of a huge set of pages
  120266. // so local scrolling is appropriate.
  120267. if (store.getCount() === store.getTotalCount() || (store.isFiltered() && !store.remoteFilter)) {
  120268. me.stretcher.setHeight(0);
  120269. me.position = viewDom.scrollTop = 0;
  120270. // Chrome's scrolling went crazy upon zeroing of the stretcher, and left the view's scrollTop stuck at -15
  120271. // This is the only thing that fixes that
  120272. me.setTablePosition('absolute');
  120273. // We remain disabled now because no scrolling is needed - we have the full dataset in the Store
  120274. return;
  120275. }
  120276. me.stretcher.setHeight(newScrollHeight = me.getScrollHeight());
  120277. scrollTop = viewDom.scrollTop;
  120278. // Flag to the refreshSize interceptor that regular refreshSize postprocessing should be vetoed.
  120279. me.isScrollRefresh = (scrollTop > 0);
  120280. // If we have had to calculate the store position from the pure scroll bar position,
  120281. // then we must calculate the table's vertical position from the scrollProportion
  120282. if (me.scrollProportion !== undefined) {
  120283. me.setTablePosition('absolute');
  120284. me.setTableTop((me.scrollProportion ? (newScrollHeight * me.scrollProportion) - (table.offsetHeight * me.scrollProportion) : 0) + 'px');
  120285. } else {
  120286. me.setTablePosition('absolute');
  120287. me.setTableTop((tableTop = (me.tableStart||0) * me.rowHeight) + 'px');
  120288. // ScrollOffset to a common row was calculated in beforeViewRefresh, so we can synch table position with how it was before
  120289. if (me.scrollOffset) {
  120290. rows = view.getNodes();
  120291. newScrollOffset = -viewEl.getOffsetsTo(rows[me.commonRecordIndex])[1];
  120292. scrollDelta = newScrollOffset - me.scrollOffset;
  120293. me.position = (viewDom.scrollTop += scrollDelta);
  120294. }
  120295. // If the table is not fully in view view, scroll to where it is in view.
  120296. // This will happen when the page goes out of view unexpectedly, outside the
  120297. // control of the PagingScroller. For example, a refresh caused by a remote sort or filter reverting
  120298. // back to page 1.
  120299. // Note that with buffered Stores, only remote sorting is allowed, otherwise the locally
  120300. // sorted page will be out of order with the whole dataset.
  120301. else if ((tableTop > scrollTop) || ((tableTop + table.offsetHeight) < scrollTop + viewDom.clientHeight)) {
  120302. me.lastScrollDirection = -1;
  120303. me.position = viewDom.scrollTop = tableTop;
  120304. }
  120305. }
  120306. // Re-enable upon function exit
  120307. me.disabled = false;
  120308. },
  120309. setTablePosition: function(position) {
  120310. this.setViewTableStyle(this.view, 'position', position);
  120311. },
  120312. setTableTop: function(top){
  120313. this.setViewTableStyle(this.view, 'top', top);
  120314. },
  120315. setViewTableStyle: function(view, prop, value) {
  120316. view.el.child('table', true).style[prop] = value;
  120317. view = view.lockingPartner;
  120318. if (view) {
  120319. view.el.child('table', true).style[prop] = value;
  120320. }
  120321. },
  120322. beforeViewrefreshSize: function() {
  120323. // Veto the refreshSize if the refresh is due to a scroll.
  120324. if (this.isScrollRefresh) {
  120325. // If we're vetoing refreshSize, attach the table DOM to the View's Flyweight.
  120326. this.view.table.attach(this.view.el.child('table', true));
  120327. return (this.isScrollRefresh = false);
  120328. }
  120329. },
  120330. onGuaranteedRange: function(range, start, end) {
  120331. var me = this,
  120332. ds = me.store;
  120333. // this should never happen
  120334. if (range.length && me.visibleStart < range[0].index) {
  120335. return;
  120336. }
  120337. // Cache last table position in dataset so that if we are using variableRowHeight,
  120338. // we can attempt to locate a common row to align the table on.
  120339. me.previousStart = me.tableStart;
  120340. me.previousEnd = me.tableEnd;
  120341. me.tableStart = start;
  120342. me.tableEnd = end;
  120343. ds.loadRecords(range, {
  120344. start: start
  120345. });
  120346. },
  120347. onViewScroll: function(e, t) {
  120348. var me = this,
  120349. view = me.view,
  120350. lastPosition = me.position;
  120351. me.position = view.el.dom.scrollTop;
  120352. // Flag set when the scrollTop is programatically set to zero upon cache clear.
  120353. // We must not attempt to process that as a scroll event.
  120354. if (me.ignoreNextScrollEvent) {
  120355. me.ignoreNextScrollEvent = false;
  120356. return;
  120357. }
  120358. // Only check for nearing the edge if we are enabled.
  120359. // If there is no paging to be done (Store's dataset is all in memory) we will be disabled.
  120360. if (!me.disabled) {
  120361. me.lastScrollDirection = me.position > lastPosition ? 1 : -1;
  120362. // Check the position so we ignore horizontal scrolling
  120363. if (lastPosition !== me.position) {
  120364. me.handleViewScroll(me.lastScrollDirection);
  120365. }
  120366. }
  120367. },
  120368. handleViewScroll: function(direction) {
  120369. var me = this,
  120370. store = me.store,
  120371. view = me.view,
  120372. viewSize = me.viewSize,
  120373. totalCount = store.getTotalCount(),
  120374. highestStartPoint = totalCount - viewSize,
  120375. visibleStart = me.getFirstVisibleRowIndex(),
  120376. visibleEnd = me.getLastVisibleRowIndex(),
  120377. el = view.el.dom,
  120378. requestStart,
  120379. requestEnd;
  120380. // Only process if the total rows is larger than the visible page size
  120381. if (totalCount >= viewSize) {
  120382. // This is only set if we are using variable row height, and the thumb is dragged so that
  120383. // There are no remaining visible rows to vertically anchor the new table to.
  120384. // In this case we use the scrollProprtion to anchor the table to the correct relative
  120385. // position on the vertical axis.
  120386. me.scrollProportion = undefined;
  120387. // We're scrolling up
  120388. if (direction == -1) {
  120389. // If table starts at record zero, we have nothing to do
  120390. if (me.tableStart) {
  120391. if (visibleStart !== undefined) {
  120392. if (visibleStart < (me.tableStart + me.numFromEdge)) {
  120393. requestStart = Math.max(0, visibleEnd + me.trailingBufferZone - viewSize);
  120394. }
  120395. }
  120396. // The only way we can end up without a visible start is if, in variableRowHeight mode, the user drags
  120397. // the thumb up out of the visible range. In this case, we have to estimate the start row index
  120398. else {
  120399. // If we have no visible rows to orientate with, then use the scroll proportion
  120400. me.scrollProportion = el.scrollTop / (el.scrollHeight - el.clientHeight);
  120401. requestStart = Math.max(0, totalCount * me.scrollProportion - (viewSize / 2) - me.numFromEdge - ((me.leadingBufferZone + me.trailingBufferZone) / 2));
  120402. }
  120403. }
  120404. }
  120405. // We're scrolling down
  120406. else {
  120407. if (visibleStart !== undefined) {
  120408. if (visibleEnd > (me.tableEnd - me.numFromEdge)) {
  120409. requestStart = Math.max(0, visibleStart - me.trailingBufferZone);
  120410. }
  120411. }
  120412. // The only way we can end up without a visible end is if, in variableRowHeight mode, the user drags
  120413. // the thumb down out of the visible range. In this case, we have to estimate the start row index
  120414. else {
  120415. // If we have no visible rows to orientate with, then use the scroll proportion
  120416. me.scrollProportion = el.scrollTop / (el.scrollHeight - el.clientHeight);
  120417. requestStart = totalCount * me.scrollProportion - (viewSize / 2) - me.numFromEdge - ((me.leadingBufferZone + me.trailingBufferZone) / 2);
  120418. }
  120419. }
  120420. // We scrolled close to the edge and the Store needs reloading
  120421. if (requestStart !== undefined) {
  120422. // The calculation walked off the end; Request the highest possible chunk which starts on an even row count (Because of row striping)
  120423. if (requestStart > highestStartPoint) {
  120424. requestStart = highestStartPoint & ~1;
  120425. requestEnd = totalCount - 1;
  120426. }
  120427. // Make sure first row is even to ensure correct even/odd row striping
  120428. else {
  120429. requestStart = requestStart & ~1;
  120430. requestEnd = requestStart + viewSize - 1;
  120431. }
  120432. // If range is satsfied within the prefetch buffer, then just draw it from the prefetch buffer
  120433. if (store.rangeCached(requestStart, requestEnd)) {
  120434. me.cancelLoad();
  120435. store.guaranteeRange(requestStart, requestEnd);
  120436. }
  120437. // Required range is not in the prefetch buffer. Ask the store to prefetch it.
  120438. // We will recieve a guaranteedrange event when that is done.
  120439. else {
  120440. me.attemptLoad(requestStart, requestEnd);
  120441. }
  120442. }
  120443. }
  120444. },
  120445. getFirstVisibleRowIndex: function() {
  120446. var me = this,
  120447. view = me.view,
  120448. scrollTop = view.el.dom.scrollTop,
  120449. rows,
  120450. count,
  120451. i,
  120452. rowBottom;
  120453. if (me.variableRowHeight) {
  120454. rows = view.getNodes();
  120455. count = rows.length;
  120456. if (!count) {
  120457. return;
  120458. }
  120459. rowBottom = Ext.fly(rows[0]).getOffsetsTo(view.el)[1];
  120460. for (i = 0; i < count; i++) {
  120461. rowBottom += rows[i].offsetHeight;
  120462. // Searching for the first visible row, and off the bottom of the clientArea, then there's no visible first row!
  120463. if (rowBottom > view.el.dom.clientHeight) {
  120464. return;
  120465. }
  120466. // Return the index *within the total dataset* of the first visible row
  120467. // We cannot use the loop index to offset from the table's start index because of possible intervening group headers.
  120468. if (rowBottom > 0) {
  120469. return view.getRecord(rows[i]).index;
  120470. }
  120471. }
  120472. } else {
  120473. return Math.floor(scrollTop / me.rowHeight);
  120474. }
  120475. },
  120476. getLastVisibleRowIndex: function() {
  120477. var me = this,
  120478. store = me.store,
  120479. view = me.view,
  120480. clientHeight = view.el.dom.clientHeight,
  120481. rows,
  120482. count,
  120483. i,
  120484. rowTop;
  120485. if (me.variableRowHeight) {
  120486. rows = view.getNodes();
  120487. if (!rows.length) {
  120488. return;
  120489. }
  120490. count = store.getCount() - 1;
  120491. rowTop = Ext.fly(rows[count]).getOffsetsTo(view.el)[1] + rows[count].offsetHeight;
  120492. for (i = count; i >= 0; i--) {
  120493. rowTop -= rows[i].offsetHeight;
  120494. // Searching for the last visible row, and off the top of the clientArea, then there's no visible last row!
  120495. if (rowTop < 0) {
  120496. return;
  120497. }
  120498. // Return the index *within the total dataset* of the last visible row.
  120499. // We cannot use the loop index to offset from the table's start index because of possible intervening group headers.
  120500. if (rowTop < clientHeight) {
  120501. return view.getRecord(rows[i]).index;
  120502. }
  120503. }
  120504. } else {
  120505. return me.getFirstVisibleRowIndex() + Math.ceil(clientHeight / me.rowHeight) + 1;
  120506. }
  120507. },
  120508. getScrollHeight: function() {
  120509. var me = this,
  120510. view = me.view,
  120511. table,
  120512. firstRow,
  120513. store = me.store,
  120514. deltaHeight = 0,
  120515. doCalcHeight = !me.hasOwnProperty('rowHeight');
  120516. if (me.variableRowHeight) {
  120517. table = me.view.table.dom;
  120518. if (doCalcHeight) {
  120519. me.initialTableHeight = table.offsetHeight;
  120520. me.rowHeight = me.initialTableHeight / me.store.getCount();
  120521. } else {
  120522. deltaHeight = table.offsetHeight - me.initialTableHeight;
  120523. // Store size has been bumped because of odd end row.
  120524. if (store.getCount() > me.viewSize) {
  120525. deltaHeight -= me.rowHeight;
  120526. }
  120527. }
  120528. } else if (doCalcHeight) {
  120529. firstRow = view.el.down(view.getItemSelector());
  120530. if (firstRow) {
  120531. me.rowHeight = firstRow.getHeight(false, true);
  120532. }
  120533. }
  120534. return Math.floor(store.getTotalCount() * me.rowHeight) + deltaHeight;
  120535. },
  120536. attemptLoad: function(start, end) {
  120537. var me = this;
  120538. if (me.scrollToLoadBuffer) {
  120539. if (!me.loadTask) {
  120540. me.loadTask = new Ext.util.DelayedTask(me.doAttemptLoad, me, []);
  120541. }
  120542. me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
  120543. } else {
  120544. me.store.guaranteeRange(start, end);
  120545. }
  120546. },
  120547. cancelLoad: function() {
  120548. if (this.loadTask) {
  120549. this.loadTask.cancel();
  120550. }
  120551. },
  120552. doAttemptLoad: function(start, end) {
  120553. this.store.guaranteeRange(start, end);
  120554. },
  120555. destroy: function() {
  120556. var me = this,
  120557. scrollListener = me.viewListeners.scroll;
  120558. me.store.un({
  120559. guaranteedrange: me.onGuaranteedRange,
  120560. scope: me
  120561. });
  120562. me.view.un(me.viewListeners);
  120563. if (me.view.rendered) {
  120564. me.stretcher.remove();
  120565. me.view.el.un('scroll', scrollListener.fn, scrollListener.scope);
  120566. }
  120567. }
  120568. });
  120569. /**
  120570. * This is a base class for layouts that contain a single item that automatically expands to fill the layout's
  120571. * container. This class is intended to be extended or created via the layout:'fit'
  120572. * {@link Ext.container.Container#layout} config, and should generally not need to be created directly via the new keyword.
  120573. *
  120574. * Fit layout does not have any direct config options (other than inherited ones). To fit a panel to a container using
  120575. * Fit layout, simply set `layout: 'fit'` on the container and add a single panel to it.
  120576. *
  120577. * @example
  120578. * Ext.create('Ext.panel.Panel', {
  120579. * title: 'Fit Layout',
  120580. * width: 300,
  120581. * height: 150,
  120582. * layout:'fit',
  120583. * items: {
  120584. * title: 'Inner Panel',
  120585. * html: 'This is the inner panel content',
  120586. * bodyPadding: 20,
  120587. * border: false
  120588. * },
  120589. * renderTo: Ext.getBody()
  120590. * });
  120591. *
  120592. * If the container has multiple items, all of the items will all be equally sized. This is usually not
  120593. * desired, so to avoid this, place only a **single** item in the container. This sizing of all items
  120594. * can be used to provide a background {@link Ext.Img image} that is "behind" another item
  120595. * such as a {@link Ext.view.View dataview} if you also absolutely position the items.
  120596. */
  120597. Ext.define('Ext.layout.container.Fit', {
  120598. /* Begin Definitions */
  120599. extend: 'Ext.layout.container.Container',
  120600. alternateClassName: 'Ext.layout.FitLayout',
  120601. alias: 'layout.fit',
  120602. /* End Definitions */
  120603. itemCls: Ext.baseCSSPrefix + 'fit-item',
  120604. targetCls: Ext.baseCSSPrefix + 'layout-fit',
  120605. type: 'fit',
  120606. /**
  120607. * @cfg {Object} defaultMargins
  120608. * If the individual contained items do not have a margins property specified or margin specified via CSS, the
  120609. * default margins from this property will be applied to each item.
  120610. *
  120611. * This property may be specified as an object containing margins to apply in the format:
  120612. *
  120613. * {
  120614. * top: (top margin),
  120615. * right: (right margin),
  120616. * bottom: (bottom margin),
  120617. * left: (left margin)
  120618. * }
  120619. *
  120620. * This property may also be specified as a string containing space-separated, numeric margin values. The order of
  120621. * the sides associated with each value matches the way CSS processes margin values:
  120622. *
  120623. * - If there is only one value, it applies to all sides.
  120624. * - If there are two values, the top and bottom borders are set to the first value and the right and left are
  120625. * set to the second.
  120626. * - If there are three values, the top is set to the first value, the left and right are set to the second,
  120627. * and the bottom is set to the third.
  120628. * - If there are four values, they apply to the top, right, bottom, and left, respectively.
  120629. *
  120630. */
  120631. defaultMargins: {
  120632. top: 0,
  120633. right: 0,
  120634. bottom: 0,
  120635. left: 0
  120636. },
  120637. manageMargins: true,
  120638. sizePolicies: {
  120639. 0: { setsWidth: 0, setsHeight: 0 },
  120640. 1: { setsWidth: 1, setsHeight: 0 },
  120641. 2: { setsWidth: 0, setsHeight: 1 },
  120642. 3: { setsWidth: 1, setsHeight: 1 }
  120643. },
  120644. getItemSizePolicy: function (item, ownerSizeModel) {
  120645. // this layout's sizePolicy is derived from its owner's sizeModel:
  120646. var sizeModel = ownerSizeModel || this.owner.getSizeModel(),
  120647. mode = (sizeModel.width.shrinkWrap ? 0 : 1) |
  120648. (sizeModel.height.shrinkWrap ? 0 : 2);
  120649. return this.sizePolicies[mode];
  120650. },
  120651. beginLayoutCycle: function (ownerContext, firstCycle) {
  120652. var me = this,
  120653. // determine these before the lastSizeModels get updated:
  120654. resetHeight = me.lastHeightModel && me.lastHeightModel.calculated,
  120655. resetWidth = me.lastWidthModel && me.lastWidthModel.calculated,
  120656. resetSizes = resetWidth || resetHeight,
  120657. maxChildMinHeight = 0, maxChildMinWidth = 0,
  120658. c, childItems, i, item, length, margins, minHeight, minWidth, style, undef;
  120659. me.callParent(arguments);
  120660. // Clear any dimensions which we set before calculation, in case the current
  120661. // settings affect the available size. This particularly effects self-sizing
  120662. // containers such as fields, in which the target element is naturally sized,
  120663. // and should not be stretched by a sized child item.
  120664. if (resetSizes && ownerContext.targetContext.el.dom.tagName.toUpperCase() != 'TD') {
  120665. resetSizes = resetWidth = resetHeight = false;
  120666. }
  120667. childItems = ownerContext.childItems;
  120668. length = childItems.length;
  120669. for (i = 0; i < length; ++i) {
  120670. item = childItems[i];
  120671. // On the firstCycle, we determine the max of the minWidth/Height of the items
  120672. // since these can cause the container to grow scrollbars despite our attempts
  120673. // to fit the child to the container.
  120674. if (firstCycle) {
  120675. c = item.target;
  120676. minHeight = c.minHeight;
  120677. minWidth = c.minWidth;
  120678. if (minWidth || minHeight) {
  120679. margins = item.marginInfo || item.getMarginInfo();
  120680. // if the child item has undefined minWidth/Height, these will become
  120681. // NaN by adding the margins...
  120682. minHeight += margins.height;
  120683. minWidth += margins.height;
  120684. // if the child item has undefined minWidth/Height, these comparisons
  120685. // will evaluate to false... that is, "0 < NaN" == false...
  120686. if (maxChildMinHeight < minHeight) {
  120687. maxChildMinHeight = minHeight;
  120688. }
  120689. if (maxChildMinWidth < minWidth) {
  120690. maxChildMinWidth = minWidth;
  120691. }
  120692. }
  120693. }
  120694. if (resetSizes) {
  120695. style = item.el.dom.style;
  120696. if (resetHeight) {
  120697. style.height = '';
  120698. }
  120699. if (resetWidth) {
  120700. style.width = '';
  120701. }
  120702. }
  120703. }
  120704. if (firstCycle) {
  120705. ownerContext.maxChildMinHeight = maxChildMinHeight;
  120706. ownerContext.maxChildMinWidth = maxChildMinWidth;
  120707. }
  120708. // Cache the overflowX/Y flags, but make them false in shrinkWrap mode (since we
  120709. // won't be triggering overflow in that case) and false if we have no minSize (so
  120710. // no child to trigger an overflow).
  120711. c = ownerContext.target;
  120712. ownerContext.overflowX = (!ownerContext.widthModel.shrinkWrap &&
  120713. ownerContext.maxChildMinWidth &&
  120714. (c.autoScroll || c.overflowX)) || undef;
  120715. ownerContext.overflowY = (!ownerContext.heightModel.shrinkWrap &&
  120716. ownerContext.maxChildMinHeight &&
  120717. (c.autoScroll || c.overflowY)) || undef;
  120718. },
  120719. calculate : function (ownerContext) {
  120720. var me = this,
  120721. childItems = ownerContext.childItems,
  120722. length = childItems.length,
  120723. containerSize = me.getContainerSize(ownerContext),
  120724. info = {
  120725. length: length,
  120726. ownerContext: ownerContext,
  120727. targetSize: containerSize
  120728. },
  120729. shrinkWrapWidth = ownerContext.widthModel.shrinkWrap,
  120730. shrinkWrapHeight = ownerContext.heightModel.shrinkWrap,
  120731. overflowX = ownerContext.overflowX,
  120732. overflowY = ownerContext.overflowY,
  120733. scrollbars, scrollbarSize, padding, i, contentWidth, contentHeight;
  120734. if (overflowX || overflowY) {
  120735. // If we have children that have minHeight/Width, we may be forced to overflow
  120736. // and gain scrollbars. If so, we want to remove their space from the other
  120737. // axis so that we fit things inside the scrollbars rather than under them.
  120738. scrollbars = me.getScrollbarsNeeded(
  120739. overflowX && containerSize.width, overflowY && containerSize.height,
  120740. ownerContext.maxChildMinWidth, ownerContext.maxChildMinHeight);
  120741. if (scrollbars) {
  120742. scrollbarSize = Ext.getScrollbarSize();
  120743. if (scrollbars & 1) { // if we need the hscrollbar, remove its height
  120744. containerSize.height -= scrollbarSize.height;
  120745. }
  120746. if (scrollbars & 2) { // if we need the vscrollbar, remove its width
  120747. containerSize.width -= scrollbarSize.width;
  120748. }
  120749. }
  120750. }
  120751. // Size the child items to the container (if non-shrinkWrap):
  120752. for (i = 0; i < length; ++i) {
  120753. info.index = i;
  120754. me.fitItem(childItems[i], info);
  120755. }
  120756. if (shrinkWrapHeight || shrinkWrapWidth) {
  120757. padding = ownerContext.targetContext.getPaddingInfo();
  120758. if (shrinkWrapWidth) {
  120759. if (overflowY && !containerSize.gotHeight) {
  120760. // if we might overflow vertically and don't have the container height,
  120761. // we don't know if we will need a vscrollbar or not, so we must wait
  120762. // for that height so that we can determine the contentWidth...
  120763. me.done = false;
  120764. } else {
  120765. contentWidth = info.contentWidth + padding.width;
  120766. // the scrollbar flag (if set) will indicate that an overflow exists on
  120767. // the horz(1) or vert(2) axis... if not set, then there could never be
  120768. // an overflow...
  120769. if (scrollbars & 2) { // if we need the vscrollbar, add its width
  120770. contentWidth += scrollbarSize.width;
  120771. }
  120772. if (!ownerContext.setContentWidth(contentWidth)) {
  120773. me.done = false;
  120774. }
  120775. }
  120776. }
  120777. if (shrinkWrapHeight) {
  120778. if (overflowX && !containerSize.gotWidth) {
  120779. // if we might overflow horizontally and don't have the container width,
  120780. // we don't know if we will need a hscrollbar or not, so we must wait
  120781. // for that width so that we can determine the contentHeight...
  120782. me.done = false;
  120783. } else {
  120784. contentHeight = info.contentHeight + padding.height;
  120785. // the scrollbar flag (if set) will indicate that an overflow exists on
  120786. // the horz(1) or vert(2) axis... if not set, then there could never be
  120787. // an overflow...
  120788. if (scrollbars & 1) { // if we need the hscrollbar, add its height
  120789. contentHeight += scrollbarSize.height;
  120790. }
  120791. if (!ownerContext.setContentHeight(contentHeight)) {
  120792. me.done = false;
  120793. }
  120794. }
  120795. }
  120796. }
  120797. },
  120798. fitItem: function (itemContext, info) {
  120799. var me = this;
  120800. if (itemContext.invalid) {
  120801. me.done = false;
  120802. return;
  120803. }
  120804. info.margins = itemContext.getMarginInfo();
  120805. info.needed = info.got = 0;
  120806. me.fitItemWidth(itemContext, info);
  120807. me.fitItemHeight(itemContext, info);
  120808. // If not all required dimensions have been satisfied, we're not done.
  120809. if (info.got != info.needed) {
  120810. me.done = false;
  120811. }
  120812. },
  120813. fitItemWidth: function (itemContext, info) {
  120814. var contentWidth, width;
  120815. // Attempt to set only dimensions that are being controlled, not shrinkWrap dimensions
  120816. if (info.ownerContext.widthModel.shrinkWrap) {
  120817. // contentWidth must include the margins to be consistent with setItemWidth
  120818. width = itemContext.getProp('width') + info.margins.width;
  120819. // because we add margins, width will be NaN or a number (not undefined)
  120820. contentWidth = info.contentWidth;
  120821. if (contentWidth === undefined) {
  120822. info.contentWidth = width;
  120823. } else {
  120824. info.contentWidth = Math.max(contentWidth, width);
  120825. }
  120826. } else if (itemContext.widthModel.calculated) {
  120827. ++info.needed;
  120828. if (info.targetSize.gotWidth) {
  120829. ++info.got;
  120830. this.setItemWidth(itemContext, info);
  120831. }
  120832. }
  120833. this.positionItemX(itemContext, info);
  120834. },
  120835. fitItemHeight: function (itemContext, info) {
  120836. var contentHeight, height;
  120837. if (info.ownerContext.heightModel.shrinkWrap) {
  120838. // contentHeight must include the margins to be consistent with setItemHeight
  120839. height = itemContext.getProp('height') + info.margins.height;
  120840. // because we add margins, height will be NaN or a number (not undefined)
  120841. contentHeight = info.contentHeight;
  120842. if (contentHeight === undefined) {
  120843. info.contentHeight = height;
  120844. } else {
  120845. info.contentHeight = Math.max(contentHeight, height);
  120846. }
  120847. } else if (itemContext.heightModel.calculated) {
  120848. ++info.needed;
  120849. if (info.targetSize.gotHeight) {
  120850. ++info.got;
  120851. this.setItemHeight(itemContext, info);
  120852. }
  120853. }
  120854. this.positionItemY(itemContext, info);
  120855. },
  120856. positionItemX: function (itemContext, info) {
  120857. var margins = info.margins;
  120858. // Adjust position to account for configured margins or if we have multiple items
  120859. // (all items should overlap):
  120860. if (info.index || margins.left) {
  120861. itemContext.setProp('x', margins.left);
  120862. }
  120863. if (margins.width) {
  120864. // Need the margins for shrink-wrapping but old IE sometimes collapses the left margin into the padding
  120865. itemContext.setProp('margin-right', margins.width);
  120866. }
  120867. },
  120868. positionItemY: function (itemContext, info) {
  120869. var margins = info.margins;
  120870. if (info.index || margins.top) {
  120871. itemContext.setProp('y', margins.top);
  120872. }
  120873. if (margins.height) {
  120874. // Need the margins for shrink-wrapping but old IE sometimes collapses the top margin into the padding
  120875. itemContext.setProp('margin-bottom', margins.height);
  120876. }
  120877. },
  120878. setItemHeight: function (itemContext, info) {
  120879. itemContext.setHeight(info.targetSize.height - info.margins.height);
  120880. },
  120881. setItemWidth: function (itemContext, info) {
  120882. itemContext.setWidth(info.targetSize.width - info.margins.width);
  120883. }
  120884. });
  120885. /**
  120886. * @author Nicolas Ferrero
  120887. *
  120888. * TablePanel is the basis of both {@link Ext.tree.Panel TreePanel} and {@link Ext.grid.Panel GridPanel}.
  120889. *
  120890. * TablePanel aggregates:
  120891. *
  120892. * - a Selection Model
  120893. * - a View
  120894. * - a Store
  120895. * - Scrollers
  120896. * - Ext.grid.header.Container
  120897. */
  120898. Ext.define('Ext.panel.Table', {
  120899. extend: 'Ext.panel.Panel',
  120900. alias: 'widget.tablepanel',
  120901. uses: [
  120902. 'Ext.selection.RowModel',
  120903. 'Ext.selection.CellModel',
  120904. 'Ext.selection.CheckboxModel',
  120905. 'Ext.grid.PagingScroller',
  120906. 'Ext.grid.header.Container',
  120907. 'Ext.grid.Lockable'
  120908. ],
  120909. extraBaseCls: Ext.baseCSSPrefix + 'grid',
  120910. extraBodyCls: Ext.baseCSSPrefix + 'grid-body',
  120911. layout: 'fit',
  120912. /**
  120913. * @property {Boolean} hasView
  120914. * True to indicate that a view has been injected into the panel.
  120915. */
  120916. hasView: false,
  120917. // each panel should dictate what viewType and selType to use
  120918. /**
  120919. * @cfg {String} viewType
  120920. * An xtype of view to use. This is automatically set to 'gridview' by {@link Ext.grid.Panel Grid}
  120921. * and to 'treeview' by {@link Ext.tree.Panel Tree}.
  120922. * @protected
  120923. */
  120924. viewType: null,
  120925. /**
  120926. * @cfg {Object} viewConfig
  120927. * A config object that will be applied to the grid's UI view. Any of the config options available for
  120928. * {@link Ext.view.Table} can be specified here. This option is ignored if {@link #view} is specified.
  120929. */
  120930. /**
  120931. * @cfg {Ext.view.Table} view
  120932. * The {@link Ext.view.Table} used by the grid. Use {@link #viewConfig} to just supply some config options to
  120933. * view (instead of creating an entire View instance).
  120934. */
  120935. /**
  120936. * @cfg {String} selType
  120937. * An xtype of selection model to use. Defaults to 'rowmodel'. This is used to create selection model if just
  120938. * a config object or nothing at all given in {@link #selModel} config.
  120939. */
  120940. selType: 'rowmodel',
  120941. /**
  120942. * @cfg {Ext.selection.Model/Object} selModel
  120943. * A {@link Ext.selection.Model selection model} instance or config object. In latter case the {@link #selType}
  120944. * config option determines to which type of selection model this config is applied.
  120945. */
  120946. /**
  120947. * @cfg {Boolean} [multiSelect=false]
  120948. * True to enable 'MULTI' selection mode on selection model.
  120949. * @deprecated 4.1.1 Use {@link Ext.selection.Model#mode} 'MULTI' instead.
  120950. */
  120951. /**
  120952. * @cfg {Boolean} [simpleSelect=false]
  120953. * True to enable 'SIMPLE' selection mode on selection model.
  120954. * @deprecated 4.1.1 Use {@link Ext.selection.Model#mode} 'SIMPLE' instead.
  120955. */
  120956. /**
  120957. * @cfg {Ext.data.Store} store (required)
  120958. * The {@link Ext.data.Store Store} the grid should use as its data source.
  120959. */
  120960. /**
  120961. * @cfg {String/Boolean} scroll
  120962. * Scrollers configuration. Valid values are 'both', 'horizontal' or 'vertical'.
  120963. * True implies 'both'. False implies 'none'.
  120964. */
  120965. scroll: true,
  120966. /**
  120967. * @cfg {Ext.grid.column.Column[]/Object} columns
  120968. * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this
  120969. * grid. Each column definition provides the header text for the column, and a definition of where the data for that
  120970. * column comes from.
  120971. *
  120972. * This can also be a configuration object for a {Ext.grid.header.Container HeaderContainer} which may override
  120973. * certain default configurations if necessary. For example, the special layout may be overridden to use a simpler
  120974. * layout, or one can set default values shared by all columns:
  120975. *
  120976. * columns: {
  120977. * items: [
  120978. * {
  120979. * text: "Column A"
  120980. * dataIndex: "field_A"
  120981. * },{
  120982. * text: "Column B",
  120983. * dataIndex: "field_B"
  120984. * },
  120985. * ...
  120986. * ],
  120987. * defaults: {
  120988. * flex: 1
  120989. * }
  120990. * }
  120991. */
  120992. /**
  120993. * @cfg {Boolean} forceFit
  120994. * Ttrue to force the columns to fit into the available width. Headers are first sized according to configuration,
  120995. * whether that be a specific width, or flex. Then they are all proportionally changed in width so that the entire
  120996. * content width is used. For more accurate control, it is more optimal to specify a flex setting on the columns
  120997. * that are to be stretched & explicit widths on columns that are not.
  120998. */
  120999. /**
  121000. * @cfg {Ext.grid.feature.Feature[]} features
  121001. * An array of grid Features to be added to this grid. See {@link Ext.grid.feature.Feature} for usage.
  121002. */
  121003. /**
  121004. * @cfg {Boolean} [hideHeaders=false]
  121005. * True to hide column headers.
  121006. */
  121007. /**
  121008. * @cfg {Boolean} deferRowRender
  121009. * Defaults to true to enable deferred row rendering.
  121010. *
  121011. * This allows the View to execute a refresh quickly, with the expensive update of the row structure deferred so
  121012. * that layouts with GridPanels appear, and lay out more quickly.
  121013. */
  121014. /**
  121015. * @cfg {Object} verticalScroller
  121016. * A config object to be used when configuring the {@link Ext.grid.PagingScroller scroll monitor} to control
  121017. * refreshing of data in an "infinite grid".
  121018. *
  121019. * Configurations of this object allow fine tuning of data caching which can improve performance and usability
  121020. * of the infinite grid.
  121021. */
  121022. deferRowRender: true,
  121023. /**
  121024. * @cfg {Boolean} sortableColumns
  121025. * False to disable column sorting via clicking the header and via the Sorting menu items.
  121026. */
  121027. sortableColumns: true,
  121028. /**
  121029. * @cfg {Boolean} [enableLocking=false]
  121030. * True to enable locking support for this grid. Alternatively, locking will also be automatically
  121031. * enabled if any of the columns in the column configuration contain the locked config option.
  121032. */
  121033. enableLocking: false,
  121034. // private property used to determine where to go down to find views
  121035. // this is here to support locking.
  121036. scrollerOwner: true,
  121037. /**
  121038. * @cfg {Boolean} [enableColumnMove=true]
  121039. * False to disable column dragging within this grid.
  121040. */
  121041. enableColumnMove: true,
  121042. /**
  121043. * @cfg {Boolean} [sealedColumns=false]
  121044. * True to constrain column dragging so that a column cannot be dragged in or out of it's
  121045. * current group. Only relevant while {@link #enableColumnMove} is enabled.
  121046. */
  121047. sealedColumns: false,
  121048. /**
  121049. * @cfg {Boolean} [enableColumnResize=true]
  121050. * False to disable column resizing within this grid.
  121051. */
  121052. enableColumnResize: true,
  121053. /**
  121054. * @cfg {Boolean} [enableColumnHide=true]
  121055. * False to disable column hiding within this grid.
  121056. */
  121057. enableColumnHide: true,
  121058. /**
  121059. * @cfg {Boolean} columnLines Adds column line styling
  121060. */
  121061. /**
  121062. * @cfg {Boolean} [rowLines=true] Adds row line styling
  121063. */
  121064. rowLines: true,
  121065. /**
  121066. * @cfg {Boolean} [disableSelection=false]
  121067. * True to disable selection model.
  121068. */
  121069. /**
  121070. * @cfg {String} emptyText Default text (html tags are accepted) to display in the Panel body when the Store
  121071. * is empty. When specified, and the Store is empty, the text will be rendered inside a DIV with the CSS class "x-grid-empty".
  121072. */
  121073. /**
  121074. * @cfg {Boolean} [allowDeselect=false]
  121075. * True to allow deselecting a record. This config is forwarded to {@link Ext.selection.Model#allowDeselect}.
  121076. */
  121077. /**
  121078. * @property {Boolean} optimizedColumnMove
  121079. * If you are writing a grid plugin or a {Ext.grid.feature.Feature Feature} which creates a column-based structure which
  121080. * needs a view refresh when columns are moved, then set this property in the grid.
  121081. *
  121082. * An example is the built in {@link Ext.grid.feature.AbstractSummary Summary} Feature. This creates summary rows, and the
  121083. * summary columns must be in the same order as the data columns. This plugin sets the `optimizedColumnMove` to `false.
  121084. */
  121085. initComponent: function() {
  121086. if (!this.viewType) {
  121087. Ext.Error.raise("You must specify a viewType config.");
  121088. }
  121089. if (this.headers) {
  121090. Ext.Error.raise("The headers config is not supported. Please specify columns instead.");
  121091. }
  121092. var me = this,
  121093. scroll = me.scroll,
  121094. vertical = false,
  121095. horizontal = false,
  121096. headerCtCfg = me.columns || me.colModel,
  121097. view,
  121098. border = me.border,
  121099. i, len;
  121100. if (me.columnLines) {
  121101. me.addCls(Ext.baseCSSPrefix + 'grid-with-col-lines');
  121102. }
  121103. if (me.rowLines) {
  121104. me.addCls(Ext.baseCSSPrefix + 'grid-with-row-lines');
  121105. }
  121106. // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
  121107. me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
  121108. if (!headerCtCfg) {
  121109. Ext.Error.raise("A column configuration must be specified");
  121110. }
  121111. // The columns/colModel config may be either a fully instantiated HeaderContainer, or an array of Column definitions, or a config object of a HeaderContainer
  121112. // Either way, we extract a columns property referencing an array of Column definitions.
  121113. if (headerCtCfg instanceof Ext.grid.header.Container) {
  121114. me.headerCt = headerCtCfg;
  121115. me.headerCt.border = border;
  121116. me.columns = me.headerCt.items.items;
  121117. } else {
  121118. if (Ext.isArray(headerCtCfg)) {
  121119. headerCtCfg = {
  121120. items: headerCtCfg,
  121121. border: border
  121122. };
  121123. }
  121124. Ext.apply(headerCtCfg, {
  121125. forceFit: me.forceFit,
  121126. sortable: me.sortableColumns,
  121127. enableColumnMove: me.enableColumnMove,
  121128. enableColumnResize: me.enableColumnResize,
  121129. enableColumnHide: me.enableColumnHide,
  121130. border: border,
  121131. sealed: me.sealedColumns
  121132. });
  121133. me.columns = headerCtCfg.items;
  121134. // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a
  121135. // special view will be injected by the Ext.grid.Lockable mixin, so no processing of .
  121136. if (me.enableLocking || Ext.ComponentQuery.query('{locked !== undefined}{processed != true}', me.columns).length) {
  121137. me.self.mixin('lockable', Ext.grid.Lockable);
  121138. me.injectLockable();
  121139. }
  121140. }
  121141. me.scrollTask = new Ext.util.DelayedTask(me.syncHorizontalScroll, me);
  121142. me.addEvents(
  121143. // documented on GridPanel
  121144. 'reconfigure',
  121145. /**
  121146. * @event viewready
  121147. * Fires when the grid view is available (use this for selecting a default row).
  121148. * @param {Ext.panel.Table} this
  121149. */
  121150. 'viewready'
  121151. );
  121152. me.bodyCls = me.bodyCls || '';
  121153. me.bodyCls += (' ' + me.extraBodyCls);
  121154. me.cls = me.cls || '';
  121155. me.cls += (' ' + me.extraBaseCls);
  121156. // autoScroll is not a valid configuration
  121157. delete me.autoScroll;
  121158. // If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property)
  121159. // than a special lockable view containing 2 side-by-side grids will have been injected so we do not need to set up any UI.
  121160. if (!me.hasView) {
  121161. // If we were not configured with a ready-made headerCt (either by direct config with a headerCt property, or by passing
  121162. // a HeaderContainer instance as the 'columns' property, then go ahead and create one from the config object created above.
  121163. if (!me.headerCt) {
  121164. me.headerCt = new Ext.grid.header.Container(headerCtCfg);
  121165. }
  121166. // Extract the array of Column objects
  121167. me.columns = me.headerCt.items.items;
  121168. // If the Store is paging blocks of the dataset in, then it can only be sorted remotely.
  121169. if (me.store.buffered && !me.store.remoteSort) {
  121170. for (i = 0, len = me.columns.length; i < len; i++) {
  121171. me.columns[i].sortable = false;
  121172. }
  121173. }
  121174. if (me.hideHeaders) {
  121175. me.headerCt.height = 0;
  121176. me.headerCt.addCls(Ext.baseCSSPrefix + 'grid-header-ct-hidden');
  121177. me.addCls(Ext.baseCSSPrefix + 'grid-header-hidden');
  121178. // IE Quirks Mode fix
  121179. // If hidden configuration option was used, several layout calculations will be bypassed.
  121180. if (Ext.isIEQuirks) {
  121181. me.headerCt.style = {
  121182. display: 'none'
  121183. };
  121184. }
  121185. }
  121186. // turn both on.
  121187. if (scroll === true || scroll === 'both') {
  121188. vertical = horizontal = true;
  121189. } else if (scroll === 'horizontal') {
  121190. horizontal = true;
  121191. } else if (scroll === 'vertical') {
  121192. vertical = true;
  121193. }
  121194. me.relayHeaderCtEvents(me.headerCt);
  121195. me.features = me.features || [];
  121196. if (!Ext.isArray(me.features)) {
  121197. me.features = [me.features];
  121198. }
  121199. me.dockedItems = [].concat(me.dockedItems || []);
  121200. me.dockedItems.unshift(me.headerCt);
  121201. me.viewConfig = me.viewConfig || {};
  121202. // Buffered scrolling must preserve scroll on refresh
  121203. if (me.store && me.store.buffered) {
  121204. me.viewConfig.preserveScrollOnRefresh = true;
  121205. } else if (me.invalidateScrollerOnRefresh !== undefined) {
  121206. me.viewConfig.preserveScrollOnRefresh = !me.invalidateScrollerOnRefresh;
  121207. }
  121208. // AbstractDataView will look up a Store configured as an object
  121209. // getView converts viewConfig into a View instance
  121210. view = me.getView();
  121211. me.items = [view];
  121212. me.hasView = true;
  121213. if (vertical) {
  121214. // If the Store is buffered, create a PagingScroller to monitor the View's scroll progress,
  121215. // load the Store's prefetch buffer when it detects we are nearing an edge.
  121216. if (me.store.buffered) {
  121217. me.verticalScroller = new Ext.grid.PagingScroller(Ext.apply({
  121218. panel: me,
  121219. store: me.store,
  121220. view: me.view
  121221. }, me.verticalScroller));
  121222. }
  121223. }
  121224. if (horizontal) {
  121225. // Add a listener to synchronize the horizontal scroll position of the headers
  121226. // with the table view's element... Unless we are not showing headers!
  121227. if (!me.hideHeaders) {
  121228. view.on({
  121229. scroll: {
  121230. fn: me.onHorizontalScroll,
  121231. element: 'el',
  121232. scope: me
  121233. }
  121234. });
  121235. }
  121236. }
  121237. me.mon(view.store, {
  121238. load: me.onStoreLoad,
  121239. scope: me
  121240. });
  121241. me.mon(view, {
  121242. viewready: me.onViewReady,
  121243. refresh: me.onRestoreHorzScroll,
  121244. scope: me
  121245. });
  121246. }
  121247. // Relay events from the View whether it be a LockingView, or a regular GridView
  121248. this.relayEvents(me.view, [
  121249. /**
  121250. * @event beforeitemmousedown
  121251. * @inheritdoc Ext.view.View#beforeitemmousedown
  121252. */
  121253. 'beforeitemmousedown',
  121254. /**
  121255. * @event beforeitemmouseup
  121256. * @inheritdoc Ext.view.View#beforeitemmouseup
  121257. */
  121258. 'beforeitemmouseup',
  121259. /**
  121260. * @event beforeitemmouseenter
  121261. * @inheritdoc Ext.view.View#beforeitemmouseenter
  121262. */
  121263. 'beforeitemmouseenter',
  121264. /**
  121265. * @event beforeitemmouseleave
  121266. * @inheritdoc Ext.view.View#beforeitemmouseleave
  121267. */
  121268. 'beforeitemmouseleave',
  121269. /**
  121270. * @event beforeitemclick
  121271. * @inheritdoc Ext.view.View#beforeitemclick
  121272. */
  121273. 'beforeitemclick',
  121274. /**
  121275. * @event beforeitemdblclick
  121276. * @inheritdoc Ext.view.View#beforeitemdblclick
  121277. */
  121278. 'beforeitemdblclick',
  121279. /**
  121280. * @event beforeitemcontextmenu
  121281. * @inheritdoc Ext.view.View#beforeitemcontextmenu
  121282. */
  121283. 'beforeitemcontextmenu',
  121284. /**
  121285. * @event itemmousedown
  121286. * @inheritdoc Ext.view.View#itemmousedown
  121287. */
  121288. 'itemmousedown',
  121289. /**
  121290. * @event itemmouseup
  121291. * @inheritdoc Ext.view.View#itemmouseup
  121292. */
  121293. 'itemmouseup',
  121294. /**
  121295. * @event itemmouseenter
  121296. * @inheritdoc Ext.view.View#itemmouseenter
  121297. */
  121298. 'itemmouseenter',
  121299. /**
  121300. * @event itemmouseleave
  121301. * @inheritdoc Ext.view.View#itemmouseleave
  121302. */
  121303. 'itemmouseleave',
  121304. /**
  121305. * @event itemclick
  121306. * @inheritdoc Ext.view.View#itemclick
  121307. */
  121308. 'itemclick',
  121309. /**
  121310. * @event itemdblclick
  121311. * @inheritdoc Ext.view.View#itemdblclick
  121312. */
  121313. 'itemdblclick',
  121314. /**
  121315. * @event itemcontextmenu
  121316. * @inheritdoc Ext.view.View#itemcontextmenu
  121317. */
  121318. 'itemcontextmenu',
  121319. /**
  121320. * @event beforecontainermousedown
  121321. * @inheritdoc Ext.view.View#beforecontainermousedown
  121322. */
  121323. 'beforecontainermousedown',
  121324. /**
  121325. * @event beforecontainermouseup
  121326. * @inheritdoc Ext.view.View#beforecontainermouseup
  121327. */
  121328. 'beforecontainermouseup',
  121329. /**
  121330. * @event beforecontainermouseover
  121331. * @inheritdoc Ext.view.View#beforecontainermouseover
  121332. */
  121333. 'beforecontainermouseover',
  121334. /**
  121335. * @event beforecontainermouseout
  121336. * @inheritdoc Ext.view.View#beforecontainermouseout
  121337. */
  121338. 'beforecontainermouseout',
  121339. /**
  121340. * @event beforecontainerclick
  121341. * @inheritdoc Ext.view.View#beforecontainerclick
  121342. */
  121343. 'beforecontainerclick',
  121344. /**
  121345. * @event beforecontainerdblclick
  121346. * @inheritdoc Ext.view.View#beforecontainerdblclick
  121347. */
  121348. 'beforecontainerdblclick',
  121349. /**
  121350. * @event beforecontainercontextmenu
  121351. * @inheritdoc Ext.view.View#beforecontainercontextmenu
  121352. */
  121353. 'beforecontainercontextmenu',
  121354. /**
  121355. * @event containermouseup
  121356. * @inheritdoc Ext.view.View#containermouseup
  121357. */
  121358. 'containermouseup',
  121359. /**
  121360. * @event containermouseover
  121361. * @inheritdoc Ext.view.View#containermouseover
  121362. */
  121363. 'containermouseover',
  121364. /**
  121365. * @event containermouseout
  121366. * @inheritdoc Ext.view.View#containermouseout
  121367. */
  121368. 'containermouseout',
  121369. /**
  121370. * @event containerclick
  121371. * @inheritdoc Ext.view.View#containerclick
  121372. */
  121373. 'containerclick',
  121374. /**
  121375. * @event containerdblclick
  121376. * @inheritdoc Ext.view.View#containerdblclick
  121377. */
  121378. 'containerdblclick',
  121379. /**
  121380. * @event containercontextmenu
  121381. * @inheritdoc Ext.view.View#containercontextmenu
  121382. */
  121383. 'containercontextmenu',
  121384. /**
  121385. * @event selectionchange
  121386. * @inheritdoc Ext.selection.Model#selectionchange
  121387. */
  121388. 'selectionchange',
  121389. /**
  121390. * @event beforeselect
  121391. * @inheritdoc Ext.selection.RowModel#beforeselect
  121392. */
  121393. 'beforeselect',
  121394. /**
  121395. * @event select
  121396. * @inheritdoc Ext.selection.RowModel#select
  121397. */
  121398. 'select',
  121399. /**
  121400. * @event beforedeselect
  121401. * @inheritdoc Ext.selection.RowModel#beforedeselect
  121402. */
  121403. 'beforedeselect',
  121404. /**
  121405. * @event deselect
  121406. * @inheritdoc Ext.selection.RowModel#deselect
  121407. */
  121408. 'deselect'
  121409. ]);
  121410. me.callParent(arguments);
  121411. me.addStateEvents(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange']);
  121412. if (me.headerCt) {
  121413. me.headerCt.on('afterlayout', me.onRestoreHorzScroll, me);
  121414. }
  121415. },
  121416. relayHeaderCtEvents: function (headerCt) {
  121417. this.relayEvents(headerCt, [
  121418. /**
  121419. * @event columnresize
  121420. * @inheritdoc Ext.grid.header.Container#columnresize
  121421. */
  121422. 'columnresize',
  121423. /**
  121424. * @event columnmove
  121425. * @inheritdoc Ext.grid.header.Container#columnmove
  121426. */
  121427. 'columnmove',
  121428. /**
  121429. * @event columnhide
  121430. * @inheritdoc Ext.grid.header.Container#columnhide
  121431. */
  121432. 'columnhide',
  121433. /**
  121434. * @event columnshow
  121435. * @inheritdoc Ext.grid.header.Container#columnshow
  121436. */
  121437. 'columnshow',
  121438. /**
  121439. * @event sortchange
  121440. * @inheritdoc Ext.grid.header.Container#sortchange
  121441. */
  121442. 'sortchange'
  121443. ]);
  121444. },
  121445. getState: function(){
  121446. var me = this,
  121447. state = me.callParent(),
  121448. sorter = me.store.sorters.first();
  121449. state = me.addPropertyToState(state, 'columns', (me.headerCt || me).getColumnsState());
  121450. if (sorter) {
  121451. state = me.addPropertyToState(state, 'sort', {
  121452. property: sorter.property,
  121453. direction: sorter.direction,
  121454. root: sorter.root
  121455. });
  121456. }
  121457. return state;
  121458. },
  121459. applyState: function(state) {
  121460. var me = this,
  121461. sorter = state.sort,
  121462. store = me.store,
  121463. columns = state.columns;
  121464. delete state.columns;
  121465. // Ensure superclass has applied *its* state.
  121466. // AbstractComponent saves dimensions (and anchor/flex) plus collapsed state.
  121467. me.callParent(arguments);
  121468. if (columns) {
  121469. (me.headerCt || me).applyColumnsState(columns);
  121470. }
  121471. if (sorter) {
  121472. if (store.remoteSort) {
  121473. // Pass false to prevent a sort from occurring
  121474. store.sort({
  121475. property: sorter.property,
  121476. direction: sorter.direction,
  121477. root: sorter.root
  121478. }, null, false);
  121479. } else {
  121480. store.sort(sorter.property, sorter.direction);
  121481. }
  121482. }
  121483. },
  121484. /**
  121485. * Returns the store associated with this Panel.
  121486. * @return {Ext.data.Store} The store
  121487. */
  121488. getStore: function(){
  121489. return this.store;
  121490. },
  121491. /**
  121492. * Gets the view for this panel.
  121493. * @return {Ext.view.Table}
  121494. */
  121495. getView: function() {
  121496. var me = this,
  121497. sm;
  121498. if (!me.view) {
  121499. sm = me.getSelectionModel();
  121500. me.view = Ext.widget(Ext.apply({}, me.viewConfig, {
  121501. // Features need a reference to the grid, so configure a reference into the View
  121502. grid: me,
  121503. deferInitialRefresh: me.deferRowRender !== false,
  121504. scroll: me.scroll,
  121505. xtype: me.viewType,
  121506. store: me.store,
  121507. headerCt: me.headerCt,
  121508. selModel: sm,
  121509. features: me.features,
  121510. panel: me,
  121511. emptyText : me.emptyText ? '<div class="' + Ext.baseCSSPrefix + 'grid-empty">' + me.emptyText + '</div>' : ''
  121512. }));
  121513. // TableView's custom component layout, Ext.view.TableLayout requires a reference to the headerCt because it depends on the headerCt doing its work.
  121514. me.view.getComponentLayout().headerCt = me.headerCt;
  121515. me.mon(me.view, {
  121516. uievent: me.processEvent,
  121517. scope: me
  121518. });
  121519. sm.view = me.view;
  121520. me.headerCt.view = me.view;
  121521. me.relayEvents(me.view, [
  121522. /**
  121523. * @event cellclick
  121524. * Fired when table cell is clicked.
  121525. * @param {Ext.view.Table} this
  121526. * @param {HTMLElement} td The TD element that was clicked.
  121527. * @param {Number} cellIndex
  121528. * @param {Ext.data.Model} record
  121529. * @param {HTMLElement} tr The TR element that was clicked.
  121530. * @param {Number} rowIndex
  121531. * @param {Ext.EventObject} e
  121532. */
  121533. 'cellclick',
  121534. /**
  121535. * @event celldblclick
  121536. * Fired when table cell is double clicked.
  121537. * @param {Ext.view.Table} this
  121538. * @param {HTMLElement} td The TD element that was clicked.
  121539. * @param {Number} cellIndex
  121540. * @param {Ext.data.Model} record
  121541. * @param {HTMLElement} tr The TR element that was clicked.
  121542. * @param {Number} rowIndex
  121543. * @param {Ext.EventObject} e
  121544. */
  121545. 'celldblclick'
  121546. ]);
  121547. }
  121548. return me.view;
  121549. },
  121550. /**
  121551. * @private
  121552. * autoScroll is never valid for all classes which extend TablePanel.
  121553. */
  121554. setAutoScroll: Ext.emptyFn,
  121555. /**
  121556. * @private
  121557. * Processes UI events from the view. Propagates them to whatever internal Components need to process them.
  121558. * @param {String} type Event type, eg 'click'
  121559. * @param {Ext.view.Table} view TableView Component
  121560. * @param {HTMLElement} cell Cell HtmlElement the event took place within
  121561. * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
  121562. * @param {Number} cellIndex Cell index within the row
  121563. * @param {Ext.EventObject} e Original event
  121564. */
  121565. processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
  121566. var me = this,
  121567. header;
  121568. if (cellIndex !== -1) {
  121569. header = me.headerCt.getGridColumns()[cellIndex];
  121570. return header.processEvent.apply(header, arguments);
  121571. }
  121572. },
  121573. /**
  121574. * This method is obsolete in 4.1. The closest equivalent in
  121575. * 4.1 is {@link #doLayout}, but it is also possible that no
  121576. * layout is needed.
  121577. * @deprecated 4.1
  121578. */
  121579. determineScrollbars: function () {
  121580. Ext.log.warn('Obsolete');
  121581. },
  121582. /**
  121583. * This method is obsolete in 4.1. The closest equivalent in 4.1 is
  121584. * {@link Ext.AbstractComponent#updateLayout}, but it is also possible that no layout
  121585. * is needed.
  121586. * @deprecated 4.1
  121587. */
  121588. invalidateScroller: function () {
  121589. Ext.log.warn('Obsolete');
  121590. },
  121591. scrollByDeltaY: function(yDelta, animate) {
  121592. this.getView().scrollBy(0, yDelta, animate);
  121593. },
  121594. scrollByDeltaX: function(xDelta, animate) {
  121595. this.getView().scrollBy(xDelta, 0, animate);
  121596. },
  121597. afterCollapse: function() {
  121598. var me = this;
  121599. me.saveScrollPos();
  121600. me.saveScrollPos();
  121601. me.callParent(arguments);
  121602. },
  121603. afterExpand: function() {
  121604. var me = this;
  121605. me.callParent(arguments);
  121606. me.restoreScrollPos();
  121607. me.restoreScrollPos();
  121608. },
  121609. saveScrollPos: Ext.emptyFn,
  121610. restoreScrollPos: Ext.emptyFn,
  121611. onHeaderResize: function(){
  121612. this.delayScroll();
  121613. },
  121614. // Update the view when a header moves
  121615. onHeaderMove: function(headerCt, header, colsToMove, fromIdx, toIdx) {
  121616. var me = this;
  121617. // If there are Features or Plugins which create DOM which must match column order, they set the optimizedColumnMove flag to false.
  121618. // In this case we must refresh the view on column move.
  121619. if (me.optimizedColumnMove === false) {
  121620. me.view.refresh();
  121621. }
  121622. // Simplest case for default DOM structure is just to swap the columns round in the view.
  121623. else {
  121624. me.view.moveColumn(fromIdx, toIdx, colsToMove);
  121625. }
  121626. me.delayScroll();
  121627. },
  121628. // Section onHeaderHide is invoked after view.
  121629. onHeaderHide: function(headerCt, header) {
  121630. this.delayScroll();
  121631. },
  121632. onHeaderShow: function(headerCt, header) {
  121633. this.delayScroll();
  121634. },
  121635. delayScroll: function(){
  121636. var target = this.getScrollTarget().el;
  121637. if (target) {
  121638. this.scrollTask.delay(10, null, null, [target.dom.scrollLeft]);
  121639. }
  121640. },
  121641. /**
  121642. * @private
  121643. * Fires the TablePanel's viewready event when the view declares that its internal DOM is ready
  121644. */
  121645. onViewReady: function() {
  121646. this.fireEvent('viewready', this);
  121647. },
  121648. /**
  121649. * @private
  121650. * Tracks when things happen to the view and preserves the horizontal scroll position.
  121651. */
  121652. onRestoreHorzScroll: function() {
  121653. var left = this.scrollLeftPos;
  121654. if (left) {
  121655. // We need to restore the body scroll position here
  121656. this.syncHorizontalScroll(left, true);
  121657. }
  121658. },
  121659. getScrollerOwner: function() {
  121660. var rootCmp = this;
  121661. if (!this.scrollerOwner) {
  121662. rootCmp = this.up('[scrollerOwner]');
  121663. }
  121664. return rootCmp;
  121665. },
  121666. /**
  121667. * Gets left hand side marker for header resizing.
  121668. * @private
  121669. */
  121670. getLhsMarker: function() {
  121671. var me = this;
  121672. return me.lhsMarker || (me.lhsMarker = Ext.DomHelper.append(me.el, {
  121673. cls: Ext.baseCSSPrefix + 'grid-resize-marker'
  121674. }, true));
  121675. },
  121676. /**
  121677. * Gets right hand side marker for header resizing.
  121678. * @private
  121679. */
  121680. getRhsMarker: function() {
  121681. var me = this;
  121682. return me.rhsMarker || (me.rhsMarker = Ext.DomHelper.append(me.el, {
  121683. cls: Ext.baseCSSPrefix + 'grid-resize-marker'
  121684. }, true));
  121685. },
  121686. /**
  121687. * Returns the selection model being used and creates it via the configuration if it has not been created already.
  121688. * @return {Ext.selection.Model} selModel
  121689. */
  121690. getSelectionModel: function(){
  121691. if (!this.selModel) {
  121692. this.selModel = {};
  121693. }
  121694. var mode = 'SINGLE',
  121695. type;
  121696. if (this.simpleSelect) {
  121697. mode = 'SIMPLE';
  121698. } else if (this.multiSelect) {
  121699. mode = 'MULTI';
  121700. }
  121701. Ext.applyIf(this.selModel, {
  121702. allowDeselect: this.allowDeselect,
  121703. mode: mode
  121704. });
  121705. if (!this.selModel.events) {
  121706. type = this.selModel.selType || this.selType;
  121707. this.selModel = Ext.create('selection.' + type, this.selModel);
  121708. }
  121709. if (!this.selModel.hasRelaySetup) {
  121710. this.relayEvents(this.selModel, [
  121711. 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
  121712. ]);
  121713. this.selModel.hasRelaySetup = true;
  121714. }
  121715. // lock the selection model if user
  121716. // has disabled selection
  121717. if (this.disableSelection) {
  121718. this.selModel.locked = true;
  121719. }
  121720. return this.selModel;
  121721. },
  121722. getScrollTarget: function(){
  121723. var owner = this.getScrollerOwner(),
  121724. items = owner.query('tableview');
  121725. return items[1] || items[0];
  121726. },
  121727. onHorizontalScroll: function(event, target) {
  121728. this.syncHorizontalScroll(target.scrollLeft);
  121729. },
  121730. syncHorizontalScroll: function(left, setBody) {
  121731. var me = this,
  121732. scrollTarget;
  121733. setBody = setBody === true;
  121734. // Only set the horizontal scroll if we've changed position,
  121735. // so that we don't set this on vertical scrolls
  121736. if (me.rendered && (setBody || left !== me.scrollLeftPos)) {
  121737. // Only set the body position if we're reacting to a refresh, otherwise
  121738. // we just need to set the header.
  121739. if (setBody) {
  121740. scrollTarget = me.getScrollTarget();
  121741. scrollTarget.el.dom.scrollLeft = left;
  121742. }
  121743. me.headerCt.el.dom.scrollLeft = left;
  121744. me.scrollLeftPos = left;
  121745. }
  121746. },
  121747. // template method meant to be overriden
  121748. onStoreLoad: Ext.emptyFn,
  121749. getEditorParent: function() {
  121750. return this.body;
  121751. },
  121752. bindStore: function(store) {
  121753. var me = this;
  121754. me.store = store;
  121755. me.getView().bindStore(store);
  121756. },
  121757. beforeDestroy: function(){
  121758. Ext.destroy(this.verticalScroller);
  121759. this.callParent();
  121760. },
  121761. // documented on GridPanel
  121762. reconfigure: function(store, columns) {
  121763. var me = this,
  121764. headerCt = me.headerCt;
  121765. if (me.lockable) {
  121766. me.reconfigureLockable(store, columns);
  121767. } else {
  121768. Ext.suspendLayouts();
  121769. if (columns) {
  121770. // new columns, delete scroll pos
  121771. delete me.scrollLeftPos;
  121772. headerCt.removeAll();
  121773. headerCt.add(columns);
  121774. }
  121775. if (store) {
  121776. store = Ext.StoreManager.lookup(store);
  121777. me.bindStore(store);
  121778. } else {
  121779. me.getView().refresh();
  121780. }
  121781. headerCt.setSortState();
  121782. Ext.resumeLayouts(true);
  121783. }
  121784. me.fireEvent('reconfigure', me, store, columns);
  121785. }
  121786. });
  121787. /**
  121788. * The grid View class provides extra {@link Ext.grid.Panel} specific functionality to the
  121789. * {@link Ext.view.Table}. In general, this class is not instanced directly, instead a viewConfig
  121790. * option is passed to the grid:
  121791. *
  121792. * Ext.create('Ext.grid.Panel', {
  121793. * // other options
  121794. * viewConfig: {
  121795. * stripeRows: false
  121796. * }
  121797. * });
  121798. *
  121799. * ## Drag Drop
  121800. *
  121801. * Drag and drop functionality can be achieved in the grid by attaching a {@link Ext.grid.plugin.DragDrop} plugin
  121802. * when creating the view.
  121803. *
  121804. * Ext.create('Ext.grid.Panel', {
  121805. * // other options
  121806. * viewConfig: {
  121807. * plugins: {
  121808. * ddGroup: 'people-group',
  121809. * ptype: 'gridviewdragdrop',
  121810. * enableDrop: false
  121811. * }
  121812. * }
  121813. * });
  121814. */
  121815. Ext.define('Ext.grid.View', {
  121816. extend: 'Ext.view.Table',
  121817. alias: 'widget.gridview',
  121818. /**
  121819. * @cfg
  121820. * True to stripe the rows.
  121821. *
  121822. * This causes the CSS class **`x-grid-row-alt`** to be added to alternate rows of the grid. A default CSS rule is
  121823. * provided which sets a background color, but you can override this with a rule which either overrides the
  121824. * **background-color** style using the `!important` modifier, or which uses a CSS selector of higher specificity.
  121825. */
  121826. stripeRows: true,
  121827. autoScroll: true
  121828. });
  121829. /**
  121830. * @author Aaron Conran
  121831. * @docauthor Ed Spencer
  121832. *
  121833. * Grids are an excellent way of showing large amounts of tabular data on the client side. Essentially a supercharged
  121834. * `<table>`, GridPanel makes it easy to fetch, sort and filter large amounts of data.
  121835. *
  121836. * Grids are composed of two main pieces - a {@link Ext.data.Store Store} full of data and a set of columns to render.
  121837. *
  121838. * ## Basic GridPanel
  121839. *
  121840. * @example
  121841. * Ext.create('Ext.data.Store', {
  121842. * storeId:'simpsonsStore',
  121843. * fields:['name', 'email', 'phone'],
  121844. * data:{'items':[
  121845. * { 'name': 'Lisa', "email":"lisa@simpsons.com", "phone":"555-111-1224" },
  121846. * { 'name': 'Bart', "email":"bart@simpsons.com", "phone":"555-222-1234" },
  121847. * { 'name': 'Homer', "email":"home@simpsons.com", "phone":"555-222-1244" },
  121848. * { 'name': 'Marge', "email":"marge@simpsons.com", "phone":"555-222-1254" }
  121849. * ]},
  121850. * proxy: {
  121851. * type: 'memory',
  121852. * reader: {
  121853. * type: 'json',
  121854. * root: 'items'
  121855. * }
  121856. * }
  121857. * });
  121858. *
  121859. * Ext.create('Ext.grid.Panel', {
  121860. * title: 'Simpsons',
  121861. * store: Ext.data.StoreManager.lookup('simpsonsStore'),
  121862. * columns: [
  121863. * { text: 'Name', dataIndex: 'name' },
  121864. * { text: 'Email', dataIndex: 'email', flex: 1 },
  121865. * { text: 'Phone', dataIndex: 'phone' }
  121866. * ],
  121867. * height: 200,
  121868. * width: 400,
  121869. * renderTo: Ext.getBody()
  121870. * });
  121871. *
  121872. * The code above produces a simple grid with three columns. We specified a Store which will load JSON data inline.
  121873. * In most apps we would be placing the grid inside another container and wouldn't need to use the
  121874. * {@link #height}, {@link #width} and {@link #renderTo} configurations but they are included here to make it easy to get
  121875. * up and running.
  121876. *
  121877. * The grid we created above will contain a header bar with a title ('Simpsons'), a row of column headers directly underneath
  121878. * and finally the grid rows under the headers.
  121879. *
  121880. * ## Configuring columns
  121881. *
  121882. * By default, each column is sortable and will toggle between ASC and DESC sorting when you click on its header. Each
  121883. * column header is also reorderable by default, and each gains a drop-down menu with options to hide and show columns.
  121884. * It's easy to configure each column - here we use the same example as above and just modify the columns config:
  121885. *
  121886. * columns: [
  121887. * {
  121888. * text: 'Name',
  121889. * dataIndex: 'name',
  121890. * sortable: false,
  121891. * hideable: false,
  121892. * flex: 1
  121893. * },
  121894. * {
  121895. * text: 'Email',
  121896. * dataIndex: 'email',
  121897. * hidden: true
  121898. * },
  121899. * {
  121900. * text: 'Phone',
  121901. * dataIndex: 'phone',
  121902. * width: 100
  121903. * }
  121904. * ]
  121905. *
  121906. * We turned off sorting and hiding on the 'Name' column so clicking its header now has no effect. We also made the Email
  121907. * column hidden by default (it can be shown again by using the menu on any other column). We also set the Phone column to
  121908. * a fixed with of 100px and flexed the Name column, which means it takes up all remaining width after the other columns
  121909. * have been accounted for. See the {@link Ext.grid.column.Column column docs} for more details.
  121910. *
  121911. * ## Renderers
  121912. *
  121913. * As well as customizing columns, it's easy to alter the rendering of individual cells using renderers. A renderer is
  121914. * tied to a particular column and is passed the value that would be rendered into each cell in that column. For example,
  121915. * we could define a renderer function for the email column to turn each email address into a mailto link:
  121916. *
  121917. * columns: [
  121918. * {
  121919. * text: 'Email',
  121920. * dataIndex: 'email',
  121921. * renderer: function(value) {
  121922. * return Ext.String.format('<a href="mailto:{0}">{1}</a>', value, value);
  121923. * }
  121924. * }
  121925. * ]
  121926. *
  121927. * See the {@link Ext.grid.column.Column column docs} for more information on renderers.
  121928. *
  121929. * ## Selection Models
  121930. *
  121931. * Sometimes all you want is to render data onto the screen for viewing, but usually it's necessary to interact with or
  121932. * update that data. Grids use a concept called a Selection Model, which is simply a mechanism for selecting some part of
  121933. * the data in the grid. The two main types of Selection Model are RowSelectionModel, where entire rows are selected, and
  121934. * CellSelectionModel, where individual cells are selected.
  121935. *
  121936. * Grids use a Row Selection Model by default, but this is easy to customise like so:
  121937. *
  121938. * Ext.create('Ext.grid.Panel', {
  121939. * selType: 'cellmodel',
  121940. * store: ...
  121941. * });
  121942. *
  121943. * Specifying the `cellmodel` changes a couple of things. Firstly, clicking on a cell now
  121944. * selects just that cell (using a {@link Ext.selection.RowModel rowmodel} will select the entire row), and secondly the
  121945. * keyboard navigation will walk from cell to cell instead of row to row. Cell-based selection models are usually used in
  121946. * conjunction with editing.
  121947. *
  121948. * ## Sorting & Filtering
  121949. *
  121950. * Every grid is attached to a {@link Ext.data.Store Store}, which provides multi-sort and filtering capabilities. It's
  121951. * easy to set up a grid to be sorted from the start:
  121952. *
  121953. * var myGrid = Ext.create('Ext.grid.Panel', {
  121954. * store: {
  121955. * fields: ['name', 'email', 'phone'],
  121956. * sorters: ['name', 'phone']
  121957. * },
  121958. * columns: [
  121959. * { text: 'Name', dataIndex: 'name' },
  121960. * { text: 'Email', dataIndex: 'email' }
  121961. * ]
  121962. * });
  121963. *
  121964. * Sorting at run time is easily accomplished by simply clicking each column header. If you need to perform sorting on
  121965. * more than one field at run time it's easy to do so by adding new sorters to the store:
  121966. *
  121967. * myGrid.store.sort([
  121968. * { property: 'name', direction: 'ASC' },
  121969. * { property: 'email', direction: 'DESC' }
  121970. * ]);
  121971. *
  121972. * See {@link Ext.data.Store} for examples of filtering.
  121973. *
  121974. * ## State saving
  121975. *
  121976. * When configured {@link #stateful}, grids save their column state (order and width) encapsulated within the default
  121977. * Panel state of changed width and height and collapsed/expanded state.
  121978. *
  121979. * Each {@link #columns column} of the grid may be configured with a {@link Ext.grid.column.Column#stateId stateId} which
  121980. * identifies that column locally within the grid.
  121981. *
  121982. * ## Plugins and Features
  121983. *
  121984. * Grid supports addition of extra functionality through features and plugins:
  121985. *
  121986. * - {@link Ext.grid.plugin.CellEditing CellEditing} - editing grid contents one cell at a time.
  121987. *
  121988. * - {@link Ext.grid.plugin.RowEditing RowEditing} - editing grid contents an entire row at a time.
  121989. *
  121990. * - {@link Ext.grid.plugin.DragDrop DragDrop} - drag-drop reordering of grid rows.
  121991. *
  121992. * - {@link Ext.toolbar.Paging Paging toolbar} - paging through large sets of data.
  121993. *
  121994. * - {@link Ext.grid.PagingScroller Infinite scrolling} - another way to handle large sets of data.
  121995. *
  121996. * - {@link Ext.grid.RowNumberer RowNumberer} - automatically numbered rows.
  121997. *
  121998. * - {@link Ext.grid.feature.Grouping Grouping} - grouping together rows having the same value in a particular field.
  121999. *
  122000. * - {@link Ext.grid.feature.Summary Summary} - a summary row at the bottom of a grid.
  122001. *
  122002. * - {@link Ext.grid.feature.GroupingSummary GroupingSummary} - a summary row at the bottom of each group.
  122003. */
  122004. Ext.define('Ext.grid.Panel', {
  122005. extend: 'Ext.panel.Table',
  122006. requires: ['Ext.grid.View'],
  122007. alias: ['widget.gridpanel', 'widget.grid'],
  122008. alternateClassName: ['Ext.list.ListView', 'Ext.ListView', 'Ext.grid.GridPanel'],
  122009. viewType: 'gridview',
  122010. lockable: false,
  122011. // Required for the Lockable Mixin. These are the configurations which will be copied to the
  122012. // normal and locked sub tablepanels
  122013. bothCfgCopy: [
  122014. 'invalidateScrollerOnRefresh',
  122015. 'hideHeaders',
  122016. 'enableColumnHide',
  122017. 'enableColumnMove',
  122018. 'enableColumnResize',
  122019. 'sortableColumns'
  122020. ],
  122021. normalCfgCopy: [
  122022. 'verticalScroller',
  122023. 'verticalScrollDock',
  122024. 'verticalScrollerType',
  122025. 'scroll'
  122026. ],
  122027. lockedCfgCopy: [],
  122028. /**
  122029. * @cfg {Boolean} rowLines False to remove row line styling
  122030. */
  122031. rowLines: true
  122032. // Columns config is required in Grid
  122033. /**
  122034. * @cfg {Ext.grid.column.Column[]/Object} columns (required)
  122035. * @inheritdoc
  122036. */
  122037. /**
  122038. * @event reconfigure
  122039. * Fires after a reconfigure.
  122040. * @param {Ext.grid.Panel} this
  122041. * @param {Ext.data.Store} store The store that was passed to the {@link #method-reconfigure} method
  122042. * @param {Object[]} columns The column configs that were passed to the {@link #method-reconfigure} method
  122043. */
  122044. /**
  122045. * @method reconfigure
  122046. * Reconfigures the grid with a new store/columns. Either the store or the columns can be omitted if you don't wish
  122047. * to change them.
  122048. * @param {Ext.data.Store} store (Optional) The new store.
  122049. * @param {Object[]} columns (Optional) An array of column configs
  122050. */
  122051. });
  122052. // Currently has the following issues:
  122053. // - Does not handle postEditValue
  122054. // - Fields without editors need to sync with their values in Store
  122055. // - starting to edit another record while already editing and dirty should probably prevent it
  122056. // - aggregating validation messages
  122057. // - tabIndex is not managed bc we leave elements in dom, and simply move via positioning
  122058. // - layout issues when changing sizes/width while hidden (layout bug)
  122059. /**
  122060. * Internal utility class used to provide row editing functionality. For developers, they should use
  122061. * the RowEditing plugin to use this functionality with a grid.
  122062. *
  122063. * @private
  122064. */
  122065. Ext.define('Ext.grid.RowEditor', {
  122066. extend: 'Ext.form.Panel',
  122067. requires: [
  122068. 'Ext.tip.ToolTip',
  122069. 'Ext.util.HashMap',
  122070. 'Ext.util.KeyNav'
  122071. ],
  122072. //<locale>
  122073. saveBtnText : 'Update',
  122074. //</locale>
  122075. //<locale>
  122076. cancelBtnText: 'Cancel',
  122077. //</locale>
  122078. //<locale>
  122079. errorsText: 'Errors',
  122080. //</locale>
  122081. //<locale>
  122082. dirtyText: 'You need to commit or cancel your changes',
  122083. //</locale>
  122084. lastScrollLeft: 0,
  122085. lastScrollTop: 0,
  122086. border: false,
  122087. // Change the hideMode to offsets so that we get accurate measurements when
  122088. // the roweditor is hidden for laying out things like a TriggerField.
  122089. hideMode: 'offsets',
  122090. initComponent: function() {
  122091. var me = this,
  122092. form;
  122093. me.cls = Ext.baseCSSPrefix + 'grid-row-editor';
  122094. me.layout = {
  122095. type: 'hbox',
  122096. align: 'middle'
  122097. };
  122098. // Maintain field-to-column mapping
  122099. // It's easy to get a field from a column, but not vice versa
  122100. me.columns = new Ext.util.HashMap();
  122101. me.columns.getKey = function(columnHeader) {
  122102. var f;
  122103. if (columnHeader.getEditor) {
  122104. f = columnHeader.getEditor();
  122105. if (f) {
  122106. return f.id;
  122107. }
  122108. }
  122109. return columnHeader.id;
  122110. };
  122111. me.mon(me.columns, {
  122112. add: me.onFieldAdd,
  122113. remove: me.onFieldRemove,
  122114. replace: me.onFieldReplace,
  122115. scope: me
  122116. });
  122117. me.callParent(arguments);
  122118. if (me.fields) {
  122119. me.setField(me.fields);
  122120. delete me.fields;
  122121. }
  122122. me.mon(Ext.container.Container.hierarchyEventSource, {
  122123. scope: me,
  122124. show: me.repositionIfVisible
  122125. });
  122126. form = me.getForm();
  122127. form.trackResetOnLoad = true;
  122128. },
  122129. onFieldChange: function() {
  122130. var me = this,
  122131. form = me.getForm(),
  122132. valid = form.isValid();
  122133. if (me.errorSummary && me.isVisible()) {
  122134. me[valid ? 'hideToolTip' : 'showToolTip']();
  122135. }
  122136. me.updateButton(valid);
  122137. me.isValid = valid;
  122138. },
  122139. updateButton: function(valid){
  122140. var buttons = this.floatingButtons;
  122141. if (buttons) {
  122142. buttons.child('#update').setDisabled(!valid);
  122143. }
  122144. },
  122145. afterRender: function() {
  122146. var me = this,
  122147. plugin = me.editingPlugin;
  122148. me.callParent(arguments);
  122149. me.mon(me.renderTo, 'scroll', me.onCtScroll, me, { buffer: 100 });
  122150. // Prevent from bubbling click events to the grid view
  122151. me.mon(me.el, {
  122152. click: Ext.emptyFn,
  122153. stopPropagation: true
  122154. });
  122155. me.el.swallowEvent([
  122156. 'keypress',
  122157. 'keydown'
  122158. ]);
  122159. me.keyNav = new Ext.util.KeyNav(me.el, {
  122160. enter: plugin.completeEdit,
  122161. esc: plugin.onEscKey,
  122162. scope: plugin
  122163. });
  122164. me.mon(plugin.view, {
  122165. beforerefresh: me.onBeforeViewRefresh,
  122166. refresh: me.onViewRefresh,
  122167. itemremove: me.onViewItemRemove,
  122168. scope: me
  122169. });
  122170. },
  122171. onBeforeViewRefresh: function(view) {
  122172. var me = this,
  122173. viewDom = view.el.dom;
  122174. if (me.el.dom.parentNode === viewDom) {
  122175. viewDom.removeChild(me.el.dom);
  122176. }
  122177. },
  122178. onViewRefresh: function(view) {
  122179. var me = this,
  122180. viewDom = view.el.dom,
  122181. context = me.context,
  122182. idx;
  122183. viewDom.appendChild(me.el.dom);
  122184. // Recover our row node after a view refresh
  122185. if (context && (idx = context.store.indexOf(context.record)) >= 0) {
  122186. context.row = view.getNode(idx);
  122187. me.reposition();
  122188. if (me.tooltip && me.tooltip.isVisible()) {
  122189. me.tooltip.setTarget(context.row);
  122190. }
  122191. } else {
  122192. me.editingPlugin.cancelEdit();
  122193. }
  122194. },
  122195. onViewItemRemove: function(record, index) {
  122196. var context = this.context;
  122197. if (context && record === context.record) {
  122198. // if the record being edited was removed, cancel editing
  122199. this.editingPlugin.cancelEdit();
  122200. }
  122201. },
  122202. onCtScroll: function(e, target) {
  122203. var me = this,
  122204. scrollTop = target.scrollTop,
  122205. scrollLeft = target.scrollLeft;
  122206. if (scrollTop !== me.lastScrollTop) {
  122207. me.lastScrollTop = scrollTop;
  122208. if ((me.tooltip && me.tooltip.isVisible()) || me.hiddenTip) {
  122209. me.repositionTip();
  122210. }
  122211. }
  122212. if (scrollLeft !== me.lastScrollLeft) {
  122213. me.lastScrollLeft = scrollLeft;
  122214. me.reposition();
  122215. }
  122216. },
  122217. onColumnAdd: function(column) {
  122218. if (!column.isGroupHeader) {
  122219. this.setField(column);
  122220. }
  122221. },
  122222. onColumnRemove: function(column) {
  122223. this.columns.remove(column);
  122224. },
  122225. onColumnResize: function(column, width) {
  122226. if (!column.isGroupHeader) {
  122227. column.getEditor().setWidth(width - 2);
  122228. this.repositionIfVisible();
  122229. }
  122230. },
  122231. onColumnHide: function(column) {
  122232. if (!column.isGroupHeader) {
  122233. column.getEditor().hide();
  122234. this.repositionIfVisible();
  122235. }
  122236. },
  122237. onColumnShow: function(column) {
  122238. var field = column.getEditor();
  122239. field.setWidth(column.getWidth() - 2).show();
  122240. this.repositionIfVisible();
  122241. },
  122242. onColumnMove: function(column, fromIdx, toIdx) {
  122243. if (!column.isGroupHeader) {
  122244. var field = column.getEditor();
  122245. if (this.items.indexOf(field) != toIdx) {
  122246. this.move(fromIdx, toIdx);
  122247. }
  122248. }
  122249. },
  122250. onFieldAdd: function(map, fieldId, column) {
  122251. var me = this,
  122252. colIdx,
  122253. field;
  122254. if (!column.isGroupHeader) {
  122255. colIdx = me.editingPlugin.grid.headerCt.getHeaderIndex(column);
  122256. field = column.getEditor({ xtype: 'displayfield' });
  122257. me.insert(colIdx, field);
  122258. }
  122259. },
  122260. onFieldRemove: function(map, fieldId, column) {
  122261. var me = this,
  122262. field,
  122263. fieldEl;
  122264. if (!column.isGroupHeader) {
  122265. field = column.getEditor();
  122266. fieldEl = field.el;
  122267. me.remove(field, false);
  122268. if (fieldEl) {
  122269. fieldEl.remove();
  122270. }
  122271. }
  122272. },
  122273. onFieldReplace: function(map, fieldId, column, oldColumn) {
  122274. this.onFieldRemove(map, fieldId, oldColumn);
  122275. },
  122276. clearFields: function() {
  122277. var map = this.columns,
  122278. key;
  122279. for (key in map) {
  122280. if (map.hasOwnProperty(key)) {
  122281. map.removeAtKey(key);
  122282. }
  122283. }
  122284. },
  122285. getFloatingButtons: function() {
  122286. var me = this,
  122287. cssPrefix = Ext.baseCSSPrefix,
  122288. btnsCss = cssPrefix + 'grid-row-editor-buttons',
  122289. plugin = me.editingPlugin,
  122290. minWidth = Ext.panel.Panel.prototype.minButtonWidth,
  122291. btns;
  122292. if (!me.floatingButtons) {
  122293. btns = me.floatingButtons = new Ext.Container({
  122294. renderTpl: [
  122295. '<div class="{baseCls}-ml"></div>',
  122296. '<div class="{baseCls}-mr"></div>',
  122297. '<div class="{baseCls}-bl"></div>',
  122298. '<div class="{baseCls}-br"></div>',
  122299. '<div class="{baseCls}-bc"></div>',
  122300. '{%this.renderContainer(out,values)%}'
  122301. ],
  122302. width: 200,
  122303. renderTo: me.el,
  122304. baseCls: btnsCss,
  122305. layout: {
  122306. type: 'hbox',
  122307. align: 'middle'
  122308. },
  122309. defaults: {
  122310. flex: 1,
  122311. margins: '0 1 0 1'
  122312. },
  122313. items: [{
  122314. itemId: 'update',
  122315. xtype: 'button',
  122316. handler: plugin.completeEdit,
  122317. scope: plugin,
  122318. text: me.saveBtnText,
  122319. minWidth: minWidth
  122320. }, {
  122321. xtype: 'button',
  122322. handler: plugin.cancelEdit,
  122323. scope: plugin,
  122324. text: me.cancelBtnText,
  122325. minWidth: minWidth
  122326. }]
  122327. });
  122328. // Prevent from bubbling click events to the grid view
  122329. me.mon(btns.el, {
  122330. // BrowserBug: Opera 11.01
  122331. // causes the view to scroll when a button is focused from mousedown
  122332. mousedown: Ext.emptyFn,
  122333. click: Ext.emptyFn,
  122334. stopEvent: true
  122335. });
  122336. }
  122337. return me.floatingButtons;
  122338. },
  122339. repositionIfVisible: function(c){
  122340. var me = this,
  122341. view = me.view;
  122342. // If we're showing ourselves, jump out
  122343. // If the component we're showing doesn't contain the view
  122344. if (c && (c == me || !view.isDescendantOf(c))) {
  122345. return;
  122346. }
  122347. if (me.isVisible() && view.isVisible(true)) {
  122348. me.reposition();
  122349. }
  122350. },
  122351. reposition: function(animateConfig) {
  122352. var me = this,
  122353. context = me.context,
  122354. row = context && Ext.get(context.row),
  122355. btns = me.getFloatingButtons(),
  122356. btnEl = btns.el,
  122357. grid = me.editingPlugin.grid,
  122358. viewEl = grid.view.el,
  122359. // always get data from ColumnModel as its what drives
  122360. // the GridView's sizing
  122361. mainBodyWidth = grid.headerCt.getFullWidth(),
  122362. scrollerWidth = grid.getWidth(),
  122363. // use the minimum as the columns may not fill up the entire grid
  122364. // width
  122365. width = Math.min(mainBodyWidth, scrollerWidth),
  122366. scrollLeft = grid.view.el.dom.scrollLeft,
  122367. btnWidth = btns.getWidth(),
  122368. left = (width - btnWidth) / 2 + scrollLeft,
  122369. y, rowH, newHeight,
  122370. invalidateScroller = function() {
  122371. btnEl.scrollIntoView(viewEl, false);
  122372. if (animateConfig && animateConfig.callback) {
  122373. animateConfig.callback.call(animateConfig.scope || me);
  122374. }
  122375. },
  122376. animObj;
  122377. // need to set both top/left
  122378. if (row && Ext.isElement(row.dom)) {
  122379. // Bring our row into view if necessary, so a row editor that's already
  122380. // visible and animated to the row will appear smooth
  122381. row.scrollIntoView(viewEl, false);
  122382. // Get the y position of the row relative to its top-most static parent.
  122383. // offsetTop will be relative to the table, and is incorrect
  122384. // when mixed with certain grid features (e.g., grouping).
  122385. y = row.getXY()[1] - 5;
  122386. rowH = row.getHeight();
  122387. newHeight = rowH + (me.editingPlugin.grid.rowLines ? 9 : 10);
  122388. // Set editor height to match the row height
  122389. if (me.getHeight() != newHeight) {
  122390. me.setHeight(newHeight);
  122391. me.el.setLeft(0);
  122392. }
  122393. if (animateConfig) {
  122394. animObj = {
  122395. to: {
  122396. y: y
  122397. },
  122398. duration: animateConfig.duration || 125,
  122399. listeners: {
  122400. afteranimate: function() {
  122401. invalidateScroller();
  122402. y = row.getXY()[1] - 5;
  122403. }
  122404. }
  122405. };
  122406. me.el.animate(animObj);
  122407. } else {
  122408. me.el.setY(y);
  122409. invalidateScroller();
  122410. }
  122411. }
  122412. if (me.getWidth() != mainBodyWidth) {
  122413. me.setWidth(mainBodyWidth);
  122414. }
  122415. btnEl.setLeft(left);
  122416. },
  122417. getEditor: function(fieldInfo) {
  122418. var me = this;
  122419. if (Ext.isNumber(fieldInfo)) {
  122420. // Query only form fields. This just future-proofs us in case we add
  122421. // other components to RowEditor later on. Don't want to mess with
  122422. // indices.
  122423. return me.query('>[isFormField]')[fieldInfo];
  122424. } else if (fieldInfo.isHeader && !fieldInfo.isGroupHeader) {
  122425. return fieldInfo.getEditor();
  122426. }
  122427. },
  122428. removeField: function(field) {
  122429. var me = this;
  122430. // Incase we pass a column instead, which is fine
  122431. field = me.getEditor(field);
  122432. me.mun(field, 'validitychange', me.onValidityChange, me);
  122433. // Remove field/column from our mapping, which will fire the event to
  122434. // remove the field from our container
  122435. me.columns.removeAtKey(field.id);
  122436. Ext.destroy(field);
  122437. },
  122438. setField: function(column) {
  122439. var me = this,
  122440. i,
  122441. length, field;
  122442. if (Ext.isArray(column)) {
  122443. length = column.length;
  122444. for (i = 0; i < length; i++) {
  122445. me.setField(column[i]);
  122446. }
  122447. return;
  122448. }
  122449. // Get a default display field if necessary
  122450. field = column.getEditor(null, {
  122451. xtype: 'displayfield',
  122452. // Override Field's implementation so that the default display fields will not return values. This is done because
  122453. // the display field will pick up column renderers from the grid.
  122454. getModelData: function() {
  122455. return null;
  122456. }
  122457. });
  122458. field.margins = '0 0 0 2';
  122459. me.mon(field, 'change', me.onFieldChange, me);
  122460. if (me.isVisible() && me.context) {
  122461. if (field.is('displayfield')) {
  122462. me.renderColumnData(field, me.context.record, column);
  122463. } else {
  122464. field.suspendEvents();
  122465. field.setValue(me.context.record.get(column.dataIndex));
  122466. field.resumeEvents();
  122467. }
  122468. }
  122469. // Maintain mapping of fields-to-columns
  122470. // This will fire events that maintain our container items
  122471. me.columns.add(field.id, column);
  122472. if (column.hidden) {
  122473. me.onColumnHide(column);
  122474. } else if (column.rendered) {
  122475. // Setting after initial render
  122476. me.onColumnShow(column);
  122477. }
  122478. },
  122479. loadRecord: function(record) {
  122480. var me = this,
  122481. form = me.getForm(),
  122482. fields = form.getFields(),
  122483. items = fields.items,
  122484. length = items.length,
  122485. i, displayFields,
  122486. isValid;
  122487. // temporarily suspend events on form fields before loading record to prevent the fields' change events from firing
  122488. for (i = 0; i < length; i++) {
  122489. items[i].suspendEvents();
  122490. }
  122491. form.loadRecord(record);
  122492. for (i = 0; i < length; i++) {
  122493. items[i].resumeEvents();
  122494. }
  122495. isValid = form.isValid();
  122496. if (me.errorSummary) {
  122497. if (isValid) {
  122498. me.hideToolTip();
  122499. } else {
  122500. me.showToolTip();
  122501. }
  122502. }
  122503. me.updateButton(isValid);
  122504. // render display fields so they honor the column renderer/template
  122505. displayFields = me.query('>displayfield');
  122506. length = displayFields.length;
  122507. for (i = 0; i < length; i++) {
  122508. me.renderColumnData(displayFields[i], record);
  122509. }
  122510. },
  122511. renderColumnData: function(field, record, activeColumn) {
  122512. var me = this,
  122513. grid = me.editingPlugin.grid,
  122514. headerCt = grid.headerCt,
  122515. view = grid.view,
  122516. store = view.store,
  122517. column = activeColumn || me.columns.get(field.id),
  122518. value = record.get(column.dataIndex),
  122519. renderer = column.editRenderer || column.renderer,
  122520. metaData,
  122521. rowIdx,
  122522. colIdx;
  122523. // honor our column's renderer (TemplateHeader sets renderer for us!)
  122524. if (renderer) {
  122525. metaData = { tdCls: '', style: '' };
  122526. rowIdx = store.indexOf(record);
  122527. colIdx = headerCt.getHeaderIndex(column);
  122528. value = renderer.call(
  122529. column.scope || headerCt.ownerCt,
  122530. value,
  122531. metaData,
  122532. record,
  122533. rowIdx,
  122534. colIdx,
  122535. store,
  122536. view
  122537. );
  122538. }
  122539. field.setRawValue(value);
  122540. field.resetOriginalValue();
  122541. },
  122542. beforeEdit: function() {
  122543. var me = this;
  122544. if (me.isVisible() && me.errorSummary && !me.autoCancel && me.isDirty()) {
  122545. me.showToolTip();
  122546. return false;
  122547. }
  122548. },
  122549. /**
  122550. * Start editing the specified grid at the specified position.
  122551. * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
  122552. * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited.
  122553. */
  122554. startEdit: function(record, columnHeader) {
  122555. var me = this,
  122556. grid = me.editingPlugin.grid,
  122557. store = grid.store,
  122558. context = me.context = Ext.apply(me.editingPlugin.context, {
  122559. view: grid.getView(),
  122560. store: store
  122561. });
  122562. // make sure our row is selected before editing
  122563. context.grid.getSelectionModel().select(record);
  122564. // Reload the record data
  122565. me.loadRecord(record);
  122566. if (!me.isVisible()) {
  122567. me.show();
  122568. me.focusContextCell();
  122569. } else {
  122570. me.reposition({
  122571. callback: this.focusContextCell
  122572. });
  122573. }
  122574. },
  122575. // Focus the cell on start edit based upon the current context
  122576. focusContextCell: function() {
  122577. var field = this.getEditor(this.context.colIdx);
  122578. if (field && field.focus) {
  122579. field.focus();
  122580. }
  122581. },
  122582. cancelEdit: function() {
  122583. var me = this,
  122584. form = me.getForm(),
  122585. fields = form.getFields(),
  122586. items = fields.items,
  122587. length = items.length,
  122588. i;
  122589. me.hide();
  122590. form.clearInvalid();
  122591. // temporarily suspend events on form fields before reseting the form to prevent the fields' change events from firing
  122592. for (i = 0; i < length; i++) {
  122593. items[i].suspendEvents();
  122594. }
  122595. form.reset();
  122596. for (i = 0; i < length; i++) {
  122597. items[i].resumeEvents();
  122598. }
  122599. },
  122600. completeEdit: function() {
  122601. var me = this,
  122602. form = me.getForm();
  122603. if (!form.isValid()) {
  122604. return;
  122605. }
  122606. form.updateRecord(me.context.record);
  122607. me.hide();
  122608. return true;
  122609. },
  122610. onShow: function() {
  122611. this.callParent(arguments);
  122612. this.reposition();
  122613. },
  122614. onHide: function() {
  122615. var me = this;
  122616. me.callParent(arguments);
  122617. if (me.tooltip) {
  122618. me.hideToolTip();
  122619. }
  122620. if (me.context) {
  122621. me.context.view.focus();
  122622. me.context = null;
  122623. }
  122624. },
  122625. isDirty: function() {
  122626. var me = this,
  122627. form = me.getForm();
  122628. return form.isDirty();
  122629. },
  122630. getToolTip: function() {
  122631. return this.tooltip || (this.tooltip = new Ext.tip.ToolTip({
  122632. cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',
  122633. title: this.errorsText,
  122634. autoHide: false,
  122635. closable: true,
  122636. closeAction: 'disable',
  122637. anchor: 'left'
  122638. }));
  122639. },
  122640. hideToolTip: function() {
  122641. var me = this,
  122642. tip = me.getToolTip();
  122643. if (tip.rendered) {
  122644. tip.disable();
  122645. }
  122646. me.hiddenTip = false;
  122647. },
  122648. showToolTip: function() {
  122649. var me = this,
  122650. tip = me.getToolTip(),
  122651. context = me.context,
  122652. row = Ext.get(context.row),
  122653. viewEl = context.grid.view.el;
  122654. tip.setTarget(row);
  122655. tip.showAt([-10000, -10000]);
  122656. tip.update(me.getErrors());
  122657. tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];
  122658. me.repositionTip();
  122659. tip.doLayout();
  122660. tip.enable();
  122661. },
  122662. repositionTip: function() {
  122663. var me = this,
  122664. tip = me.getToolTip(),
  122665. context = me.context,
  122666. row = Ext.get(context.row),
  122667. viewEl = context.grid.view.el,
  122668. viewHeight = viewEl.getHeight(),
  122669. viewTop = me.lastScrollTop,
  122670. viewBottom = viewTop + viewHeight,
  122671. rowHeight = row.getHeight(),
  122672. rowTop = row.dom.offsetTop,
  122673. rowBottom = rowTop + rowHeight;
  122674. if (rowBottom > viewTop && rowTop < viewBottom) {
  122675. tip.show();
  122676. me.hiddenTip = false;
  122677. } else {
  122678. tip.hide();
  122679. me.hiddenTip = true;
  122680. }
  122681. },
  122682. getErrors: function() {
  122683. var me = this,
  122684. dirtyText = !me.autoCancel && me.isDirty() ? me.dirtyText + '<br />' : '',
  122685. errors = [],
  122686. fields = me.query('>[isFormField]'),
  122687. length = fields.length,
  122688. i;
  122689. function createListItem(e) {
  122690. return '<li>' + e + '</li>';
  122691. }
  122692. for (i = 0; i < length; i++) {
  122693. errors = errors.concat(
  122694. Ext.Array.map(fields[i].getErrors(), createListItem)
  122695. );
  122696. }
  122697. return dirtyText + '<ul>' + errors.join('') + '</ul>';
  122698. },
  122699. beforeDestroy: function(){
  122700. Ext.destroy(this.floatingButtons, this.tooltip);
  122701. this.callParent();
  122702. }
  122703. });
  122704. /**
  122705. * Plugin to add header resizing functionality to a HeaderContainer.
  122706. * Always resizing header to the left of the splitter you are resizing.
  122707. */
  122708. Ext.define('Ext.grid.plugin.HeaderResizer', {
  122709. extend: 'Ext.AbstractPlugin',
  122710. requires: ['Ext.dd.DragTracker', 'Ext.util.Region'],
  122711. alias: 'plugin.gridheaderresizer',
  122712. disabled: false,
  122713. config: {
  122714. /**
  122715. * @cfg {Boolean} dynamic
  122716. * True to resize on the fly rather than using a proxy marker.
  122717. * @accessor
  122718. */
  122719. dynamic: false
  122720. },
  122721. colHeaderCls: Ext.baseCSSPrefix + 'column-header',
  122722. minColWidth: 40,
  122723. maxColWidth: 1000,
  122724. wResizeCursor: 'col-resize',
  122725. eResizeCursor: 'col-resize',
  122726. // not using w and e resize bc we are only ever resizing one
  122727. // column
  122728. //wResizeCursor: Ext.isWebKit ? 'w-resize' : 'col-resize',
  122729. //eResizeCursor: Ext.isWebKit ? 'e-resize' : 'col-resize',
  122730. init: function(headerCt) {
  122731. this.headerCt = headerCt;
  122732. headerCt.on('render', this.afterHeaderRender, this, {single: true});
  122733. },
  122734. /**
  122735. * @private
  122736. * AbstractComponent calls destroy on all its plugins at destroy time.
  122737. */
  122738. destroy: function() {
  122739. if (this.tracker) {
  122740. this.tracker.destroy();
  122741. }
  122742. },
  122743. afterHeaderRender: function() {
  122744. var headerCt = this.headerCt,
  122745. el = headerCt.el;
  122746. headerCt.mon(el, 'mousemove', this.onHeaderCtMouseMove, this);
  122747. this.tracker = new Ext.dd.DragTracker({
  122748. disabled: this.disabled,
  122749. onBeforeStart: Ext.Function.bind(this.onBeforeStart, this),
  122750. onStart: Ext.Function.bind(this.onStart, this),
  122751. onDrag: Ext.Function.bind(this.onDrag, this),
  122752. onEnd: Ext.Function.bind(this.onEnd, this),
  122753. tolerance: 3,
  122754. autoStart: 300,
  122755. el: el
  122756. });
  122757. },
  122758. // As we mouse over individual headers, change the cursor to indicate
  122759. // that resizing is available, and cache the resize target header for use
  122760. // if/when they mousedown.
  122761. onHeaderCtMouseMove: function(e, t) {
  122762. var me = this,
  122763. prevSiblings,
  122764. headerEl, overHeader, resizeHeader, resizeHeaderOwnerGrid, ownerGrid;
  122765. if (me.headerCt.dragging) {
  122766. if (me.activeHd) {
  122767. me.activeHd.el.dom.style.cursor = '';
  122768. delete me.activeHd;
  122769. }
  122770. } else {
  122771. headerEl = e.getTarget('.' + me.colHeaderCls, 3, true);
  122772. if (headerEl){
  122773. overHeader = Ext.getCmp(headerEl.id);
  122774. // On left edge, go back to the previous non-hidden header.
  122775. if (overHeader.isOnLeftEdge(e)) {
  122776. resizeHeader = overHeader.previousNode('gridcolumn:not([hidden]):not([isGroupHeader])')
  122777. // There may not *be* a previous non-hidden header.
  122778. if (resizeHeader) {
  122779. ownerGrid = me.headerCt.up('tablepanel');
  122780. resizeHeaderOwnerGrid = resizeHeader.up('tablepanel');
  122781. // Need to check that previousNode didn't go outside the current grid/tree
  122782. // But in the case of a Grid which contains a locked and normal grid, allow previousNode to jump
  122783. // from the first column of the normalGrid to the last column of the lockedGrid
  122784. if (!((resizeHeaderOwnerGrid === ownerGrid) || ((ownerGrid.ownerCt.isXType('tablepanel')) && ownerGrid.ownerCt.view.lockedGrid === resizeHeaderOwnerGrid))) {
  122785. resizeHeader = null;
  122786. }
  122787. }
  122788. }
  122789. // Else, if on the right edge, we're resizing the column we are over
  122790. else if (overHeader.isOnRightEdge(e)) {
  122791. resizeHeader = overHeader;
  122792. }
  122793. // Between the edges: we are not resizing
  122794. else {
  122795. resizeHeader = null;
  122796. }
  122797. // We *are* resizing
  122798. if (resizeHeader) {
  122799. // If we're attempting to resize a group header, that cannot be resized,
  122800. // so find its last visible leaf header; Group headers are sized
  122801. // by the size of their child headers.
  122802. if (resizeHeader.isGroupHeader) {
  122803. prevSiblings = resizeHeader.getGridColumns();
  122804. resizeHeader = prevSiblings[prevSiblings.length - 1];
  122805. }
  122806. // Check if the header is resizable. Continue checking the old "fixed" property, bug also
  122807. // check whether the resizablwe property is set to false.
  122808. if (resizeHeader && !(resizeHeader.fixed || (resizeHeader.resizable === false) || me.disabled)) {
  122809. me.activeHd = resizeHeader;
  122810. overHeader.el.dom.style.cursor = me.eResizeCursor;
  122811. }
  122812. // reset
  122813. } else {
  122814. overHeader.el.dom.style.cursor = '';
  122815. delete me.activeHd;
  122816. }
  122817. }
  122818. }
  122819. },
  122820. // only start when there is an activeHd
  122821. onBeforeStart : function(e){
  122822. var t = e.getTarget();
  122823. // cache the activeHd because it will be cleared.
  122824. this.dragHd = this.activeHd;
  122825. if (!!this.dragHd && !Ext.fly(t).hasCls(Ext.baseCSSPrefix + 'column-header-trigger') && !this.headerCt.dragging) {
  122826. //this.headerCt.dragging = true;
  122827. this.tracker.constrainTo = this.getConstrainRegion();
  122828. return true;
  122829. } else {
  122830. this.headerCt.dragging = false;
  122831. return false;
  122832. }
  122833. },
  122834. // get the region to constrain to, takes into account max and min col widths
  122835. getConstrainRegion: function() {
  122836. var me = this,
  122837. dragHdEl = me.dragHd.el,
  122838. region = Ext.util.Region.getRegion(dragHdEl),
  122839. nextHd;
  122840. // If forceFit, then right constraint is based upon not being able to force the next header
  122841. // beyond the minColWidth. If there is no next header, then the header may not be expanded.
  122842. if (me.headerCt.forceFit) {
  122843. nextHd = me.dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
  122844. }
  122845. return region.adjust(
  122846. 0,
  122847. me.headerCt.forceFit ? (nextHd ? nextHd.getWidth() - me.minColWidth : 0) : me.maxColWidth - dragHdEl.getWidth(),
  122848. 0,
  122849. me.minColWidth
  122850. );
  122851. },
  122852. // initialize the left and right hand side markers around
  122853. // the header that we are resizing
  122854. onStart: function(e){
  122855. var me = this,
  122856. dragHd = me.dragHd,
  122857. dragHdEl = dragHd.el,
  122858. width = dragHdEl.getWidth(),
  122859. headerCt = me.headerCt,
  122860. t = e.getTarget(),
  122861. xy, gridSection, dragHct, firstSection, lhsMarker, rhsMarker, el, offsetLeft, offsetTop, topLeft, markerHeight, top;
  122862. if (me.dragHd && !Ext.fly(t).hasCls(Ext.baseCSSPrefix + 'column-header-trigger')) {
  122863. headerCt.dragging = true;
  122864. }
  122865. me.origWidth = width;
  122866. // setup marker proxies
  122867. if (!me.dynamic) {
  122868. xy = dragHdEl.getXY();
  122869. gridSection = headerCt.up('[scrollerOwner]');
  122870. dragHct = me.dragHd.up(':not([isGroupHeader])');
  122871. firstSection = dragHct.up();
  122872. lhsMarker = gridSection.getLhsMarker();
  122873. rhsMarker = gridSection.getRhsMarker();
  122874. el = rhsMarker.parent();
  122875. offsetLeft = el.getLocalX();
  122876. offsetTop = el.getLocalY();
  122877. topLeft = el.translatePoints(xy);
  122878. markerHeight = firstSection.body.getHeight() + headerCt.getHeight();
  122879. top = topLeft.top - offsetTop;
  122880. lhsMarker.setTop(top);
  122881. rhsMarker.setTop(top);
  122882. lhsMarker.setHeight(markerHeight);
  122883. rhsMarker.setHeight(markerHeight);
  122884. lhsMarker.setLeft(topLeft.left - offsetLeft);
  122885. rhsMarker.setLeft(topLeft.left + width - offsetLeft);
  122886. }
  122887. },
  122888. // synchronize the rhsMarker with the mouse movement
  122889. onDrag: function(e){
  122890. if (!this.dynamic) {
  122891. var xy = this.tracker.getXY('point'),
  122892. gridSection = this.headerCt.up('[scrollerOwner]'),
  122893. rhsMarker = gridSection.getRhsMarker(),
  122894. el = rhsMarker.parent(),
  122895. topLeft = el.translatePoints(xy),
  122896. offsetLeft = el.getLocalX();
  122897. rhsMarker.setLeft(topLeft.left - offsetLeft);
  122898. // Resize as user interacts
  122899. } else {
  122900. this.doResize();
  122901. }
  122902. },
  122903. onEnd: function(e){
  122904. this.headerCt.dragging = false;
  122905. if (this.dragHd) {
  122906. if (!this.dynamic) {
  122907. var dragHd = this.dragHd,
  122908. gridSection = this.headerCt.up('[scrollerOwner]'),
  122909. lhsMarker = gridSection.getLhsMarker(),
  122910. rhsMarker = gridSection.getRhsMarker(),
  122911. offscreen = -9999;
  122912. // hide markers
  122913. lhsMarker.setLeft(offscreen);
  122914. rhsMarker.setLeft(offscreen);
  122915. }
  122916. this.doResize();
  122917. }
  122918. },
  122919. doResize: function() {
  122920. if (this.dragHd) {
  122921. var dragHd = this.dragHd,
  122922. nextHd,
  122923. offset = this.tracker.getOffset('point');
  122924. // resize the dragHd
  122925. if (dragHd.flex) {
  122926. delete dragHd.flex;
  122927. }
  122928. Ext.suspendLayouts();
  122929. // Set the new column width.
  122930. dragHd.setWidth(this.origWidth + offset[0]);
  122931. // In the case of forceFit, change the following Header width.
  122932. // Constraining so that neither neighbour can be sized to below minWidth is handled in getConstrainRegion
  122933. if (this.headerCt.forceFit) {
  122934. nextHd = dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
  122935. if (nextHd) {
  122936. delete nextHd.flex;
  122937. nextHd.setWidth(nextHd.getWidth() - offset[0]);
  122938. }
  122939. }
  122940. // Apply the two width changes by laying out the owning HeaderContainer
  122941. Ext.resumeLayouts(true);
  122942. }
  122943. },
  122944. disable: function() {
  122945. this.disabled = true;
  122946. if (this.tracker) {
  122947. this.tracker.disable();
  122948. }
  122949. },
  122950. enable: function() {
  122951. this.disabled = false;
  122952. if (this.tracker) {
  122953. this.tracker.enable();
  122954. }
  122955. }
  122956. });
  122957. /**
  122958. * @private
  122959. */
  122960. Ext.define('Ext.grid.header.DragZone', {
  122961. extend: 'Ext.dd.DragZone',
  122962. colHeaderCls: Ext.baseCSSPrefix + 'column-header',
  122963. maxProxyWidth: 120,
  122964. constructor: function(headerCt) {
  122965. this.headerCt = headerCt;
  122966. this.ddGroup = this.getDDGroup();
  122967. this.callParent([headerCt.el]);
  122968. this.proxy.el.addCls(Ext.baseCSSPrefix + 'grid-col-dd');
  122969. },
  122970. getDDGroup: function() {
  122971. return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
  122972. },
  122973. getDragData: function(e) {
  122974. var header = e.getTarget('.'+this.colHeaderCls),
  122975. headerCmp,
  122976. ddel;
  122977. if (header) {
  122978. headerCmp = Ext.getCmp(header.id);
  122979. if (!this.headerCt.dragging && headerCmp.draggable && !(headerCmp.isOnLeftEdge(e) || headerCmp.isOnRightEdge(e))) {
  122980. ddel = document.createElement('div');
  122981. ddel.innerHTML = Ext.getCmp(header.id).text;
  122982. return {
  122983. ddel: ddel,
  122984. header: headerCmp
  122985. };
  122986. }
  122987. }
  122988. return false;
  122989. },
  122990. onBeforeDrag: function() {
  122991. return !(this.headerCt.dragging || this.disabled);
  122992. },
  122993. onInitDrag: function() {
  122994. this.headerCt.dragging = true;
  122995. this.callParent(arguments);
  122996. },
  122997. onDragDrop: function() {
  122998. this.headerCt.dragging = false;
  122999. this.callParent(arguments);
  123000. },
  123001. afterRepair: function() {
  123002. this.callParent();
  123003. this.headerCt.dragging = false;
  123004. },
  123005. getRepairXY: function() {
  123006. return this.dragData.header.el.getXY();
  123007. },
  123008. disable: function() {
  123009. this.disabled = true;
  123010. },
  123011. enable: function() {
  123012. this.disabled = false;
  123013. }
  123014. });
  123015. /**
  123016. * @private
  123017. */
  123018. Ext.define('Ext.grid.header.DropZone', {
  123019. extend: 'Ext.dd.DropZone',
  123020. colHeaderCls: Ext.baseCSSPrefix + 'column-header',
  123021. proxyOffsets: [-4, -9],
  123022. constructor: function(headerCt){
  123023. this.headerCt = headerCt;
  123024. this.ddGroup = this.getDDGroup();
  123025. this.callParent([headerCt.el]);
  123026. },
  123027. getDDGroup: function() {
  123028. return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
  123029. },
  123030. getTargetFromEvent : function(e){
  123031. return e.getTarget('.' + this.colHeaderCls);
  123032. },
  123033. getTopIndicator: function() {
  123034. if (!this.topIndicator) {
  123035. this.topIndicator = Ext.DomHelper.append(Ext.getBody(), {
  123036. cls: "col-move-top",
  123037. html: "&#160;"
  123038. }, true);
  123039. }
  123040. return this.topIndicator;
  123041. },
  123042. getBottomIndicator: function() {
  123043. if (!this.bottomIndicator) {
  123044. this.bottomIndicator = Ext.DomHelper.append(Ext.getBody(), {
  123045. cls: "col-move-bottom",
  123046. html: "&#160;"
  123047. }, true);
  123048. }
  123049. return this.bottomIndicator;
  123050. },
  123051. getLocation: function(e, t) {
  123052. var x = e.getXY()[0],
  123053. region = Ext.fly(t).getRegion(),
  123054. pos, header;
  123055. if ((region.right - x) <= (region.right - region.left) / 2) {
  123056. pos = "after";
  123057. } else {
  123058. pos = "before";
  123059. }
  123060. return {
  123061. pos: pos,
  123062. header: Ext.getCmp(t.id),
  123063. node: t
  123064. };
  123065. },
  123066. positionIndicator: function(draggedHeader, node, e){
  123067. var location = this.getLocation(e, node),
  123068. header = location.header,
  123069. pos = location.pos,
  123070. nextHd = draggedHeader.nextSibling('gridcolumn:not([hidden])'),
  123071. prevHd = draggedHeader.previousSibling('gridcolumn:not([hidden])'),
  123072. topIndicator, bottomIndicator, topAnchor, bottomAnchor,
  123073. topXY, bottomXY, headerCtEl, minX, maxX,
  123074. allDropZones, ln, i, dropZone;
  123075. // Cannot drag beyond non-draggable start column
  123076. if (!header.draggable && header.getIndex() === 0) {
  123077. return false;
  123078. }
  123079. this.lastLocation = location;
  123080. if ((draggedHeader !== header) &&
  123081. ((pos === "before" && nextHd !== header) ||
  123082. (pos === "after" && prevHd !== header)) &&
  123083. !header.isDescendantOf(draggedHeader)) {
  123084. // As we move in between different DropZones that are in the same
  123085. // group (such as the case when in a locked grid), invalidateDrop
  123086. // on the other dropZones.
  123087. allDropZones = Ext.dd.DragDropManager.getRelated(this);
  123088. ln = allDropZones.length;
  123089. i = 0;
  123090. for (; i < ln; i++) {
  123091. dropZone = allDropZones[i];
  123092. if (dropZone !== this && dropZone.invalidateDrop) {
  123093. dropZone.invalidateDrop();
  123094. }
  123095. }
  123096. this.valid = true;
  123097. topIndicator = this.getTopIndicator();
  123098. bottomIndicator = this.getBottomIndicator();
  123099. if (pos === 'before') {
  123100. topAnchor = 'tl';
  123101. bottomAnchor = 'bl';
  123102. } else {
  123103. topAnchor = 'tr';
  123104. bottomAnchor = 'br';
  123105. }
  123106. topXY = header.el.getAnchorXY(topAnchor);
  123107. bottomXY = header.el.getAnchorXY(bottomAnchor);
  123108. // constrain the indicators to the viewable section
  123109. headerCtEl = this.headerCt.el;
  123110. minX = headerCtEl.getLeft();
  123111. maxX = headerCtEl.getRight();
  123112. topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);
  123113. bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);
  123114. // adjust by offsets, this is to center the arrows so that they point
  123115. // at the split point
  123116. topXY[0] -= 4;
  123117. topXY[1] -= 9;
  123118. bottomXY[0] -= 4;
  123119. // position and show indicators
  123120. topIndicator.setXY(topXY);
  123121. bottomIndicator.setXY(bottomXY);
  123122. topIndicator.show();
  123123. bottomIndicator.show();
  123124. // invalidate drop operation and hide indicators
  123125. } else {
  123126. this.invalidateDrop();
  123127. }
  123128. },
  123129. invalidateDrop: function() {
  123130. this.valid = false;
  123131. this.hideIndicators();
  123132. },
  123133. onNodeOver: function(node, dragZone, e, data) {
  123134. var me = this,
  123135. header = me.headerCt,
  123136. doPosition = true,
  123137. from = data.header,
  123138. to;
  123139. if (data.header.el.dom === node) {
  123140. doPosition = false;
  123141. } else {
  123142. to = me.getLocation(e, node).header;
  123143. doPosition = (from.ownerCt === to.ownerCt) || (!from.ownerCt.sealed && !to.ownerCt.sealed);
  123144. }
  123145. if (doPosition) {
  123146. me.positionIndicator(data.header, node, e);
  123147. } else {
  123148. me.valid = false;
  123149. }
  123150. return me.valid ? me.dropAllowed : me.dropNotAllowed;
  123151. },
  123152. hideIndicators: function() {
  123153. this.getTopIndicator().hide();
  123154. this.getBottomIndicator().hide();
  123155. },
  123156. onNodeOut: function() {
  123157. this.hideIndicators();
  123158. },
  123159. onNodeDrop: function(node, dragZone, e, data) {
  123160. if (this.valid) {
  123161. var dragHeader = data.header,
  123162. lastLocation = this.lastLocation,
  123163. targetHeader = lastLocation.header,
  123164. fromCt = dragHeader.ownerCt,
  123165. fromHeader = dragHeader.up('headercontainer:not(gridcolumn)'),
  123166. localFromIdx = fromCt.items.indexOf(dragHeader), // Container.items is a MixedCollection
  123167. toCt = targetHeader.ownerCt,
  123168. toHeader = targetHeader.up('headercontainer:not(gridcolumn)'),
  123169. localToIdx = toCt.items.indexOf(targetHeader),
  123170. headerCt = this.headerCt,
  123171. fromIdx = headerCt.getHeaderIndex(dragHeader),
  123172. colsToMove = dragHeader.isGroupHeader ? dragHeader.query(':not([isGroupHeader])').length : 1,
  123173. toIdx = headerCt.getHeaderIndex(targetHeader),
  123174. groupCt,
  123175. scrollerOwner;
  123176. // Drop position is to the right of the targetHeader, increment the toIdx correctly
  123177. if (lastLocation.pos === 'after') {
  123178. localToIdx++;
  123179. toIdx += targetHeader.isGroupHeader ? targetHeader.query(':not([isGroupHeader])').length : 1;
  123180. }
  123181. // If we are dragging in between two HeaderContainers that have had the lockable
  123182. // mixin injected we will lock/unlock headers in between sections, and then continue
  123183. // with another execution of onNodeDrop to ensure the header is dropped into the correct group
  123184. if (fromHeader !== toHeader && fromHeader.lockableInjected && toHeader.lockableInjected && toHeader.lockedCt) {
  123185. scrollerOwner = fromCt.up('[scrollerOwner]');
  123186. scrollerOwner.lock(dragHeader, localToIdx);
  123187. // Now that the header has been transferred into the correct HeaderContainer, recurse, and continue the drop operation with the same dragData
  123188. this.onNodeDrop(node, dragZone, e, data);
  123189. } else if (fromHeader !== toHeader && fromHeader.lockableInjected && toHeader.lockableInjected && fromHeader.lockedCt) {
  123190. scrollerOwner = fromCt.up('[scrollerOwner]');
  123191. scrollerOwner.unlock(dragHeader, localToIdx);
  123192. // Now that the header has been transferred into the correct HeaderContainer, recurse, and continue the drop operation with the same dragData
  123193. this.onNodeDrop(node, dragZone, e, data);
  123194. }
  123195. // This is a drop within the same HeaderContainer.
  123196. else {
  123197. this.invalidateDrop();
  123198. // If dragging rightwards, then after removal, the insertion index will be less when moving
  123199. // within the same container.
  123200. if ((fromCt === toCt) && (localToIdx > localFromIdx)) {
  123201. // Wer're dragging whole headers, so locally, the adjustment is only one
  123202. localToIdx -= 1;
  123203. }
  123204. // Suspend layouts while we sort all this out.
  123205. Ext.suspendLayouts();
  123206. // Remove dragged header from where it was.
  123207. if (fromCt !== toCt) {
  123208. fromCt.remove(dragHeader, false);
  123209. // Dragged the last header out of the fromCt group... The fromCt group must die
  123210. if (fromCt.isGroupHeader) {
  123211. if (!fromCt.items.getCount()) {
  123212. groupCt = fromCt.ownerCt;
  123213. groupCt.remove(fromCt, false);
  123214. fromCt.el.dom.parentNode.removeChild(fromCt.el.dom);
  123215. }
  123216. }
  123217. }
  123218. // Move dragged header into its drop position
  123219. if (fromCt === toCt) {
  123220. toCt.move(localFromIdx, localToIdx);
  123221. } else {
  123222. toCt.insert(localToIdx, dragHeader);
  123223. }
  123224. // Group headers acquire the aggregate width of their child headers
  123225. // Therefore a child header may not flex; it must contribute a fixed width.
  123226. // But we restore the flex value when moving back into the main header container
  123227. if (toCt.isGroupHeader) {
  123228. // Adjust the width of the "to" group header only if we dragged in from somewhere else.
  123229. if (toCt !== fromCt) {
  123230. dragHeader.savedFlex = dragHeader.flex;
  123231. delete dragHeader.flex;
  123232. dragHeader.width = dragHeader.getWidth();
  123233. }
  123234. } else {
  123235. if (dragHeader.savedFlex) {
  123236. dragHeader.flex = dragHeader.savedFlex;
  123237. delete dragHeader.width;
  123238. }
  123239. }
  123240. // Refresh columns cache in case we remove an emptied group column
  123241. headerCt.purgeCache();
  123242. Ext.resumeLayouts(true);
  123243. headerCt.onHeaderMoved(dragHeader, colsToMove, fromIdx, toIdx);
  123244. // Emptied group header can only be destroyed after the header and grid have been refreshed
  123245. if (!fromCt.items.getCount()) {
  123246. fromCt.destroy();
  123247. }
  123248. }
  123249. }
  123250. }
  123251. });
  123252. /**
  123253. * @private
  123254. */
  123255. Ext.define('Ext.grid.plugin.HeaderReorderer', {
  123256. extend: 'Ext.AbstractPlugin',
  123257. requires: ['Ext.grid.header.DragZone', 'Ext.grid.header.DropZone'],
  123258. alias: 'plugin.gridheaderreorderer',
  123259. init: function(headerCt) {
  123260. this.headerCt = headerCt;
  123261. headerCt.on({
  123262. render: this.onHeaderCtRender,
  123263. single: true,
  123264. scope: this
  123265. });
  123266. },
  123267. /**
  123268. * @private
  123269. * AbstractComponent calls destroy on all its plugins at destroy time.
  123270. */
  123271. destroy: function() {
  123272. Ext.destroy(this.dragZone, this.dropZone);
  123273. },
  123274. onHeaderCtRender: function() {
  123275. var me = this;
  123276. me.dragZone = new Ext.grid.header.DragZone(me.headerCt);
  123277. me.dropZone = new Ext.grid.header.DropZone(me.headerCt);
  123278. if (me.disabled) {
  123279. me.dragZone.disable();
  123280. }
  123281. },
  123282. enable: function() {
  123283. this.disabled = false;
  123284. if (this.dragZone) {
  123285. this.dragZone.enable();
  123286. }
  123287. },
  123288. disable: function() {
  123289. this.disabled = true;
  123290. if (this.dragZone) {
  123291. this.dragZone.disable();
  123292. }
  123293. }
  123294. });
  123295. /**
  123296. * Container which holds headers and is docked at the top or bottom of a TablePanel.
  123297. * The HeaderContainer drives resizing/moving/hiding of columns within the TableView.
  123298. * As headers are hidden, moved or resized the headercontainer is responsible for
  123299. * triggering changes within the view.
  123300. */
  123301. Ext.define('Ext.grid.header.Container', {
  123302. extend: 'Ext.container.Container',
  123303. requires: [
  123304. 'Ext.grid.ColumnLayout',
  123305. 'Ext.grid.plugin.HeaderResizer',
  123306. 'Ext.grid.plugin.HeaderReorderer'
  123307. ],
  123308. uses: [
  123309. 'Ext.grid.column.Column',
  123310. 'Ext.menu.Menu',
  123311. 'Ext.menu.CheckItem',
  123312. 'Ext.menu.Separator'
  123313. ],
  123314. border: true,
  123315. alias: 'widget.headercontainer',
  123316. baseCls: Ext.baseCSSPrefix + 'grid-header-ct',
  123317. dock: 'top',
  123318. /**
  123319. * @cfg {Number} weight
  123320. * HeaderContainer overrides the default weight of 0 for all docked items to 100.
  123321. * This is so that it has more priority over things like toolbars.
  123322. */
  123323. weight: 100,
  123324. defaultType: 'gridcolumn',
  123325. detachOnRemove: false,
  123326. /**
  123327. * @cfg {Number} defaultWidth
  123328. * Width of the header if no width or flex is specified.
  123329. */
  123330. defaultWidth: 100,
  123331. /**
  123332. * @cfg {Boolean} [sealed=false]
  123333. * Specify as `true` to constrain column dragging so that a column cannot be dragged into or out of this column.
  123334. *
  123335. * **Note that this config is only valid for column headers which contain child column headers, eg:**
  123336. * {
  123337. * sealed: true
  123338. * text: 'ExtJS',
  123339. * columns: [{
  123340. * text: '3.0.4',
  123341. * dataIndex: 'ext304'
  123342. * }, {
  123343. * text: '4.1.0',
  123344. * dataIndex: 'ext410'
  123345. * }
  123346. * }
  123347. *
  123348. */
  123349. //<locale>
  123350. sortAscText: 'Sort Ascending',
  123351. //</locale>
  123352. //<locale>
  123353. sortDescText: 'Sort Descending',
  123354. //</locale>
  123355. //<locale>
  123356. sortClearText: 'Clear Sort',
  123357. //</locale>
  123358. //<locale>
  123359. columnsText: 'Columns',
  123360. //</locale>
  123361. headerOpenCls: Ext.baseCSSPrefix + 'column-header-open',
  123362. // private; will probably be removed by 4.0
  123363. triStateSort: false,
  123364. ddLock: false,
  123365. dragging: false,
  123366. /**
  123367. * @property {Boolean} isGroupHeader
  123368. * True if this HeaderContainer is in fact a group header which contains sub headers.
  123369. */
  123370. /**
  123371. * @cfg {Boolean} sortable
  123372. * Provides the default sortable state for all Headers within this HeaderContainer.
  123373. * Also turns on or off the menus in the HeaderContainer. Note that the menu is
  123374. * shared across every header and therefore turning it off will remove the menu
  123375. * items for every header.
  123376. */
  123377. sortable: true,
  123378. initComponent: function() {
  123379. var me = this;
  123380. me.headerCounter = 0;
  123381. me.plugins = me.plugins || [];
  123382. // TODO: Pass in configurations to turn on/off dynamic
  123383. // resizing and disable resizing all together
  123384. // Only set up a Resizer and Reorderer for the topmost HeaderContainer.
  123385. // Nested Group Headers are themselves HeaderContainers
  123386. if (!me.isHeader) {
  123387. if (me.enableColumnResize) {
  123388. me.resizer = new Ext.grid.plugin.HeaderResizer();
  123389. me.plugins.push(me.resizer);
  123390. }
  123391. if (me.enableColumnMove) {
  123392. me.reorderer = new Ext.grid.plugin.HeaderReorderer();
  123393. me.plugins.push(me.reorderer);
  123394. }
  123395. }
  123396. // Base headers do not need a box layout
  123397. if (me.isHeader && !me.items) {
  123398. me.layout = me.layout || 'auto';
  123399. }
  123400. // HeaderContainer and Group header needs a gridcolumn layout.
  123401. else {
  123402. me.layout = Ext.apply({
  123403. type: 'gridcolumn',
  123404. align: 'stretchmax'
  123405. }, me.initialConfig.layout);
  123406. }
  123407. me.defaults = me.defaults || {};
  123408. Ext.applyIf(me.defaults, {
  123409. triStateSort: me.triStateSort,
  123410. sortable: me.sortable
  123411. });
  123412. me.menuTask = new Ext.util.DelayedTask(me.updateMenuDisabledState, me);
  123413. me.callParent();
  123414. me.addEvents(
  123415. /**
  123416. * @event columnresize
  123417. * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
  123418. * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
  123419. * @param {Number} width
  123420. */
  123421. 'columnresize',
  123422. /**
  123423. * @event headerclick
  123424. * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
  123425. * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
  123426. * @param {Ext.EventObject} e
  123427. * @param {HTMLElement} t
  123428. */
  123429. 'headerclick',
  123430. /**
  123431. * @event headertriggerclick
  123432. * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
  123433. * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
  123434. * @param {Ext.EventObject} e
  123435. * @param {HTMLElement} t
  123436. */
  123437. 'headertriggerclick',
  123438. /**
  123439. * @event columnmove
  123440. * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
  123441. * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
  123442. * @param {Number} fromIdx
  123443. * @param {Number} toIdx
  123444. */
  123445. 'columnmove',
  123446. /**
  123447. * @event columnhide
  123448. * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
  123449. * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
  123450. */
  123451. 'columnhide',
  123452. /**
  123453. * @event columnshow
  123454. * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
  123455. * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
  123456. */
  123457. 'columnshow',
  123458. /**
  123459. * @event sortchange
  123460. * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
  123461. * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
  123462. * @param {String} direction
  123463. */
  123464. 'sortchange',
  123465. /**
  123466. * @event menucreate
  123467. * Fired immediately after the column header menu is created.
  123468. * @param {Ext.grid.header.Container} ct This instance
  123469. * @param {Ext.menu.Menu} menu The Menu that was created
  123470. */
  123471. 'menucreate'
  123472. );
  123473. },
  123474. onDestroy: function() {
  123475. var me = this;
  123476. me.menuTask.cancel();
  123477. Ext.destroy(me.resizer, me.reorderer);
  123478. me.callParent();
  123479. },
  123480. applyColumnsState: function(columns) {
  123481. if (!columns || !columns.length) {
  123482. return;
  123483. }
  123484. var me = this,
  123485. items = me.items.items,
  123486. count = items.length,
  123487. i = 0,
  123488. length = columns.length,
  123489. c, col, columnState, index;
  123490. for (c = 0; c < length; c++) {
  123491. columnState = columns[c];
  123492. for (index = count; index--; ) {
  123493. col = items[index];
  123494. if (col.getStateId && col.getStateId() == columnState.id) {
  123495. // If a column in the new grid matches up with a saved state...
  123496. // Ensure that the column is restored to the state order.
  123497. // i is incremented upon every column match, so all persistent
  123498. // columns are ordered before any new columns.
  123499. if (i !== index) {
  123500. me.moveHeader(index, i);
  123501. }
  123502. if (col.applyColumnState) {
  123503. col.applyColumnState(columnState);
  123504. }
  123505. ++i;
  123506. break;
  123507. }
  123508. }
  123509. }
  123510. },
  123511. getColumnsState: function () {
  123512. var me = this,
  123513. columns = [],
  123514. state;
  123515. me.items.each(function (col) {
  123516. state = col.getColumnState && col.getColumnState();
  123517. if (state) {
  123518. columns.push(state);
  123519. }
  123520. });
  123521. return columns;
  123522. },
  123523. // Invalidate column cache on add
  123524. // We cannot refresh the View on every add because this method is called
  123525. // when the HeaderDropZone moves Headers around, that will also refresh the view
  123526. onAdd: function(c) {
  123527. var me = this,
  123528. headerCt = me.isHeader ? me.getOwnerHeaderCt() : me;
  123529. if (!c.headerId) {
  123530. c.headerId = c.initialConfig.id || Ext.id(null, 'header-');
  123531. }
  123532. if (!c.stateId) {
  123533. // This was the headerId generated in 4.0, so to preserve saved state, we now
  123534. // assign a default stateId in that same manner. The stateId's of a column are
  123535. // not global at the stateProvider, but are local to the grid state data. The
  123536. // headerId should still follow our standard naming convention.
  123537. c.stateId = c.initialConfig.id || ('h' + (++me.headerCounter));
  123538. }
  123539. if (Ext.global.console && Ext.global.console.warn) {
  123540. if (!me._usedIDs) {
  123541. me._usedIDs = {};
  123542. }
  123543. if (me._usedIDs[c.headerId]) {
  123544. Ext.global.console.warn(this.$className, 'attempted to reuse an existing id', c.headerId);
  123545. }
  123546. me._usedIDs[c.headerId] = true;
  123547. }
  123548. me.callParent(arguments);
  123549. // Upon add of any column we need to purge the *HeaderContainer's* cache of leaf view columns.
  123550. if (headerCt) {
  123551. headerCt.purgeCache();
  123552. }
  123553. },
  123554. // Invalidate column cache on remove
  123555. // We cannot refresh the View on every remove because this method is called
  123556. // when the HeaderDropZone moves Headers around, that will also refresh the view
  123557. onRemove: function(c) {
  123558. var me = this,
  123559. headerCt = me.isHeader ? me.getOwnerHeaderCt() : me;
  123560. me.callParent(arguments);
  123561. if (!me._usedIDs) {
  123562. me._usedIDs = {};
  123563. }
  123564. delete me._usedIDs[c.headerId];
  123565. // Upon removal of any column we need to purge the *HeaderContainer's* cache of leaf view columns.
  123566. if (headerCt) {
  123567. me.purgeCache();
  123568. }
  123569. },
  123570. // @private
  123571. applyDefaults: function(config) {
  123572. var ret;
  123573. /*
  123574. * Ensure header.Container defaults don't get applied to a RowNumberer
  123575. * if an xtype is supplied. This isn't an ideal solution however it's
  123576. * much more likely that a RowNumberer with no options will be created,
  123577. * wanting to use the defaults specified on the class as opposed to
  123578. * those setup on the Container.
  123579. */
  123580. if (config && !config.isComponent && config.xtype == 'rownumberer') {
  123581. ret = config;
  123582. } else {
  123583. ret = this.callParent(arguments);
  123584. // Apply default width unless it's a group header (in which case it must be left to shrinkwrap), or it's flexed
  123585. if (!config.isGroupHeader && !('width' in ret) && !ret.flex) {
  123586. ret.width = this.defaultWidth;
  123587. }
  123588. }
  123589. return ret;
  123590. },
  123591. afterRender: function() {
  123592. this.callParent();
  123593. this.setSortState();
  123594. },
  123595. setSortState: function(){
  123596. var store = this.up('[store]').store,
  123597. // grab the first sorter, since there may also be groupers
  123598. // in this collection
  123599. first = store.getFirstSorter(),
  123600. hd;
  123601. if (first) {
  123602. hd = this.down('gridcolumn[dataIndex=' + first.property +']');
  123603. if (hd) {
  123604. hd.setSortState(first.direction, false, true);
  123605. }
  123606. } else {
  123607. this.clearOtherSortStates(null);
  123608. }
  123609. },
  123610. getHeaderMenu: function(){
  123611. var menu = this.getMenu(),
  123612. item;
  123613. if (menu) {
  123614. item = menu.child('#columnItem');
  123615. if (item) {
  123616. return item.menu;
  123617. }
  123618. }
  123619. return null;
  123620. },
  123621. onHeaderVisibilityChange: function(header, visible){
  123622. var me = this,
  123623. menu = me.getHeaderMenu(),
  123624. item;
  123625. if (menu) {
  123626. // If the header was hidden programmatically, sync the Menu state
  123627. item = me.getMenuItemForHeader(menu, header);
  123628. if (item) {
  123629. item.setChecked(visible, true);
  123630. }
  123631. // delay this since the headers may fire a number of times if we're hiding/showing groups
  123632. me.menuTask.delay(50);
  123633. }
  123634. },
  123635. /**
  123636. * @private
  123637. * Gets all "leaf" menu nodes and returns the checked count for those leaves.
  123638. * Only includes columns that are hideable via the menu
  123639. */
  123640. getLeafMenuItems: function() {
  123641. var me = this,
  123642. columns = me.getGridColumns(),
  123643. items = [],
  123644. i = 0,
  123645. count = 0,
  123646. len = columns.length,
  123647. menu = me.getMenu(),
  123648. item;
  123649. for (; i < len; ++i) {
  123650. item = columns[i];
  123651. if (item.hideable) {
  123652. item = me.getMenuItemForHeader(menu, item);
  123653. if (item) {
  123654. items.push(item);
  123655. if (item.checked) {
  123656. ++count;
  123657. }
  123658. }
  123659. } else if (!item.hidden && !item.menuDisabled) {
  123660. ++count;
  123661. }
  123662. }
  123663. return {
  123664. items: items,
  123665. checkedCount: count
  123666. };
  123667. },
  123668. updateMenuDisabledState: function(){
  123669. var me = this,
  123670. result = me.getLeafMenuItems(),
  123671. total = result.checkedCount,
  123672. items = result.items,
  123673. len = items.length,
  123674. i = 0,
  123675. rootItem = me.getMenu().child('#columnItem');
  123676. if (total <= 1) {
  123677. // only one column visible, prevent hiding of the remaining item
  123678. me.disableMenuItems(rootItem, Ext.ComponentQuery.query('[checked=true]', items)[0]);
  123679. } else {
  123680. // at least 2 visible, set the state appropriately
  123681. for (; i < len; ++i) {
  123682. me.setMenuItemState(total, rootItem, items[i]);
  123683. }
  123684. }
  123685. },
  123686. disableMenuItems: function(rootItem, item){
  123687. while (item && item != rootItem) {
  123688. item.disableCheckChange();
  123689. item = item.parentMenu.ownerItem;
  123690. }
  123691. },
  123692. setMenuItemState: function(total, rootItem, item){
  123693. var parentMenu,
  123694. checkedChildren;
  123695. while (item && item != rootItem) {
  123696. parentMenu = item.parentMenu;
  123697. checkedChildren = item.parentMenu.query('[checked=true]:not([menu])').length;
  123698. item.enableCheckChange();
  123699. item = parentMenu.ownerItem;
  123700. if (checkedChildren === total) {
  123701. // contains all the checked children, jump out the item and all parents
  123702. break;
  123703. }
  123704. }
  123705. // while we're not at the top, disable from the current item up
  123706. this.disableMenuItems(rootItem, item);
  123707. },
  123708. getMenuItemForHeader: function(menu, header){
  123709. return header ? menu.down('menucheckitem[headerId=' + header.id + ']') : null;
  123710. },
  123711. onHeaderShow: function(header) {
  123712. // Pass up to the GridSection
  123713. var me = this,
  123714. gridSection = me.ownerCt;
  123715. me.onHeaderVisibilityChange(header, true);
  123716. // Only update the grid UI when we are notified about base level Header shows;
  123717. // Group header shows just cause a layout of the HeaderContainer
  123718. if (!header.isGroupHeader) {
  123719. if (gridSection) {
  123720. gridSection.onHeaderShow(me, header);
  123721. }
  123722. }
  123723. me.fireEvent('columnshow', me, header);
  123724. },
  123725. onHeaderHide: function(header) {
  123726. // Pass up to the GridSection
  123727. var me = this,
  123728. gridSection = me.ownerCt;
  123729. me.onHeaderVisibilityChange(header, false);
  123730. // Only update the UI when we are notified about base level Header hides;
  123731. if (!header.isGroupHeader) {
  123732. if (gridSection) {
  123733. gridSection.onHeaderHide(me, header);
  123734. }
  123735. }
  123736. me.fireEvent('columnhide', me, header);
  123737. },
  123738. /**
  123739. * Temporarily lock the headerCt. This makes it so that clicking on headers
  123740. * don't trigger actions like sorting or opening of the header menu. This is
  123741. * done because extraneous events may be fired on the headers after interacting
  123742. * with a drag drop operation.
  123743. * @private
  123744. */
  123745. tempLock: function() {
  123746. this.ddLock = true;
  123747. Ext.Function.defer(function() {
  123748. this.ddLock = false;
  123749. }, 200, this);
  123750. },
  123751. onHeaderResize: function(header, w, suppressFocus) {
  123752. var me = this,
  123753. view = me.view,
  123754. gridSection = me.ownerCt;
  123755. // Do not react to header sizing during initial Panel layout when there is no view content to size.
  123756. if (view && view.table.dom) {
  123757. me.tempLock();
  123758. if (gridSection) {
  123759. gridSection.onHeaderResize(me, header, w);
  123760. }
  123761. }
  123762. me.fireEvent('columnresize', this, header, w);
  123763. },
  123764. onHeaderClick: function(header, e, t) {
  123765. header.fireEvent('headerclick', this, header, e, t);
  123766. this.fireEvent("headerclick", this, header, e, t);
  123767. },
  123768. onHeaderTriggerClick: function(header, e, t) {
  123769. // generate and cache menu, provide ability to cancel/etc
  123770. var me = this;
  123771. if (header.fireEvent('headertriggerclick', me, header, e, t) !== false && me.fireEvent("headertriggerclick", me, header, e, t) !== false) {
  123772. me.showMenuBy(t, header);
  123773. }
  123774. },
  123775. showMenuBy: function(t, header) {
  123776. var menu = this.getMenu(),
  123777. ascItem = menu.down('#ascItem'),
  123778. descItem = menu.down('#descItem'),
  123779. sortableMth;
  123780. menu.activeHeader = menu.ownerCt = header;
  123781. menu.setFloatParent(header);
  123782. // TODO: remove coupling to Header's titleContainer el
  123783. header.titleEl.addCls(this.headerOpenCls);
  123784. // enable or disable asc & desc menu items based on header being sortable
  123785. sortableMth = header.sortable ? 'enable' : 'disable';
  123786. if (ascItem) {
  123787. ascItem[sortableMth]();
  123788. }
  123789. if (descItem) {
  123790. descItem[sortableMth]();
  123791. }
  123792. menu.showBy(t);
  123793. },
  123794. // remove the trigger open class when the menu is hidden
  123795. onMenuDeactivate: function() {
  123796. var menu = this.getMenu();
  123797. // TODO: remove coupling to Header's titleContainer el
  123798. menu.activeHeader.titleEl.removeCls(this.headerOpenCls);
  123799. },
  123800. moveHeader: function(fromIdx, toIdx) {
  123801. // An automatically expiring lock
  123802. this.tempLock();
  123803. this.onHeaderMoved(this.move(fromIdx, toIdx), 1, fromIdx, toIdx);
  123804. },
  123805. purgeCache: function() {
  123806. var me = this;
  123807. // Delete column cache - column order has changed.
  123808. delete me.gridDataColumns;
  123809. delete me.hideableColumns;
  123810. // Menu changes when columns are moved. It will be recreated.
  123811. if (me.menu) {
  123812. // Must hide before destroy so that trigger el is deactivated
  123813. me.menu.hide();
  123814. me.menu.destroy();
  123815. delete me.menu;
  123816. }
  123817. },
  123818. onHeaderMoved: function(header, colsToMove, fromIdx, toIdx) {
  123819. var me = this,
  123820. gridSection = me.ownerCt;
  123821. if (gridSection && gridSection.onHeaderMove) {
  123822. gridSection.onHeaderMove(me, header, colsToMove, fromIdx, toIdx);
  123823. }
  123824. me.fireEvent("columnmove", me, header, fromIdx, toIdx);
  123825. },
  123826. /**
  123827. * Gets the menu (and will create it if it doesn't already exist)
  123828. * @private
  123829. */
  123830. getMenu: function() {
  123831. var me = this;
  123832. if (!me.menu) {
  123833. me.menu = new Ext.menu.Menu({
  123834. hideOnParentHide: false, // Persists when owning ColumnHeader is hidden
  123835. items: me.getMenuItems(),
  123836. listeners: {
  123837. deactivate: me.onMenuDeactivate,
  123838. scope: me
  123839. }
  123840. });
  123841. me.updateMenuDisabledState();
  123842. me.fireEvent('menucreate', me, me.menu);
  123843. }
  123844. return me.menu;
  123845. },
  123846. /**
  123847. * Returns an array of menu items to be placed into the shared menu
  123848. * across all headers in this header container.
  123849. * @returns {Array} menuItems
  123850. */
  123851. getMenuItems: function() {
  123852. var me = this,
  123853. menuItems = [],
  123854. hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null;
  123855. if (me.sortable) {
  123856. menuItems = [{
  123857. itemId: 'ascItem',
  123858. text: me.sortAscText,
  123859. cls: Ext.baseCSSPrefix + 'hmenu-sort-asc',
  123860. handler: me.onSortAscClick,
  123861. scope: me
  123862. },{
  123863. itemId: 'descItem',
  123864. text: me.sortDescText,
  123865. cls: Ext.baseCSSPrefix + 'hmenu-sort-desc',
  123866. handler: me.onSortDescClick,
  123867. scope: me
  123868. }];
  123869. }
  123870. if (hideableColumns && hideableColumns.length) {
  123871. menuItems.push('-', {
  123872. itemId: 'columnItem',
  123873. text: me.columnsText,
  123874. cls: Ext.baseCSSPrefix + 'cols-icon',
  123875. menu: hideableColumns
  123876. });
  123877. }
  123878. return menuItems;
  123879. },
  123880. // sort asc when clicking on item in menu
  123881. onSortAscClick: function() {
  123882. var menu = this.getMenu(),
  123883. activeHeader = menu.activeHeader;
  123884. activeHeader.setSortState('ASC');
  123885. },
  123886. // sort desc when clicking on item in menu
  123887. onSortDescClick: function() {
  123888. var menu = this.getMenu(),
  123889. activeHeader = menu.activeHeader;
  123890. activeHeader.setSortState('DESC');
  123891. },
  123892. /**
  123893. * Returns an array of menu CheckItems corresponding to all immediate children
  123894. * of the passed Container which have been configured as hideable.
  123895. */
  123896. getColumnMenu: function(headerContainer) {
  123897. var menuItems = [],
  123898. i = 0,
  123899. item,
  123900. items = headerContainer.query('>gridcolumn[hideable]'),
  123901. itemsLn = items.length,
  123902. menuItem;
  123903. for (; i < itemsLn; i++) {
  123904. item = items[i];
  123905. menuItem = new Ext.menu.CheckItem({
  123906. text: item.menuText || item.text,
  123907. checked: !item.hidden,
  123908. hideOnClick: false,
  123909. headerId: item.id,
  123910. menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined,
  123911. checkHandler: this.onColumnCheckChange,
  123912. scope: this
  123913. });
  123914. menuItems.push(menuItem);
  123915. // If the header is ever destroyed - for instance by dragging out the last remaining sub header,
  123916. // then the associated menu item must also be destroyed.
  123917. item.on({
  123918. destroy: Ext.Function.bind(menuItem.destroy, menuItem)
  123919. });
  123920. }
  123921. return menuItems;
  123922. },
  123923. onColumnCheckChange: function(checkItem, checked) {
  123924. var header = Ext.getCmp(checkItem.headerId);
  123925. header[checked ? 'show' : 'hide']();
  123926. },
  123927. /**
  123928. * Get the columns used for generating a template via TableChunker.
  123929. * Returns an array of all columns and their
  123930. *
  123931. * - dataIndex
  123932. * - align
  123933. * - width
  123934. * - id
  123935. * - columnId - used to create an identifying CSS class
  123936. * - cls The tdCls configuration from the Column object
  123937. *
  123938. * @private
  123939. */
  123940. getColumnsForTpl: function(flushCache) {
  123941. var cols = [],
  123942. headers = this.getGridColumns(flushCache),
  123943. headersLn = headers.length,
  123944. i = 0,
  123945. header,
  123946. width;
  123947. for (; i < headersLn; i++) {
  123948. header = headers[i];
  123949. if (header.hidden || header.up('headercontainer[hidden=true]')) {
  123950. width = 0;
  123951. } else {
  123952. width = header.getDesiredWidth();
  123953. }
  123954. cols.push({
  123955. dataIndex: header.dataIndex,
  123956. align: header.align,
  123957. width: width,
  123958. id: header.id,
  123959. cls: header.tdCls,
  123960. columnId: header.getItemId()
  123961. });
  123962. }
  123963. return cols;
  123964. },
  123965. /**
  123966. * Returns the number of <b>grid columns</b> descended from this HeaderContainer.
  123967. * Group Columns are HeaderContainers. All grid columns are returned, including hidden ones.
  123968. */
  123969. getColumnCount: function() {
  123970. return this.getGridColumns().length;
  123971. },
  123972. /**
  123973. * Gets the full width of all columns that are visible.
  123974. */
  123975. getFullWidth: function(flushCache) {
  123976. var fullWidth = 0,
  123977. headers = this.getVisibleGridColumns(flushCache),
  123978. headersLn = headers.length,
  123979. i = 0,
  123980. header;
  123981. for (; i < headersLn; i++) {
  123982. header = headers[i];
  123983. // use headers getDesiredWidth if its there
  123984. if (header.getDesiredWidth) {
  123985. fullWidth += header.getDesiredWidth() || 0;
  123986. // if injected a diff cmp use getWidth
  123987. } else {
  123988. fullWidth += header.getWidth();
  123989. }
  123990. }
  123991. return fullWidth;
  123992. },
  123993. // invoked internally by a header when not using triStateSorting
  123994. clearOtherSortStates: function(activeHeader) {
  123995. var headers = this.getGridColumns(),
  123996. headersLn = headers.length,
  123997. i = 0;
  123998. for (; i < headersLn; i++) {
  123999. if (headers[i] !== activeHeader) {
  124000. // unset the sortstate and dont recurse
  124001. headers[i].setSortState(null, true);
  124002. }
  124003. }
  124004. },
  124005. /**
  124006. * Returns an array of the **visible** columns in the grid. This goes down to the lowest column header
  124007. * level, and does not return **grouped** headers which contain sub headers.
  124008. * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
  124009. * @returns {Array}
  124010. */
  124011. getVisibleGridColumns: function(refreshCache) {
  124012. return Ext.ComponentQuery.query(':not([hidden])', this.getGridColumns(refreshCache));
  124013. },
  124014. /**
  124015. * Returns an array of all columns which map to Store fields. This goes down to the lowest column header
  124016. * level, and does not return **grouped** headers which contain sub headers.
  124017. * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
  124018. * @returns {Array}
  124019. */
  124020. getGridColumns: function(refreshCache) {
  124021. var me = this,
  124022. result = refreshCache ? null : me.gridDataColumns;
  124023. // Not already got the column cache, so collect the base columns
  124024. if (!result) {
  124025. me.gridDataColumns = result = [];
  124026. me.cascade(function(c) {
  124027. if ((c !== me) && !c.isGroupHeader) {
  124028. result.push(c);
  124029. }
  124030. });
  124031. }
  124032. return result;
  124033. },
  124034. /**
  124035. * @private
  124036. * For use by column headers in determining whether there are any hideable columns when deciding whether or not
  124037. * the header menu should be disabled.
  124038. */
  124039. getHideableColumns: function(refreshCache) {
  124040. var me = this,
  124041. result = refreshCache ? null : me.hideableColumns;
  124042. if (!result) {
  124043. result = me.hideableColumns = me.query('[hideable]');
  124044. }
  124045. return result;
  124046. },
  124047. /**
  124048. * Returns the index of a leaf level header regardless of what the nesting
  124049. * structure is.
  124050. *
  124051. * If a group header is passed, the index of the first leaf level heder within it is returned.
  124052. *
  124053. * @param {Ext.grid.column.Column} header The header to find the index of
  124054. * @return {Number} The index of the specified column header
  124055. */
  124056. getHeaderIndex: function(header) {
  124057. // If we are being asked the index of a group header, find the first leaf header node, and return the index of that
  124058. if (header.isGroupHeader) {
  124059. header = header.down(':not([isgroupHeader])');
  124060. }
  124061. return Ext.Array.indexOf(this.getGridColumns(), header);
  124062. },
  124063. /**
  124064. * Get a leaf level header by index regardless of what the nesting
  124065. * structure is.
  124066. * @param {Number} The column index for which to retrieve the column.
  124067. */
  124068. getHeaderAtIndex: function(index) {
  124069. var columns = this.getGridColumns();
  124070. return columns.length ? columns[index] : null;
  124071. },
  124072. /**
  124073. * When passed a column index, returns the closet *visible* column to that. If the column at the passed index is visible,
  124074. * that is returned. If it is hidden, either the next visible, or the previous visible column is returned.
  124075. * @param {Number} index Position at which to find the closest visible column.
  124076. */
  124077. getVisibleHeaderClosestToIndex: function(index) {
  124078. var result = this.getHeaderAtIndex(index);
  124079. if (result && result.hidden) {
  124080. result = result.next(':not([hidden])') || result.prev(':not([hidden])');
  124081. }
  124082. return result;
  124083. },
  124084. /**
  124085. * Maps the record data to base it on the header id's.
  124086. * This correlates to the markup/template generated by
  124087. * TableChunker.
  124088. */
  124089. prepareData: function(data, rowIdx, record, view, panel) {
  124090. var me = this,
  124091. obj = {},
  124092. headers = me.gridDataColumns || me.getGridColumns(),
  124093. headersLn = headers.length,
  124094. colIdx = 0,
  124095. header,
  124096. headerId,
  124097. renderer,
  124098. value,
  124099. metaData,
  124100. store = panel.store;
  124101. for (; colIdx < headersLn; colIdx++) {
  124102. metaData = {
  124103. tdCls: '',
  124104. style: ''
  124105. };
  124106. header = headers[colIdx];
  124107. headerId = header.id;
  124108. renderer = header.renderer;
  124109. value = data[header.dataIndex];
  124110. if (typeof renderer == "function") {
  124111. value = renderer.call(
  124112. header.scope || me.ownerCt,
  124113. value,
  124114. // metadata per cell passing an obj by reference so that
  124115. // it can be manipulated inside the renderer
  124116. metaData,
  124117. record,
  124118. rowIdx,
  124119. colIdx,
  124120. store,
  124121. view
  124122. );
  124123. }
  124124. if (metaData.css) {
  124125. // This warning attribute is used by the compat layer
  124126. obj.cssWarning = true;
  124127. metaData.tdCls = metaData.css;
  124128. delete metaData.css;
  124129. }
  124130. if (me.markDirty) {
  124131. obj[headerId + '-modified'] = record.isModified(header.dataIndex) ? Ext.baseCSSPrefix + 'grid-dirty-cell' : '';
  124132. }
  124133. obj[headerId+'-tdCls'] = metaData.tdCls;
  124134. obj[headerId+'-tdAttr'] = metaData.tdAttr;
  124135. obj[headerId+'-style'] = metaData.style;
  124136. if (typeof value === 'undefined' || value === null || value === '') {
  124137. value = header.emptyCellText;
  124138. }
  124139. obj[headerId] = value;
  124140. }
  124141. return obj;
  124142. },
  124143. expandToFit: function(header) {
  124144. var view = this.view;
  124145. if (view) {
  124146. view.expandToFit(header);
  124147. }
  124148. }
  124149. });
  124150. /**
  124151. * This class specifies the definition for a column inside a {@link Ext.grid.Panel}. It encompasses
  124152. * both the grid header configuration as well as displaying data within the grid itself. If the
  124153. * {@link #columns} configuration is specified, this column will become a column group and can
  124154. * contain other columns inside. In general, this class will not be created directly, rather
  124155. * an array of column configurations will be passed to the grid:
  124156. *
  124157. * @example
  124158. * Ext.create('Ext.data.Store', {
  124159. * storeId:'employeeStore',
  124160. * fields:['firstname', 'lastname', 'seniority', 'dep', 'hired'],
  124161. * data:[
  124162. * {firstname:"Michael", lastname:"Scott", seniority:7, dep:"Management", hired:"01/10/2004"},
  124163. * {firstname:"Dwight", lastname:"Schrute", seniority:2, dep:"Sales", hired:"04/01/2004"},
  124164. * {firstname:"Jim", lastname:"Halpert", seniority:3, dep:"Sales", hired:"02/22/2006"},
  124165. * {firstname:"Kevin", lastname:"Malone", seniority:4, dep:"Accounting", hired:"06/10/2007"},
  124166. * {firstname:"Angela", lastname:"Martin", seniority:5, dep:"Accounting", hired:"10/21/2008"}
  124167. * ]
  124168. * });
  124169. *
  124170. * Ext.create('Ext.grid.Panel', {
  124171. * title: 'Column Demo',
  124172. * store: Ext.data.StoreManager.lookup('employeeStore'),
  124173. * columns: [
  124174. * {text: 'First Name', dataIndex:'firstname'},
  124175. * {text: 'Last Name', dataIndex:'lastname'},
  124176. * {text: 'Hired Month', dataIndex:'hired', xtype:'datecolumn', format:'M'},
  124177. * {text: 'Department (Yrs)', xtype:'templatecolumn', tpl:'{dep} ({seniority})'}
  124178. * ],
  124179. * width: 400,
  124180. * forceFit: true,
  124181. * renderTo: Ext.getBody()
  124182. * });
  124183. *
  124184. * # Convenience Subclasses
  124185. *
  124186. * There are several column subclasses that provide default rendering for various data types
  124187. *
  124188. * - {@link Ext.grid.column.Action}: Renders icons that can respond to click events inline
  124189. * - {@link Ext.grid.column.Boolean}: Renders for boolean values
  124190. * - {@link Ext.grid.column.Date}: Renders for date values
  124191. * - {@link Ext.grid.column.Number}: Renders for numeric values
  124192. * - {@link Ext.grid.column.Template}: Renders a value using an {@link Ext.XTemplate} using the record data
  124193. *
  124194. * # Setting Sizes
  124195. *
  124196. * The columns are laid out by a {@link Ext.layout.container.HBox} layout, so a column can either
  124197. * be given an explicit width value or a flex configuration. If no width is specified the grid will
  124198. * automatically the size the column to 100px. For column groups, the size is calculated by measuring
  124199. * the width of the child columns, so a width option should not be specified in that case.
  124200. *
  124201. * # Header Options
  124202. *
  124203. * - {@link #text}: Sets the header text for the column
  124204. * - {@link #sortable}: Specifies whether the column can be sorted by clicking the header or using the column menu
  124205. * - {@link #hideable}: Specifies whether the column can be hidden using the column menu
  124206. * - {@link #menuDisabled}: Disables the column header menu
  124207. * - {@link #cfg-draggable}: Specifies whether the column header can be reordered by dragging
  124208. * - {@link #groupable}: Specifies whether the grid can be grouped by the column dataIndex. See also {@link Ext.grid.feature.Grouping}
  124209. *
  124210. * # Data Options
  124211. *
  124212. * - {@link #dataIndex}: The dataIndex is the field in the underlying {@link Ext.data.Store} to use as the value for the column.
  124213. * - {@link #renderer}: Allows the underlying store value to be transformed before being displayed in the grid
  124214. */
  124215. Ext.define('Ext.grid.column.Column', {
  124216. extend: 'Ext.grid.header.Container',
  124217. alias: 'widget.gridcolumn',
  124218. requires: ['Ext.util.KeyNav', 'Ext.grid.ColumnComponentLayout', 'Ext.grid.ColumnLayout'],
  124219. alternateClassName: 'Ext.grid.Column',
  124220. baseCls: Ext.baseCSSPrefix + 'column-header ' + Ext.baseCSSPrefix + 'unselectable',
  124221. // Not the standard, automatically applied overCls because we must filter out overs of child headers.
  124222. hoverCls: Ext.baseCSSPrefix + 'column-header-over',
  124223. handleWidth: 5,
  124224. sortState: null,
  124225. possibleSortStates: ['ASC', 'DESC'],
  124226. childEls: [
  124227. 'titleEl', 'triggerEl', 'textEl'
  124228. ],
  124229. renderTpl:
  124230. '<div id="{id}-titleEl" {tipMarkup}class="' + Ext.baseCSSPrefix + 'column-header-inner">' +
  124231. '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'column-header-text">' +
  124232. '{text}' +
  124233. '</span>' +
  124234. '<tpl if="!menuDisabled">'+
  124235. '<div id="{id}-triggerEl" class="' + Ext.baseCSSPrefix + 'column-header-trigger"></div>'+
  124236. '</tpl>' +
  124237. '</div>' +
  124238. '{%this.renderContainer(out,values)%}',
  124239. /**
  124240. * @cfg {Object[]} columns
  124241. * An optional array of sub-column definitions. This column becomes a group, and houses the columns defined in the
  124242. * `columns` config.
  124243. *
  124244. * Group columns may not be sortable. But they may be hideable and moveable. And you may move headers into and out
  124245. * of a group. Note that if all sub columns are dragged out of a group, the group is destroyed.
  124246. */
  124247. /**
  124248. * @override
  124249. * @cfg {String} stateId
  124250. * An identifier which identifies this column uniquely within the owning grid's {@link #stateful state}.
  124251. *
  124252. * This does not have to be *globally* unique. A column's state is not saved standalone. It is encapsulated within
  124253. * the owning grid's state.
  124254. */
  124255. /**
  124256. * @cfg {String} dataIndex
  124257. * The name of the field in the grid's {@link Ext.data.Store}'s {@link Ext.data.Model} definition from
  124258. * which to draw the column's value. **Required.**
  124259. */
  124260. dataIndex: null,
  124261. /**
  124262. * @cfg {String} text
  124263. * The header text to be used as innerHTML (html tags are accepted) to display in the Grid.
  124264. * **Note**: to have a clickable header with no text displayed you can use the default of `&#160;` aka `&nbsp;`.
  124265. */
  124266. text: '&#160;',
  124267. /**
  124268. * @cfg {String} header
  124269. * The header text.
  124270. * @deprecated 4.0 Use {@link #text} instead.
  124271. */
  124272. /**
  124273. * @cfg {String} menuText
  124274. * The text to render in the column visibility selection menu for this column. If not
  124275. * specified, will default to the text value.
  124276. */
  124277. menuText: null,
  124278. /**
  124279. * @cfg {String} [emptyCellText=undefined]
  124280. * The text to diplay in empty cells (cells with a value of `undefined`, `null`, or `''`).
  124281. *
  124282. * Defaults to `&#160;` aka `&nbsp;`.
  124283. */
  124284. emptyCellText: '&#160;',
  124285. /**
  124286. * @cfg {Boolean} sortable
  124287. * False to disable sorting of this column. Whether local/remote sorting is used is specified in
  124288. * `{@link Ext.data.Store#remoteSort}`.
  124289. */
  124290. sortable: true,
  124291. /**
  124292. * @cfg {Boolean} groupable
  124293. * If the grid uses a {@link Ext.grid.feature.Grouping}, this option may be used to disable the header menu
  124294. * item to group by the column selected. By default, the header menu group option is enabled. Set to false to
  124295. * disable (but still show) the group option in the header menu for the column.
  124296. */
  124297. /**
  124298. * @cfg {Boolean} fixed
  124299. * True to prevent the column from being resizable.
  124300. * @deprecated 4.0 Use {@link #resizable} instead.
  124301. */
  124302. /**
  124303. * @cfg {Boolean} [locked=false]
  124304. * True to lock this column in place. Implicitly enables locking on the grid.
  124305. * See also {@link Ext.grid.Panel#enableLocking}.
  124306. */
  124307. /**
  124308. * @cfg {Boolean} resizable
  124309. * False to prevent the column from being resizable.
  124310. */
  124311. resizable: true,
  124312. /**
  124313. * @cfg {Boolean} hideable
  124314. * False to prevent the user from hiding this column.
  124315. */
  124316. hideable: true,
  124317. /**
  124318. * @cfg {Boolean} menuDisabled
  124319. * True to disable the column header menu containing sort/hide options.
  124320. */
  124321. menuDisabled: false,
  124322. /**
  124323. * @cfg {Function/String} renderer
  124324. * A renderer is an 'interceptor' method which can be used to transform data (value, appearance, etc.)
  124325. * before it is rendered. Example:
  124326. *
  124327. * {
  124328. * renderer: function(value){
  124329. * if (value === 1) {
  124330. * return '1 person';
  124331. * }
  124332. * return value + ' people';
  124333. * }
  124334. * }
  124335. *
  124336. * Additionally a string naming an {@link Ext.util.Format} method can be passed:
  124337. *
  124338. * {
  124339. * renderer: 'uppercase'
  124340. * }
  124341. *
  124342. * @cfg {Object} renderer.value The data value for the current cell
  124343. * @cfg {Object} renderer.metaData A collection of metadata about the current cell; can be used or modified
  124344. * by the renderer. Recognized properties are: tdCls, tdAttr, and style.
  124345. * @cfg {Ext.data.Model} renderer.record The record for the current row
  124346. * @cfg {Number} renderer.rowIndex The index of the current row
  124347. * @cfg {Number} renderer.colIndex The index of the current column
  124348. * @cfg {Ext.data.Store} renderer.store The data store
  124349. * @cfg {Ext.view.View} renderer.view The current view
  124350. * @cfg {String} renderer.return The HTML string to be rendered.
  124351. */
  124352. renderer: false,
  124353. /**
  124354. * @cfg {Object} scope
  124355. * The scope to use when calling the {@link #renderer} function.
  124356. */
  124357. /**
  124358. * @method defaultRenderer
  124359. * When defined this will take precedence over the {@link Ext.grid.column.Column#renderer renderer} config.
  124360. * This is meant to be defined in subclasses that wish to supply their own renderer.
  124361. * @protected
  124362. * @template
  124363. */
  124364. /**
  124365. * @cfg {Function} editRenderer
  124366. * A renderer to be used in conjunction with {@link Ext.grid.plugin.RowEditing RowEditing}. This renderer is used to
  124367. * display a custom value for non-editable fields.
  124368. */
  124369. editRenderer: false,
  124370. /**
  124371. * @cfg {String} align
  124372. * Sets the alignment of the header and rendered columns.
  124373. * Possible values are: `'left'`, `'center'`, and `'right'`.
  124374. */
  124375. align: 'left',
  124376. /**
  124377. * @cfg {Boolean} draggable
  124378. * False to disable drag-drop reordering of this column.
  124379. */
  124380. draggable: true,
  124381. /**
  124382. * @cfg {String} tooltip
  124383. * A tooltip to display for this column header
  124384. */
  124385. /**
  124386. * @cfg {String} [tooltipType="qtip"]
  124387. * The type of {@link #tooltip} to use. Either 'qtip' for QuickTips or 'title' for title attribute.
  124388. */
  124389. tooltipType: 'qtip',
  124390. // Header does not use the typical ComponentDraggable class and therefore we
  124391. // override this with an emptyFn. It is controlled at the HeaderDragZone.
  124392. initDraggable: Ext.emptyFn,
  124393. /**
  124394. * @cfg {String} tdCls
  124395. * A CSS class names to apply to the table cells for this column.
  124396. */
  124397. /**
  124398. * @cfg {Object/String} editor
  124399. * An optional xtype or config object for a {@link Ext.form.field.Field Field} to use for editing.
  124400. * Only applicable if the grid is using an {@link Ext.grid.plugin.Editing Editing} plugin.
  124401. */
  124402. /**
  124403. * @cfg {Object/String} field
  124404. * Alias for {@link #editor}.
  124405. * @deprecated 4.0.5 Use {@link #editor} instead.
  124406. */
  124407. /**
  124408. * @property {Ext.Element} triggerEl
  124409. * Element that acts as button for column header dropdown menu.
  124410. */
  124411. /**
  124412. * @property {Ext.Element} textEl
  124413. * Element that contains the text in column header.
  124414. */
  124415. /**
  124416. * @property {Boolean} isHeader
  124417. * Set in this class to identify, at runtime, instances which are not instances of the
  124418. * HeaderContainer base class, but are in fact, the subclass: Header.
  124419. */
  124420. isHeader: true,
  124421. componentLayout: 'columncomponent',
  124422. // We need to override the default component resizable behaviour here
  124423. initResizable: Ext.emptyFn,
  124424. initComponent: function() {
  124425. var me = this,
  124426. renderer;
  124427. if (Ext.isDefined(me.header)) {
  124428. me.text = me.header;
  124429. delete me.header;
  124430. }
  124431. if (!me.triStateSort) {
  124432. me.possibleSortStates.length = 2;
  124433. }
  124434. // A group header; It contains items which are themselves Headers
  124435. if (Ext.isDefined(me.columns)) {
  124436. me.isGroupHeader = true;
  124437. if (me.dataIndex) {
  124438. Ext.Error.raise('Ext.grid.column.Column: Group header may not accept a dataIndex');
  124439. }
  124440. if ((me.width && me.width !== Ext.grid.header.Container.prototype.defaultWidth) || me.flex) {
  124441. Ext.Error.raise('Ext.grid.column.Column: Group header does not support setting explicit widths or flexs. The group header width is calculated by the sum of its children.');
  124442. }
  124443. // The headers become child items
  124444. me.items = me.columns;
  124445. delete me.columns;
  124446. delete me.flex;
  124447. delete me.width;
  124448. me.cls = (me.cls||'') + ' ' + Ext.baseCSSPrefix + 'group-header';
  124449. me.sortable = false;
  124450. me.resizable = false;
  124451. me.align = 'center';
  124452. } else {
  124453. // If we are not a group header, then this is not to be used as a container, and must not have a container layout executed, and it must
  124454. // acquire layout height from DOM content, not from child items.
  124455. me.isContainer = false;
  124456. // Flexed Headers need to have a minWidth defined so that they can never be squeezed out of existence by the
  124457. // HeaderContainer's specialized Box layout, the ColumnLayout. The ColumnLayout's overridden calculateChildboxes
  124458. // method extends the available layout space to accommodate the "desiredWidth" of all the columns.
  124459. if (me.flex) {
  124460. me.minWidth = me.minWidth || Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
  124461. }
  124462. }
  124463. me.addCls(Ext.baseCSSPrefix + 'column-header-align-' + me.align);
  124464. renderer = me.renderer;
  124465. if (renderer) {
  124466. // When specifying a renderer as a string, it always resolves
  124467. // to Ext.util.Format
  124468. if (typeof renderer == 'string') {
  124469. me.renderer = Ext.util.Format[renderer];
  124470. }
  124471. me.hasCustomRenderer = true;
  124472. } else if (me.defaultRenderer) {
  124473. me.scope = me;
  124474. me.renderer = me.defaultRenderer;
  124475. }
  124476. // Initialize as a HeaderContainer
  124477. me.callParent(arguments);
  124478. me.on({
  124479. element: 'el',
  124480. click: me.onElClick,
  124481. dblclick: me.onElDblClick,
  124482. scope: me
  124483. });
  124484. me.on({
  124485. element: 'titleEl',
  124486. mouseenter: me.onTitleMouseOver,
  124487. mouseleave: me.onTitleMouseOut,
  124488. scope: me
  124489. });
  124490. },
  124491. onAdd: function(childHeader) {
  124492. childHeader.isSubHeader = true;
  124493. childHeader.addCls(Ext.baseCSSPrefix + 'group-sub-header');
  124494. this.callParent(arguments);
  124495. },
  124496. onRemove: function(childHeader) {
  124497. childHeader.isSubHeader = false;
  124498. childHeader.removeCls(Ext.baseCSSPrefix + 'group-sub-header');
  124499. this.callParent(arguments);
  124500. },
  124501. initRenderData: function() {
  124502. var me = this,
  124503. tipMarkup = '',
  124504. tip = me.tooltip,
  124505. attr = me.tooltipType == 'qtip' ? 'data-qtip' : 'title';
  124506. if (!Ext.isEmpty(tip)) {
  124507. tipMarkup = attr + '="' + tip + '" ';
  124508. }
  124509. return Ext.applyIf(me.callParent(arguments), {
  124510. text: me.text,
  124511. menuDisabled: me.menuDisabled,
  124512. tipMarkup: tipMarkup
  124513. });
  124514. },
  124515. applyColumnState: function (state) {
  124516. var me = this,
  124517. defined = Ext.isDefined;
  124518. // apply any columns
  124519. me.applyColumnsState(state.columns);
  124520. // Only state properties which were saved should be restored.
  124521. // (Only user-changed properties were saved by getState)
  124522. if (defined(state.hidden)) {
  124523. me.hidden = state.hidden;
  124524. }
  124525. if (defined(state.locked)) {
  124526. me.locked = state.locked;
  124527. }
  124528. if (defined(state.sortable)) {
  124529. me.sortable = state.sortable;
  124530. }
  124531. if (defined(state.width)) {
  124532. delete me.flex;
  124533. me.width = state.width;
  124534. } else if (defined(state.flex)) {
  124535. delete me.width;
  124536. me.flex = state.flex;
  124537. }
  124538. },
  124539. getColumnState: function () {
  124540. var me = this,
  124541. items = me.items.items,
  124542. // Check for the existence of items, since column.Action won't have them
  124543. iLen = items ? items.length : 0,
  124544. i,
  124545. columns = [],
  124546. state = {
  124547. id: me.getStateId()
  124548. };
  124549. me.savePropsToState(['hidden', 'sortable', 'locked', 'flex', 'width'], state);
  124550. if (me.isGroupHeader) {
  124551. for (i = 0; i < iLen; i++) {
  124552. columns.push(items[i].getColumnState());
  124553. }
  124554. if (columns.length) {
  124555. state.columns = columns;
  124556. }
  124557. } else if (me.isSubHeader && me.ownerCt.hidden) {
  124558. // don't set hidden on the children so they can auto height
  124559. delete me.hidden;
  124560. }
  124561. if ('width' in state) {
  124562. delete state.flex; // width wins
  124563. }
  124564. return state;
  124565. },
  124566. getStateId: function () {
  124567. return this.stateId || this.headerId;
  124568. },
  124569. /**
  124570. * Sets the header text for this Column.
  124571. * @param {String} text The header to display on this Column.
  124572. */
  124573. setText: function(text) {
  124574. this.text = text;
  124575. if (this.rendered) {
  124576. this.textEl.update(text);
  124577. }
  124578. },
  124579. // Find the topmost HeaderContainer: An ancestor which is NOT a Header.
  124580. // Group Headers are themselves HeaderContainers
  124581. getOwnerHeaderCt: function() {
  124582. return this.up(':not([isHeader])');
  124583. },
  124584. /**
  124585. * Returns the index of this column only if this column is a base level Column. If it
  124586. * is a group column, it returns `false`.
  124587. * @return {Number}
  124588. */
  124589. getIndex: function() {
  124590. return this.isGroupColumn ? false : this.getOwnerHeaderCt().getHeaderIndex(this);
  124591. },
  124592. /**
  124593. * Returns the index of this column in the list of *visible* columns only if this column is a base level Column. If it
  124594. * is a group column, it returns `false`.
  124595. * @return {Number}
  124596. */
  124597. getVisibleIndex: function() {
  124598. return this.isGroupColumn ? false : Ext.Array.indexOf(this.getOwnerHeaderCt().getVisibleGridColumns(), this);
  124599. },
  124600. beforeRender: function() {
  124601. var me = this,
  124602. grid = me.up('tablepanel');
  124603. me.callParent();
  124604. // Disable the menu if there's nothing to show in the menu, ie:
  124605. // Column cannot be sorted, grouped or locked, and there are no grid columns which may be hidden
  124606. if (grid && (!me.sortable || grid.sortableColumns === false) && !me.groupable &&
  124607. !me.lockable && (grid.enableColumnHide === false ||
  124608. !me.getOwnerHeaderCt().getHideableColumns().length)) {
  124609. me.menuDisabled = true;
  124610. }
  124611. },
  124612. afterRender: function() {
  124613. var me = this,
  124614. el = me.el;
  124615. me.callParent(arguments);
  124616. if (me.overCls) {
  124617. el.addClsOnOver(me.overCls);
  124618. }
  124619. // BrowserBug: Ie8 Strict Mode, this will break the focus for this browser,
  124620. // must be fixed when focus management will be implemented.
  124621. if (!Ext.isIE8 || !Ext.isStrict) {
  124622. me.mon(me.getFocusEl(), {
  124623. focus: me.onTitleMouseOver,
  124624. blur: me.onTitleMouseOut,
  124625. scope: me
  124626. });
  124627. }
  124628. me.keyNav = new Ext.util.KeyNav(el, {
  124629. enter: me.onEnterKey,
  124630. down: me.onDownKey,
  124631. scope: me
  124632. });
  124633. },
  124634. // private
  124635. // Inform the header container about the resize
  124636. afterComponentLayout: function(width, height, oldWidth, oldHeight) {
  124637. var me = this,
  124638. ownerHeaderCt = me.getOwnerHeaderCt();
  124639. me.callParent(arguments);
  124640. if (ownerHeaderCt && (oldWidth != null || me.flex) && width !== oldWidth) {
  124641. ownerHeaderCt.onHeaderResize(me, width, true);
  124642. }
  124643. },
  124644. // private
  124645. // After the container has laid out and stretched, it calls this to correctly pad the inner to center the text vertically
  124646. // Total available header height must be passed to enable padding for inner elements to be calculated.
  124647. setPadding: function(headerHeight) {
  124648. var me = this,
  124649. lineHeight = parseInt(me.textEl.getStyle('line-height'), 10),
  124650. textHeight = me.textEl.dom.offsetHeight,
  124651. titleEl = me.titleEl,
  124652. availableHeight = headerHeight - me.el.getBorderWidth('tb'),
  124653. titleElHeight;
  124654. // Top title containing element must stretch to match height of sibling group headers
  124655. if (!me.isGroupHeader) {
  124656. if (titleEl.getHeight() < availableHeight) {
  124657. titleEl.setHeight(availableHeight);
  124658. // the column el's parent element (the 'innerCt') may have an incorrect height
  124659. // at this point because it may have been shrink wrapped prior to the titleEl's
  124660. // height being set, so we need to sync it up here
  124661. me.ownerCt.layout.innerCt.setHeight(headerHeight);
  124662. }
  124663. }
  124664. titleElHeight = titleEl.getViewSize().height;
  124665. // Vertically center the header text in potentially vertically stretched header
  124666. if (textHeight) {
  124667. if(lineHeight) {
  124668. textHeight = Math.ceil(textHeight / lineHeight) * lineHeight;
  124669. }
  124670. titleEl.setStyle({
  124671. paddingTop: Math.floor(Math.max(((titleElHeight - textHeight) / 2), 0)) + 'px'
  124672. });
  124673. }
  124674. // Only IE needs this
  124675. if (Ext.isIE && me.triggerEl) {
  124676. me.triggerEl.setHeight(titleElHeight);
  124677. }
  124678. },
  124679. onDestroy: function() {
  124680. var me = this;
  124681. // force destroy on the textEl, IE reports a leak
  124682. Ext.destroy(me.textEl, me.keyNav, me.field);
  124683. delete me.keyNav;
  124684. me.callParent(arguments);
  124685. },
  124686. onTitleMouseOver: function() {
  124687. this.titleEl.addCls(this.hoverCls);
  124688. },
  124689. onTitleMouseOut: function() {
  124690. this.titleEl.removeCls(this.hoverCls);
  124691. },
  124692. onDownKey: function(e) {
  124693. if (this.triggerEl) {
  124694. this.onElClick(e, this.triggerEl.dom || this.el.dom);
  124695. }
  124696. },
  124697. onEnterKey: function(e) {
  124698. this.onElClick(e, this.el.dom);
  124699. },
  124700. /**
  124701. * @private
  124702. * Double click
  124703. * @param e
  124704. * @param t
  124705. */
  124706. onElDblClick: function(e, t) {
  124707. var me = this,
  124708. ownerCt = me.ownerCt;
  124709. if (ownerCt && Ext.Array.indexOf(ownerCt.items, me) !== 0 && me.isOnLeftEdge(e) ) {
  124710. ownerCt.expandToFit(me.previousSibling('gridcolumn'));
  124711. }
  124712. },
  124713. onElClick: function(e, t) {
  124714. // The grid's docked HeaderContainer.
  124715. var me = this,
  124716. ownerHeaderCt = me.getOwnerHeaderCt();
  124717. if (ownerHeaderCt && !ownerHeaderCt.ddLock) {
  124718. // Firefox doesn't check the current target in a within check.
  124719. // Therefore we check the target directly and then within (ancestors)
  124720. if (me.triggerEl && (e.target === me.triggerEl.dom || t === me.triggerEl.dom || e.within(me.triggerEl))) {
  124721. ownerHeaderCt.onHeaderTriggerClick(me, e, t);
  124722. // if its not on the left hand edge, sort
  124723. } else if (e.getKey() || (!me.isOnLeftEdge(e) && !me.isOnRightEdge(e))) {
  124724. me.toggleSortState();
  124725. ownerHeaderCt.onHeaderClick(me, e, t);
  124726. }
  124727. }
  124728. },
  124729. /**
  124730. * @private
  124731. * Process UI events from the view. The owning TablePanel calls this method, relaying events from the TableView
  124732. * @param {String} type Event type, eg 'click'
  124733. * @param {Ext.view.Table} view TableView Component
  124734. * @param {HTMLElement} cell Cell HtmlElement the event took place within
  124735. * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
  124736. * @param {Number} cellIndex Cell index within the row
  124737. * @param {Ext.EventObject} e Original event
  124738. */
  124739. processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
  124740. return this.fireEvent.apply(this, arguments);
  124741. },
  124742. toggleSortState: function() {
  124743. var me = this,
  124744. idx,
  124745. nextIdx;
  124746. if (me.sortable) {
  124747. idx = Ext.Array.indexOf(me.possibleSortStates, me.sortState);
  124748. nextIdx = (idx + 1) % me.possibleSortStates.length;
  124749. me.setSortState(me.possibleSortStates[nextIdx]);
  124750. }
  124751. },
  124752. doSort: function(state) {
  124753. var ds = this.up('tablepanel').store;
  124754. ds.sort({
  124755. property: this.getSortParam(),
  124756. direction: state
  124757. });
  124758. },
  124759. /**
  124760. * Returns the parameter to sort upon when sorting this header. By default this returns the dataIndex and will not
  124761. * need to be overriden in most cases.
  124762. * @return {String}
  124763. */
  124764. getSortParam: function() {
  124765. return this.dataIndex;
  124766. },
  124767. //setSortState: function(state, updateUI) {
  124768. //setSortState: function(state, doSort) {
  124769. setSortState: function(state, skipClear, initial) {
  124770. var me = this,
  124771. colSortClsPrefix = Ext.baseCSSPrefix + 'column-header-sort-',
  124772. ascCls = colSortClsPrefix + 'ASC',
  124773. descCls = colSortClsPrefix + 'DESC',
  124774. nullCls = colSortClsPrefix + 'null',
  124775. ownerHeaderCt = me.getOwnerHeaderCt(),
  124776. oldSortState = me.sortState;
  124777. if (oldSortState !== state && me.getSortParam()) {
  124778. me.addCls(colSortClsPrefix + state);
  124779. // don't trigger a sort on the first time, we just want to update the UI
  124780. if (state && !initial) {
  124781. me.doSort(state);
  124782. }
  124783. switch (state) {
  124784. case 'DESC':
  124785. me.removeCls([ascCls, nullCls]);
  124786. break;
  124787. case 'ASC':
  124788. me.removeCls([descCls, nullCls]);
  124789. break;
  124790. case null:
  124791. me.removeCls([ascCls, descCls]);
  124792. break;
  124793. }
  124794. if (ownerHeaderCt && !me.triStateSort && !skipClear) {
  124795. ownerHeaderCt.clearOtherSortStates(me);
  124796. }
  124797. me.sortState = state;
  124798. // we only want to fire the event if we have a null state when using triStateSort
  124799. if (me.triStateSort || state != null) {
  124800. ownerHeaderCt.fireEvent('sortchange', ownerHeaderCt, me, state);
  124801. }
  124802. }
  124803. },
  124804. hide: function(fromOwner) {
  124805. var me = this,
  124806. ownerHeaderCt = me.getOwnerHeaderCt(),
  124807. owner = me.ownerCt,
  124808. ownerIsGroup = owner.isGroupHeader,
  124809. item, items, len, i;
  124810. // owner is a group, hide call didn't come from the owner
  124811. if (ownerIsGroup && !fromOwner) {
  124812. items = owner.query('>:not([hidden])');
  124813. // only have one item that isn't hidden, this is it.
  124814. if (items.length === 1 && items[0] == me) {
  124815. me.ownerCt.hide();
  124816. return;
  124817. }
  124818. }
  124819. Ext.suspendLayouts();
  124820. if (me.isGroupHeader) {
  124821. items = me.items.items;
  124822. for (i = 0, len = items.length; i < len; i++) {
  124823. item = items[i];
  124824. if (!item.hidden) {
  124825. item.hide(true);
  124826. }
  124827. }
  124828. }
  124829. me.callParent();
  124830. // Notify owning HeaderContainer
  124831. ownerHeaderCt.onHeaderHide(me);
  124832. Ext.resumeLayouts(true);
  124833. },
  124834. show: function(fromOwner, fromChild) {
  124835. var me = this,
  124836. ownerCt = me.ownerCt,
  124837. items,
  124838. len, i,
  124839. item;
  124840. Ext.suspendLayouts();
  124841. // If a sub header, ensure that the group header is visible
  124842. if (me.isSubHeader && ownerCt.hidden) {
  124843. ownerCt.show(false, true);
  124844. }
  124845. me.callParent(arguments);
  124846. // If we've just shown a group with all its sub headers hidden, then show all its sub headers
  124847. if (me.isGroupHeader && fromChild !== true && !me.query(':not([hidden])').length) {
  124848. items = me.query('>*');
  124849. for (i = 0, len = items.length; i < len; i++) {
  124850. item = items[i];
  124851. if (item.hidden) {
  124852. item.show(true);
  124853. }
  124854. }
  124855. }
  124856. Ext.resumeLayouts(true);
  124857. // Notify owning HeaderContainer AFTER layout has been flushed so that header and headerCt widths are all correct
  124858. ownerCt = me.getOwnerHeaderCt();
  124859. if (ownerCt) {
  124860. ownerCt.onHeaderShow(me);
  124861. }
  124862. },
  124863. getDesiredWidth: function() {
  124864. var me = this;
  124865. if (me.rendered && me.componentLayout && me.componentLayout.lastComponentSize) {
  124866. // headers always have either a width or a flex
  124867. // because HeaderContainer sets a defaults width
  124868. // therefore we can ignore the natural width
  124869. // we use the componentLayout's tracked width so that
  124870. // we can calculate the desired width when rendered
  124871. // but not visible because its being obscured by a layout
  124872. return me.componentLayout.lastComponentSize.width;
  124873. // Flexed but yet to be rendered this could be the case
  124874. // where a HeaderContainer and Headers are simply used as data
  124875. // structures and not rendered.
  124876. }
  124877. else if (me.flex) {
  124878. // this is going to be wrong, the defaultWidth
  124879. return me.width;
  124880. }
  124881. else {
  124882. return me.width;
  124883. }
  124884. },
  124885. getCellSelector: function() {
  124886. return '.' + Ext.baseCSSPrefix + 'grid-cell-' + this.getItemId();
  124887. },
  124888. getCellInnerSelector: function() {
  124889. return this.getCellSelector() + ' .' + Ext.baseCSSPrefix + 'grid-cell-inner';
  124890. },
  124891. isOnLeftEdge: function(e) {
  124892. return (e.getXY()[0] - this.el.getLeft() <= this.handleWidth);
  124893. },
  124894. isOnRightEdge: function(e) {
  124895. return (this.el.getRight() - e.getXY()[0] <= this.handleWidth);
  124896. }
  124897. // intentionally omit getEditor and setEditor definitions bc we applyIf into columns
  124898. // when the editing plugin is injected
  124899. /**
  124900. * @method getEditor
  124901. * Retrieves the editing field for editing associated with this header. Returns false if there is no field
  124902. * associated with the Header the method will return false. If the field has not been instantiated it will be
  124903. * created. Note: These methods only has an implementation if a Editing plugin has been enabled on the grid.
  124904. * @param {Object} record The {@link Ext.data.Model Model} instance being edited.
  124905. * @param {Object} defaultField An object representing a default field to be created
  124906. * @return {Ext.form.field.Field} field
  124907. */
  124908. /**
  124909. * @method setEditor
  124910. * Sets the form field to be used for editing. Note: This method only has an implementation if an Editing plugin has
  124911. * been enabled on the grid.
  124912. * @param {Object} field An object representing a field to be created. If no xtype is specified a 'textfield' is
  124913. * assumed.
  124914. */
  124915. });
  124916. /**
  124917. * This is a utility class that can be passed into a {@link Ext.grid.column.Column} as a column config that provides
  124918. * an automatic row numbering column.
  124919. *
  124920. * Usage:
  124921. *
  124922. * columns: [
  124923. * {xtype: 'rownumberer'},
  124924. * {text: "Company", flex: 1, sortable: true, dataIndex: 'company'},
  124925. * {text: "Price", width: 120, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
  124926. * {text: "Change", width: 120, sortable: true, dataIndex: 'change'},
  124927. * {text: "% Change", width: 120, sortable: true, dataIndex: 'pctChange'},
  124928. * {text: "Last Updated", width: 120, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
  124929. * ]
  124930. *
  124931. */
  124932. Ext.define('Ext.grid.RowNumberer', {
  124933. extend: 'Ext.grid.column.Column',
  124934. alias: 'widget.rownumberer',
  124935. /**
  124936. * @cfg {String} text
  124937. * Any valid text or HTML fragment to display in the header cell for the row number column.
  124938. */
  124939. text: "&#160",
  124940. /**
  124941. * @cfg {Number} width
  124942. * The default width in pixels of the row number column.
  124943. */
  124944. width: 23,
  124945. /**
  124946. * @cfg {Boolean} sortable
  124947. * @hide
  124948. */
  124949. sortable: false,
  124950. /**
  124951. * @cfg {Boolean} [draggable=false]
  124952. * False to disable drag-drop reordering of this column.
  124953. */
  124954. draggable: false,
  124955. align: 'right',
  124956. constructor : function(config){
  124957. // Copy the prototype's default width setting into an instance property to provide
  124958. // a default width which will not be overridden by AbstractContainer.applyDefaults use of Ext.applyIf
  124959. this.width = this.width;
  124960. this.callParent(arguments);
  124961. if (this.rowspan) {
  124962. this.renderer = Ext.Function.bind(this.renderer, this);
  124963. }
  124964. },
  124965. // private
  124966. resizable: false,
  124967. hideable: false,
  124968. menuDisabled: true,
  124969. dataIndex: '',
  124970. cls: Ext.baseCSSPrefix + 'row-numberer',
  124971. rowspan: undefined,
  124972. // private
  124973. renderer: function(value, metaData, record, rowIdx, colIdx, store) {
  124974. if (this.rowspan){
  124975. metaData.cellAttr = 'rowspan="'+this.rowspan+'"';
  124976. }
  124977. metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
  124978. return store.indexOfTotal(record) + 1;
  124979. }
  124980. });
  124981. Ext.define('Ext.grid.Scroller', {
  124982. constructor: Ext.deprecated()
  124983. });
  124984. /**
  124985. * @private
  124986. */
  124987. Ext.define('Ext.view.DropZone', {
  124988. extend: 'Ext.dd.DropZone',
  124989. indicatorHtml: '<div class="' + Ext.baseCSSPrefix + 'grid-drop-indicator-left"></div><div class="' + Ext.baseCSSPrefix + 'grid-drop-indicator-right"></div>',
  124990. indicatorCls: Ext.baseCSSPrefix + 'grid-drop-indicator',
  124991. constructor: function(config) {
  124992. var me = this;
  124993. Ext.apply(me, config);
  124994. // Create a ddGroup unless one has been configured.
  124995. // User configuration of ddGroups allows users to specify which
  124996. // DD instances can interact with each other. Using one
  124997. // based on the id of the View would isolate it and mean it can only
  124998. // interact with a DragZone on the same View also using a generated ID.
  124999. if (!me.ddGroup) {
  125000. me.ddGroup = 'view-dd-zone-' + me.view.id;
  125001. }
  125002. // The DropZone's encapsulating element is the View's main element. It must be this because drop gestures
  125003. // may require scrolling on hover near a scrolling boundary. In Ext 4.x two DD instances may not use the
  125004. // same element, so a DragZone on this same View must use the View's parent element as its element.
  125005. me.callParent([me.view.el]);
  125006. },
  125007. // Fire an event through the client DataView. Lock this DropZone during the event processing so that
  125008. // its data does not become corrupted by processing mouse events.
  125009. fireViewEvent: function() {
  125010. var me = this,
  125011. result;
  125012. me.lock();
  125013. result = me.view.fireEvent.apply(me.view, arguments);
  125014. me.unlock();
  125015. return result;
  125016. },
  125017. getTargetFromEvent : function(e) {
  125018. var node = e.getTarget(this.view.getItemSelector()),
  125019. mouseY, nodeList, testNode, i, len, box;
  125020. // Not over a row node: The content may be narrower than the View's encapsulating element, so return the closest.
  125021. // If we fall through because the mouse is below the nodes (or there are no nodes), we'll get an onContainerOver call.
  125022. if (!node) {
  125023. mouseY = e.getPageY();
  125024. for (i = 0, nodeList = this.view.getNodes(), len = nodeList.length; i < len; i++) {
  125025. testNode = nodeList[i];
  125026. box = Ext.fly(testNode).getBox();
  125027. if (mouseY <= box.bottom) {
  125028. return testNode;
  125029. }
  125030. }
  125031. }
  125032. return node;
  125033. },
  125034. getIndicator: function() {
  125035. var me = this;
  125036. if (!me.indicator) {
  125037. me.indicator = new Ext.Component({
  125038. html: me.indicatorHtml,
  125039. cls: me.indicatorCls,
  125040. ownerCt: me.view,
  125041. floating: true,
  125042. shadow: false
  125043. });
  125044. }
  125045. return me.indicator;
  125046. },
  125047. getPosition: function(e, node) {
  125048. var y = e.getXY()[1],
  125049. region = Ext.fly(node).getRegion(),
  125050. pos;
  125051. if ((region.bottom - y) >= (region.bottom - region.top) / 2) {
  125052. pos = "before";
  125053. } else {
  125054. pos = "after";
  125055. }
  125056. return pos;
  125057. },
  125058. /**
  125059. * @private Determines whether the record at the specified offset from the passed record
  125060. * is in the drag payload.
  125061. * @param records
  125062. * @param record
  125063. * @param offset
  125064. * @returns {Boolean} True if the targeted record is in the drag payload
  125065. */
  125066. containsRecordAtOffset: function(records, record, offset) {
  125067. if (!record) {
  125068. return false;
  125069. }
  125070. var view = this.view,
  125071. recordIndex = view.indexOf(record),
  125072. nodeBefore = view.getNode(recordIndex + offset),
  125073. recordBefore = nodeBefore ? view.getRecord(nodeBefore) : null;
  125074. return recordBefore && Ext.Array.contains(records, recordBefore);
  125075. },
  125076. positionIndicator: function(node, data, e) {
  125077. var me = this,
  125078. view = me.view,
  125079. pos = me.getPosition(e, node),
  125080. overRecord = view.getRecord(node),
  125081. draggingRecords = data.records,
  125082. indicatorY;
  125083. if (!Ext.Array.contains(draggingRecords, overRecord) && (
  125084. pos == 'before' && !me.containsRecordAtOffset(draggingRecords, overRecord, -1) ||
  125085. pos == 'after' && !me.containsRecordAtOffset(draggingRecords, overRecord, 1)
  125086. )) {
  125087. me.valid = true;
  125088. if (me.overRecord != overRecord || me.currentPosition != pos) {
  125089. indicatorY = Ext.fly(node).getY() - view.el.getY() - 1;
  125090. if (pos == 'after') {
  125091. indicatorY += Ext.fly(node).getHeight();
  125092. }
  125093. me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, indicatorY);
  125094. // Cache the overRecord and the 'before' or 'after' indicator.
  125095. me.overRecord = overRecord;
  125096. me.currentPosition = pos;
  125097. }
  125098. } else {
  125099. me.invalidateDrop();
  125100. }
  125101. },
  125102. invalidateDrop: function() {
  125103. if (this.valid) {
  125104. this.valid = false;
  125105. this.getIndicator().hide();
  125106. }
  125107. },
  125108. // The mouse is over a View node
  125109. onNodeOver: function(node, dragZone, e, data) {
  125110. var me = this;
  125111. if (!Ext.Array.contains(data.records, me.view.getRecord(node))) {
  125112. me.positionIndicator(node, data, e);
  125113. }
  125114. return me.valid ? me.dropAllowed : me.dropNotAllowed;
  125115. },
  125116. // Moved out of the DropZone without dropping.
  125117. // Remove drop position indicator
  125118. notifyOut: function(node, dragZone, e, data) {
  125119. var me = this;
  125120. me.callParent(arguments);
  125121. delete me.overRecord;
  125122. delete me.currentPosition;
  125123. if (me.indicator) {
  125124. me.indicator.hide();
  125125. }
  125126. },
  125127. // The mouse is past the end of all nodes (or there are no nodes)
  125128. onContainerOver : function(dd, e, data) {
  125129. var me = this,
  125130. view = me.view,
  125131. count = view.store.getCount();
  125132. // There are records, so position after the last one
  125133. if (count) {
  125134. me.positionIndicator(view.getNode(count - 1), data, e);
  125135. }
  125136. // No records, position the indicator at the top
  125137. else {
  125138. delete me.overRecord;
  125139. delete me.currentPosition;
  125140. me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, 0);
  125141. me.valid = true;
  125142. }
  125143. return me.dropAllowed;
  125144. },
  125145. onContainerDrop : function(dd, e, data) {
  125146. return this.onNodeDrop(dd, null, e, data);
  125147. },
  125148. onNodeDrop: function(node, dragZone, e, data) {
  125149. var me = this,
  125150. dropHandled = false,
  125151. // Create a closure to perform the operation which the event handler may use.
  125152. // Users may now set the wait parameter in the beforedrop handler, and perform any kind
  125153. // of asynchronous processing such as an Ext.Msg.confirm, or an Ajax request,
  125154. // and complete the drop gesture at some point in the future by calling either the
  125155. // processDrop or cancelDrop methods.
  125156. dropHandlers = {
  125157. wait: false,
  125158. processDrop: function () {
  125159. me.invalidateDrop();
  125160. me.handleNodeDrop(data, me.overRecord, me.currentPosition);
  125161. dropHandled = true;
  125162. me.fireViewEvent('drop', node, data, me.overRecord, me.currentPosition);
  125163. },
  125164. cancelDrop: function() {
  125165. me.invalidateDrop();
  125166. dropHandled = true;
  125167. }
  125168. },
  125169. performOperation = false;
  125170. if (me.valid) {
  125171. performOperation = me.fireViewEvent('beforedrop', node, data, me.overRecord, me.currentPosition, dropHandlers);
  125172. if (dropHandlers.wait) {
  125173. return;
  125174. }
  125175. if (performOperation !== false) {
  125176. // If either of the drop handlers were called in the event handler, do not do it again.
  125177. if (!dropHandled) {
  125178. dropHandlers.processDrop();
  125179. }
  125180. }
  125181. }
  125182. return performOperation;
  125183. },
  125184. destroy: function(){
  125185. Ext.destroy(this.indicator);
  125186. delete this.indicator;
  125187. this.callParent();
  125188. }
  125189. });
  125190. /**
  125191. * @private
  125192. */
  125193. Ext.define('Ext.grid.ViewDropZone', {
  125194. extend: 'Ext.view.DropZone',
  125195. indicatorHtml: '<div class="' + Ext.baseCSSPrefix + 'grid-drop-indicator-left"></div><div class="' + Ext.baseCSSPrefix + 'grid-drop-indicator-right"></div>',
  125196. indicatorCls: Ext.baseCSSPrefix + 'grid-drop-indicator',
  125197. handleNodeDrop : function(data, record, position) {
  125198. var view = this.view,
  125199. store = view.getStore(),
  125200. index, records, i, len;
  125201. // If the copy flag is set, create a copy of the Models with the same IDs
  125202. if (data.copy) {
  125203. records = data.records;
  125204. data.records = [];
  125205. for (i = 0, len = records.length; i < len; i++) {
  125206. data.records.push(records[i].copy(records[i].getId()));
  125207. }
  125208. } else {
  125209. /*
  125210. * Remove from the source store. We do this regardless of whether the store
  125211. * is the same bacsue the store currently doesn't handle moving records
  125212. * within the store. In the future it should be possible to do this.
  125213. * Here was pass the isMove parameter if we're moving to the same view.
  125214. */
  125215. data.view.store.remove(data.records, data.view === view);
  125216. }
  125217. index = store.indexOf(record);
  125218. // 'after', or undefined (meaning a drop at index -1 on an empty View)...
  125219. if (position !== 'before') {
  125220. index++;
  125221. }
  125222. store.insert(index, data.records);
  125223. view.getSelectionModel().select(data.records);
  125224. }
  125225. });
  125226. /**
  125227. * A Grid header type which renders an icon, or a series of icons in a grid cell, and offers a scoped click
  125228. * handler for each icon.
  125229. *
  125230. * @example
  125231. * Ext.create('Ext.data.Store', {
  125232. * storeId:'employeeStore',
  125233. * fields:['firstname', 'lastname', 'seniority', 'dep', 'hired'],
  125234. * data:[
  125235. * {firstname:"Michael", lastname:"Scott"},
  125236. * {firstname:"Dwight", lastname:"Schrute"},
  125237. * {firstname:"Jim", lastname:"Halpert"},
  125238. * {firstname:"Kevin", lastname:"Malone"},
  125239. * {firstname:"Angela", lastname:"Martin"}
  125240. * ]
  125241. * });
  125242. *
  125243. * Ext.create('Ext.grid.Panel', {
  125244. * title: 'Action Column Demo',
  125245. * store: Ext.data.StoreManager.lookup('employeeStore'),
  125246. * columns: [
  125247. * {text: 'First Name', dataIndex:'firstname'},
  125248. * {text: 'Last Name', dataIndex:'lastname'},
  125249. * {
  125250. * xtype:'actioncolumn',
  125251. * width:50,
  125252. * items: [{
  125253. * icon: 'extjs/examples/shared/icons/fam/cog_edit.png', // Use a URL in the icon config
  125254. * tooltip: 'Edit',
  125255. * handler: function(grid, rowIndex, colIndex) {
  125256. * var rec = grid.getStore().getAt(rowIndex);
  125257. * alert("Edit " + rec.get('firstname'));
  125258. * }
  125259. * },{
  125260. * icon: 'extjs/examples/restful/images/delete.png',
  125261. * tooltip: 'Delete',
  125262. * handler: function(grid, rowIndex, colIndex) {
  125263. * var rec = grid.getStore().getAt(rowIndex);
  125264. * alert("Terminate " + rec.get('firstname'));
  125265. * }
  125266. * }]
  125267. * }
  125268. * ],
  125269. * width: 250,
  125270. * renderTo: Ext.getBody()
  125271. * });
  125272. *
  125273. * The action column can be at any index in the columns array, and a grid can have any number of
  125274. * action columns.
  125275. */
  125276. Ext.define('Ext.grid.column.Action', {
  125277. extend: 'Ext.grid.column.Column',
  125278. alias: ['widget.actioncolumn'],
  125279. alternateClassName: 'Ext.grid.ActionColumn',
  125280. /**
  125281. * @cfg {String} icon
  125282. * The URL of an image to display as the clickable element in the column.
  125283. *
  125284. * Defaults to `{@link Ext#BLANK_IMAGE_URL}`.
  125285. */
  125286. /**
  125287. * @cfg {String} iconCls
  125288. * A CSS class to apply to the icon image. To determine the class dynamically, configure the Column with
  125289. * a `{@link #getClass}` function.
  125290. */
  125291. /**
  125292. * @cfg {Function} handler
  125293. * A function called when the icon is clicked.
  125294. * @cfg {Ext.view.Table} handler.view The owning TableView.
  125295. * @cfg {Number} handler.rowIndex The row index clicked on.
  125296. * @cfg {Number} handler.colIndex The column index clicked on.
  125297. * @cfg {Object} handler.item The clicked item (or this Column if multiple {@link #cfg-items} were not configured).
  125298. * @cfg {Event} handler.e The click event.
  125299. * @cfg {Ext.data.Model} handler.record The Record underlying the clicked row.
  125300. * @cfg {HtmlElement} row The table row clicked upon.
  125301. */
  125302. /**
  125303. * @cfg {Object} scope
  125304. * The scope (**this** reference) in which the `{@link #handler}` and `{@link #getClass}` fuctions are executed.
  125305. * Defaults to this Column.
  125306. */
  125307. /**
  125308. * @cfg {String} tooltip
  125309. * A tooltip message to be displayed on hover. {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must
  125310. * have been initialized.
  125311. */
  125312. /**
  125313. * @cfg {Boolean} disabled
  125314. * If true, the action will not respond to click events, and will be displayed semi-opaque.
  125315. */
  125316. /**
  125317. * @cfg {Boolean} [stopSelection=true]
  125318. * Prevent grid selection upon mousedown.
  125319. */
  125320. /**
  125321. * @cfg {Function} getClass
  125322. * A function which returns the CSS class to apply to the icon image.
  125323. *
  125324. * @cfg {Object} getClass.v The value of the column's configured field (if any).
  125325. *
  125326. * @cfg {Object} getClass.metadata An object in which you may set the following attributes:
  125327. * @cfg {String} getClass.metadata.css A CSS class name to add to the cell's TD element.
  125328. * @cfg {String} getClass.metadata.attr An HTML attribute definition string to apply to the data container
  125329. * element *within* the table cell (e.g. 'style="color:red;"').
  125330. *
  125331. * @cfg {Ext.data.Model} getClass.r The Record providing the data.
  125332. *
  125333. * @cfg {Number} getClass.rowIndex The row index..
  125334. *
  125335. * @cfg {Number} getClass.colIndex The column index.
  125336. *
  125337. * @cfg {Ext.data.Store} getClass.store The Store which is providing the data Model.
  125338. */
  125339. /**
  125340. * @cfg {Object[]} items
  125341. * An Array which may contain multiple icon definitions, each element of which may contain:
  125342. *
  125343. * @cfg {String} items.icon The url of an image to display as the clickable element in the column.
  125344. *
  125345. * @cfg {String} items.iconCls A CSS class to apply to the icon image. To determine the class dynamically,
  125346. * configure the item with a `getClass` function.
  125347. *
  125348. * @cfg {Function} items.getClass A function which returns the CSS class to apply to the icon image.
  125349. * @cfg {Object} items.getClass.v The value of the column's configured field (if any).
  125350. * @cfg {Object} items.getClass.metadata An object in which you may set the following attributes:
  125351. * @cfg {String} items.getClass.metadata.css A CSS class name to add to the cell's TD element.
  125352. * @cfg {String} items.getClass.metadata.attr An HTML attribute definition string to apply to the data
  125353. * container element _within_ the table cell (e.g. 'style="color:red;"').
  125354. * @cfg {Ext.data.Model} items.getClass.r The Record providing the data.
  125355. * @cfg {Number} items.getClass.rowIndex The row index..
  125356. * @cfg {Number} items.getClass.colIndex The column index.
  125357. * @cfg {Ext.data.Store} items.getClass.store The Store which is providing the data Model.
  125358. *
  125359. * @cfg {Function} items.handler A function called when the icon is clicked.
  125360. *
  125361. * @cfg {Object} items.scope The scope (`this` reference) in which the `handler` and `getClass` functions
  125362. * are executed. Fallback defaults are this Column's configured scope, then this Column.
  125363. *
  125364. * @cfg {String} items.tooltip A tooltip message to be displayed on hover.
  125365. * @cfg {Boolean} items.disabled If true, the action will not respond to click events, and will be displayed semi-opaque.
  125366. * {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must have been initialized.
  125367. */
  125368. /**
  125369. * @property {Array} items
  125370. * An array of action items copied from the configured {@link #cfg-items items} configuration. Each will have
  125371. * an `enable` and `disable` method added which will enable and disable the associated action, and
  125372. * update the displayed icon accordingly.
  125373. */
  125374. actionIdRe: new RegExp(Ext.baseCSSPrefix + 'action-col-(\\d+)'),
  125375. /**
  125376. * @cfg {String} altText
  125377. * The alt text to use for the image element.
  125378. */
  125379. altText: '',
  125380. /**
  125381. * @cfg {String} menuText=[<i>Actions</i>]
  125382. * Text to display in this column's menu item if no {@link #text} was specified as a header.
  125383. */
  125384. menuText: '<i>Actions</i>',
  125385. sortable: false,
  125386. constructor: function(config) {
  125387. var me = this,
  125388. cfg = Ext.apply({}, config),
  125389. items = cfg.items || [me],
  125390. hasGetClass,
  125391. i,
  125392. len;
  125393. me.origRenderer = cfg.renderer || me.renderer;
  125394. me.origScope = cfg.scope || me.scope;
  125395. delete me.renderer;
  125396. delete me.scope;
  125397. delete cfg.renderer;
  125398. delete cfg.scope;
  125399. // This is a Container. Delete the items config to be reinstated after construction.
  125400. delete cfg.items;
  125401. me.callParent([cfg]);
  125402. // Items is an array property of ActionColumns
  125403. me.items = items;
  125404. for (i = 0, len = items.length; i < len; ++i) {
  125405. if (items[i].getClass) {
  125406. hasGetClass = true;
  125407. break;
  125408. }
  125409. }
  125410. // Also need to check for getClass, since it changes how the cell renders
  125411. if (me.origRenderer || hasGetClass) {
  125412. me.hasCustomRenderer = true;
  125413. }
  125414. },
  125415. // Renderer closure iterates through items creating an <img> element for each and tagging with an identifying
  125416. // class name x-action-col-{n}
  125417. defaultRenderer: function(v, meta){
  125418. var me = this,
  125419. prefix = Ext.baseCSSPrefix,
  125420. scope = me.origScope || me,
  125421. items = me.items,
  125422. len = items.length,
  125423. i = 0,
  125424. item;
  125425. // Allow a configured renderer to create initial value (And set the other values in the "metadata" argument!)
  125426. v = Ext.isFunction(me.origRenderer) ? me.origRenderer.apply(scope, arguments) || '' : '';
  125427. meta.tdCls += ' ' + Ext.baseCSSPrefix + 'action-col-cell';
  125428. for (; i < len; i++) {
  125429. item = items[i];
  125430. // Only process the item action setup once.
  125431. if (!item.hasActionConfiguration) {
  125432. // Apply our documented default to all items
  125433. item.stopSelection = me.stopSelection;
  125434. item.disable = Ext.Function.bind(me.disableAction, me, [i], 0);
  125435. item.enable = Ext.Function.bind(me.enableAction, me, [i], 0);
  125436. item.hasActionConfiguration = true;
  125437. }
  125438. v += '<img alt="' + (item.altText || me.altText) + '" src="' + (item.icon || Ext.BLANK_IMAGE_URL) +
  125439. '" class="' + prefix + 'action-col-icon ' + prefix + 'action-col-' + String(i) + ' ' + (item.disabled ? prefix + 'item-disabled' : ' ') +
  125440. ' ' + (Ext.isFunction(item.getClass) ? item.getClass.apply(item.scope || scope, arguments) : (item.iconCls || me.iconCls || '')) + '"' +
  125441. ((item.tooltip) ? ' data-qtip="' + item.tooltip + '"' : '') + ' />';
  125442. }
  125443. return v;
  125444. },
  125445. /**
  125446. * Enables this ActionColumn's action at the specified index.
  125447. * @param {Number/Ext.grid.column.Action} index
  125448. * @param {Boolean} [silent=false]
  125449. */
  125450. enableAction: function(index, silent) {
  125451. var me = this;
  125452. if (!index) {
  125453. index = 0;
  125454. } else if (!Ext.isNumber(index)) {
  125455. index = Ext.Array.indexOf(me.items, index);
  125456. }
  125457. me.items[index].disabled = false;
  125458. me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).removeCls(me.disabledCls);
  125459. if (!silent) {
  125460. me.fireEvent('enable', me);
  125461. }
  125462. },
  125463. /**
  125464. * Disables this ActionColumn's action at the specified index.
  125465. * @param {Number/Ext.grid.column.Action} index
  125466. * @param {Boolean} [silent=false]
  125467. */
  125468. disableAction: function(index, silent) {
  125469. var me = this;
  125470. if (!index) {
  125471. index = 0;
  125472. } else if (!Ext.isNumber(index)) {
  125473. index = Ext.Array.indexOf(me.items, index);
  125474. }
  125475. me.items[index].disabled = true;
  125476. me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).addCls(me.disabledCls);
  125477. if (!silent) {
  125478. me.fireEvent('disable', me);
  125479. }
  125480. },
  125481. destroy: function() {
  125482. delete this.items;
  125483. delete this.renderer;
  125484. return this.callParent(arguments);
  125485. },
  125486. /**
  125487. * @private
  125488. * Process and refire events routed from the GridView's processEvent method.
  125489. * Also fires any configured click handlers. By default, cancels the mousedown event to prevent selection.
  125490. * Returns the event handler's status to allow canceling of GridView's bubbling process.
  125491. */
  125492. processEvent : function(type, view, cell, recordIndex, cellIndex, e, record, row){
  125493. var me = this,
  125494. target = e.getTarget(),
  125495. match,
  125496. item, fn,
  125497. key = type == 'keydown' && e.getKey();
  125498. // If the target was not within a cell (ie it's a keydown event from the View), then
  125499. // rely on the selection data injected by View.processUIEvent to grab the
  125500. // first action icon from the selected cell.
  125501. if (key && !Ext.fly(target).findParent(view.cellSelector)) {
  125502. target = Ext.fly(cell).down('.' + Ext.baseCSSPrefix + 'action-col-icon', true);
  125503. }
  125504. // NOTE: The statement below tests the truthiness of an assignment.
  125505. if (target && (match = target.className.match(me.actionIdRe))) {
  125506. item = me.items[parseInt(match[1], 10)];
  125507. if (item) {
  125508. if (type == 'click' || (key == e.ENTER || key == e.SPACE)) {
  125509. fn = item.handler || me.handler;
  125510. if (fn && !item.disabled) {
  125511. fn.call(item.scope || me.origScope || me, view, recordIndex, cellIndex, item, e, record, row);
  125512. }
  125513. } else if (type == 'mousedown' && item.stopSelection !== false) {
  125514. return false;
  125515. }
  125516. }
  125517. }
  125518. return me.callParent(arguments);
  125519. },
  125520. cascade: function(fn, scope) {
  125521. fn.call(scope||this, this);
  125522. },
  125523. // Private override because this cannot function as a Container, and it has an items property which is an Array, NOT a MixedCollection.
  125524. getRefItems: function() {
  125525. return [];
  125526. }
  125527. });
  125528. /**
  125529. * A Column definition class which renders boolean data fields. See the {@link Ext.grid.column.Column#xtype xtype}
  125530. * config option of {@link Ext.grid.column.Column} for more details.
  125531. *
  125532. * @example
  125533. * Ext.create('Ext.data.Store', {
  125534. * storeId:'sampleStore',
  125535. * fields:[
  125536. * {name: 'framework', type: 'string'},
  125537. * {name: 'rocks', type: 'boolean'}
  125538. * ],
  125539. * data:{'items':[
  125540. * { 'framework': "Ext JS 4", 'rocks': true },
  125541. * { 'framework': "Sencha Touch", 'rocks': true },
  125542. * { 'framework': "Ext GWT", 'rocks': true },
  125543. * { 'framework': "Other Guys", 'rocks': false }
  125544. * ]},
  125545. * proxy: {
  125546. * type: 'memory',
  125547. * reader: {
  125548. * type: 'json',
  125549. * root: 'items'
  125550. * }
  125551. * }
  125552. * });
  125553. *
  125554. * Ext.create('Ext.grid.Panel', {
  125555. * title: 'Boolean Column Demo',
  125556. * store: Ext.data.StoreManager.lookup('sampleStore'),
  125557. * columns: [
  125558. * { text: 'Framework', dataIndex: 'framework', flex: 1 },
  125559. * {
  125560. * xtype: 'booleancolumn',
  125561. * text: 'Rocks',
  125562. * trueText: 'Yes',
  125563. * falseText: 'No',
  125564. * dataIndex: 'rocks'
  125565. * }
  125566. * ],
  125567. * height: 200,
  125568. * width: 400,
  125569. * renderTo: Ext.getBody()
  125570. * });
  125571. */
  125572. Ext.define('Ext.grid.column.Boolean', {
  125573. extend: 'Ext.grid.column.Column',
  125574. alias: ['widget.booleancolumn'],
  125575. alternateClassName: 'Ext.grid.BooleanColumn',
  125576. //<locale>
  125577. /**
  125578. * @cfg {String} trueText
  125579. * The string returned by the renderer when the column value is not falsey.
  125580. */
  125581. trueText: 'true',
  125582. //</locale>
  125583. //<locale>
  125584. /**
  125585. * @cfg {String} falseText
  125586. * The string returned by the renderer when the column value is falsey (but not undefined).
  125587. */
  125588. falseText: 'false',
  125589. //</locale>
  125590. /**
  125591. * @cfg {String} undefinedText
  125592. * The string returned by the renderer when the column value is undefined.
  125593. */
  125594. undefinedText: '&#160;',
  125595. /**
  125596. * @cfg renderer
  125597. * @hide
  125598. */
  125599. /**
  125600. * @cfg scope
  125601. * @hide
  125602. */
  125603. defaultRenderer: function(value){
  125604. if (value === undefined) {
  125605. return this.undefinedText;
  125606. }
  125607. if (!value || value === 'false') {
  125608. return this.falseText;
  125609. }
  125610. return this.trueText;
  125611. }
  125612. });
  125613. /**
  125614. * A Column definition class which renders a passed date according to the default locale, or a configured
  125615. * {@link #format}.
  125616. *
  125617. * @example
  125618. * Ext.create('Ext.data.Store', {
  125619. * storeId:'sampleStore',
  125620. * fields:[
  125621. * { name: 'symbol', type: 'string' },
  125622. * { name: 'date', type: 'date' },
  125623. * { name: 'change', type: 'number' },
  125624. * { name: 'volume', type: 'number' },
  125625. * { name: 'topday', type: 'date' }
  125626. * ],
  125627. * data:[
  125628. * { symbol: "msft", date: '2011/04/22', change: 2.43, volume: 61606325, topday: '04/01/2010' },
  125629. * { symbol: "goog", date: '2011/04/22', change: 0.81, volume: 3053782, topday: '04/11/2010' },
  125630. * { symbol: "apple", date: '2011/04/22', change: 1.35, volume: 24484858, topday: '04/28/2010' },
  125631. * { symbol: "sencha", date: '2011/04/22', change: 8.85, volume: 5556351, topday: '04/22/2010' }
  125632. * ]
  125633. * });
  125634. *
  125635. * Ext.create('Ext.grid.Panel', {
  125636. * title: 'Date Column Demo',
  125637. * store: Ext.data.StoreManager.lookup('sampleStore'),
  125638. * columns: [
  125639. * { text: 'Symbol', dataIndex: 'symbol', flex: 1 },
  125640. * { text: 'Date', dataIndex: 'date', xtype: 'datecolumn', format:'Y-m-d' },
  125641. * { text: 'Change', dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
  125642. * { text: 'Volume', dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' },
  125643. * { text: 'Top Day', dataIndex: 'topday', xtype: 'datecolumn', format:'l' }
  125644. * ],
  125645. * height: 200,
  125646. * width: 450,
  125647. * renderTo: Ext.getBody()
  125648. * });
  125649. */
  125650. Ext.define('Ext.grid.column.Date', {
  125651. extend: 'Ext.grid.column.Column',
  125652. alias: ['widget.datecolumn'],
  125653. requires: ['Ext.Date'],
  125654. alternateClassName: 'Ext.grid.DateColumn',
  125655. /**
  125656. * @cfg {String} format
  125657. * A formatting string as used by {@link Ext.Date#format} to format a Date for this Column.
  125658. *
  125659. * Defaults to the default date from {@link Ext.Date#defaultFormat} which itself my be overridden
  125660. * in a locale file.
  125661. */
  125662. /**
  125663. * @cfg renderer
  125664. * @hide
  125665. */
  125666. /**
  125667. * @cfg scope
  125668. * @hide
  125669. */
  125670. initComponent: function(){
  125671. if (!this.format) {
  125672. this.format = Ext.Date.defaultFormat;
  125673. }
  125674. this.callParent(arguments);
  125675. },
  125676. defaultRenderer: function(value){
  125677. return Ext.util.Format.date(value, this.format);
  125678. }
  125679. });
  125680. /**
  125681. * A Column definition class which renders a numeric data field according to a {@link #format} string.
  125682. *
  125683. * @example
  125684. * Ext.create('Ext.data.Store', {
  125685. * storeId:'sampleStore',
  125686. * fields:[
  125687. * { name: 'symbol', type: 'string' },
  125688. * { name: 'price', type: 'number' },
  125689. * { name: 'change', type: 'number' },
  125690. * { name: 'volume', type: 'number' }
  125691. * ],
  125692. * data:[
  125693. * { symbol: "msft", price: 25.76, change: 2.43, volume: 61606325 },
  125694. * { symbol: "goog", price: 525.73, change: 0.81, volume: 3053782 },
  125695. * { symbol: "apple", price: 342.41, change: 1.35, volume: 24484858 },
  125696. * { symbol: "sencha", price: 142.08, change: 8.85, volume: 5556351 }
  125697. * ]
  125698. * });
  125699. *
  125700. * Ext.create('Ext.grid.Panel', {
  125701. * title: 'Number Column Demo',
  125702. * store: Ext.data.StoreManager.lookup('sampleStore'),
  125703. * columns: [
  125704. * { text: 'Symbol', dataIndex: 'symbol', flex: 1 },
  125705. * { text: 'Current Price', dataIndex: 'price', renderer: Ext.util.Format.usMoney },
  125706. * { text: 'Change', dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
  125707. * { text: 'Volume', dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' }
  125708. * ],
  125709. * height: 200,
  125710. * width: 400,
  125711. * renderTo: Ext.getBody()
  125712. * });
  125713. */
  125714. Ext.define('Ext.grid.column.Number', {
  125715. extend: 'Ext.grid.column.Column',
  125716. alias: ['widget.numbercolumn'],
  125717. requires: ['Ext.util.Format'],
  125718. alternateClassName: 'Ext.grid.NumberColumn',
  125719. //<locale>
  125720. /**
  125721. * @cfg {String} format
  125722. * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column.
  125723. */
  125724. format : '0,000.00',
  125725. //</locale>
  125726. /**
  125727. * @cfg renderer
  125728. * @hide
  125729. */
  125730. /**
  125731. * @cfg scope
  125732. * @hide
  125733. */
  125734. defaultRenderer: function(value){
  125735. return Ext.util.Format.number(value, this.format);
  125736. }
  125737. });
  125738. /**
  125739. * A Column definition class which renders a value by processing a {@link Ext.data.Model Model}'s
  125740. * {@link Ext.data.Model#persistenceProperty data} using a {@link #tpl configured}
  125741. * {@link Ext.XTemplate XTemplate}.
  125742. *
  125743. * @example
  125744. * Ext.create('Ext.data.Store', {
  125745. * storeId:'employeeStore',
  125746. * fields:['firstname', 'lastname', 'seniority', 'department'],
  125747. * groupField: 'department',
  125748. * data:[
  125749. * { firstname: "Michael", lastname: "Scott", seniority: 7, department: "Management" },
  125750. * { firstname: "Dwight", lastname: "Schrute", seniority: 2, department: "Sales" },
  125751. * { firstname: "Jim", lastname: "Halpert", seniority: 3, department: "Sales" },
  125752. * { firstname: "Kevin", lastname: "Malone", seniority: 4, department: "Accounting" },
  125753. * { firstname: "Angela", lastname: "Martin", seniority: 5, department: "Accounting" }
  125754. * ]
  125755. * });
  125756. *
  125757. * Ext.create('Ext.grid.Panel', {
  125758. * title: 'Column Template Demo',
  125759. * store: Ext.data.StoreManager.lookup('employeeStore'),
  125760. * columns: [
  125761. * { text: 'Full Name', xtype: 'templatecolumn', tpl: '{firstname} {lastname}', flex:1 },
  125762. * { text: 'Department (Yrs)', xtype: 'templatecolumn', tpl: '{department} ({seniority})' }
  125763. * ],
  125764. * height: 200,
  125765. * width: 300,
  125766. * renderTo: Ext.getBody()
  125767. * });
  125768. */
  125769. Ext.define('Ext.grid.column.Template', {
  125770. extend: 'Ext.grid.column.Column',
  125771. alias: ['widget.templatecolumn'],
  125772. requires: ['Ext.XTemplate'],
  125773. alternateClassName: 'Ext.grid.TemplateColumn',
  125774. /**
  125775. * @cfg {String/Ext.XTemplate} tpl
  125776. * An {@link Ext.XTemplate XTemplate}, or an XTemplate *definition string* to use to process a
  125777. * {@link Ext.data.Model Model}'s {@link Ext.data.Model#persistenceProperty data} to produce a
  125778. * column's rendered value.
  125779. */
  125780. /**
  125781. * @cfg renderer
  125782. * @hide
  125783. */
  125784. /**
  125785. * @cfg scope
  125786. * @hide
  125787. */
  125788. initComponent: function(){
  125789. var me = this;
  125790. me.tpl = (!Ext.isPrimitive(me.tpl) && me.tpl.compile) ? me.tpl : new Ext.XTemplate(me.tpl);
  125791. // Set this here since the template may access any record values,
  125792. // so we must always run the update for this column
  125793. me.hasCustomRenderer = true;
  125794. me.callParent(arguments);
  125795. },
  125796. defaultRenderer: function(value, meta, record) {
  125797. var data = Ext.apply({}, record.data, record.getAssociatedData());
  125798. return this.tpl.apply(data);
  125799. }
  125800. });
  125801. /**
  125802. * A feature is a type of plugin that is specific to the {@link Ext.grid.Panel}. It provides several
  125803. * hooks that allows the developer to inject additional functionality at certain points throughout the
  125804. * grid creation cycle. This class provides the base template methods that are available to the developer,
  125805. * it should be extended.
  125806. *
  125807. * There are several built in features that extend this class, for example:
  125808. *
  125809. * - {@link Ext.grid.feature.Grouping} - Shows grid rows in groups as specified by the {@link Ext.data.Store}
  125810. * - {@link Ext.grid.feature.RowBody} - Adds a body section for each grid row that can contain markup.
  125811. * - {@link Ext.grid.feature.Summary} - Adds a summary row at the bottom of the grid with aggregate totals for a column.
  125812. *
  125813. * ## Using Features
  125814. * A feature is added to the grid by specifying it an array of features in the configuration:
  125815. *
  125816. * var groupingFeature = Ext.create('Ext.grid.feature.Grouping');
  125817. * Ext.create('Ext.grid.Panel', {
  125818. * // other options
  125819. * features: [groupingFeature]
  125820. * });
  125821. *
  125822. * @abstract
  125823. */
  125824. Ext.define('Ext.grid.feature.Feature', {
  125825. extend: 'Ext.util.Observable',
  125826. alias: 'feature.feature',
  125827. /*
  125828. * @property {Boolean} isFeature
  125829. * `true` in this class to identify an object as an instantiated Feature, or subclass thereof.
  125830. */
  125831. isFeature: true,
  125832. /**
  125833. * True when feature is disabled.
  125834. */
  125835. disabled: false,
  125836. /**
  125837. * @property {Boolean}
  125838. * Most features will expose additional events, some may not and will
  125839. * need to change this to false.
  125840. */
  125841. hasFeatureEvent: true,
  125842. /**
  125843. * @property {String}
  125844. * Prefix to use when firing events on the view.
  125845. * For example a prefix of group would expose "groupclick", "groupcontextmenu", "groupdblclick".
  125846. */
  125847. eventPrefix: null,
  125848. /**
  125849. * @property {String}
  125850. * Selector used to determine when to fire the event with the eventPrefix.
  125851. */
  125852. eventSelector: null,
  125853. /**
  125854. * @property {Ext.view.Table}
  125855. * Reference to the TableView.
  125856. */
  125857. view: null,
  125858. /**
  125859. * @property {Ext.grid.Panel}
  125860. * Reference to the grid panel
  125861. */
  125862. grid: null,
  125863. /**
  125864. * Most features will not modify the data returned to the view.
  125865. * This is limited to one feature that manipulates the data per grid view.
  125866. */
  125867. collectData: false,
  125868. constructor: function(config) {
  125869. this.initialConfig = config;
  125870. this.callParent(arguments);
  125871. },
  125872. clone: function() {
  125873. return new this.self(this.initialConfig);
  125874. },
  125875. init: Ext.emptyFn,
  125876. getFeatureTpl: function() {
  125877. return '';
  125878. },
  125879. /**
  125880. * Abstract method to be overriden when a feature should add additional
  125881. * arguments to its event signature. By default the event will fire:
  125882. *
  125883. * - view - The underlying Ext.view.Table
  125884. * - featureTarget - The matched element by the defined {@link #eventSelector}
  125885. *
  125886. * The method must also return the eventName as the first index of the array
  125887. * to be passed to fireEvent.
  125888. * @template
  125889. */
  125890. getFireEventArgs: function(eventName, view, featureTarget, e) {
  125891. return [eventName, view, featureTarget, e];
  125892. },
  125893. /**
  125894. * Approriate place to attach events to the view, selectionmodel, headerCt, etc
  125895. * @template
  125896. */
  125897. attachEvents: function() {
  125898. },
  125899. getFragmentTpl: Ext.emptyFn,
  125900. /**
  125901. * Allows a feature to mutate the metaRowTpl.
  125902. * The array received as a single argument can be manipulated to add things
  125903. * on the end/begining of a particular row.
  125904. * @param {Array} metaRowTplArray A String array to be used constructing an {@link Ext.XTemplate XTemplate}
  125905. * to render the rows. This Array may be changed to provide extra DOM structure.
  125906. * @template
  125907. */
  125908. mutateMetaRowTpl: Ext.emptyFn,
  125909. /**
  125910. * Allows a feature to inject member methods into the metaRowTpl. This is
  125911. * important for embedding functionality which will become part of the proper
  125912. * row tpl.
  125913. * @template
  125914. */
  125915. getMetaRowTplFragments: function() {
  125916. return {};
  125917. },
  125918. getTableFragments: function() {
  125919. return {};
  125920. },
  125921. /**
  125922. * Provide additional data to the prepareData call within the grid view.
  125923. * @param {Object} data The data for this particular record.
  125924. * @param {Number} idx The row index for this record.
  125925. * @param {Ext.data.Model} record The record instance
  125926. * @param {Object} orig The original result from the prepareData call to massage.
  125927. * @template
  125928. */
  125929. getAdditionalData: function(data, idx, record, orig) {
  125930. return {};
  125931. },
  125932. /**
  125933. * Enables the feature.
  125934. */
  125935. enable: function() {
  125936. this.disabled = false;
  125937. },
  125938. /**
  125939. * Disables the feature.
  125940. */
  125941. disable: function() {
  125942. this.disabled = true;
  125943. }
  125944. });
  125945. /**
  125946. * A small abstract class that contains the shared behaviour for any summary
  125947. * calculations to be used in the grid.
  125948. */
  125949. Ext.define('Ext.grid.feature.AbstractSummary', {
  125950. /* Begin Definitions */
  125951. extend: 'Ext.grid.feature.Feature',
  125952. alias: 'feature.abstractsummary',
  125953. /* End Definitions */
  125954. /**
  125955. * @cfg
  125956. * True to show the summary row.
  125957. */
  125958. showSummaryRow: true,
  125959. // @private
  125960. nestedIdRe: /\{\{id\}([\w\-]*)\}/g,
  125961. // Listen for store updates. Eg, from an Editor.
  125962. init: function() {
  125963. var me = this;
  125964. // Summary rows must be kept in column order, so view must be refreshed on column move
  125965. me.grid.optimizedColumnMove = false;
  125966. me.view.mon(me.view.store, {
  125967. update: me.onStoreUpdate,
  125968. scope: me
  125969. });
  125970. },
  125971. // Refresh the whole view on edit so that the Summary gets updated
  125972. onStoreUpdate: function() {
  125973. var v = this.view;
  125974. if (this.showSummaryRow) {
  125975. v.saveScrollState();
  125976. v.refresh();
  125977. v.restoreScrollState();
  125978. }
  125979. },
  125980. /**
  125981. * Toggle whether or not to show the summary row.
  125982. * @param {Boolean} visible True to show the summary row
  125983. */
  125984. toggleSummaryRow: function(visible){
  125985. this.showSummaryRow = !!visible;
  125986. },
  125987. /**
  125988. * Gets any fragments to be used in the tpl
  125989. * @private
  125990. * @return {Object} The fragments
  125991. */
  125992. getSummaryFragments: function(){
  125993. var fragments = {};
  125994. if (this.showSummaryRow) {
  125995. Ext.apply(fragments, {
  125996. printSummaryRow: Ext.bind(this.printSummaryRow, this)
  125997. });
  125998. }
  125999. return fragments;
  126000. },
  126001. /**
  126002. * Prints a summary row
  126003. * @private
  126004. * @param {Object} index The index in the template
  126005. * @return {String} The value of the summary row
  126006. */
  126007. printSummaryRow: function(index){
  126008. var inner = this.view.getTableChunker().metaRowTpl.join(''),
  126009. prefix = Ext.baseCSSPrefix;
  126010. inner = inner.replace(prefix + 'grid-row', prefix + 'grid-row-summary');
  126011. inner = inner.replace('{{id}}', '{gridSummaryValue}');
  126012. inner = inner.replace(this.nestedIdRe, '{id$1}');
  126013. inner = inner.replace('{[this.embedRowCls()]}', '{rowCls}');
  126014. inner = inner.replace('{[this.embedRowAttr()]}', '{rowAttr}');
  126015. inner = new Ext.XTemplate(inner, {
  126016. firstOrLastCls: Ext.view.TableChunker.firstOrLastCls
  126017. });
  126018. return inner.applyTemplate({
  126019. columns: this.getPrintData(index)
  126020. });
  126021. },
  126022. /**
  126023. * Gets the value for the column from the attached data.
  126024. * @param {Ext.grid.column.Column} column The header
  126025. * @param {Object} data The current data
  126026. * @return {String} The value to be rendered
  126027. */
  126028. getColumnValue: function(column, summaryData){
  126029. var comp = Ext.getCmp(column.id),
  126030. value = summaryData[column.id],
  126031. renderer = comp.summaryRenderer;
  126032. if (!value && value !== 0) {
  126033. value = '\u00a0';
  126034. }
  126035. if (renderer) {
  126036. value = renderer.call(
  126037. comp.scope || this,
  126038. value,
  126039. summaryData,
  126040. column.dataIndex
  126041. );
  126042. }
  126043. return value;
  126044. },
  126045. /**
  126046. * Get the summary data for a field.
  126047. * @private
  126048. * @param {Ext.data.Store} store The store to get the data from
  126049. * @param {String/Function} type The type of aggregation. If a function is specified it will
  126050. * be passed to the stores aggregate function.
  126051. * @param {String} field The field to aggregate on
  126052. * @param {Boolean} group True to aggregate in grouped mode
  126053. * @return {Number/String/Object} See the return type for the store functions.
  126054. */
  126055. getSummary: function(store, type, field, group){
  126056. if (type) {
  126057. if (Ext.isFunction(type)) {
  126058. return store.aggregate(type, null, group);
  126059. }
  126060. switch (type) {
  126061. case 'count':
  126062. return store.count(group);
  126063. case 'min':
  126064. return store.min(field, group);
  126065. case 'max':
  126066. return store.max(field, group);
  126067. case 'sum':
  126068. return store.sum(field, group);
  126069. case 'average':
  126070. return store.average(field, group);
  126071. default:
  126072. return group ? {} : '';
  126073. }
  126074. }
  126075. }
  126076. });
  126077. /**
  126078. *
  126079. */
  126080. Ext.define('Ext.grid.feature.Chunking', {
  126081. extend: 'Ext.grid.feature.Feature',
  126082. alias: 'feature.chunking',
  126083. chunkSize: 20,
  126084. rowHeight: Ext.isIE ? 27 : 26,
  126085. visibleChunk: 0,
  126086. hasFeatureEvent: false,
  126087. attachEvents: function() {
  126088. this.view.el.on('scroll', this.onBodyScroll, this, {buffer: 300});
  126089. },
  126090. onBodyScroll: function(e, t) {
  126091. var view = this.view,
  126092. top = t.scrollTop,
  126093. nextChunk = Math.floor(top / this.rowHeight / this.chunkSize);
  126094. if (nextChunk !== this.visibleChunk) {
  126095. this.visibleChunk = nextChunk;
  126096. view.refresh();
  126097. view.el.dom.scrollTop = top;
  126098. //BrowserBug: IE6,7,8 quirks mode takes setting scrollTop 2x.
  126099. view.el.dom.scrollTop = top;
  126100. }
  126101. },
  126102. collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
  126103. //headerCt = this.view.headerCt,
  126104. //colums = headerCt.getColumnsForTpl(),
  126105. var me = this,
  126106. recordCount = o.rows.length,
  126107. start = 0,
  126108. i = 0,
  126109. visibleChunk = me.visibleChunk,
  126110. rows,
  126111. chunkLength,
  126112. origRows = o.rows;
  126113. delete o.rows;
  126114. o.chunks = [];
  126115. for (; start < recordCount; start += me.chunkSize, i++) {
  126116. if (start + me.chunkSize > recordCount) {
  126117. chunkLength = recordCount - start;
  126118. } else {
  126119. chunkLength = me.chunkSize;
  126120. }
  126121. if (i >= visibleChunk - 1 && i <= visibleChunk + 1) {
  126122. rows = origRows.slice(start, start + me.chunkSize);
  126123. } else {
  126124. rows = [];
  126125. }
  126126. o.chunks.push({
  126127. rows: rows,
  126128. fullWidth: fullWidth,
  126129. chunkHeight: chunkLength * me.rowHeight
  126130. });
  126131. }
  126132. return o;
  126133. },
  126134. getTableFragments: function() {
  126135. return {
  126136. openTableWrap: function() {
  126137. return '<tpl for="chunks"><div class="' + Ext.baseCSSPrefix + 'grid-chunk" style="height: {chunkHeight}px;">';
  126138. },
  126139. closeTableWrap: function() {
  126140. return '</div></tpl>';
  126141. }
  126142. };
  126143. }
  126144. });
  126145. /**
  126146. * This feature allows to display the grid rows aggregated into groups as specified by the {@link Ext.data.Store#groupers}
  126147. * specified on the Store. The group will show the title for the group name and then the appropriate records for the group
  126148. * underneath. The groups can also be expanded and collapsed.
  126149. *
  126150. * ## Extra Events
  126151. *
  126152. * This feature adds several extra events that will be fired on the grid to interact with the groups:
  126153. *
  126154. * - {@link #groupclick}
  126155. * - {@link #groupdblclick}
  126156. * - {@link #groupcontextmenu}
  126157. * - {@link #groupexpand}
  126158. * - {@link #groupcollapse}
  126159. *
  126160. * ## Menu Augmentation
  126161. *
  126162. * This feature adds extra options to the grid column menu to provide the user with functionality to modify the grouping.
  126163. * This can be disabled by setting the {@link #enableGroupingMenu} option. The option to disallow grouping from being turned off
  126164. * by the user is {@link #enableNoGroups}.
  126165. *
  126166. * ## Controlling Group Text
  126167. *
  126168. * The {@link #groupHeaderTpl} is used to control the rendered title for each group. It can modified to customized
  126169. * the default display.
  126170. *
  126171. * ## Example Usage
  126172. *
  126173. * @example
  126174. * var store = Ext.create('Ext.data.Store', {
  126175. * storeId:'employeeStore',
  126176. * fields:['name', 'seniority', 'department'],
  126177. * groupField: 'department',
  126178. * data: {'employees':[
  126179. * { "name": "Michael Scott", "seniority": 7, "department": "Management" },
  126180. * { "name": "Dwight Schrute", "seniority": 2, "department": "Sales" },
  126181. * { "name": "Jim Halpert", "seniority": 3, "department": "Sales" },
  126182. * { "name": "Kevin Malone", "seniority": 4, "department": "Accounting" },
  126183. * { "name": "Angela Martin", "seniority": 5, "department": "Accounting" }
  126184. * ]},
  126185. * proxy: {
  126186. * type: 'memory',
  126187. * reader: {
  126188. * type: 'json',
  126189. * root: 'employees'
  126190. * }
  126191. * }
  126192. * });
  126193. *
  126194. * Ext.create('Ext.grid.Panel', {
  126195. * title: 'Employees',
  126196. * store: Ext.data.StoreManager.lookup('employeeStore'),
  126197. * columns: [
  126198. * { text: 'Name', dataIndex: 'name' },
  126199. * { text: 'Seniority', dataIndex: 'seniority' }
  126200. * ],
  126201. * features: [{ftype:'grouping'}],
  126202. * width: 200,
  126203. * height: 275,
  126204. * renderTo: Ext.getBody()
  126205. * });
  126206. *
  126207. * **Note:** To use grouping with a grid that has {@link Ext.grid.column.Column#locked locked columns}, you need to supply
  126208. * the grouping feature as a config object - so the grid can create two instances of the grouping feature.
  126209. *
  126210. * @author Nicolas Ferrero
  126211. */
  126212. Ext.define('Ext.grid.feature.Grouping', {
  126213. extend: 'Ext.grid.feature.Feature',
  126214. alias: 'feature.grouping',
  126215. eventPrefix: 'group',
  126216. eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
  126217. bodySelector: '.' + Ext.baseCSSPrefix + 'grid-group-body',
  126218. constructor: function() {
  126219. var me = this;
  126220. me.collapsedState = {};
  126221. me.callParent(arguments);
  126222. },
  126223. /**
  126224. * @event groupclick
  126225. * @param {Ext.view.Table} view
  126226. * @param {HTMLElement} node
  126227. * @param {String} group The name of the group
  126228. * @param {Ext.EventObject} e
  126229. */
  126230. /**
  126231. * @event groupdblclick
  126232. * @param {Ext.view.Table} view
  126233. * @param {HTMLElement} node
  126234. * @param {String} group The name of the group
  126235. * @param {Ext.EventObject} e
  126236. */
  126237. /**
  126238. * @event groupcontextmenu
  126239. * @param {Ext.view.Table} view
  126240. * @param {HTMLElement} node
  126241. * @param {String} group The name of the group
  126242. * @param {Ext.EventObject} e
  126243. */
  126244. /**
  126245. * @event groupcollapse
  126246. * @param {Ext.view.Table} view
  126247. * @param {HTMLElement} node
  126248. * @param {String} group The name of the group
  126249. */
  126250. /**
  126251. * @event groupexpand
  126252. * @param {Ext.view.Table} view
  126253. * @param {HTMLElement} node
  126254. * @param {String} group The name of the group
  126255. */
  126256. /**
  126257. * @cfg {String/Array/Ext.Template} groupHeaderTpl
  126258. * A string Template snippet, an array of strings (optionally followed by an object containing Template methods) to be used to construct a Template, or a Template instance.
  126259. *
  126260. * - Example 1 (Template snippet):
  126261. *
  126262. * groupHeaderTpl: 'Group: {name}'
  126263. *
  126264. * - Example 2 (Array):
  126265. *
  126266. * groupHeaderTpl: [
  126267. * 'Group: ',
  126268. * '<div>{name:this.formatName}</div>',
  126269. * {
  126270. * formatName: function(name) {
  126271. * return Ext.String.trim(name);
  126272. * }
  126273. * }
  126274. * ]
  126275. *
  126276. * - Example 3 (Template Instance):
  126277. *
  126278. * groupHeaderTpl: Ext.create('Ext.XTemplate',
  126279. * 'Group: ',
  126280. * '<div>{name:this.formatName}</div>',
  126281. * {
  126282. * formatName: function(name) {
  126283. * return Ext.String.trim(name);
  126284. * }
  126285. * }
  126286. * )
  126287. *
  126288. * @cfg {String} groupHeaderTpl.groupField The field name being grouped by.
  126289. * @cfg {String} groupHeaderTpl.columnName The column header associated with the field being grouped by *if there is a column for the field*, falls back to the groupField name.
  126290. * @cfg {Mixed} groupHeaderTpl.groupValue The value of the {@link Ext.data.Store#groupField groupField} for the group header being rendered.
  126291. * @cfg {String} groupHeaderTpl.renderedGroupValue The rendered value of the {@link Ext.data.Store#groupField groupField} for the group header being rendered, as produced by the column renderer.
  126292. * @cfg {String} groupHeaderTpl.name An alias for renderedGroupValue
  126293. * @cfg {Object[]} groupHeaderTpl.rows An array of child row data objects as returned by the View's {@link Ext.view.AbstractView#prepareData prepareData} method.
  126294. * @cfg {Ext.data.Model[]} groupHeaderTpl.children An array containing the child records for the group being rendered.
  126295. */
  126296. groupHeaderTpl: '{columnName}: {name}',
  126297. /**
  126298. * @cfg {Number} [depthToIndent=17]
  126299. * Number of pixels to indent per grouping level
  126300. */
  126301. depthToIndent: 17,
  126302. collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
  126303. hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
  126304. hdCollapsibleCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsible',
  126305. //<locale>
  126306. /**
  126307. * @cfg {String} [groupByText="Group by this field"]
  126308. * Text displayed in the grid header menu for grouping by header.
  126309. */
  126310. groupByText : 'Group by this field',
  126311. //</locale>
  126312. //<locale>
  126313. /**
  126314. * @cfg {String} [showGroupsText="Show in groups"]
  126315. * Text displayed in the grid header for enabling/disabling grouping.
  126316. */
  126317. showGroupsText : 'Show in groups',
  126318. //</locale>
  126319. /**
  126320. * @cfg {Boolean} [hideGroupedHeader=false]
  126321. * True to hide the header that is currently grouped.
  126322. */
  126323. hideGroupedHeader : false,
  126324. /**
  126325. * @cfg {Boolean} [startCollapsed=false]
  126326. * True to start all groups collapsed.
  126327. */
  126328. startCollapsed : false,
  126329. /**
  126330. * @cfg {Boolean} [enableGroupingMenu=true]
  126331. * True to enable the grouping control in the header menu.
  126332. */
  126333. enableGroupingMenu : true,
  126334. /**
  126335. * @cfg {Boolean} [enableNoGroups=true]
  126336. * True to allow the user to turn off grouping.
  126337. */
  126338. enableNoGroups : true,
  126339. /**
  126340. * @cfg {Boolean} [collapsible=true]
  126341. * Set to `falsee` to disable collapsing groups from the UI.
  126342. *
  126343. * This is set to `false` when the associated {@link Ext.data.Store store} is
  126344. * {@link Ext.data.Store#buffered buffered}.
  126345. */
  126346. collapsible: true,
  126347. enable: function() {
  126348. var me = this,
  126349. view = me.view,
  126350. store = view.store,
  126351. groupToggleMenuItem;
  126352. me.lastGroupField = me.getGroupField();
  126353. if (me.lastGroupIndex) {
  126354. me.block();
  126355. store.group(me.lastGroupIndex);
  126356. me.unblock();
  126357. }
  126358. me.callParent();
  126359. groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
  126360. groupToggleMenuItem.setChecked(true, true);
  126361. me.refreshIf();
  126362. },
  126363. disable: function() {
  126364. var me = this,
  126365. view = me.view,
  126366. store = view.store,
  126367. remote = store.remoteGroup,
  126368. groupToggleMenuItem,
  126369. lastGroup;
  126370. lastGroup = store.groupers.first();
  126371. if (lastGroup) {
  126372. me.lastGroupIndex = lastGroup.property;
  126373. me.block();
  126374. store.clearGrouping();
  126375. me.unblock();
  126376. }
  126377. me.callParent();
  126378. groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
  126379. groupToggleMenuItem.setChecked(true, true);
  126380. groupToggleMenuItem.setChecked(false, true);
  126381. me.refreshIf();
  126382. },
  126383. refreshIf: function() {
  126384. var ownerCt = this.grid.ownerCt,
  126385. view = this.view;
  126386. if (!view.store.remoteGroup && !this.blockRefresh) {
  126387. // We are one side of a lockable grid, so refresh the locking view
  126388. if (ownerCt && ownerCt.lockable) {
  126389. ownerCt.view.refresh();
  126390. } else {
  126391. view.refresh();
  126392. }
  126393. }
  126394. },
  126395. getFeatureTpl: function(values, parent, x, xcount) {
  126396. return [
  126397. '<tpl if="typeof rows !== \'undefined\'">',
  126398. // group row tpl
  126399. '<tr id="{groupHeaderId}" class="' + Ext.baseCSSPrefix + 'grid-group-hd {hdCollapsedCls} {collapsibleClass}"><td class="' + Ext.baseCSSPrefix + 'grid-cell" colspan="' + parent.columns.length + '" {[this.indentByDepth(values)]}><div class="' + Ext.baseCSSPrefix + 'grid-cell-inner"><div class="' + Ext.baseCSSPrefix + 'grid-group-title">{collapsed}{[this.renderGroupHeaderTpl(values, parent)]}</div></div></td></tr>',
  126400. // this is the rowbody
  126401. '<tr id="{groupBodyId}" class="' + Ext.baseCSSPrefix + 'grid-group-body {collapsedCls}"><td colspan="' + parent.columns.length + '">{[this.recurse(values)]}</td></tr>',
  126402. '</tpl>'
  126403. ].join('');
  126404. },
  126405. getFragmentTpl: function() {
  126406. var me = this;
  126407. return {
  126408. indentByDepth: me.indentByDepth,
  126409. depthToIndent: me.depthToIndent,
  126410. renderGroupHeaderTpl: function(values, parent) {
  126411. return Ext.XTemplate.getTpl(me, 'groupHeaderTpl').apply(values, parent);
  126412. }
  126413. };
  126414. },
  126415. indentByDepth: function(values) {
  126416. return 'style="padding-left:'+ ((values.depth || 0) * this.depthToIndent) + 'px;"';
  126417. },
  126418. // Containers holding these components are responsible for
  126419. // destroying them, we are just deleting references.
  126420. destroy: function() {
  126421. delete this.view;
  126422. delete this.prunedHeader;
  126423. },
  126424. // perhaps rename to afterViewRender
  126425. attachEvents: function() {
  126426. var me = this,
  126427. view = me.view;
  126428. view.on({
  126429. scope: me,
  126430. groupclick: me.onGroupClick,
  126431. rowfocus: me.onRowFocus
  126432. });
  126433. view.mon(view.store, {
  126434. scope: me,
  126435. groupchange: me.onGroupChange,
  126436. remove: me.onRemove,
  126437. add: me.onAdd,
  126438. update: me.onUpdate
  126439. });
  126440. if (me.enableGroupingMenu) {
  126441. me.injectGroupingMenu();
  126442. }
  126443. me.pruneGroupedHeader();
  126444. me.lastGroupField = me.getGroupField();
  126445. me.block();
  126446. me.onGroupChange();
  126447. me.unblock();
  126448. },
  126449. // If we add a new item that doesn't belong to a rendered group, refresh the view
  126450. onAdd: function(store, records){
  126451. var me = this,
  126452. view = me.view,
  126453. groupField = me.getGroupField(),
  126454. i = 0,
  126455. len = records.length,
  126456. activeGroups,
  126457. addedGroups,
  126458. groups,
  126459. needsRefresh,
  126460. group;
  126461. if (view.rendered) {
  126462. addedGroups = {};
  126463. activeGroups = {};
  126464. for (; i < len; ++i) {
  126465. group = records[i].get(groupField);
  126466. if (addedGroups[group] === undefined) {
  126467. addedGroups[group] = 0;
  126468. }
  126469. addedGroups[group] += 1;
  126470. }
  126471. groups = store.getGroups();
  126472. for (i = 0, len = groups.length; i < len; ++i) {
  126473. group = groups[i];
  126474. activeGroups[group.name] = group.children.length;
  126475. }
  126476. for (group in addedGroups) {
  126477. if (addedGroups[group] === activeGroups[group]) {
  126478. needsRefresh = true;
  126479. break;
  126480. }
  126481. }
  126482. if (needsRefresh) {
  126483. view.refresh();
  126484. }
  126485. }
  126486. },
  126487. onUpdate: function(store, record, type, changedFields){
  126488. var view = this.view;
  126489. if (view.rendered && !changedFields || Ext.Array.contains(changedFields, this.getGroupField())) {
  126490. view.refresh();
  126491. }
  126492. },
  126493. onRemove: function(store, record) {
  126494. var me = this,
  126495. groupField = me.getGroupField(),
  126496. removedGroup = record.get(groupField),
  126497. view = me.view;
  126498. if (view.rendered) {
  126499. // If that was the last one in the group, force a refresh
  126500. if (store.findExact(groupField, removedGroup) === -1) {
  126501. me.view.refresh();
  126502. }
  126503. }
  126504. },
  126505. injectGroupingMenu: function() {
  126506. var me = this,
  126507. headerCt = me.view.headerCt;
  126508. headerCt.showMenuBy = me.showMenuBy;
  126509. headerCt.getMenuItems = me.getMenuItems();
  126510. },
  126511. showMenuBy: function(t, header) {
  126512. var menu = this.getMenu(),
  126513. groupMenuItem = menu.down('#groupMenuItem'),
  126514. groupableMth = header.groupable === false ? 'disable' : 'enable';
  126515. groupMenuItem[groupableMth]();
  126516. Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
  126517. },
  126518. getMenuItems: function() {
  126519. var me = this,
  126520. groupByText = me.groupByText,
  126521. disabled = me.disabled || !me.getGroupField(),
  126522. showGroupsText = me.showGroupsText,
  126523. enableNoGroups = me.enableNoGroups,
  126524. getMenuItems = me.view.headerCt.getMenuItems;
  126525. // runs in the scope of headerCt
  126526. return function() {
  126527. // We cannot use the method from HeaderContainer's prototype here
  126528. // because other plugins or features may already have injected an implementation
  126529. var o = getMenuItems.call(this);
  126530. o.push('-', {
  126531. iconCls: Ext.baseCSSPrefix + 'group-by-icon',
  126532. itemId: 'groupMenuItem',
  126533. text: groupByText,
  126534. handler: me.onGroupMenuItemClick,
  126535. scope: me
  126536. });
  126537. if (enableNoGroups) {
  126538. o.push({
  126539. itemId: 'groupToggleMenuItem',
  126540. text: showGroupsText,
  126541. checked: !disabled,
  126542. checkHandler: me.onGroupToggleMenuItemClick,
  126543. scope: me
  126544. });
  126545. }
  126546. return o;
  126547. };
  126548. },
  126549. /**
  126550. * Group by the header the user has clicked on.
  126551. * @private
  126552. */
  126553. onGroupMenuItemClick: function(menuItem, e) {
  126554. var me = this,
  126555. menu = menuItem.parentMenu,
  126556. hdr = menu.activeHeader,
  126557. view = me.view,
  126558. store = view.store;
  126559. delete me.lastGroupIndex;
  126560. me.block();
  126561. me.enable();
  126562. store.group(hdr.dataIndex);
  126563. me.pruneGroupedHeader();
  126564. me.unblock();
  126565. me.refreshIf();
  126566. },
  126567. block: function(){
  126568. this.blockRefresh = this.view.blockRefresh = true;
  126569. },
  126570. unblock: function(){
  126571. this.blockRefresh = this.view.blockRefresh = false;
  126572. },
  126573. /**
  126574. * Turn on and off grouping via the menu
  126575. * @private
  126576. */
  126577. onGroupToggleMenuItemClick: function(menuItem, checked) {
  126578. this[checked ? 'enable' : 'disable']();
  126579. },
  126580. /**
  126581. * Prunes the grouped header from the header container
  126582. * @private
  126583. */
  126584. pruneGroupedHeader: function() {
  126585. var me = this,
  126586. header = me.getGroupedHeader();
  126587. if (me.hideGroupedHeader && header) {
  126588. if (me.prunedHeader) {
  126589. me.prunedHeader.show();
  126590. }
  126591. me.prunedHeader = header;
  126592. header.hide();
  126593. }
  126594. },
  126595. getGroupedHeader: function(){
  126596. var groupField = this.getGroupField(),
  126597. headerCt = this.view.headerCt;
  126598. return groupField ? headerCt.down('[dataIndex=' + groupField + ']') : null;
  126599. },
  126600. getGroupField: function(){
  126601. var group = this.view.store.groupers.first();
  126602. if (group) {
  126603. return group.property;
  126604. }
  126605. return '';
  126606. },
  126607. /**
  126608. * When a row gains focus, expand the groups above it
  126609. * @private
  126610. */
  126611. onRowFocus: function(rowIdx) {
  126612. var node = this.view.getNode(rowIdx),
  126613. groupBd = Ext.fly(node).up('.' + this.collapsedCls);
  126614. if (groupBd) {
  126615. // for multiple level groups, should expand every groupBd
  126616. // above
  126617. this.expand(groupBd);
  126618. }
  126619. },
  126620. /**
  126621. * Returns `true` if the named group is expanded.
  126622. * @param {String} groupName The group name as returned from {@link Ext.data.Store#getGroupString getGroupString}. This is usually the value of
  126623. * the {@link Ext.data.Store#groupField groupField}.
  126624. * @return {Boolean} `true` if the group defined by that value is expanded.
  126625. */
  126626. isExpanded: function(groupName) {
  126627. return (this.collapsedState[groupName] === false);
  126628. },
  126629. /**
  126630. * Expand a group
  126631. * @param {String/Ext.Element} groupName The group name, or the element that contains the group body
  126632. * @param {Boolean} focus Pass `true` to focus the group after expand.
  126633. */
  126634. expand: function(groupName, focus, /*private*/ preventSizeCalculation) {
  126635. var me = this,
  126636. view = me.view,
  126637. groupHeader,
  126638. groupBody,
  126639. lockingPartner = me.lockingPartner;
  126640. // We've been passed the group name
  126641. if (Ext.isString(groupName)) {
  126642. groupBody = Ext.fly(me.getGroupBodyId(groupName), '_grouping');
  126643. }
  126644. // We've been passed an element
  126645. else {
  126646. groupBody = Ext.fly(groupName, '_grouping')
  126647. groupName = me.getGroupName(groupBody);
  126648. }
  126649. groupHeader = Ext.get(me.getGroupHeaderId(groupName));
  126650. // If we are collapsed...
  126651. if (me.collapsedState[groupName]) {
  126652. groupBody.removeCls(me.collapsedCls);
  126653. groupBody.prev().removeCls(me.hdCollapsedCls);
  126654. if (preventSizeCalculation !== true) {
  126655. view.refreshSize();
  126656. }
  126657. view.fireEvent('groupexpand', view, groupHeader, groupName);
  126658. me.collapsedState[groupName] = false;
  126659. // If we are one side of a locking view, the other side has to stay in sync
  126660. if (lockingPartner) {
  126661. lockingPartner.expand(groupName, focus, preventSizeCalculation);
  126662. }
  126663. if (focus) {
  126664. groupBody.scrollIntoView(view.el, null, true);
  126665. }
  126666. }
  126667. },
  126668. /**
  126669. * Expand all groups
  126670. */
  126671. expandAll: function(){
  126672. var me = this,
  126673. view = me.view,
  126674. els = view.el.select(me.eventSelector).elements,
  126675. e,
  126676. eLen = els.length;
  126677. for (e = 0; e < eLen; e++) {
  126678. me.expand(Ext.fly(els[e]).next(), false, true);
  126679. }
  126680. view.refreshSize();
  126681. },
  126682. /**
  126683. * Collapse a group
  126684. * @param {String/Ext.Element} groupName The group name, or the element that contains group body
  126685. * @param {Boolean} focus Pass `true` to focus the group after expand.
  126686. */
  126687. collapse: function(groupName, focus, /*private*/ preventSizeCalculation) {
  126688. var me = this,
  126689. view = me.view,
  126690. groupHeader,
  126691. groupBody,
  126692. lockingPartner = me.lockingPartner;
  126693. // We've been passed the group name
  126694. if (Ext.isString(groupName)) {
  126695. groupBody = Ext.fly(me.getGroupBodyId(groupName), '_grouping');
  126696. }
  126697. // We've been passed an element
  126698. else {
  126699. groupBody = Ext.fly(groupName, '_grouping')
  126700. groupName = me.getGroupName(groupBody);
  126701. }
  126702. groupHeader = Ext.get(me.getGroupHeaderId(groupName));
  126703. // If we are not collapsed...
  126704. if (!me.collapsedState[groupName]) {
  126705. groupBody.addCls(me.collapsedCls);
  126706. groupBody.prev().addCls(me.hdCollapsedCls);
  126707. if (preventSizeCalculation !== true) {
  126708. view.refreshSize();
  126709. }
  126710. view.fireEvent('groupcollapse', view, groupHeader, groupName);
  126711. me.collapsedState[groupName] = true;
  126712. // If we are one side of a locking view, the other side has to stay in sync
  126713. if (lockingPartner) {
  126714. lockingPartner.collapse(groupName, focus, preventSizeCalculation);
  126715. }
  126716. if (focus) {
  126717. groupHeader.scrollIntoView(view.el, null, true);
  126718. }
  126719. }
  126720. },
  126721. /**
  126722. * Collapse all groups
  126723. */
  126724. collapseAll: function() {
  126725. var me = this,
  126726. view = me.view,
  126727. els = view.el.select(me.eventSelector).elements,
  126728. e,
  126729. eLen = els.length;
  126730. for (e = 0; e < eLen; e++) {
  126731. me.collapse(Ext.fly(els[e]).next(), false, true);
  126732. }
  126733. view.refreshSize();
  126734. },
  126735. onGroupChange: function(){
  126736. var me = this,
  126737. field = me.getGroupField(),
  126738. menuItem,
  126739. visibleGridColumns,
  126740. groupingByLastVisibleColumn;
  126741. if (me.hideGroupedHeader) {
  126742. if (me.lastGroupField) {
  126743. menuItem = me.getMenuItem(me.lastGroupField);
  126744. if (menuItem) {
  126745. menuItem.setChecked(true);
  126746. }
  126747. }
  126748. if (field) {
  126749. visibleGridColumns = me.view.headerCt.getVisibleGridColumns();
  126750. // See if we are being asked to group by the sole remaining visible column.
  126751. // If so, then do not hide that column.
  126752. groupingByLastVisibleColumn = ((visibleGridColumns.length === 1) && (visibleGridColumns[0].dataIndex == field));
  126753. menuItem = me.getMenuItem(field);
  126754. if (menuItem && !groupingByLastVisibleColumn) {
  126755. menuItem.setChecked(false);
  126756. }
  126757. }
  126758. }
  126759. me.refreshIf();
  126760. me.lastGroupField = field;
  126761. },
  126762. /**
  126763. * Gets the related menu item for a dataIndex
  126764. * @private
  126765. * @return {Ext.grid.header.Container} The header
  126766. */
  126767. getMenuItem: function(dataIndex){
  126768. var view = this.view,
  126769. header = view.headerCt.down('gridcolumn[dataIndex=' + dataIndex + ']'),
  126770. menu = view.headerCt.getMenu();
  126771. return header ? menu.down('menuitem[headerId='+ header.id +']') : null;
  126772. },
  126773. /**
  126774. * Toggle between expanded/collapsed state when clicking on
  126775. * the group.
  126776. * @private
  126777. */
  126778. onGroupClick: function(view, rowElement, groupName, e) {
  126779. var me = this;
  126780. if (me.collapsible) {
  126781. if (me.collapsedState[groupName]) {
  126782. me.expand(groupName);
  126783. } else {
  126784. me.collapse(groupName);
  126785. }
  126786. }
  126787. },
  126788. // Injects isRow and closeRow into the metaRowTpl.
  126789. getMetaRowTplFragments: function() {
  126790. return {
  126791. isRow: this.isRow,
  126792. closeRow: this.closeRow
  126793. };
  126794. },
  126795. // injected into rowtpl and wrapped around metaRowTpl
  126796. // becomes part of the standard tpl
  126797. isRow: function() {
  126798. return '<tpl if="typeof rows === \'undefined\'">';
  126799. },
  126800. // injected into rowtpl and wrapped around metaRowTpl
  126801. // becomes part of the standard tpl
  126802. closeRow: function() {
  126803. return '</tpl>';
  126804. },
  126805. // isRow and closeRow are injected via getMetaRowTplFragments
  126806. mutateMetaRowTpl: function(metaRowTpl) {
  126807. metaRowTpl.unshift('{[this.isRow()]}');
  126808. metaRowTpl.push('{[this.closeRow()]}');
  126809. },
  126810. // injects an additional style attribute via tdAttrKey with the proper
  126811. // amount of padding
  126812. getAdditionalData: function(data, idx, record, orig) {
  126813. var view = this.view,
  126814. hCt = view.headerCt,
  126815. col = hCt.items.getAt(0),
  126816. o = {},
  126817. tdAttrKey;
  126818. // If there *are* any columne in this grid (possible empty side of a locking grid)...
  126819. // Add the padding-left style to indent the row according to grouping depth.
  126820. // Preserve any current tdAttr that a user may have set.
  126821. if (col) {
  126822. tdAttrKey = col.id + '-tdAttr';
  126823. o[tdAttrKey] = this.indentByDepth(data) + " " + (orig[tdAttrKey] ? orig[tdAttrKey] : '');
  126824. o.collapsed = 'true';
  126825. o.data = record.getData();
  126826. }
  126827. return o;
  126828. },
  126829. // return matching preppedRecords
  126830. getGroupRows: function(group, records, preppedRecords, fullWidth) {
  126831. var me = this,
  126832. children = group.children,
  126833. rows = group.rows = [],
  126834. view = me.view,
  126835. header = me.getGroupedHeader(),
  126836. groupField = me.getGroupField(),
  126837. index = -1,
  126838. r,
  126839. rLen = records.length,
  126840. record;
  126841. // Buffered rendering implies that user collapsing is disabled.
  126842. if (view.store.buffered) {
  126843. me.collapsible = false;
  126844. }
  126845. group.viewId = view.id;
  126846. for (r = 0; r < rLen; r++) {
  126847. record = records[r];
  126848. if (record.get(groupField) == group.name) {
  126849. index = r;
  126850. }
  126851. if (Ext.Array.indexOf(children, record) != -1) {
  126852. rows.push(Ext.apply(preppedRecords[r], {
  126853. depth : 1
  126854. }));
  126855. }
  126856. }
  126857. group.groupField = groupField,
  126858. group.groupHeaderId = me.getGroupHeaderId(group.name);
  126859. group.groupBodyId = me.getGroupBodyId(group.name);
  126860. group.fullWidth = fullWidth;
  126861. group.columnName = header ? header.text : groupField;
  126862. group.groupValue = group.name;
  126863. // Here we attempt to overwrite the group name value from the Store with
  126864. // the get the rendered value of the column from the *prepped* record
  126865. if (header && index > -1) {
  126866. group.name = group.renderedValue = preppedRecords[index][header.id];
  126867. }
  126868. if (me.collapsedState[group.name]) {
  126869. group.collapsedCls = me.collapsedCls;
  126870. group.hdCollapsedCls = me.hdCollapsedCls;
  126871. } else {
  126872. group.collapsedCls = group.hdCollapsedCls = '';
  126873. }
  126874. // Collapsibility of groups may be disabled.
  126875. if (me.collapsible) {
  126876. group.collapsibleClass = me.hdCollapsibleCls;
  126877. } else {
  126878. group.collapsibleClass = '';
  126879. }
  126880. return group;
  126881. },
  126882. // Create an associated DOM id for the group's header element given the group name
  126883. getGroupHeaderId: function(groupName) {
  126884. return this.view.id + '-hd-' + groupName;
  126885. },
  126886. // Create an associated DOM id for the group's body element given the group name
  126887. getGroupBodyId: function(groupName) {
  126888. return this.view.id + '-bd-' + groupName;
  126889. },
  126890. // Get the group name from an associated element whether it's within a header or a body
  126891. getGroupName: function(element) {
  126892. var me = this,
  126893. targetEl;
  126894. // See if element is, or is within a group header. If so, we can extract its name
  126895. targetEl = Ext.fly(element).findParent(me.eventSelector);
  126896. if (targetEl) {
  126897. return targetEl.id.split(this.view.id + '-hd-')[1];
  126898. }
  126899. // See if element is, or is within a group body. If so, we can extract its name
  126900. targetEl = Ext.fly(element).findParent(me.bodySelector);
  126901. if (targetEl) {
  126902. return targetEl.id.split(this.view.id + '-bd-')[1];
  126903. }
  126904. },
  126905. // return the data in a grouped format.
  126906. collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
  126907. var me = this,
  126908. store = me.view.store,
  126909. collapsedState = me.collapsedState,
  126910. collapseGroups,
  126911. g,
  126912. groups, gLen, group;
  126913. if (me.startCollapsed) {
  126914. // If we start collapse, we'll set the state of the groups here
  126915. // and unset the flag so any subsequent expand/collapse is
  126916. // managed by the feature
  126917. me.startCollapsed = false;
  126918. collapseGroups = true;
  126919. }
  126920. if (!me.disabled && store.isGrouped()) {
  126921. o.rows = groups = store.getGroups();
  126922. gLen = groups.length;
  126923. for (g = 0; g < gLen; g++) {
  126924. group = groups[g];
  126925. if (collapseGroups) {
  126926. collapsedState[group.name] = true;
  126927. }
  126928. me.getGroupRows(group, records, preppedRecords, fullWidth);
  126929. }
  126930. }
  126931. return o;
  126932. },
  126933. // adds the groupName to the groupclick, groupdblclick, groupcontextmenu
  126934. // events that are fired on the view. Chose not to return the actual
  126935. // group itself because of its expense and because developers can simply
  126936. // grab the group via store.getGroups(groupName)
  126937. getFireEventArgs: function(type, view, targetEl, e) {
  126938. return [type, view, targetEl, this.getGroupName(targetEl), e];
  126939. }
  126940. });
  126941. /**
  126942. * This feature adds an aggregate summary row at the bottom of each group that is provided
  126943. * by the {@link Ext.grid.feature.Grouping} feature. There are two aspects to the summary:
  126944. *
  126945. * ## Calculation
  126946. *
  126947. * The summary value needs to be calculated for each column in the grid. This is controlled
  126948. * by the summaryType option specified on the column. There are several built in summary types,
  126949. * which can be specified as a string on the column configuration. These call underlying methods
  126950. * on the store:
  126951. *
  126952. * - {@link Ext.data.Store#count count}
  126953. * - {@link Ext.data.Store#sum sum}
  126954. * - {@link Ext.data.Store#min min}
  126955. * - {@link Ext.data.Store#max max}
  126956. * - {@link Ext.data.Store#average average}
  126957. *
  126958. * Alternatively, the summaryType can be a function definition. If this is the case,
  126959. * the function is called with an array of records to calculate the summary value.
  126960. *
  126961. * ## Rendering
  126962. *
  126963. * Similar to a column, the summary also supports a summaryRenderer function. This
  126964. * summaryRenderer is called before displaying a value. The function is optional, if
  126965. * not specified the default calculated value is shown. The summaryRenderer is called with:
  126966. *
  126967. * - value {Object} - The calculated value.
  126968. * - summaryData {Object} - Contains all raw summary values for the row.
  126969. * - field {String} - The name of the field we are calculating
  126970. *
  126971. * ## Example Usage
  126972. *
  126973. * @example
  126974. * Ext.define('TestResult', {
  126975. * extend: 'Ext.data.Model',
  126976. * fields: ['student', 'subject', {
  126977. * name: 'mark',
  126978. * type: 'int'
  126979. * }]
  126980. * });
  126981. *
  126982. * Ext.create('Ext.grid.Panel', {
  126983. * width: 200,
  126984. * height: 240,
  126985. * renderTo: document.body,
  126986. * features: [{
  126987. * groupHeaderTpl: 'Subject: {name}',
  126988. * ftype: 'groupingsummary'
  126989. * }],
  126990. * store: {
  126991. * model: 'TestResult',
  126992. * groupField: 'subject',
  126993. * data: [{
  126994. * student: 'Student 1',
  126995. * subject: 'Math',
  126996. * mark: 84
  126997. * },{
  126998. * student: 'Student 1',
  126999. * subject: 'Science',
  127000. * mark: 72
  127001. * },{
  127002. * student: 'Student 2',
  127003. * subject: 'Math',
  127004. * mark: 96
  127005. * },{
  127006. * student: 'Student 2',
  127007. * subject: 'Science',
  127008. * mark: 68
  127009. * }]
  127010. * },
  127011. * columns: [{
  127012. * dataIndex: 'student',
  127013. * text: 'Name',
  127014. * summaryType: 'count',
  127015. * summaryRenderer: function(value){
  127016. * return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : '');
  127017. * }
  127018. * }, {
  127019. * dataIndex: 'mark',
  127020. * text: 'Mark',
  127021. * summaryType: 'average'
  127022. * }]
  127023. * });
  127024. */
  127025. Ext.define('Ext.grid.feature.GroupingSummary', {
  127026. /* Begin Definitions */
  127027. extend: 'Ext.grid.feature.Grouping',
  127028. alias: 'feature.groupingsummary',
  127029. mixins: {
  127030. summary: 'Ext.grid.feature.AbstractSummary'
  127031. },
  127032. /* End Definitions */
  127033. init: function() {
  127034. this.mixins.summary.init.call(this);
  127035. },
  127036. /**
  127037. * Modifies the row template to include the summary row.
  127038. * @private
  127039. * @return {String} The modified template
  127040. */
  127041. getFeatureTpl: function() {
  127042. var tpl = this.callParent(arguments);
  127043. if (this.showSummaryRow) {
  127044. // lop off the end </tpl> so we can attach it
  127045. tpl = tpl.replace('</tpl>', '');
  127046. tpl += '{[this.printSummaryRow(xindex)]}</tpl>';
  127047. }
  127048. return tpl;
  127049. },
  127050. /**
  127051. * Gets any fragments needed for the template.
  127052. * @private
  127053. * @return {Object} The fragments
  127054. */
  127055. getFragmentTpl: function() {
  127056. var me = this,
  127057. fragments = me.callParent();
  127058. Ext.apply(fragments, me.getSummaryFragments());
  127059. if (me.showSummaryRow) {
  127060. // this gets called before render, so we'll setup the data here.
  127061. me.summaryGroups = me.view.store.getGroups();
  127062. me.summaryData = me.generateSummaryData();
  127063. }
  127064. return fragments;
  127065. },
  127066. /**
  127067. * Gets the data for printing a template row
  127068. * @private
  127069. * @param {Number} index The index in the template
  127070. * @return {Array} The template values
  127071. */
  127072. getPrintData: function(index){
  127073. var me = this,
  127074. columns = me.view.headerCt.getColumnsForTpl(),
  127075. i = 0,
  127076. length = columns.length,
  127077. data = [],
  127078. name = me.summaryGroups[index - 1].name,
  127079. active = me.summaryData[name],
  127080. column;
  127081. for (; i < length; ++i) {
  127082. column = columns[i];
  127083. column.gridSummaryValue = this.getColumnValue(column, active);
  127084. data.push(column);
  127085. }
  127086. return data;
  127087. },
  127088. /**
  127089. * Generates all of the summary data to be used when processing the template
  127090. * @private
  127091. * @return {Object} The summary data
  127092. */
  127093. generateSummaryData: function(){
  127094. var me = this,
  127095. data = {},
  127096. remoteData = {},
  127097. store = me.view.store,
  127098. groupField = this.getGroupField(),
  127099. reader = store.proxy.reader,
  127100. groups = me.summaryGroups,
  127101. columns = me.view.headerCt.getColumnsForTpl(),
  127102. remote,
  127103. i,
  127104. length,
  127105. fieldData,
  127106. root,
  127107. key,
  127108. comp,
  127109. summaryRows,
  127110. s,
  127111. sLen,
  127112. convertedSummaryRow;
  127113. for (i = 0, length = groups.length; i < length; ++i) {
  127114. data[groups[i].name] = {};
  127115. }
  127116. /**
  127117. * @cfg {String} [remoteRoot=undefined]
  127118. * The name of the property which contains the Array of summary objects.
  127119. * It allows to use server-side calculated summaries.
  127120. */
  127121. if (me.remoteRoot && reader.rawData) {
  127122. // reset reader root and rebuild extractors to extract summaries data
  127123. root = reader.root;
  127124. reader.root = me.remoteRoot;
  127125. reader.buildExtractors(true);
  127126. summaryRows = reader.getRoot(reader.rawData);
  127127. sLen = summaryRows.length;
  127128. // Ensure the Reader has a data conversion function to convert a raw data row into a Record data hash
  127129. if (!reader.convertRecordData) {
  127130. reader.buildExtractors();
  127131. }
  127132. for (s = 0; s < sLen; s++) {
  127133. convertedSummaryRow = {};
  127134. // Convert a raw data row into a Record's hash object using the Reader
  127135. reader.convertRecordData(convertedSummaryRow, summaryRows[s]);
  127136. remoteData[convertedSummaryRow[groupField]] = convertedSummaryRow;
  127137. }
  127138. // restore initial reader configuration
  127139. reader.root = root;
  127140. reader.buildExtractors(true);
  127141. }
  127142. for (i = 0, length = columns.length; i < length; ++i) {
  127143. comp = Ext.getCmp(columns[i].id);
  127144. fieldData = me.getSummary(store, comp.summaryType, comp.dataIndex, true);
  127145. for (key in fieldData) {
  127146. if (fieldData.hasOwnProperty(key)) {
  127147. data[key][comp.id] = fieldData[key];
  127148. }
  127149. }
  127150. for (key in remoteData) {
  127151. if (remoteData.hasOwnProperty(key)) {
  127152. remote = remoteData[key][comp.dataIndex];
  127153. if (remote !== undefined && data[key] !== undefined) {
  127154. data[key][comp.id] = remote;
  127155. }
  127156. }
  127157. }
  127158. }
  127159. return data;
  127160. }
  127161. });
  127162. /**
  127163. * The rowbody feature enhances the grid's markup to have an additional
  127164. * tr -> td -> div which spans the entire width of the original row.
  127165. *
  127166. * This is useful to to associate additional information with a particular
  127167. * record in a grid.
  127168. *
  127169. * Rowbodies are initially hidden unless you override getAdditionalData.
  127170. *
  127171. * Will expose additional events on the gridview with the prefix of 'rowbody'.
  127172. * For example: 'rowbodyclick', 'rowbodydblclick', 'rowbodycontextmenu'.
  127173. *
  127174. * # Example
  127175. *
  127176. * @example
  127177. * Ext.define('Animal', {
  127178. * extend: 'Ext.data.Model',
  127179. * fields: ['name', 'latin', 'desc']
  127180. * });
  127181. *
  127182. * Ext.create('Ext.grid.Panel', {
  127183. * width: 400,
  127184. * height: 300,
  127185. * renderTo: Ext.getBody(),
  127186. * store: {
  127187. * model: 'Animal',
  127188. * data: [
  127189. * {name: 'Tiger', latin: 'Panthera tigris',
  127190. * desc: 'The largest cat species, weighing up to 306 kg (670 lb).'},
  127191. * {name: 'Roman snail', latin: 'Helix pomatia',
  127192. * desc: 'A species of large, edible, air-breathing land snail.'},
  127193. * {name: 'Yellow-winged darter', latin: 'Sympetrum flaveolum',
  127194. * desc: 'A dragonfly found in Europe and mid and Northern China.'},
  127195. * {name: 'Superb Fairy-wren', latin: 'Malurus cyaneus',
  127196. * desc: 'Common and familiar across south-eastern Australia.'}
  127197. * ]
  127198. * },
  127199. * columns: [{
  127200. * dataIndex: 'name',
  127201. * text: 'Common name',
  127202. * width: 125
  127203. * }, {
  127204. * dataIndex: 'latin',
  127205. * text: 'Scientific name',
  127206. * flex: 1
  127207. * }],
  127208. * features: [{
  127209. * ftype: 'rowbody',
  127210. * getAdditionalData: function(data, rowIndex, record, orig) {
  127211. * var headerCt = this.view.headerCt,
  127212. * colspan = headerCt.getColumnCount();
  127213. * // Usually you would style the my-body-class in CSS file
  127214. * return {
  127215. * rowBody: '<div style="padding: 1em">'+record.get("desc")+'</div>',
  127216. * rowBodyCls: "my-body-class",
  127217. * rowBodyColspan: colspan
  127218. * };
  127219. * }
  127220. * }]
  127221. * });
  127222. */
  127223. Ext.define('Ext.grid.feature.RowBody', {
  127224. extend: 'Ext.grid.feature.Feature',
  127225. alias: 'feature.rowbody',
  127226. rowBodyHiddenCls: Ext.baseCSSPrefix + 'grid-row-body-hidden',
  127227. rowBodyTrCls: Ext.baseCSSPrefix + 'grid-rowbody-tr',
  127228. rowBodyTdCls: Ext.baseCSSPrefix + 'grid-cell-rowbody',
  127229. rowBodyDivCls: Ext.baseCSSPrefix + 'grid-rowbody',
  127230. eventPrefix: 'rowbody',
  127231. eventSelector: '.' + Ext.baseCSSPrefix + 'grid-rowbody-tr',
  127232. getRowBody: function(values) {
  127233. return [
  127234. '<tr class="' + this.rowBodyTrCls + ' {rowBodyCls}">',
  127235. '<td class="' + this.rowBodyTdCls + '" colspan="{rowBodyColspan}">',
  127236. '<div class="' + this.rowBodyDivCls + '">{rowBody}</div>',
  127237. '</td>',
  127238. '</tr>'
  127239. ].join('');
  127240. },
  127241. // injects getRowBody into the metaRowTpl.
  127242. getMetaRowTplFragments: function() {
  127243. return {
  127244. getRowBody: this.getRowBody,
  127245. rowBodyTrCls: this.rowBodyTrCls,
  127246. rowBodyTdCls: this.rowBodyTdCls,
  127247. rowBodyDivCls: this.rowBodyDivCls
  127248. };
  127249. },
  127250. mutateMetaRowTpl: function(metaRowTpl) {
  127251. metaRowTpl.push('{[this.getRowBody(values)]}');
  127252. },
  127253. /**
  127254. * Provides additional data to the prepareData call within the grid view.
  127255. * The rowbody feature adds 3 additional variables into the grid view's template.
  127256. * These are rowBodyCls, rowBodyColspan, and rowBody.
  127257. * @param {Object} data The data for this particular record.
  127258. * @param {Number} idx The row index for this record.
  127259. * @param {Ext.data.Model} record The record instance
  127260. * @param {Object} orig The original result from the prepareData call to massage.
  127261. */
  127262. getAdditionalData: function(data, idx, record, orig) {
  127263. var headerCt = this.view.headerCt,
  127264. colspan = headerCt.getColumnCount();
  127265. return {
  127266. rowBody: "",
  127267. rowBodyCls: this.rowBodyCls,
  127268. rowBodyColspan: colspan
  127269. };
  127270. }
  127271. });
  127272. /**
  127273. * @private
  127274. */
  127275. Ext.define('Ext.grid.feature.RowWrap', {
  127276. extend: 'Ext.grid.feature.Feature',
  127277. alias: 'feature.rowwrap',
  127278. // turn off feature events.
  127279. hasFeatureEvent: false,
  127280. init: function() {
  127281. if (!this.disabled) {
  127282. this.enable();
  127283. }
  127284. },
  127285. getRowSelector: function(){
  127286. return 'tr:has(> ' + this.view.cellSelector + ')';
  127287. },
  127288. enable: function(){
  127289. var me = this,
  127290. view = me.view;
  127291. me.callParent();
  127292. // we need to mutate the rowSelector since the template changes the ordering
  127293. me.savedRowSelector = view.rowSelector;
  127294. view.rowSelector = me.getRowSelector();
  127295. // Extra functionality needed on header resize when row is wrapped:
  127296. // Every individual cell in a column needs its width syncing.
  127297. // So we produce a different column selector which includes al TDs in a column
  127298. view.getComponentLayout().getColumnSelector = me.getColumnSelector;
  127299. },
  127300. disable: function(){
  127301. var me = this,
  127302. view = me.view,
  127303. saved = me.savedRowSelector;
  127304. me.callParent();
  127305. if (saved) {
  127306. view.rowSelector = saved;
  127307. }
  127308. delete me.savedRowSelector;
  127309. },
  127310. mutateMetaRowTpl: function(metaRowTpl) {
  127311. var prefix = Ext.baseCSSPrefix;
  127312. // Remove "x-grid-row" from the first row, note this could be wrong
  127313. // if some other feature unshifted things in front.
  127314. metaRowTpl[0] = metaRowTpl[0].replace(prefix + 'grid-row', '');
  127315. metaRowTpl[0] = metaRowTpl[0].replace("{[this.embedRowCls()]}", "");
  127316. // 2
  127317. metaRowTpl.unshift('<table class="' + prefix + 'grid-table ' + prefix + 'grid-table-resizer" style="width: {[this.embedFullWidth()]}px;">');
  127318. // 1
  127319. metaRowTpl.unshift('<tr class="' + prefix + 'grid-row {[this.embedRowCls()]}"><td colspan="{[this.embedColSpan()]}"><div class="' + prefix + 'grid-rowwrap-div">');
  127320. // 3
  127321. metaRowTpl.push('</table>');
  127322. // 4
  127323. metaRowTpl.push('</div></td></tr>');
  127324. },
  127325. embedColSpan: function() {
  127326. return '{colspan}';
  127327. },
  127328. embedFullWidth: function() {
  127329. return '{fullWidth}';
  127330. },
  127331. getAdditionalData: function(data, idx, record, orig) {
  127332. var headerCt = this.view.headerCt,
  127333. colspan = headerCt.getColumnCount(),
  127334. fullWidth = headerCt.getFullWidth(),
  127335. items = headerCt.query('gridcolumn'),
  127336. itemsLn = items.length,
  127337. i = 0,
  127338. o = {
  127339. colspan: colspan,
  127340. fullWidth: fullWidth
  127341. },
  127342. id,
  127343. tdClsKey,
  127344. colResizerCls;
  127345. for (; i < itemsLn; i++) {
  127346. id = items[i].id;
  127347. tdClsKey = id + '-tdCls';
  127348. colResizerCls = Ext.baseCSSPrefix + 'grid-col-resizer-'+id;
  127349. // give the inner td's the resizer class
  127350. // while maintaining anything a user may have injected via a custom
  127351. // renderer
  127352. o[tdClsKey] = colResizerCls + " " + (orig[tdClsKey] ? orig[tdClsKey] : '');
  127353. // TODO: Unhackify the initial rendering width's
  127354. o[id+'-tdAttr'] = " style=\"width: " + (items[i].hidden ? 0 : items[i].getDesiredWidth()) + "px;\" "/* + (i === 0 ? " rowspan=\"2\"" : "")*/;
  127355. if (orig[id+'-tdAttr']) {
  127356. o[id+'-tdAttr'] += orig[id+'-tdAttr'];
  127357. }
  127358. }
  127359. return o;
  127360. },
  127361. getMetaRowTplFragments: function() {
  127362. return {
  127363. embedFullWidth: this.embedFullWidth,
  127364. embedColSpan: this.embedColSpan
  127365. };
  127366. },
  127367. getColumnSelector: function(header) {
  127368. var s = Ext.baseCSSPrefix + 'grid-col-resizer-' + header.id;
  127369. return 'th.' + s + ',td.' + s;
  127370. }
  127371. });
  127372. /**
  127373. * This feature is used to place a summary row at the bottom of the grid. If using a grouping,
  127374. * see {@link Ext.grid.feature.GroupingSummary}. There are 2 aspects to calculating the summaries,
  127375. * calculation and rendering.
  127376. *
  127377. * ## Calculation
  127378. * The summary value needs to be calculated for each column in the grid. This is controlled
  127379. * by the summaryType option specified on the column. There are several built in summary types,
  127380. * which can be specified as a string on the column configuration. These call underlying methods
  127381. * on the store:
  127382. *
  127383. * - {@link Ext.data.Store#count count}
  127384. * - {@link Ext.data.Store#sum sum}
  127385. * - {@link Ext.data.Store#min min}
  127386. * - {@link Ext.data.Store#max max}
  127387. * - {@link Ext.data.Store#average average}
  127388. *
  127389. * Alternatively, the summaryType can be a function definition. If this is the case,
  127390. * the function is called with an array of records to calculate the summary value.
  127391. *
  127392. * ## Rendering
  127393. * Similar to a column, the summary also supports a summaryRenderer function. This
  127394. * summaryRenderer is called before displaying a value. The function is optional, if
  127395. * not specified the default calculated value is shown. The summaryRenderer is called with:
  127396. *
  127397. * - value {Object} - The calculated value.
  127398. * - summaryData {Object} - Contains all raw summary values for the row.
  127399. * - field {String} - The name of the field we are calculating
  127400. *
  127401. * ## Example Usage
  127402. *
  127403. * @example
  127404. * Ext.define('TestResult', {
  127405. * extend: 'Ext.data.Model',
  127406. * fields: ['student', {
  127407. * name: 'mark',
  127408. * type: 'int'
  127409. * }]
  127410. * });
  127411. *
  127412. * Ext.create('Ext.grid.Panel', {
  127413. * width: 200,
  127414. * height: 140,
  127415. * renderTo: document.body,
  127416. * features: [{
  127417. * ftype: 'summary'
  127418. * }],
  127419. * store: {
  127420. * model: 'TestResult',
  127421. * data: [{
  127422. * student: 'Student 1',
  127423. * mark: 84
  127424. * },{
  127425. * student: 'Student 2',
  127426. * mark: 72
  127427. * },{
  127428. * student: 'Student 3',
  127429. * mark: 96
  127430. * },{
  127431. * student: 'Student 4',
  127432. * mark: 68
  127433. * }]
  127434. * },
  127435. * columns: [{
  127436. * dataIndex: 'student',
  127437. * text: 'Name',
  127438. * summaryType: 'count',
  127439. * summaryRenderer: function(value, summaryData, dataIndex) {
  127440. * return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : '');
  127441. * }
  127442. * }, {
  127443. * dataIndex: 'mark',
  127444. * text: 'Mark',
  127445. * summaryType: 'average'
  127446. * }]
  127447. * });
  127448. */
  127449. Ext.define('Ext.grid.feature.Summary', {
  127450. /* Begin Definitions */
  127451. extend: 'Ext.grid.feature.AbstractSummary',
  127452. alias: 'feature.summary',
  127453. /* End Definitions */
  127454. /**
  127455. * Gets any fragments needed for the template.
  127456. * @private
  127457. * @return {Object} The fragments
  127458. */
  127459. getFragmentTpl: function() {
  127460. // this gets called before render, so we'll setup the data here.
  127461. this.summaryData = this.generateSummaryData();
  127462. return this.getSummaryFragments();
  127463. },
  127464. /**
  127465. * Overrides the closeRows method on the template so we can include our own custom
  127466. * footer.
  127467. * @private
  127468. * @return {Object} The custom fragments
  127469. */
  127470. getTableFragments: function(){
  127471. if (this.showSummaryRow) {
  127472. return {
  127473. closeRows: this.closeRows
  127474. };
  127475. }
  127476. },
  127477. /**
  127478. * Provide our own custom footer for the grid.
  127479. * @private
  127480. * @return {String} The custom footer
  127481. */
  127482. closeRows: function() {
  127483. return '</tpl>{[this.printSummaryRow()]}';
  127484. },
  127485. /**
  127486. * Gets the data for printing a template row
  127487. * @private
  127488. * @param {Number} index The index in the template
  127489. * @return {Array} The template values
  127490. */
  127491. getPrintData: function(index){
  127492. var me = this,
  127493. columns = me.view.headerCt.getColumnsForTpl(),
  127494. i = 0,
  127495. length = columns.length,
  127496. data = [],
  127497. active = me.summaryData,
  127498. column;
  127499. for (; i < length; ++i) {
  127500. column = columns[i];
  127501. column.gridSummaryValue = this.getColumnValue(column, active);
  127502. data.push(column);
  127503. }
  127504. return data;
  127505. },
  127506. /**
  127507. * Generates all of the summary data to be used when processing the template
  127508. * @private
  127509. * @return {Object} The summary data
  127510. */
  127511. generateSummaryData: function(){
  127512. var me = this,
  127513. data = {},
  127514. store = me.view.store,
  127515. columns = me.view.headerCt.getColumnsForTpl(),
  127516. i = 0,
  127517. length = columns.length,
  127518. fieldData,
  127519. key,
  127520. comp;
  127521. for (i = 0, length = columns.length; i < length; ++i) {
  127522. comp = Ext.getCmp(columns[i].id);
  127523. data[comp.id] = me.getSummary(store, comp.summaryType, comp.dataIndex, false);
  127524. }
  127525. return data;
  127526. }
  127527. });
  127528. /**
  127529. * This class provides an abstract grid editing plugin on selected {@link Ext.grid.column.Column columns}.
  127530. * The editable columns are specified by providing an {@link Ext.grid.column.Column#editor editor}
  127531. * in the {@link Ext.grid.column.Column column configuration}.
  127532. *
  127533. * **Note:** This class should not be used directly. See {@link Ext.grid.plugin.CellEditing} and
  127534. * {@link Ext.grid.plugin.RowEditing}.
  127535. */
  127536. Ext.define('Ext.grid.plugin.Editing', {
  127537. alias: 'editing.editing',
  127538. extend: 'Ext.AbstractPlugin',
  127539. requires: [
  127540. 'Ext.grid.column.Column',
  127541. 'Ext.util.KeyNav'
  127542. ],
  127543. mixins: {
  127544. observable: 'Ext.util.Observable'
  127545. },
  127546. /**
  127547. * @cfg {Number} clicksToEdit
  127548. * The number of clicks on a grid required to display the editor.
  127549. * The only accepted values are **1** and **2**.
  127550. */
  127551. clicksToEdit: 2,
  127552. /**
  127553. * @cfg {String} triggerEvent
  127554. * The event which triggers editing. Supercedes the {@link #clicksToEdit} configuration. Maybe one of:
  127555. *
  127556. * * cellclick
  127557. * * celldblclick
  127558. * * cellfocus
  127559. * * rowfocus
  127560. */
  127561. triggerEvent: undefined,
  127562. // private
  127563. defaultFieldXType: 'textfield',
  127564. // cell, row, form
  127565. editStyle: '',
  127566. constructor: function(config) {
  127567. var me = this;
  127568. me.addEvents(
  127569. /**
  127570. * @event beforeedit
  127571. * Fires before editing is triggered. Return false from event handler to stop the editing.
  127572. *
  127573. * @param {Ext.grid.plugin.Editing} editor
  127574. * @param {Object} e An edit event with the following properties:
  127575. *
  127576. * - grid - The grid
  127577. * - record - The record being edited
  127578. * - field - The field name being edited
  127579. * - value - The value for the field being edited.
  127580. * - row - The grid table row
  127581. * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
  127582. * - rowIdx - The row index that is being edited
  127583. * - colIdx - The column index that is being edited
  127584. * - cancel - Set this to true to cancel the edit or return false from your handler.
  127585. * - originalValue - Alias for value (only when using {@link Ext.grid.plugin.CellEditing CellEditing}).
  127586. */
  127587. 'beforeedit',
  127588. /**
  127589. * @event edit
  127590. * Fires after a editing. Usage example:
  127591. *
  127592. * grid.on('edit', function(editor, e) {
  127593. * // commit the changes right after editing finished
  127594. * e.record.commit();
  127595. * });
  127596. *
  127597. * @param {Ext.grid.plugin.Editing} editor
  127598. * @param {Object} e An edit event with the following properties:
  127599. *
  127600. * - grid - The grid
  127601. * - record - The record that was edited
  127602. * - field - The field name that was edited
  127603. * - value - The value being set
  127604. * - row - The grid table row
  127605. * - column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.
  127606. * - rowIdx - The row index that was edited
  127607. * - colIdx - The column index that was edited
  127608. * - originalValue - The original value for the field, before the edit (only when using {@link Ext.grid.plugin.CellEditing CellEditing})
  127609. * - originalValues - The original values for the field, before the edit (only when using {@link Ext.grid.plugin.RowEditing RowEditing})
  127610. * - newValues - The new values being set (only when using {@link Ext.grid.plugin.RowEditing RowEditing})
  127611. * - view - The grid view (only when using {@link Ext.grid.plugin.RowEditing RowEditing})
  127612. * - store - The grid store (only when using {@link Ext.grid.plugin.RowEditing RowEditing})
  127613. */
  127614. 'edit',
  127615. /**
  127616. * @event validateedit
  127617. * Fires after editing, but before the value is set in the record. Return false from event handler to
  127618. * cancel the change.
  127619. *
  127620. * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
  127621. * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for example)
  127622. * and then setting the field's new value in the Record directly:
  127623. *
  127624. * grid.on('validateedit', function(editor, e) {
  127625. * var myTargetRow = 6;
  127626. *
  127627. * if (e.rowIdx == myTargetRow) {
  127628. * e.cancel = true;
  127629. * e.record.data[e.field] = e.value;
  127630. * }
  127631. * });
  127632. *
  127633. * @param {Ext.grid.plugin.Editing} editor
  127634. * @param {Object} e An edit event with the following properties:
  127635. *
  127636. * - grid - The grid
  127637. * - record - The record being edited
  127638. * - field - The field name being edited
  127639. * - value - The value being set
  127640. * - row - The grid table row
  127641. * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
  127642. * - rowIdx - The row index that is being edited
  127643. * - colIdx - The column index that is being edited
  127644. * - cancel - Set this to true to cancel the edit or return false from your handler.
  127645. * - originalValue - The original value for the field, before the edit (only when using {@link Ext.grid.plugin.CellEditing CellEditing})
  127646. * - originalValues - The original values for the field, before the edit (only when using {@link Ext.grid.plugin.RowEditing RowEditing})
  127647. * - newValues - The new values being set (only when using {@link Ext.grid.plugin.RowEditing RowEditing})
  127648. * - view - The grid view (only when using {@link Ext.grid.plugin.RowEditing RowEditing})
  127649. * - store - The grid store (only when using {@link Ext.grid.plugin.RowEditing RowEditing})
  127650. */
  127651. 'validateedit',
  127652. /**
  127653. * @event canceledit
  127654. * Fires when the user started editing but then cancelled the edit.
  127655. * @param {Ext.grid.plugin.Editing} editor
  127656. * @param {Object} e An edit event with the following properties:
  127657. *
  127658. * - grid - The grid
  127659. * - record - The record that was edited
  127660. * - field - The field name that was edited
  127661. * - value - The value being set
  127662. * - row - The grid table row
  127663. * - column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.
  127664. * - rowIdx - The row index that was edited
  127665. * - colIdx - The column index that was edited
  127666. * - view - The grid view
  127667. * - store - The grid store
  127668. */
  127669. 'canceledit'
  127670. );
  127671. me.callParent(arguments);
  127672. me.mixins.observable.constructor.call(me);
  127673. // TODO: Deprecated, remove in 5.0
  127674. me.on("edit", function(editor, e) {
  127675. me.fireEvent("afteredit", editor, e);
  127676. });
  127677. },
  127678. // private
  127679. init: function(grid) {
  127680. var me = this;
  127681. me.grid = grid;
  127682. me.view = grid.view;
  127683. me.initEvents();
  127684. me.mon(grid, 'reconfigure', me.onReconfigure, me);
  127685. me.onReconfigure();
  127686. grid.relayEvents(me, [
  127687. /**
  127688. * @event beforeedit
  127689. * Forwarded event from Ext.grid.plugin.Editing.
  127690. * @member Ext.panel.Table
  127691. * @inheritdoc Ext.grid.plugin.Editing#beforeedit
  127692. */
  127693. 'beforeedit',
  127694. /**
  127695. * @event edit
  127696. * Forwarded event from Ext.grid.plugin.Editing.
  127697. * @member Ext.panel.Table
  127698. * @inheritdoc Ext.grid.plugin.Editing#edit
  127699. */
  127700. 'edit',
  127701. /**
  127702. * @event validateedit
  127703. * Forwarded event from Ext.grid.plugin.Editing.
  127704. * @member Ext.panel.Table
  127705. * @inheritdoc Ext.grid.plugin.Editing#validateedit
  127706. */
  127707. 'validateedit',
  127708. /**
  127709. * @event canceledit
  127710. * Forwarded event from Ext.grid.plugin.Editing.
  127711. * @member Ext.panel.Table
  127712. * @inheritdoc Ext.grid.plugin.Editing#canceledit
  127713. */
  127714. 'canceledit'
  127715. ]);
  127716. // Marks the grid as editable, so that the SelectionModel
  127717. // can make appropriate decisions during navigation
  127718. grid.isEditable = true;
  127719. grid.editingPlugin = grid.view.editingPlugin = me;
  127720. },
  127721. /**
  127722. * Fires after the grid is reconfigured
  127723. * @private
  127724. */
  127725. onReconfigure: function() {
  127726. this.initFieldAccessors(this.view.getGridColumns());
  127727. },
  127728. /**
  127729. * @private
  127730. * AbstractComponent calls destroy on all its plugins at destroy time.
  127731. */
  127732. destroy: function() {
  127733. var me = this,
  127734. grid = me.grid;
  127735. Ext.destroy(me.keyNav);
  127736. me.removeFieldAccessors(grid.getView().getGridColumns());
  127737. // Clear all listeners from all our events, clear all managed listeners we added to other Observables
  127738. me.clearListeners();
  127739. delete me.grid.editingPlugin;
  127740. delete me.grid.view.editingPlugin;
  127741. delete me.grid;
  127742. delete me.view;
  127743. delete me.editor;
  127744. delete me.keyNav;
  127745. },
  127746. // private
  127747. getEditStyle: function() {
  127748. return this.editStyle;
  127749. },
  127750. // private
  127751. initFieldAccessors: function(columns) {
  127752. columns = [].concat(columns);
  127753. var me = this,
  127754. c,
  127755. cLen = columns.length,
  127756. column;
  127757. for (c = 0; c < cLen; c++) {
  127758. column = columns[c];
  127759. Ext.applyIf(column, {
  127760. getEditor: function(record, defaultField) {
  127761. return me.getColumnField(this, defaultField);
  127762. },
  127763. setEditor: function(field) {
  127764. me.setColumnField(this, field);
  127765. }
  127766. });
  127767. }
  127768. },
  127769. // private
  127770. removeFieldAccessors: function(columns) {
  127771. columns = [].concat(columns);
  127772. var c,
  127773. cLen = columns.length,
  127774. column;
  127775. for (c = 0; c < cLen; c++) {
  127776. column = columns[c];
  127777. delete column.getEditor;
  127778. delete column.setEditor;
  127779. }
  127780. },
  127781. // private
  127782. // remaps to the public API of Ext.grid.column.Column.getEditor
  127783. getColumnField: function(columnHeader, defaultField) {
  127784. var field = columnHeader.field;
  127785. if (!field && columnHeader.editor) {
  127786. field = columnHeader.editor;
  127787. delete columnHeader.editor;
  127788. }
  127789. if (!field && defaultField) {
  127790. field = defaultField;
  127791. }
  127792. if (field) {
  127793. if (Ext.isString(field)) {
  127794. field = { xtype: field };
  127795. }
  127796. if (!field.isFormField) {
  127797. field = Ext.ComponentManager.create(field, this.defaultFieldXType);
  127798. }
  127799. columnHeader.field = field;
  127800. Ext.apply(field, {
  127801. name: columnHeader.dataIndex
  127802. });
  127803. return field;
  127804. }
  127805. },
  127806. // private
  127807. // remaps to the public API of Ext.grid.column.Column.setEditor
  127808. setColumnField: function(column, field) {
  127809. if (Ext.isObject(field) && !field.isFormField) {
  127810. field = Ext.ComponentManager.create(field, this.defaultFieldXType);
  127811. }
  127812. column.field = field;
  127813. },
  127814. // private
  127815. initEvents: function() {
  127816. var me = this;
  127817. me.initEditTriggers();
  127818. me.initCancelTriggers();
  127819. },
  127820. // @abstract
  127821. initCancelTriggers: Ext.emptyFn,
  127822. // private
  127823. initEditTriggers: function() {
  127824. var me = this,
  127825. view = me.view;
  127826. // Listen for the edit trigger event.
  127827. if (me.triggerEvent == 'cellfocus') {
  127828. me.mon(view, 'cellfocus', me.onCellFocus, me);
  127829. } else if (me.triggerEvent == 'rowfocus') {
  127830. me.mon(view, 'rowfocus', me.onRowFocus, me);
  127831. } else {
  127832. // Prevent the View from processing when the SelectionModel focuses.
  127833. // This is because the SelectionModel processes the mousedown event, and
  127834. // focusing causes a scroll which means that the subsequent mouseup might
  127835. // take place at a different document XY position, and will therefore
  127836. // not trigger a click.
  127837. // This Editor must call the View's focusCell method directly when we recieve a request to edit
  127838. if (view.selModel.isCellModel) {
  127839. view.onCellFocus = Ext.Function.bind(me.beforeViewCellFocus, me);
  127840. }
  127841. // Listen for whichever click event we are configured to use
  127842. me.mon(view, me.triggerEvent || ('cell' + (me.clicksToEdit === 1 ? 'click' : 'dblclick')), me.onCellClick, me);
  127843. }
  127844. // add/remove header event listeners need to be added immediately because
  127845. // columns can be added/removed before render
  127846. me.initAddRemoveHeaderEvents()
  127847. // wait until render to initialize keynav events since they are attached to an element
  127848. view.on('render', me.initKeyNavHeaderEvents, me, {single: true});
  127849. },
  127850. // Override of View's method so that we can pre-empt the View's processing if the view is being triggered by a mousedown
  127851. beforeViewCellFocus: function(position) {
  127852. // Pass call on to view if the navigation is from the keyboard, or we are not going to edit this cell.
  127853. if (this.view.selModel.keyNavigation || !this.editing || !this.isCellEditable || !this.isCellEditable(position.row, position.columnHeader)) {
  127854. this.view.focusCell.apply(this.view, arguments);
  127855. }
  127856. },
  127857. // private. Used if we are triggered by the rowfocus event
  127858. onRowFocus: function(record, row, rowIdx) {
  127859. this.startEdit(row, 0);
  127860. },
  127861. // private. Used if we are triggered by the cellfocus event
  127862. onCellFocus: function(record, cell, position) {
  127863. this.startEdit(position.row, position.column);
  127864. },
  127865. // private. Used if we are triggered by a cellclick event
  127866. onCellClick: function(view, cell, colIdx, record, row, rowIdx, e) {
  127867. // cancel editing if the element that was clicked was a tree expander
  127868. if(!view.expanderSelector || !e.getTarget(view.expanderSelector)) {
  127869. this.startEdit(record, view.getHeaderAtIndex(colIdx));
  127870. }
  127871. },
  127872. initAddRemoveHeaderEvents: function(){
  127873. var me = this;
  127874. me.mon(me.grid.headerCt, {
  127875. scope: me,
  127876. add: me.onColumnAdd,
  127877. remove: me.onColumnRemove
  127878. });
  127879. },
  127880. initKeyNavHeaderEvents: function() {
  127881. var me = this;
  127882. me.keyNav = Ext.create('Ext.util.KeyNav', me.view.el, {
  127883. enter: me.onEnterKey,
  127884. esc: me.onEscKey,
  127885. scope: me
  127886. });
  127887. },
  127888. // private
  127889. onColumnAdd: function(ct, column) {
  127890. if (column.isHeader) {
  127891. this.initFieldAccessors(column);
  127892. }
  127893. },
  127894. // private
  127895. onColumnRemove: function(ct, column) {
  127896. if (column.isHeader) {
  127897. this.removeFieldAccessors(column);
  127898. }
  127899. },
  127900. // private
  127901. onEnterKey: function(e) {
  127902. var me = this,
  127903. grid = me.grid,
  127904. selModel = grid.getSelectionModel(),
  127905. record,
  127906. pos,
  127907. columnHeader = grid.headerCt.getHeaderAtIndex(0);
  127908. // Calculate editing start position from SelectionModel
  127909. // CellSelectionModel
  127910. if (selModel.getCurrentPosition) {
  127911. pos = selModel.getCurrentPosition();
  127912. if (pos) {
  127913. record = grid.store.getAt(pos.row);
  127914. columnHeader = grid.headerCt.getHeaderAtIndex(pos.column);
  127915. }
  127916. }
  127917. // RowSelectionModel
  127918. else {
  127919. record = selModel.getLastSelected();
  127920. }
  127921. // If there was a selection to provide a starting context...
  127922. if (record && columnHeader) {
  127923. me.startEdit(record, columnHeader);
  127924. }
  127925. },
  127926. // private
  127927. onEscKey: function(e) {
  127928. this.cancelEdit();
  127929. },
  127930. /**
  127931. * @private
  127932. * @template
  127933. * Template method called before editing begins.
  127934. * @param {Object} context The current editing context
  127935. * @return {Boolean} Return false to cancel the editing process
  127936. */
  127937. beforeEdit: Ext.emptyFn,
  127938. /**
  127939. * Starts editing the specified record, using the specified Column definition to define which field is being edited.
  127940. * @param {Ext.data.Model/Number} record The Store data record which backs the row to be edited, or index of the record in Store.
  127941. * @param {Ext.grid.column.Column/Number} columnHeader The Column object defining the column to be edited, or index of the column.
  127942. */
  127943. startEdit: function(record, columnHeader) {
  127944. var me = this,
  127945. context = me.getEditingContext(record, columnHeader);
  127946. if (context == null || me.beforeEdit(context) === false || me.fireEvent('beforeedit', me, context) === false || context.cancel || !me.grid.view.isVisible(true)) {
  127947. return false;
  127948. }
  127949. me.context = context;
  127950. /**
  127951. * @property {Boolean} editing
  127952. * Set to `true` while the editing plugin is active and an Editor is visible.
  127953. */
  127954. me.editing = true;
  127955. },
  127956. // TODO: Have this use a new class Ext.grid.CellContext for use here, and in CellSelectionModel
  127957. /**
  127958. * @private
  127959. * Collects all information necessary for any subclasses to perform their editing functions.
  127960. * @param record
  127961. * @param columnHeader
  127962. * @returns {Object/undefined} The editing context based upon the passed record and column
  127963. */
  127964. getEditingContext: function(record, columnHeader) {
  127965. var me = this,
  127966. grid = me.grid,
  127967. view = grid.getView(),
  127968. node = view.getNode(record),
  127969. rowIdx, colIdx;
  127970. // An intervening listener may have deleted the Record
  127971. if (!node) {
  127972. return;
  127973. }
  127974. // Coerce the column index to the closest visible column
  127975. columnHeader = grid.headerCt.getVisibleHeaderClosestToIndex(Ext.isNumber(columnHeader) ? columnHeader : columnHeader.getIndex());
  127976. // No corresponding column. Possible if all columns have been moved to the other side of a lockable grid pair
  127977. if (!columnHeader) {
  127978. return;
  127979. }
  127980. colIdx = columnHeader.getIndex();
  127981. if (Ext.isNumber(record)) {
  127982. // look up record if numeric row index was passed
  127983. rowIdx = record;
  127984. record = view.getRecord(node);
  127985. } else {
  127986. rowIdx = view.indexOf(node);
  127987. }
  127988. return {
  127989. grid : grid,
  127990. record : record,
  127991. field : columnHeader.dataIndex,
  127992. value : record.get(columnHeader.dataIndex),
  127993. row : view.getNode(rowIdx),
  127994. column : columnHeader,
  127995. rowIdx : rowIdx,
  127996. colIdx : colIdx
  127997. };
  127998. },
  127999. /**
  128000. * Cancels any active edit that is in progress.
  128001. */
  128002. cancelEdit: function() {
  128003. var me = this;
  128004. me.editing = false;
  128005. me.fireEvent('canceledit', me, me.context);
  128006. },
  128007. /**
  128008. * Completes the edit if there is an active edit in progress.
  128009. */
  128010. completeEdit: function() {
  128011. var me = this;
  128012. if (me.editing && me.validateEdit()) {
  128013. me.fireEvent('edit', me, me.context);
  128014. }
  128015. delete me.context;
  128016. me.editing = false;
  128017. },
  128018. // @abstract
  128019. validateEdit: function() {
  128020. var me = this,
  128021. context = me.context;
  128022. return me.fireEvent('validateedit', me, context) !== false && !context.cancel;
  128023. }
  128024. });
  128025. /**
  128026. * The Ext.grid.plugin.CellEditing plugin injects editing at a cell level for a Grid. Only a single
  128027. * cell will be editable at a time. The field that will be used for the editor is defined at the
  128028. * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
  128029. *
  128030. * If an editor is not specified for a particular column then that cell will not be editable and it will
  128031. * be skipped when activated via the mouse or the keyboard.
  128032. *
  128033. * The editor may be shared for each column in the grid, or a different one may be specified for each column.
  128034. * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
  128035. * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
  128036. *
  128037. * ## Example
  128038. *
  128039. * A grid with editor for the name and the email columns:
  128040. *
  128041. * @example
  128042. * Ext.create('Ext.data.Store', {
  128043. * storeId:'simpsonsStore',
  128044. * fields:['name', 'email', 'phone'],
  128045. * data:{'items':[
  128046. * {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
  128047. * {"name":"Bart", "email":"bart@simpsons.com", "phone":"555-222-1234"},
  128048. * {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
  128049. * {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
  128050. * ]},
  128051. * proxy: {
  128052. * type: 'memory',
  128053. * reader: {
  128054. * type: 'json',
  128055. * root: 'items'
  128056. * }
  128057. * }
  128058. * });
  128059. *
  128060. * Ext.create('Ext.grid.Panel', {
  128061. * title: 'Simpsons',
  128062. * store: Ext.data.StoreManager.lookup('simpsonsStore'),
  128063. * columns: [
  128064. * {header: 'Name', dataIndex: 'name', editor: 'textfield'},
  128065. * {header: 'Email', dataIndex: 'email', flex:1,
  128066. * editor: {
  128067. * xtype: 'textfield',
  128068. * allowBlank: false
  128069. * }
  128070. * },
  128071. * {header: 'Phone', dataIndex: 'phone'}
  128072. * ],
  128073. * selType: 'cellmodel',
  128074. * plugins: [
  128075. * Ext.create('Ext.grid.plugin.CellEditing', {
  128076. * clicksToEdit: 1
  128077. * })
  128078. * ],
  128079. * height: 200,
  128080. * width: 400,
  128081. * renderTo: Ext.getBody()
  128082. * });
  128083. *
  128084. * This requires a little explanation. We're passing in `store` and `columns` as normal, but
  128085. * we also specify a {@link Ext.grid.column.Column#field field} on two of our columns. For the
  128086. * Name column we just want a default textfield to edit the value, so we specify 'textfield'.
  128087. * For the Email column we customized the editor slightly by passing allowBlank: false, which
  128088. * will provide inline validation.
  128089. *
  128090. * To support cell editing, we also specified that the grid should use the 'cellmodel'
  128091. * {@link Ext.grid.Panel#selType selType}, and created an instance of the CellEditing plugin,
  128092. * which we configured to activate each editor after a single click.
  128093. *
  128094. */
  128095. Ext.define('Ext.grid.plugin.CellEditing', {
  128096. alias: 'plugin.cellediting',
  128097. extend: 'Ext.grid.plugin.Editing',
  128098. requires: ['Ext.grid.CellEditor', 'Ext.util.DelayedTask'],
  128099. constructor: function() {
  128100. /**
  128101. * @event beforeedit
  128102. * Fires before cell editing is triggered. Return false from event handler to stop the editing.
  128103. *
  128104. * @param {Ext.grid.plugin.CellEditing} editor
  128105. * @param {Object} e An edit event with the following properties:
  128106. *
  128107. * - grid - The grid
  128108. * - record - The record being edited
  128109. * - field - The field name being edited
  128110. * - value - The value for the field being edited.
  128111. * - row - The grid table row
  128112. * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
  128113. * - rowIdx - The row index that is being edited
  128114. * - colIdx - The column index that is being edited
  128115. * - cancel - Set this to true to cancel the edit or return false from your handler.
  128116. */
  128117. /**
  128118. * @event edit
  128119. * Fires after a cell is edited. Usage example:
  128120. *
  128121. * grid.on('edit', function(editor, e) {
  128122. * // commit the changes right after editing finished
  128123. * e.record.commit();
  128124. * });
  128125. *
  128126. * @param {Ext.grid.plugin.CellEditing} editor
  128127. * @param {Object} e An edit event with the following properties:
  128128. *
  128129. * - grid - The grid
  128130. * - record - The record that was edited
  128131. * - field - The field name that was edited
  128132. * - value - The value being set
  128133. * - originalValue - The original value for the field, before the edit.
  128134. * - row - The grid table row
  128135. * - column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.
  128136. * - rowIdx - The row index that was edited
  128137. * - colIdx - The column index that was edited
  128138. */
  128139. /**
  128140. * @event validateedit
  128141. * Fires after a cell is edited, but before the value is set in the record. Return false from event handler to
  128142. * cancel the change.
  128143. *
  128144. * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
  128145. * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for
  128146. * example) and then setting the field's new value in the Record directly:
  128147. *
  128148. * grid.on('validateedit', function(editor, e) {
  128149. * var myTargetRow = 6;
  128150. *
  128151. * if (e.row == myTargetRow) {
  128152. * e.cancel = true;
  128153. * e.record.data[e.field] = e.value;
  128154. * }
  128155. * });
  128156. *
  128157. * @param {Ext.grid.plugin.CellEditing} editor
  128158. * @param {Object} e An edit event with the following properties:
  128159. *
  128160. * - grid - The grid
  128161. * - record - The record being edited
  128162. * - field - The field name being edited
  128163. * - value - The value being set
  128164. * - originalValue - The original value for the field, before the edit.
  128165. * - row - The grid table row
  128166. * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
  128167. * - rowIdx - The row index that is being edited
  128168. * - colIdx - The column index that is being edited
  128169. * - cancel - Set this to true to cancel the edit or return false from your handler.
  128170. */
  128171. /**
  128172. * @event canceledit
  128173. * Fires when the user started editing a cell but then cancelled the edit.
  128174. * @param {Ext.grid.plugin.CellEditing} editor
  128175. * @param {Object} e An edit event with the following properties:
  128176. *
  128177. * - grid - The grid
  128178. * - record - The record that was edited
  128179. * - field - The field name that was edited
  128180. * - value - The value being set
  128181. * - row - The grid table row
  128182. * - column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.
  128183. * - rowIdx - The row index that was edited
  128184. * - colIdx - The column index that was edited
  128185. */
  128186. this.callParent(arguments);
  128187. this.editors = new Ext.util.MixedCollection(false, function(editor) {
  128188. return editor.editorId;
  128189. });
  128190. this.editTask = new Ext.util.DelayedTask();
  128191. },
  128192. onReconfigure: function(){
  128193. this.editors.clear();
  128194. this.callParent();
  128195. },
  128196. /**
  128197. * @private
  128198. * AbstractComponent calls destroy on all its plugins at destroy time.
  128199. */
  128200. destroy: function() {
  128201. var me = this;
  128202. me.editTask.cancel();
  128203. me.editors.each(Ext.destroy, Ext);
  128204. me.editors.clear();
  128205. me.callParent(arguments);
  128206. },
  128207. onBodyScroll: function() {
  128208. var me = this,
  128209. ed = me.getActiveEditor(),
  128210. scroll = me.view.el.getScroll();
  128211. // Scroll happened during editing...
  128212. if (ed && ed.editing) {
  128213. // Terminate editing only on vertical scroll. Horiz scroll can be caused by tabbing between cells.
  128214. if (scroll.top !== me.scroll.top) {
  128215. if (ed.field) {
  128216. if (ed.field.triggerBlur) {
  128217. ed.field.triggerBlur();
  128218. } else {
  128219. ed.field.blur();
  128220. }
  128221. }
  128222. }
  128223. // Horiz scroll just requires that the editor be realigned.
  128224. else {
  128225. ed.realign();
  128226. }
  128227. }
  128228. me.scroll = scroll;
  128229. },
  128230. // private
  128231. // Template method called from base class's initEvents
  128232. initCancelTriggers: function() {
  128233. var me = this,
  128234. grid = me.grid,
  128235. view = grid.view;
  128236. view.addElListener('mousewheel', me.cancelEdit, me);
  128237. me.mon(view, 'bodyscroll', me.onBodyScroll, me);
  128238. me.mon(grid, {
  128239. columnresize: me.cancelEdit,
  128240. columnmove: me.cancelEdit,
  128241. scope: me
  128242. });
  128243. },
  128244. isCellEditable: function(record, columnHeader) {
  128245. var me = this,
  128246. context = me.getEditingContext(record, columnHeader);
  128247. if (me.grid.view.isVisible(true) && context) {
  128248. columnHeader = context.column;
  128249. record = context.record;
  128250. if (columnHeader && me.getEditor(record, columnHeader)) {
  128251. return true;
  128252. }
  128253. }
  128254. },
  128255. /**
  128256. * Starts editing the specified record, using the specified Column definition to define which field is being edited.
  128257. * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
  128258. * @param {Ext.grid.column.Column} columnHeader The Column object defining the column to be edited.
  128259. * @return `true` if editing was started, `false` otherwise.
  128260. */
  128261. startEdit: function(record, columnHeader) {
  128262. var me = this,
  128263. context = me.getEditingContext(record, columnHeader),
  128264. value, ed;
  128265. // Complete the edit now, before getting the editor's target
  128266. // cell DOM element. Completing the edit causes a row refresh.
  128267. // Also allows any post-edit events to take effect before continuing
  128268. me.completeEdit();
  128269. // Cancel editing if EditingContext could not be found (possibly because record has been deleted by an intervening listener), or if the grid view is not currently visible
  128270. if (!context || !me.grid.view.isVisible(true)) {
  128271. return false;
  128272. }
  128273. record = context.record;
  128274. columnHeader = context.column;
  128275. // See if the field is editable for the requested record
  128276. if (columnHeader && !columnHeader.getEditor(record)) {
  128277. return false;
  128278. }
  128279. value = record.get(columnHeader.dataIndex);
  128280. context.originalValue = context.value = value;
  128281. if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', me, context) === false || context.cancel) {
  128282. return false;
  128283. }
  128284. ed = me.getEditor(record, columnHeader);
  128285. // Whether we are going to edit or not, ensure the edit cell is scrolled into view
  128286. me.grid.view.cancelFocus();
  128287. me.view.focusCell({
  128288. row: context.rowIdx,
  128289. column: context.colIdx
  128290. });
  128291. if (ed) {
  128292. me.editTask.delay(15, me.showEditor, me, [ed, context, value]);
  128293. return true;
  128294. }
  128295. return false;
  128296. },
  128297. showEditor: function(ed, context, value) {
  128298. var me = this,
  128299. record = context.record,
  128300. columnHeader = context.column,
  128301. sm = me.grid.getSelectionModel(),
  128302. selection = sm.getCurrentPosition();
  128303. me.context = context;
  128304. me.setActiveEditor(ed);
  128305. me.setActiveRecord(record);
  128306. me.setActiveColumn(columnHeader);
  128307. // Select cell on edit only if it's not the currently selected cell
  128308. if (sm.selectByPosition && (!selection || selection.column !== context.colIdx || selection.row !== context.rowIdx)) {
  128309. sm.selectByPosition({
  128310. row: context.rowIdx,
  128311. column: context.colIdx
  128312. });
  128313. }
  128314. ed.startEdit(me.getCell(record, columnHeader), value);
  128315. me.editing = true;
  128316. me.scroll = me.view.el.getScroll();
  128317. },
  128318. completeEdit: function() {
  128319. var activeEd = this.getActiveEditor();
  128320. if (activeEd) {
  128321. activeEd.completeEdit();
  128322. this.editing = false;
  128323. }
  128324. },
  128325. // internal getters/setters
  128326. setActiveEditor: function(ed) {
  128327. this.activeEditor = ed;
  128328. },
  128329. getActiveEditor: function() {
  128330. return this.activeEditor;
  128331. },
  128332. setActiveColumn: function(column) {
  128333. this.activeColumn = column;
  128334. },
  128335. getActiveColumn: function() {
  128336. return this.activeColumn;
  128337. },
  128338. setActiveRecord: function(record) {
  128339. this.activeRecord = record;
  128340. },
  128341. getActiveRecord: function() {
  128342. return this.activeRecord;
  128343. },
  128344. getEditor: function(record, column) {
  128345. var me = this,
  128346. editors = me.editors,
  128347. editorId = column.getItemId(),
  128348. editor = editors.getByKey(editorId);
  128349. if (editor) {
  128350. return editor;
  128351. } else {
  128352. editor = column.getEditor(record);
  128353. if (!editor) {
  128354. return false;
  128355. }
  128356. // Allow them to specify a CellEditor in the Column
  128357. // Either way, the Editor is a floating Component, and must be attached to an ownerCt
  128358. // which it uses to traverse upwards to find a ZIndexManager at render time.
  128359. if (!(editor instanceof Ext.grid.CellEditor)) {
  128360. editor = new Ext.grid.CellEditor({
  128361. editorId: editorId,
  128362. field: editor,
  128363. ownerCt: me.grid
  128364. });
  128365. } else {
  128366. editor.ownerCt = me.grid;
  128367. }
  128368. editor.editingPlugin = me;
  128369. editor.isForTree = me.grid.isTree;
  128370. editor.on({
  128371. scope: me,
  128372. specialkey: me.onSpecialKey,
  128373. complete: me.onEditComplete,
  128374. canceledit: me.cancelEdit
  128375. });
  128376. editors.add(editor);
  128377. return editor;
  128378. }
  128379. },
  128380. // inherit docs
  128381. setColumnField: function(column, field) {
  128382. var ed = this.editors.getByKey(column.getItemId());
  128383. Ext.destroy(ed, column.field);
  128384. this.editors.removeAtKey(column.getItemId());
  128385. this.callParent(arguments);
  128386. },
  128387. /**
  128388. * Gets the cell (td) for a particular record and column.
  128389. * @param {Ext.data.Model} record
  128390. * @param {Ext.grid.column.Column} column
  128391. * @private
  128392. */
  128393. getCell: function(record, column) {
  128394. return this.grid.getView().getCell(record, column);
  128395. },
  128396. onSpecialKey: function(ed, field, e) {
  128397. var me = this,
  128398. grid = me.grid,
  128399. sm;
  128400. if (e.getKey() === e.TAB) {
  128401. e.stopEvent();
  128402. if (ed) {
  128403. // Allow the field to act on tabs before onEditorTab, which ends
  128404. // up calling completeEdit. This is useful for picker type fields.
  128405. ed.onEditorTab(e);
  128406. }
  128407. sm = grid.getSelectionModel();
  128408. if (sm.onEditorTab) {
  128409. sm.onEditorTab(me, e);
  128410. }
  128411. }
  128412. },
  128413. onEditComplete : function(ed, value, startValue) {
  128414. var me = this,
  128415. grid = me.grid,
  128416. activeColumn = me.getActiveColumn(),
  128417. sm = grid.getSelectionModel(),
  128418. record;
  128419. if (activeColumn) {
  128420. record = me.context.record;
  128421. me.setActiveEditor(null);
  128422. me.setActiveColumn(null);
  128423. me.setActiveRecord(null);
  128424. if (!me.validateEdit()) {
  128425. return;
  128426. }
  128427. // Only update the record if the new value is different than the
  128428. // startValue. When the view refreshes its el will gain focus
  128429. if (!record.isEqual(value, startValue)) {
  128430. record.set(activeColumn.dataIndex, value);
  128431. }
  128432. // Restore focus back to the view's element.
  128433. if (sm.setCurrentPosition) {
  128434. sm.setCurrentPosition(sm.getCurrentPosition());
  128435. }
  128436. grid.getView().getEl(activeColumn).focus();
  128437. me.context.value = value;
  128438. me.fireEvent('edit', me, me.context);
  128439. }
  128440. },
  128441. /**
  128442. * Cancels any active editing.
  128443. */
  128444. cancelEdit: function() {
  128445. var me = this,
  128446. activeEd = me.getActiveEditor(),
  128447. viewEl = me.grid.getView().getEl(me.getActiveColumn());
  128448. me.setActiveEditor(null);
  128449. me.setActiveColumn(null);
  128450. me.setActiveRecord(null);
  128451. if (activeEd) {
  128452. activeEd.cancelEdit();
  128453. viewEl.focus();
  128454. me.callParent(arguments);
  128455. }
  128456. },
  128457. /**
  128458. * Starts editing by position (row/column)
  128459. * @param {Object} position A position with keys of row and column.
  128460. */
  128461. startEditByPosition: function(position) {
  128462. // Coerce the edit column to the closest visible column
  128463. position.column = this.view.getHeaderCt().getVisibleHeaderClosestToIndex(position.column).getIndex();
  128464. return this.startEdit(position.row, position.column);
  128465. }
  128466. });
  128467. /**
  128468. * This plugin provides drag and/or drop functionality for a GridView.
  128469. *
  128470. * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a {@link
  128471. * Ext.grid.View GridView} and loads the data object which is passed to a cooperating {@link Ext.dd.DragZone DragZone}'s
  128472. * methods with the following properties:
  128473. *
  128474. * - `copy` : Boolean
  128475. *
  128476. * The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` _and_
  128477. * the control key was pressed when the drag operation was begun.
  128478. *
  128479. * - `view` : GridView
  128480. *
  128481. * The source GridView from which the drag originated.
  128482. *
  128483. * - `ddel` : HtmlElement
  128484. *
  128485. * The drag proxy element which moves with the mouse
  128486. *
  128487. * - `item` : HtmlElement
  128488. *
  128489. * The GridView node upon which the mousedown event was registered.
  128490. *
  128491. * - `records` : Array
  128492. *
  128493. * An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
  128494. *
  128495. * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
  128496. * members of the same ddGroup which processes such data objects.
  128497. *
  128498. * Adding this plugin to a view means that two new events may be fired from the client GridView, `{@link #beforedrop
  128499. * beforedrop}` and `{@link #drop drop}`
  128500. *
  128501. * @example
  128502. * Ext.create('Ext.data.Store', {
  128503. * storeId:'simpsonsStore',
  128504. * fields:['name'],
  128505. * data: [["Lisa"], ["Bart"], ["Homer"], ["Marge"]],
  128506. * proxy: {
  128507. * type: 'memory',
  128508. * reader: 'array'
  128509. * }
  128510. * });
  128511. *
  128512. * Ext.create('Ext.grid.Panel', {
  128513. * store: 'simpsonsStore',
  128514. * columns: [
  128515. * {header: 'Name', dataIndex: 'name', flex: true}
  128516. * ],
  128517. * viewConfig: {
  128518. * plugins: {
  128519. * ptype: 'gridviewdragdrop',
  128520. * dragText: 'Drag and drop to reorganize'
  128521. * }
  128522. * },
  128523. * height: 200,
  128524. * width: 400,
  128525. * renderTo: Ext.getBody()
  128526. * });
  128527. */
  128528. Ext.define('Ext.grid.plugin.DragDrop', {
  128529. extend: 'Ext.AbstractPlugin',
  128530. alias: 'plugin.gridviewdragdrop',
  128531. uses: [
  128532. 'Ext.view.DragZone',
  128533. 'Ext.grid.ViewDropZone'
  128534. ],
  128535. /**
  128536. * @event beforedrop
  128537. * **This event is fired through the GridView. Add listeners to the GridView object**
  128538. *
  128539. * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the GridView.
  128540. *
  128541. * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
  128542. *
  128543. * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
  128544. * back to the point from which the drag began.
  128545. *
  128546. * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
  128547. * was valid, and that the repair operation should not take place.
  128548. *
  128549. * Any other return value continues with the data transfer operation.
  128550. *
  128551. * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
  128552. * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
  128553. *
  128554. * - copy : Boolean
  128555. *
  128556. * The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
  128557. * the control key was pressed when the drag operation was begun
  128558. *
  128559. * - view : GridView
  128560. *
  128561. * The source GridView from which the drag originated.
  128562. *
  128563. * - ddel : HtmlElement
  128564. *
  128565. * The drag proxy element which moves with the mouse
  128566. *
  128567. * - item : HtmlElement
  128568. *
  128569. * The GridView node upon which the mousedown event was registered.
  128570. *
  128571. * - records : Array
  128572. *
  128573. * An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
  128574. *
  128575. * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
  128576. *
  128577. * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
  128578. * of the node.
  128579. *
  128580. * @param {Function} dropFunction
  128581. *
  128582. * A function to call to complete the data transfer operation and either move or copy Model instances from the
  128583. * source View's Store to the destination View's Store.
  128584. *
  128585. * This is useful when you want to perform some kind of asynchronous processing before confirming the drop, such as
  128586. * an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
  128587. *
  128588. * Return `0` from this event handler, and call the `dropFunction` at any time to perform the data transfer.
  128589. */
  128590. /**
  128591. * @event drop
  128592. * **This event is fired through the GridView. Add listeners to the GridView object** Fired when a drop operation
  128593. * has been completed and the data has been moved or copied.
  128594. *
  128595. * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
  128596. *
  128597. * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
  128598. * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
  128599. *
  128600. * - copy : Boolean
  128601. *
  128602. * The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
  128603. * the control key was pressed when the drag operation was begun
  128604. *
  128605. * - view : GridView
  128606. *
  128607. * The source GridView from which the drag originated.
  128608. *
  128609. * - ddel : HtmlElement
  128610. *
  128611. * The drag proxy element which moves with the mouse
  128612. *
  128613. * - item : HtmlElement
  128614. *
  128615. * The GridView node upon which the mousedown event was registered.
  128616. *
  128617. * - records : Array
  128618. *
  128619. * An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
  128620. *
  128621. * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
  128622. *
  128623. * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
  128624. * of the node.
  128625. */
  128626. //<locale>
  128627. /**
  128628. * @cfg
  128629. * The text to show while dragging.
  128630. *
  128631. * Two placeholders can be used in the text:
  128632. *
  128633. * - `{0}` The number of selected items.
  128634. * - `{1}` 's' when more than 1 items (only useful for English).
  128635. */
  128636. dragText : '{0} selected row{1}',
  128637. //</locale>
  128638. /**
  128639. * @cfg {String} ddGroup
  128640. * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
  128641. * DropZone used by this plugin will only interact with other drag drop objects in the same group.
  128642. */
  128643. ddGroup : "GridDD",
  128644. /**
  128645. * @cfg {String} dragGroup
  128646. * The ddGroup to which the DragZone will belong.
  128647. *
  128648. * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
  128649. * Drag/DropZones which are members of the same ddGroup.
  128650. */
  128651. /**
  128652. * @cfg {String} dropGroup
  128653. * The ddGroup to which the DropZone will belong.
  128654. *
  128655. * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
  128656. * Drag/DropZones which are members of the same ddGroup.
  128657. */
  128658. /**
  128659. * @cfg {Boolean} enableDrop
  128660. * False to disallow the View from accepting drop gestures.
  128661. */
  128662. enableDrop: true,
  128663. /**
  128664. * @cfg {Boolean} enableDrag
  128665. * False to disallow dragging items from the View.
  128666. */
  128667. enableDrag: true,
  128668. init : function(view) {
  128669. view.on('render', this.onViewRender, this, {single: true});
  128670. },
  128671. /**
  128672. * @private
  128673. * AbstractComponent calls destroy on all its plugins at destroy time.
  128674. */
  128675. destroy: function() {
  128676. Ext.destroy(this.dragZone, this.dropZone);
  128677. },
  128678. enable: function() {
  128679. var me = this;
  128680. if (me.dragZone) {
  128681. me.dragZone.unlock();
  128682. }
  128683. if (me.dropZone) {
  128684. me.dropZone.unlock();
  128685. }
  128686. me.callParent();
  128687. },
  128688. disable: function() {
  128689. var me = this;
  128690. if (me.dragZone) {
  128691. me.dragZone.lock();
  128692. }
  128693. if (me.dropZone) {
  128694. me.dropZone.lock();
  128695. }
  128696. me.callParent();
  128697. },
  128698. onViewRender : function(view) {
  128699. var me = this;
  128700. if (me.enableDrag) {
  128701. me.dragZone = new Ext.view.DragZone({
  128702. view: view,
  128703. ddGroup: me.dragGroup || me.ddGroup,
  128704. dragText: me.dragText
  128705. });
  128706. }
  128707. if (me.enableDrop) {
  128708. me.dropZone = new Ext.grid.ViewDropZone({
  128709. view: view,
  128710. ddGroup: me.dropGroup || me.ddGroup
  128711. });
  128712. }
  128713. }
  128714. });
  128715. /**
  128716. * The Ext.grid.plugin.RowEditing plugin injects editing at a row level for a Grid. When editing begins,
  128717. * a small floating dialog will be shown for the appropriate row. Each editable column will show a field
  128718. * for editing. There is a button to save or cancel all changes for the edit.
  128719. *
  128720. * The field that will be used for the editor is defined at the
  128721. * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
  128722. * If an editor is not specified for a particular column then that column won't be editable and the value of
  128723. * the column will be displayed. To provide a custom renderer for non-editable values, use the
  128724. * {@link Ext.grid.column.Column#editRenderer editRenderer} configuration on the column.
  128725. *
  128726. * The editor may be shared for each column in the grid, or a different one may be specified for each column.
  128727. * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
  128728. * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
  128729. *
  128730. * @example
  128731. * Ext.create('Ext.data.Store', {
  128732. * storeId:'simpsonsStore',
  128733. * fields:['name', 'email', 'phone'],
  128734. * data: [
  128735. * {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
  128736. * {"name":"Bart", "email":"bart@simpsons.com", "phone":"555-222-1234"},
  128737. * {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
  128738. * {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
  128739. * ]
  128740. * });
  128741. *
  128742. * Ext.create('Ext.grid.Panel', {
  128743. * title: 'Simpsons',
  128744. * store: Ext.data.StoreManager.lookup('simpsonsStore'),
  128745. * columns: [
  128746. * {header: 'Name', dataIndex: 'name', editor: 'textfield'},
  128747. * {header: 'Email', dataIndex: 'email', flex:1,
  128748. * editor: {
  128749. * xtype: 'textfield',
  128750. * allowBlank: false
  128751. * }
  128752. * },
  128753. * {header: 'Phone', dataIndex: 'phone'}
  128754. * ],
  128755. * selType: 'rowmodel',
  128756. * plugins: [
  128757. * Ext.create('Ext.grid.plugin.RowEditing', {
  128758. * clicksToEdit: 1
  128759. * })
  128760. * ],
  128761. * height: 200,
  128762. * width: 400,
  128763. * renderTo: Ext.getBody()
  128764. * });
  128765. *
  128766. */
  128767. Ext.define('Ext.grid.plugin.RowEditing', {
  128768. extend: 'Ext.grid.plugin.Editing',
  128769. alias: 'plugin.rowediting',
  128770. requires: [
  128771. 'Ext.grid.RowEditor'
  128772. ],
  128773. editStyle: 'row',
  128774. /**
  128775. * @cfg {Boolean} autoCancel
  128776. * True to automatically cancel any pending changes when the row editor begins editing a new row.
  128777. * False to force the user to explicitly cancel the pending changes. Defaults to true.
  128778. */
  128779. autoCancel: true,
  128780. /**
  128781. * @cfg {Number} clicksToMoveEditor
  128782. * The number of clicks to move the row editor to a new row while it is visible and actively editing another row.
  128783. * This will default to the same value as {@link Ext.grid.plugin.Editing#clicksToEdit clicksToEdit}.
  128784. */
  128785. /**
  128786. * @cfg {Boolean} errorSummary
  128787. * True to show a {@link Ext.tip.ToolTip tooltip} that summarizes all validation errors present
  128788. * in the row editor. Set to false to prevent the tooltip from showing. Defaults to true.
  128789. */
  128790. errorSummary: true,
  128791. constructor: function() {
  128792. var me = this;
  128793. me.callParent(arguments);
  128794. if (!me.clicksToMoveEditor) {
  128795. me.clicksToMoveEditor = me.clicksToEdit;
  128796. }
  128797. me.autoCancel = !!me.autoCancel;
  128798. },
  128799. init: function(grid) {
  128800. this.callParent([grid]);
  128801. },
  128802. /**
  128803. * @private
  128804. * AbstractComponent calls destroy on all its plugins at destroy time.
  128805. */
  128806. destroy: function() {
  128807. var me = this;
  128808. Ext.destroy(me.editor);
  128809. me.callParent(arguments);
  128810. },
  128811. /**
  128812. * Starts editing the specified record, using the specified Column definition to define which field is being edited.
  128813. * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
  128814. * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited.
  128815. * @return `true` if editing was started, `false` otherwise.
  128816. */
  128817. startEdit: function(record, columnHeader) {
  128818. var me = this,
  128819. editor = me.getEditor();
  128820. if ((editor.beforeEdit() !== false) && (me.callParent(arguments) !== false)) {
  128821. editor.startEdit(me.context.record, me.context.column);
  128822. return true;
  128823. }
  128824. return false;
  128825. },
  128826. // private
  128827. cancelEdit: function() {
  128828. var me = this;
  128829. if (me.editing) {
  128830. me.getEditor().cancelEdit();
  128831. me.callParent(arguments);
  128832. }
  128833. },
  128834. // private
  128835. completeEdit: function() {
  128836. var me = this;
  128837. if (me.editing && me.validateEdit()) {
  128838. me.editing = false;
  128839. me.fireEvent('edit', me, me.context);
  128840. }
  128841. },
  128842. // private
  128843. validateEdit: function() {
  128844. var me = this,
  128845. editor = me.editor,
  128846. context = me.context,
  128847. record = context.record,
  128848. newValues = {},
  128849. originalValues = {},
  128850. editors = editor.items.items,
  128851. e,
  128852. eLen = editors.length,
  128853. name, item;
  128854. for (e = 0; e < eLen; e++) {
  128855. item = editors[e];
  128856. name = item.name;
  128857. newValues[name] = item.getValue();
  128858. originalValues[name] = record.get(name);
  128859. }
  128860. Ext.apply(context, {
  128861. newValues : newValues,
  128862. originalValues : originalValues
  128863. });
  128864. return me.callParent(arguments) && me.getEditor().completeEdit();
  128865. },
  128866. // private
  128867. getEditor: function() {
  128868. var me = this;
  128869. if (!me.editor) {
  128870. me.editor = me.initEditor();
  128871. }
  128872. return me.editor;
  128873. },
  128874. // private
  128875. initEditor: function() {
  128876. var me = this,
  128877. grid = me.grid,
  128878. view = me.view,
  128879. headerCt = grid.headerCt,
  128880. btns = ['saveBtnText', 'cancelBtnText', 'errorsText', 'dirtyText'],
  128881. b,
  128882. bLen = btns.length,
  128883. cfg = {
  128884. autoCancel: me.autoCancel,
  128885. errorSummary: me.errorSummary,
  128886. fields: headerCt.getGridColumns(),
  128887. hidden: true,
  128888. view: view,
  128889. // keep a reference..
  128890. editingPlugin: me,
  128891. renderTo: view.el
  128892. },
  128893. item;
  128894. for (b = 0; b < bLen; b++) {
  128895. item = btns[b];
  128896. if (Ext.isDefined(me[item])) {
  128897. cfg[item] = me[item];
  128898. }
  128899. }
  128900. return Ext.create('Ext.grid.RowEditor', cfg);
  128901. },
  128902. // private
  128903. initEditTriggers: function() {
  128904. var me = this,
  128905. view = me.view,
  128906. moveEditorEvent = me.clicksToMoveEditor === 1 ? 'click' : 'dblclick';
  128907. me.callParent(arguments);
  128908. if (me.clicksToMoveEditor !== me.clicksToEdit) {
  128909. me.mon(view, 'cell' + moveEditorEvent, me.moveEditorByClick, me);
  128910. }
  128911. view.on({
  128912. render: function() {
  128913. me.mon(me.grid.headerCt, {
  128914. scope: me,
  128915. columnresize: me.onColumnResize,
  128916. columnhide: me.onColumnHide,
  128917. columnshow: me.onColumnShow,
  128918. columnmove: me.onColumnMove
  128919. });
  128920. },
  128921. single: true
  128922. });
  128923. },
  128924. startEditByClick: function() {
  128925. var me = this;
  128926. if (!me.editing || me.clicksToMoveEditor === me.clicksToEdit) {
  128927. me.callParent(arguments);
  128928. }
  128929. },
  128930. moveEditorByClick: function() {
  128931. var me = this;
  128932. if (me.editing) {
  128933. me.superclass.onCellClick.apply(me, arguments);
  128934. }
  128935. },
  128936. // private
  128937. onColumnAdd: function(ct, column) {
  128938. if (column.isHeader) {
  128939. var me = this,
  128940. editor;
  128941. me.initFieldAccessors(column);
  128942. // Only inform the editor about a new column if the editor has already been instantiated,
  128943. // so do not use getEditor which instantiates the editor if not present.
  128944. editor = me.editor;
  128945. if (editor && editor.onColumnAdd) {
  128946. editor.onColumnAdd(column);
  128947. }
  128948. }
  128949. },
  128950. // private
  128951. onColumnRemove: function(ct, column) {
  128952. if (column.isHeader) {
  128953. var me = this,
  128954. editor = me.getEditor();
  128955. if (editor && editor.onColumnRemove) {
  128956. editor.onColumnRemove(column);
  128957. }
  128958. me.removeFieldAccessors(column);
  128959. }
  128960. },
  128961. // private
  128962. onColumnResize: function(ct, column, width) {
  128963. if (column.isHeader) {
  128964. var me = this,
  128965. editor = me.getEditor();
  128966. if (editor && editor.onColumnResize) {
  128967. editor.onColumnResize(column, width);
  128968. }
  128969. }
  128970. },
  128971. // private
  128972. onColumnHide: function(ct, column) {
  128973. // no isHeader check here since its already a columnhide event.
  128974. var me = this,
  128975. editor = me.getEditor();
  128976. if (editor && editor.onColumnHide) {
  128977. editor.onColumnHide(column);
  128978. }
  128979. },
  128980. // private
  128981. onColumnShow: function(ct, column) {
  128982. // no isHeader check here since its already a columnshow event.
  128983. var me = this,
  128984. editor = me.getEditor();
  128985. if (editor && editor.onColumnShow) {
  128986. editor.onColumnShow(column);
  128987. }
  128988. },
  128989. // private
  128990. onColumnMove: function(ct, column, fromIdx, toIdx) {
  128991. // no isHeader check here since its already a columnmove event.
  128992. var me = this,
  128993. editor = me.getEditor();
  128994. if (editor && editor.onColumnMove) {
  128995. // Must adjust the toIdx to account for removal if moving rightwards
  128996. // because RowEditor.onColumnMove just calls Container.move which does not do this.
  128997. editor.onColumnMove(column, fromIdx, toIdx - (toIdx > fromIdx ? 1 : 0));
  128998. }
  128999. },
  129000. // private
  129001. setColumnField: function(column, field) {
  129002. var me = this,
  129003. editor = me.getEditor();
  129004. editor.removeField(column);
  129005. me.callParent(arguments);
  129006. me.getEditor().setField(column);
  129007. }
  129008. });
  129009. /**
  129010. * A specialized grid implementation intended to mimic the traditional property grid as typically seen in
  129011. * development IDEs. Each row in the grid represents a property of some object, and the data is stored
  129012. * as a set of name/value pairs in {@link Ext.grid.property.Property Properties}. Example usage:
  129013. *
  129014. * @example
  129015. * Ext.create('Ext.grid.property.Grid', {
  129016. * title: 'Properties Grid',
  129017. * width: 300,
  129018. * renderTo: Ext.getBody(),
  129019. * source: {
  129020. * "(name)": "My Object",
  129021. * "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),
  129022. * "Available": false,
  129023. * "Version": 0.01,
  129024. * "Description": "A test object"
  129025. * }
  129026. * });
  129027. */
  129028. Ext.define('Ext.grid.property.Grid', {
  129029. extend: 'Ext.grid.Panel',
  129030. alias: 'widget.propertygrid',
  129031. alternateClassName: 'Ext.grid.PropertyGrid',
  129032. uses: [
  129033. 'Ext.grid.plugin.CellEditing',
  129034. 'Ext.grid.property.Store',
  129035. 'Ext.grid.property.HeaderContainer',
  129036. 'Ext.XTemplate',
  129037. 'Ext.grid.CellEditor',
  129038. 'Ext.form.field.Date',
  129039. 'Ext.form.field.Text',
  129040. 'Ext.form.field.Number',
  129041. 'Ext.form.field.ComboBox'
  129042. ],
  129043. /**
  129044. * @cfg {Object} propertyNames
  129045. * An object containing custom property name/display name pairs.
  129046. * If specified, the display name will be shown in the name column instead of the property name.
  129047. */
  129048. /**
  129049. * @cfg {Object} source
  129050. * A data object to use as the data source of the grid (see {@link #setSource} for details).
  129051. */
  129052. /**
  129053. * @cfg {Object} customEditors
  129054. * An object containing name/value pairs of custom editor type definitions that allow
  129055. * the grid to support additional types of editable fields. By default, the grid supports strongly-typed editing
  129056. * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
  129057. * associated with a custom input control by specifying a custom editor. The name of the editor
  129058. * type should correspond with the name of the property that will use the editor. Example usage:
  129059. *
  129060. * var grid = new Ext.grid.property.Grid({
  129061. *
  129062. * // Custom editors for certain property names
  129063. * customEditors: {
  129064. * evtStart: Ext.create('Ext.form.TimeField', {selectOnFocus: true})
  129065. * },
  129066. *
  129067. * // Displayed name for property names in the source
  129068. * propertyNames: {
  129069. * evtStart: 'Start Time'
  129070. * },
  129071. *
  129072. * // Data object containing properties to edit
  129073. * source: {
  129074. * evtStart: '10:00 AM'
  129075. * }
  129076. * });
  129077. */
  129078. /**
  129079. * @cfg {Object} customRenderers
  129080. * An object containing name/value pairs of custom renderer type definitions that allow
  129081. * the grid to support custom rendering of fields. By default, the grid supports strongly-typed rendering
  129082. * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
  129083. * associated with the type of the value. The name of the renderer type should correspond with the name of the property
  129084. * that it will render. Example usage:
  129085. *
  129086. * var grid = Ext.create('Ext.grid.property.Grid', {
  129087. * customRenderers: {
  129088. * Available: function(v){
  129089. * if (v) {
  129090. * return '<span style="color: green;">Yes</span>';
  129091. * } else {
  129092. * return '<span style="color: red;">No</span>';
  129093. * }
  129094. * }
  129095. * },
  129096. * source: {
  129097. * Available: true
  129098. * }
  129099. * });
  129100. */
  129101. /**
  129102. * @cfg {String} valueField
  129103. * The name of the field from the property store to use as the value field name.
  129104. * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
  129105. */
  129106. valueField: 'value',
  129107. /**
  129108. * @cfg {String} nameField
  129109. * The name of the field from the property store to use as the property field name.
  129110. * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
  129111. */
  129112. nameField: 'name',
  129113. /**
  129114. * @cfg {Number} [nameColumnWidth=115]
  129115. * Specify the width for the name column. The value column will take any remaining space.
  129116. */
  129117. // private config overrides
  129118. enableColumnMove: false,
  129119. columnLines: true,
  129120. stripeRows: false,
  129121. trackMouseOver: false,
  129122. clicksToEdit: 1,
  129123. enableHdMenu: false,
  129124. // private
  129125. initComponent : function(){
  129126. var me = this;
  129127. me.addCls(Ext.baseCSSPrefix + 'property-grid');
  129128. me.plugins = me.plugins || [];
  129129. // Enable cell editing. Inject a custom startEdit which always edits column 1 regardless of which column was clicked.
  129130. me.plugins.push(new Ext.grid.plugin.CellEditing({
  129131. clicksToEdit: me.clicksToEdit,
  129132. // Inject a startEdit which always edits the value column
  129133. startEdit: function(record, column) {
  129134. // Maintainer: Do not change this 'this' to 'me'! It is the CellEditing object's own scope.
  129135. return this.self.prototype.startEdit.call(this, record, me.headerCt.child('#' + me.valueField));
  129136. }
  129137. }));
  129138. me.selModel = {
  129139. selType: 'cellmodel',
  129140. onCellSelect: function(position) {
  129141. if (position.column != 1) {
  129142. position.column = 1;
  129143. }
  129144. return this.self.prototype.onCellSelect.call(this, position);
  129145. }
  129146. };
  129147. me.customRenderers = me.customRenderers || {};
  129148. me.customEditors = me.customEditors || {};
  129149. // Create a property.Store from the source object unless configured with a store
  129150. if (!me.store) {
  129151. me.propStore = me.store = new Ext.grid.property.Store(me, me.source);
  129152. }
  129153. if (me.sortableColumns) {
  129154. me.store.sort('name', 'ASC');
  129155. }
  129156. me.columns = new Ext.grid.property.HeaderContainer(me, me.store);
  129157. me.addEvents(
  129158. /**
  129159. * @event beforepropertychange
  129160. * Fires before a property value changes. Handlers can return false to cancel the property change
  129161. * (this will internally call {@link Ext.data.Model#reject} on the property's record).
  129162. * @param {Object} source The source data object for the grid (corresponds to the same object passed in
  129163. * as the {@link #source} config property).
  129164. * @param {String} recordId The record's id in the data store
  129165. * @param {Object} value The current edited property value
  129166. * @param {Object} oldValue The original property value prior to editing
  129167. */
  129168. 'beforepropertychange',
  129169. /**
  129170. * @event propertychange
  129171. * Fires after a property value has changed.
  129172. * @param {Object} source The source data object for the grid (corresponds to the same object passed in
  129173. * as the {@link #source} config property).
  129174. * @param {String} recordId The record's id in the data store
  129175. * @param {Object} value The current edited property value
  129176. * @param {Object} oldValue The original property value prior to editing
  129177. */
  129178. 'propertychange'
  129179. );
  129180. me.callParent();
  129181. // Inject a custom implementation of walkCells which only goes up or down
  129182. me.getView().walkCells = this.walkCells;
  129183. // Set up our default editor set for the 4 atomic data types
  129184. me.editors = {
  129185. 'date' : new Ext.grid.CellEditor({ field: new Ext.form.field.Date({selectOnFocus: true})}),
  129186. 'string' : new Ext.grid.CellEditor({ field: new Ext.form.field.Text({selectOnFocus: true})}),
  129187. 'number' : new Ext.grid.CellEditor({ field: new Ext.form.field.Number({selectOnFocus: true})}),
  129188. 'boolean' : new Ext.grid.CellEditor({ field: new Ext.form.field.ComboBox({
  129189. editable: false,
  129190. store: [[ true, me.headerCt.trueText ], [false, me.headerCt.falseText ]]
  129191. })})
  129192. };
  129193. // Track changes to the data so we can fire our events.
  129194. me.store.on('update', me.onUpdate, me);
  129195. },
  129196. // private
  129197. onUpdate : function(store, record, operation) {
  129198. var me = this,
  129199. v, oldValue;
  129200. if (me.rendered && operation == Ext.data.Model.EDIT) {
  129201. v = record.get(me.valueField);
  129202. oldValue = record.modified.value;
  129203. if (me.fireEvent('beforepropertychange', me.source, record.getId(), v, oldValue) !== false) {
  129204. if (me.source) {
  129205. me.source[record.getId()] = v;
  129206. }
  129207. record.commit();
  129208. me.fireEvent('propertychange', me.source, record.getId(), v, oldValue);
  129209. } else {
  129210. record.reject();
  129211. }
  129212. }
  129213. },
  129214. // Custom implementation of walkCells which only goes up and down.
  129215. walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
  129216. if (direction == 'left') {
  129217. direction = 'up';
  129218. } else if (direction == 'right') {
  129219. direction = 'down';
  129220. }
  129221. pos = Ext.view.Table.prototype.walkCells.call(this, pos, direction, e, preventWrap, verifierFn, scope);
  129222. if (!pos.column) {
  129223. pos.column = 1;
  129224. }
  129225. return pos;
  129226. },
  129227. // private
  129228. // returns the correct editor type for the property type, or a custom one keyed by the property name
  129229. getCellEditor : function(record, column) {
  129230. var me = this,
  129231. propName = record.get(me.nameField),
  129232. val = record.get(me.valueField),
  129233. editor = me.customEditors[propName];
  129234. // A custom editor was found. If not already wrapped with a CellEditor, wrap it, and stash it back
  129235. // If it's not even a Field, just a config object, instantiate it before wrapping it.
  129236. if (editor) {
  129237. if (!(editor instanceof Ext.grid.CellEditor)) {
  129238. if (!(editor instanceof Ext.form.field.Base)) {
  129239. editor = Ext.ComponentManager.create(editor, 'textfield');
  129240. }
  129241. editor = me.customEditors[propName] = new Ext.grid.CellEditor({ field: editor });
  129242. }
  129243. } else if (Ext.isDate(val)) {
  129244. editor = me.editors.date;
  129245. } else if (Ext.isNumber(val)) {
  129246. editor = me.editors.number;
  129247. } else if (Ext.isBoolean(val)) {
  129248. editor = me.editors['boolean'];
  129249. } else {
  129250. editor = me.editors.string;
  129251. }
  129252. // Give the editor a unique ID because the CellEditing plugin caches them
  129253. editor.editorId = propName;
  129254. return editor;
  129255. },
  129256. beforeDestroy: function() {
  129257. var me = this;
  129258. me.callParent();
  129259. me.destroyEditors(me.editors);
  129260. me.destroyEditors(me.customEditors);
  129261. delete me.source;
  129262. },
  129263. destroyEditors: function (editors) {
  129264. for (var ed in editors) {
  129265. if (editors.hasOwnProperty(ed)) {
  129266. Ext.destroy(editors[ed]);
  129267. }
  129268. }
  129269. },
  129270. /**
  129271. * Sets the source data object containing the property data. The data object can contain one or more name/value
  129272. * pairs representing all of the properties of an object to display in the grid, and this data will automatically
  129273. * be loaded into the grid's {@link #store}. The values should be supplied in the proper data type if needed,
  129274. * otherwise string type will be assumed. If the grid already contains data, this method will replace any
  129275. * existing data. See also the {@link #source} config value. Example usage:
  129276. *
  129277. * grid.setSource({
  129278. * "(name)": "My Object",
  129279. * "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'), // date type
  129280. * "Available": false, // boolean type
  129281. * "Version": .01, // decimal type
  129282. * "Description": "A test object"
  129283. * });
  129284. *
  129285. * @param {Object} source The data object
  129286. */
  129287. setSource: function(source) {
  129288. this.source = source;
  129289. this.propStore.setSource(source);
  129290. },
  129291. /**
  129292. * Gets the source data object containing the property data. See {@link #setSource} for details regarding the
  129293. * format of the data object.
  129294. * @return {Object} The data object
  129295. */
  129296. getSource: function() {
  129297. return this.propStore.getSource();
  129298. },
  129299. /**
  129300. * Sets the value of a property.
  129301. * @param {String} prop The name of the property to set
  129302. * @param {Object} value The value to test
  129303. * @param {Boolean} [create=false] True to create the property if it doesn't already exist.
  129304. */
  129305. setProperty: function(prop, value, create) {
  129306. this.propStore.setValue(prop, value, create);
  129307. },
  129308. /**
  129309. * Removes a property from the grid.
  129310. * @param {String} prop The name of the property to remove
  129311. */
  129312. removeProperty: function(prop) {
  129313. this.propStore.remove(prop);
  129314. }
  129315. /**
  129316. * @cfg store
  129317. * @private
  129318. */
  129319. /**
  129320. * @cfg columns
  129321. * @private
  129322. */
  129323. });
  129324. /**
  129325. * A custom HeaderContainer for the {@link Ext.grid.property.Grid}.
  129326. * Generally it should not need to be used directly.
  129327. */
  129328. Ext.define('Ext.grid.property.HeaderContainer', {
  129329. extend: 'Ext.grid.header.Container',
  129330. alternateClassName: 'Ext.grid.PropertyColumnModel',
  129331. nameWidth: 115,
  129332. // private - strings used for locale support
  129333. //<locale>
  129334. nameText : 'Name',
  129335. //</locale>
  129336. //<locale>
  129337. valueText : 'Value',
  129338. //</locale>
  129339. //<locale>
  129340. dateFormat : 'm/j/Y',
  129341. //</locale>
  129342. //<locale>
  129343. trueText: 'true',
  129344. //</locale>
  129345. //<locale>
  129346. falseText: 'false',
  129347. //</locale>
  129348. // private
  129349. nameColumnCls: Ext.baseCSSPrefix + 'grid-property-name',
  129350. /**
  129351. * Creates new HeaderContainer.
  129352. * @param {Ext.grid.property.Grid} grid The grid this store will be bound to
  129353. * @param {Object} source The source data config object
  129354. */
  129355. constructor : function(grid, store) {
  129356. var me = this;
  129357. me.grid = grid;
  129358. me.store = store;
  129359. me.callParent([{
  129360. items: [{
  129361. header: me.nameText,
  129362. width: grid.nameColumnWidth || me.nameWidth,
  129363. sortable: grid.sortableColumns,
  129364. dataIndex: grid.nameField,
  129365. renderer: Ext.Function.bind(me.renderProp, me),
  129366. itemId: grid.nameField,
  129367. menuDisabled :true,
  129368. tdCls: me.nameColumnCls
  129369. }, {
  129370. header: me.valueText,
  129371. renderer: Ext.Function.bind(me.renderCell, me),
  129372. getEditor: Ext.Function.bind(me.getCellEditor, me),
  129373. sortable: grid.sortableColumns,
  129374. flex: 1,
  129375. fixed: true,
  129376. dataIndex: grid.valueField,
  129377. itemId: grid.valueField,
  129378. menuDisabled: true
  129379. }]
  129380. }]);
  129381. },
  129382. getCellEditor: function(record){
  129383. return this.grid.getCellEditor(record, this);
  129384. },
  129385. // private
  129386. // Render a property name cell
  129387. renderProp : function(v) {
  129388. return this.getPropertyName(v);
  129389. },
  129390. // private
  129391. // Render a property value cell
  129392. renderCell : function(val, meta, rec) {
  129393. var me = this,
  129394. renderer = me.grid.customRenderers[rec.get(me.grid.nameField)],
  129395. result = val;
  129396. if (renderer) {
  129397. return renderer.apply(me, arguments);
  129398. }
  129399. if (Ext.isDate(val)) {
  129400. result = me.renderDate(val);
  129401. } else if (Ext.isBoolean(val)) {
  129402. result = me.renderBool(val);
  129403. }
  129404. return Ext.util.Format.htmlEncode(result);
  129405. },
  129406. // private
  129407. renderDate : Ext.util.Format.date,
  129408. // private
  129409. renderBool : function(bVal) {
  129410. return this[bVal ? 'trueText' : 'falseText'];
  129411. },
  129412. // private
  129413. // Renders custom property names instead of raw names if defined in the Grid
  129414. getPropertyName : function(name) {
  129415. var pn = this.grid.propertyNames;
  129416. return pn && pn[name] ? pn[name] : name;
  129417. }
  129418. });
  129419. /**
  129420. * A specific {@link Ext.data.Model} type that represents a name/value pair and is made to work with the
  129421. * {@link Ext.grid.property.Grid}. Typically, Properties do not need to be created directly as they can be
  129422. * created implicitly by simply using the appropriate data configs either via the
  129423. * {@link Ext.grid.property.Grid#source} config property or by calling {@link Ext.grid.property.Grid#setSource}.
  129424. * However, if the need arises, these records can also be created explicitly as shown below. Example usage:
  129425. *
  129426. * var rec = new Ext.grid.property.Property({
  129427. * name: 'birthday',
  129428. * value: Ext.Date.parse('17/06/1962', 'd/m/Y')
  129429. * });
  129430. * // Add record to an already populated grid
  129431. * grid.store.addSorted(rec);
  129432. *
  129433. * @constructor
  129434. * Creates new property.
  129435. * @param {Object} config A data object in the format:
  129436. * @param {String/String[]} config.name A name or names for the property.
  129437. * @param {Mixed/Mixed[]} config.value A value or values for the property.
  129438. * The specified value's type will be read automatically by the grid to determine the type of editor to use when
  129439. * displaying it.
  129440. * @return {Object}
  129441. */
  129442. Ext.define('Ext.grid.property.Property', {
  129443. extend: 'Ext.data.Model',
  129444. alternateClassName: 'Ext.PropGridProperty',
  129445. fields: [{
  129446. name: 'name',
  129447. type: 'string'
  129448. }, {
  129449. name: 'value'
  129450. }],
  129451. idProperty: 'name'
  129452. });
  129453. /**
  129454. * A custom {@link Ext.data.Store} for the {@link Ext.grid.property.Grid}. This class handles the mapping
  129455. * between the custom data source objects supported by the grid and the {@link Ext.grid.property.Property} format
  129456. * used by the {@link Ext.data.Store} base class.
  129457. */
  129458. Ext.define('Ext.grid.property.Store', {
  129459. extend: 'Ext.data.Store',
  129460. alternateClassName: 'Ext.grid.PropertyStore',
  129461. sortOnLoad: false,
  129462. uses: ['Ext.data.reader.Reader', 'Ext.data.proxy.Proxy', 'Ext.data.ResultSet', 'Ext.grid.property.Property'],
  129463. /**
  129464. * Creates new property store.
  129465. * @param {Ext.grid.Panel} grid The grid this store will be bound to
  129466. * @param {Object} source The source data config object
  129467. */
  129468. constructor : function(grid, source){
  129469. var me = this;
  129470. me.grid = grid;
  129471. me.source = source;
  129472. me.callParent([{
  129473. data: source,
  129474. model: Ext.grid.property.Property,
  129475. proxy: me.getProxy()
  129476. }]);
  129477. },
  129478. // Return a singleton, customized Proxy object which configures itself with a custom Reader
  129479. getProxy: function() {
  129480. if (!this.proxy) {
  129481. Ext.grid.property.Store.prototype.proxy = new Ext.data.proxy.Memory({
  129482. model: Ext.grid.property.Property,
  129483. reader: this.getReader()
  129484. });
  129485. }
  129486. return this.proxy;
  129487. },
  129488. // Return a singleton, customized Reader object which reads Ext.grid.property.Property records from an object.
  129489. getReader: function() {
  129490. if (!this.reader) {
  129491. Ext.grid.property.Store.prototype.reader = new Ext.data.reader.Reader({
  129492. model: Ext.grid.property.Property,
  129493. buildExtractors: Ext.emptyFn,
  129494. read: function(dataObject) {
  129495. return this.readRecords(dataObject);
  129496. },
  129497. readRecords: function(dataObject) {
  129498. var val,
  129499. propName,
  129500. result = {
  129501. records: [],
  129502. success: true
  129503. };
  129504. for (propName in dataObject) {
  129505. if (dataObject.hasOwnProperty(propName)) {
  129506. val = dataObject[propName];
  129507. if (this.isEditableValue(val)) {
  129508. result.records.push(new Ext.grid.property.Property({
  129509. name: propName,
  129510. value: val
  129511. }, propName));
  129512. }
  129513. }
  129514. }
  129515. result.total = result.count = result.records.length;
  129516. return new Ext.data.ResultSet(result);
  129517. },
  129518. // private
  129519. isEditableValue: function(val){
  129520. return Ext.isPrimitive(val) || Ext.isDate(val);
  129521. }
  129522. });
  129523. }
  129524. return this.reader;
  129525. },
  129526. // protected - should only be called by the grid. Use grid.setSource instead.
  129527. setSource : function(dataObject) {
  129528. var me = this;
  129529. me.source = dataObject;
  129530. me.suspendEvents();
  129531. me.removeAll();
  129532. me.proxy.data = dataObject;
  129533. me.load();
  129534. me.resumeEvents();
  129535. me.fireEvent('datachanged', me);
  129536. me.fireEvent('refresh', me);
  129537. },
  129538. // private
  129539. getProperty : function(row) {
  129540. return Ext.isNumber(row) ? this.getAt(row) : this.getById(row);
  129541. },
  129542. // private
  129543. setValue : function(prop, value, create){
  129544. var me = this,
  129545. rec = me.getRec(prop);
  129546. if (rec) {
  129547. rec.set('value', value);
  129548. me.source[prop] = value;
  129549. } else if (create) {
  129550. // only create if specified.
  129551. me.source[prop] = value;
  129552. rec = new Ext.grid.property.Property({name: prop, value: value}, prop);
  129553. me.add(rec);
  129554. }
  129555. },
  129556. // private
  129557. remove : function(prop) {
  129558. var rec = this.getRec(prop);
  129559. if (rec) {
  129560. this.callParent([rec]);
  129561. delete this.source[prop];
  129562. }
  129563. },
  129564. // private
  129565. getRec : function(prop) {
  129566. return this.getById(prop);
  129567. },
  129568. // protected - should only be called by the grid. Use grid.getSource instead.
  129569. getSource : function() {
  129570. return this.source;
  129571. }
  129572. });
  129573. /**
  129574. * This class provides a DOM ClassList API to buffer access to an element's class.
  129575. * Instances of this class are created by {@link Ext.layout.ContextItem#getClassList}.
  129576. */
  129577. Ext.define('Ext.layout.ClassList', (function () {
  129578. var splitWords = Ext.String.splitWords,
  129579. toMap = Ext.Array.toMap;
  129580. return {
  129581. dirty: false,
  129582. constructor: function (owner) {
  129583. this.owner = owner;
  129584. this.map = toMap(this.classes = splitWords(owner.el.className));
  129585. },
  129586. /**
  129587. * Adds a single class to the class list.
  129588. */
  129589. add: function (cls) {
  129590. var me = this;
  129591. if (!me.map[cls]) {
  129592. me.map[cls] = true;
  129593. me.classes.push(cls);
  129594. if (!me.dirty) {
  129595. me.dirty = true;
  129596. me.owner.markDirty();
  129597. }
  129598. }
  129599. },
  129600. /**
  129601. * Adds one or more classes in an array or space-delimited string to the class list.
  129602. */
  129603. addMany: function (classes) {
  129604. Ext.each(splitWords(classes), this.add, this);
  129605. },
  129606. contains: function (cls) {
  129607. return this.map[cls];
  129608. },
  129609. flush: function () {
  129610. this.owner.el.className = this.classes.join(' ');
  129611. this.dirty = false;
  129612. },
  129613. /**
  129614. * Removes a single class from the class list.
  129615. */
  129616. remove: function (cls) {
  129617. var me = this;
  129618. if (me.map[cls]) {
  129619. delete me.map[cls];
  129620. me.classes = Ext.Array.filter(me.classes, function (c) {
  129621. return c != cls;
  129622. });
  129623. if (!me.dirty) {
  129624. me.dirty = true;
  129625. me.owner.markDirty();
  129626. }
  129627. }
  129628. },
  129629. /**
  129630. * Removes one or more classes in an array or space-delimited string from the class
  129631. * list.
  129632. */
  129633. removeMany: function (classes) {
  129634. var me = this,
  129635. remove = toMap(splitWords(classes));
  129636. me.classes = Ext.Array.filter(me.classes, function (c) {
  129637. if (!remove[c]) {
  129638. return true;
  129639. }
  129640. delete me.map[c];
  129641. if (!me.dirty) {
  129642. me.dirty = true;
  129643. me.owner.markDirty();
  129644. }
  129645. return false;
  129646. });
  129647. }
  129648. };
  129649. }()));
  129650. /**
  129651. * An internal Queue class.
  129652. * @private
  129653. */
  129654. Ext.define('Ext.util.Queue', {
  129655. constructor: function() {
  129656. this.clear();
  129657. },
  129658. add : function(obj) {
  129659. var me = this,
  129660. key = me.getKey(obj);
  129661. if (!me.map[key]) {
  129662. ++me.length;
  129663. me.items.push(obj);
  129664. me.map[key] = obj;
  129665. }
  129666. return obj;
  129667. },
  129668. /**
  129669. * Removes all items from the collection.
  129670. */
  129671. clear : function(){
  129672. var me = this,
  129673. items = me.items;
  129674. me.items = [];
  129675. me.map = {};
  129676. me.length = 0;
  129677. return items;
  129678. },
  129679. contains: function (obj) {
  129680. var key = this.getKey(obj);
  129681. return this.map.hasOwnProperty(key);
  129682. },
  129683. /**
  129684. * Returns the number of items in the collection.
  129685. * @return {Number} the number of items in the collection.
  129686. */
  129687. getCount : function(){
  129688. return this.length;
  129689. },
  129690. getKey : function(obj){
  129691. return obj.id;
  129692. },
  129693. /**
  129694. * Remove an item from the collection.
  129695. * @param {Object} obj The item to remove.
  129696. * @return {Object} The item removed or false if no item was removed.
  129697. */
  129698. remove : function(obj){
  129699. var me = this,
  129700. key = me.getKey(obj),
  129701. items = me.items,
  129702. index;
  129703. if (me.map[key]) {
  129704. index = Ext.Array.indexOf(items, obj);
  129705. Ext.Array.erase(items, index, 1);
  129706. delete me.map[key];
  129707. --me.length;
  129708. }
  129709. return obj;
  129710. }
  129711. });
  129712. /**
  129713. * This class manages state information for a component or element during a layout.
  129714. *
  129715. * # Blocks
  129716. *
  129717. * A "block" is a required value that is preventing further calculation. When a layout has
  129718. * encountered a situation where it cannot possibly calculate results, it can associate
  129719. * itself with the context item and missing property so that it will not be rescheduled
  129720. * until that property is set.
  129721. *
  129722. * Blocks are a one-shot registration. Once the property changes, the block is removed.
  129723. *
  129724. * Be careful with blocks. If *any* further calculations can be made, a block is not the
  129725. * right choice.
  129726. *
  129727. * # Triggers
  129728. *
  129729. * Whenever any call to {@link #getProp}, {@link #getDomProp}, {@link #hasProp} or
  129730. * {@link #hasDomProp} is made, the current layout is automatically registered as being
  129731. * dependent on that property in the appropriate state. Any changes to the property will
  129732. * trigger the layout and it will be queued in the {@link Ext.layout.Context}.
  129733. *
  129734. * Triggers, once added, remain for the entire layout. Any changes to the property will
  129735. * reschedule all unfinished layouts in their trigger set.
  129736. *
  129737. * @private
  129738. */
  129739. Ext.define('Ext.layout.ContextItem', {
  129740. requires: ['Ext.layout.ClassList'],
  129741. heightModel: null,
  129742. widthModel: null,
  129743. sizeModel: null,
  129744. boxChildren: null,
  129745. boxParent: null,
  129746. children: [],
  129747. dirty: null,
  129748. // The number of dirty properties
  129749. dirtyCount: 0,
  129750. hasRawContent: true,
  129751. isContextItem: true,
  129752. isTopLevel: false,
  129753. consumersContentHeight: 0,
  129754. consumersContentWidth: 0,
  129755. consumersContainerHeight: 0,
  129756. consumersContainerWidth: 0,
  129757. consumersHeight: 0,
  129758. consumersWidth: 0,
  129759. ownerCtContext: null,
  129760. remainingChildLayouts: 0,
  129761. remainingComponentChildLayouts: 0,
  129762. remainingContainerChildLayouts: 0,
  129763. // the current set of property values:
  129764. props: null,
  129765. /**
  129766. * @property {Object} state
  129767. * State variables that are cleared when invalidated. Only applies to component items.
  129768. */
  129769. state: null,
  129770. /**
  129771. * @property {Boolean} wrapsComponent
  129772. * True if this item wraps a Component (rather than an Element).
  129773. * @readonly
  129774. */
  129775. wrapsComponent: false,
  129776. constructor: function (config) {
  129777. var me = this,
  129778. el, ownerCt, ownerCtContext, sizeModel, target;
  129779. Ext.apply(me, config);
  129780. el = me.el;
  129781. me.id = el.id;
  129782. me.lastBox = el.lastBox;
  129783. // These hold collections of layouts that are either blocked or triggered by sets
  129784. // to our properties (either ASAP or after flushing to the DOM). All of them have
  129785. // the same structure:
  129786. //
  129787. // me.blocks = {
  129788. // width: {
  129789. // 'layout-1001': layout1001
  129790. // }
  129791. // }
  129792. //
  129793. // The property name is the primary key which yields an object keyed by layout id
  129794. // with the layout instance as the value. This prevents duplicate entries for one
  129795. // layout and gives O(1) access to the layout instance when we need to iterate and
  129796. // process them.
  129797. //
  129798. // me.blocks = {};
  129799. // me.domBlocks = {};
  129800. // me.domTriggers = {};
  129801. // me.triggers = {};
  129802. me.flushedProps = {};
  129803. me.props = {};
  129804. // the set of cached styles for the element:
  129805. me.styles = {};
  129806. target = me.target;
  129807. if (target.isComponent) {
  129808. me.wrapsComponent = true;
  129809. // These items are created top-down, so the ContextItem of our ownerCt should
  129810. // be available (if it is part of this layout run).
  129811. ownerCt = target.ownerCt;
  129812. if (ownerCt && (ownerCtContext = me.context.items[ownerCt.el.id])) {
  129813. me.ownerCtContext = ownerCtContext;
  129814. }
  129815. // If our ownerCtContext is in the run, it will have a SizeModel that we use to
  129816. // optimize the determination of our sizeModel.
  129817. me.sizeModel = sizeModel = target.getSizeModel(ownerCtContext &&
  129818. ownerCtContext.widthModel.pairsByHeightOrdinal[ownerCtContext.heightModel.ordinal]);
  129819. me.widthModel = sizeModel.width;
  129820. me.heightModel = sizeModel.height;
  129821. // NOTE: The initial determination of sizeModel is valid (thankfully) and is
  129822. // needed to cope with adding components to a layout run on-the-fly (e.g., in
  129823. // the menu overflow handler of a box layout). Since this is the case, we do
  129824. // not need to recompute the sizeModel in init unless it is a "full" init (as
  129825. // our ownerCt's sizeModel could have changed in that case).
  129826. }
  129827. },
  129828. /**
  129829. * Clears all properties on this object except (perhaps) those not calculated by this
  129830. * component. This is more complex than it would seem because a layout can decide to
  129831. * invalidate its results and run the component's layouts again, but since some of the
  129832. * values may be calculated by the container, care must be taken to preserve those
  129833. * values.
  129834. *
  129835. * @param {Boolean} full True if all properties are to be invalidated, false to keep
  129836. * those calculated by the ownerCt.
  129837. * @return {Mixed} A value to pass as the first argument to {@link #initContinue}.
  129838. * @private
  129839. */
  129840. init: function (full, options) {
  129841. var me = this,
  129842. oldProps = me.props,
  129843. oldDirty = me.dirty,
  129844. ownerCtContext = me.ownerCtContext,
  129845. ownerLayout = me.target.ownerLayout,
  129846. firstTime = !me.state,
  129847. ret = full || firstTime,
  129848. children, i, n, ownerCt, sizeModel, target,
  129849. oldHeightModel = me.heightModel,
  129850. oldWidthModel = me.widthModel,
  129851. newHeightModel, newWidthModel;
  129852. me.dirty = me.invalid = false;
  129853. me.props = {};
  129854. if (me.boxChildren) {
  129855. me.boxChildren.length = 0; // keep array (more GC friendly)
  129856. }
  129857. if (!firstTime) {
  129858. me.clearAllBlocks('blocks');
  129859. me.clearAllBlocks('domBlocks');
  129860. }
  129861. // For Element wrappers, we are done...
  129862. if (!me.wrapsComponent) {
  129863. return ret;
  129864. }
  129865. // From here on, we are only concerned with Component wrappers...
  129866. target = me.target;
  129867. me.state = {}; // only Component wrappers need a "state"
  129868. if (firstTime) {
  129869. // This must occur before we proceed since it can do many things (like add
  129870. // child items perhaps):
  129871. if (target.beforeLayout) {
  129872. target.beforeLayout();
  129873. }
  129874. // Determine the ownerCtContext if we aren't given one. Normally the firstTime
  129875. // we meet a component is before the context is run, but it is possible for
  129876. // components to be added to a run that is already in progress. If so, we have
  129877. // to lookup the ownerCtContext since the odds are very high that the new
  129878. // component is a child of something already in the run. It is currently
  129879. // unsupported to drag in the owner of a running component (needs testing).
  129880. if (!ownerCtContext && (ownerCt = target.ownerCt)) {
  129881. ownerCtContext = me.context.items[ownerCt.el.id];
  129882. }
  129883. if (ownerCtContext) {
  129884. me.ownerCtContext = ownerCtContext;
  129885. me.isBoxParent = target.ownerLayout.isItemBoxParent(me);
  129886. } else {
  129887. me.isTopLevel = true; // this is used by initAnimation...
  129888. }
  129889. me.frameBodyContext = me.getEl('frameBody');
  129890. } else {
  129891. ownerCtContext = me.ownerCtContext;
  129892. // In theory (though untested), this flag can change on-the-fly...
  129893. me.isTopLevel = !ownerCtContext;
  129894. // Init the children element items since they may have dirty state (no need to
  129895. // do this the firstTime).
  129896. children = me.children;
  129897. for (i = 0, n = children.length; i < n; ++i) {
  129898. children[i].init(true);
  129899. }
  129900. }
  129901. // We need to know how we will determine content size: containers can look at the
  129902. // results of their items but non-containers or item-less containers with just raw
  129903. // markup need to be measured in the DOM:
  129904. me.hasRawContent = !(target.isContainer && target.items.items.length > 0);
  129905. if (full) {
  129906. // We must null these out or getSizeModel will assume they are the correct,
  129907. // dynamic size model and return them (the previous dynamic sizeModel).
  129908. me.widthModel = me.heightModel = null;
  129909. sizeModel = target.getSizeModel(ownerCtContext &&
  129910. ownerCtContext.widthModel.pairsByHeightOrdinal[ownerCtContext.heightModel.ordinal]);
  129911. if (firstTime) {
  129912. me.sizeModel = sizeModel;
  129913. }
  129914. me.widthModel = sizeModel.width;
  129915. me.heightModel = sizeModel.height;
  129916. } else if (oldProps) {
  129917. // these are almost always calculated by the ownerCt (we might need to track
  129918. // this at some point more carefully):
  129919. me.recoverProp('x', oldProps, oldDirty);
  129920. me.recoverProp('y', oldProps, oldDirty);
  129921. // if these are calculated by the ownerCt, don't trash them:
  129922. if (me.widthModel.calculated) {
  129923. me.recoverProp('width', oldProps, oldDirty);
  129924. }
  129925. if (me.heightModel.calculated) {
  129926. me.recoverProp('height', oldProps, oldDirty);
  129927. }
  129928. }
  129929. if (oldProps && ownerLayout && ownerLayout.manageMargins) {
  129930. me.recoverProp('margin-top', oldProps, oldDirty);
  129931. me.recoverProp('margin-right', oldProps, oldDirty);
  129932. me.recoverProp('margin-bottom', oldProps, oldDirty);
  129933. me.recoverProp('margin-left', oldProps, oldDirty);
  129934. }
  129935. // Process any invalidate options present. These can only come from explicit calls
  129936. // to the invalidate() method.
  129937. if (options) {
  129938. // Consider a container box with wrapping text. If the box is made wider, the
  129939. // text will take up less height (until there is no more wrapping). Conversely,
  129940. // if the box is made narrower, the height starts to increase due to wrapping.
  129941. //
  129942. // Imposing a minWidth constraint would increase the width. This may decrease
  129943. // the height. If the box is shrinkWrap, however, the width will already be
  129944. // such that there is no wrapping, so the height will not further decrease.
  129945. // Since the height will also not increase if we widen the box, there is no
  129946. // problem simultaneously imposing a minHeight or maxHeight constraint.
  129947. //
  129948. // When we impose as maxWidth constraint, however, we are shrinking the box
  129949. // which may increase the height. If we are imposing a maxHeight constraint,
  129950. // that is fine because a further increased height will still need to be
  129951. // constrained. But if we are imposing a minHeight constraint, we cannot know
  129952. // whether the increase in height due to wrapping will be greater than the
  129953. // minHeight. If we impose a minHeight constraint at the same time, then, we
  129954. // could easily be locking in the wrong height.
  129955. //
  129956. // It is important to note that this logic applies to simultaneously *adding*
  129957. // both a maxWidth and a minHeight constraint. It is perfectly fine to have
  129958. // a state with both constraints, but we cannot add them both at once.
  129959. newHeightModel = options.heightModel;
  129960. newWidthModel = options.widthModel;
  129961. if (newWidthModel && newHeightModel && oldWidthModel && oldHeightModel) {
  129962. if (oldWidthModel.shrinkWrap && oldHeightModel.shrinkWrap) {
  129963. if (newWidthModel.constrainedMax && newHeightModel.constrainedMin) {
  129964. newHeightModel = null;
  129965. }
  129966. }
  129967. }
  129968. // Apply size model updates (if any) and state updates (if any).
  129969. if (newWidthModel) {
  129970. me.widthModel = newWidthModel;
  129971. }
  129972. if (newHeightModel) {
  129973. me.heightModel = newHeightModel;
  129974. }
  129975. if (options.state) {
  129976. Ext.apply(me.state, options.state);
  129977. }
  129978. }
  129979. return ret;
  129980. },
  129981. /**
  129982. * @private
  129983. */
  129984. initContinue: function (full) {
  129985. var me = this,
  129986. ownerCtContext = me.ownerCtContext,
  129987. widthModel = me.widthModel,
  129988. boxParent;
  129989. if (full) {
  129990. if (ownerCtContext && widthModel.shrinkWrap) {
  129991. boxParent = ownerCtContext.isBoxParent ? ownerCtContext : ownerCtContext.boxParent;
  129992. if (boxParent) {
  129993. boxParent.addBoxChild(me);
  129994. }
  129995. } else if (widthModel.natural) {
  129996. me.boxParent = ownerCtContext;
  129997. }
  129998. }
  129999. return full;
  130000. },
  130001. /**
  130002. * @private
  130003. */
  130004. initDone: function (full, componentChildrenDone, containerChildrenDone, containerLayoutDone) {
  130005. var me = this,
  130006. props = me.props,
  130007. state = me.state;
  130008. // These properties are only set when they are true:
  130009. if (componentChildrenDone) {
  130010. props.componentChildrenDone = true;
  130011. }
  130012. if (containerChildrenDone) {
  130013. props.containerChildrenDone = true;
  130014. }
  130015. if (containerLayoutDone) {
  130016. props.containerLayoutDone = true;
  130017. }
  130018. if (me.boxChildren && me.boxChildren.length && me.widthModel.shrinkWrap) {
  130019. // set a very large width to allow the children to measure their natural
  130020. // widths (this is cleared once all children have been measured):
  130021. me.el.setWidth(10000);
  130022. // don't run layouts for this component until we clear this width...
  130023. state.blocks = (state.blocks || 0) + 1;
  130024. }
  130025. },
  130026. /**
  130027. * @private
  130028. */
  130029. initAnimation: function() {
  130030. var me = this,
  130031. target = me.target,
  130032. ownerCtContext = me.ownerCtContext;
  130033. if (ownerCtContext && ownerCtContext.isTopLevel) {
  130034. // See which properties we are supposed to animate to their new state.
  130035. // If there are any, queue ourself to be animated by the owning Context
  130036. me.animatePolicy = target.ownerLayout.getAnimatePolicy(me);
  130037. } else if (!ownerCtContext && target.isCollapsingOrExpanding && target.animCollapse) {
  130038. // Collapsing/expnding a top level Panel with animation. We need to fabricate
  130039. // an animatePolicy depending on which dimension the collapse is using,
  130040. // isCollapsingOrExpanding is set during the collapse/expand process.
  130041. me.animatePolicy = target.componentLayout.getAnimatePolicy(me);
  130042. }
  130043. if (me.animatePolicy) {
  130044. me.context.queueAnimation(me);
  130045. }
  130046. },
  130047. noFraming: { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 },
  130048. /**
  130049. * Queue the addition of a class name (or array of class names) to this ContextItem's target when next flushed.
  130050. */
  130051. addCls: function(newCls) {
  130052. this.getClassList().addMany(newCls);
  130053. },
  130054. /**
  130055. * Queue the removal of a class name (or array of class names) from this ContextItem's target when next flushed.
  130056. */
  130057. removeCls: function(removeCls) {
  130058. this.getClassList().removeMany(removeCls);
  130059. },
  130060. /**
  130061. * Adds a block.
  130062. *
  130063. * @param {String} name The name of the block list ('blocks' or 'domBlocks').
  130064. * @param {Ext.layout.Layout} layout The layout that is blocked.
  130065. * @param {String} propName The property name that blocked the layout (e.g., 'width').
  130066. * @private
  130067. */
  130068. addBlock: function (name, layout, propName) {
  130069. var me = this,
  130070. collection = me[name] || (me[name] = {}),
  130071. blockedLayouts = collection[propName] || (collection[propName] = {});
  130072. if (!blockedLayouts[layout.id]) {
  130073. blockedLayouts[layout.id] = layout;
  130074. ++layout.blockCount;
  130075. ++me.context.blockCount;
  130076. }
  130077. },
  130078. addBoxChild: function (boxChildItem) {
  130079. var me = this,
  130080. children,
  130081. widthModel = boxChildItem.widthModel;
  130082. boxChildItem.boxParent = this;
  130083. // Children that are widthModel.auto (regardless of heightModel) that measure the
  130084. // DOM (by virtue of hasRawContent), need to wait for their "box parent" to be sized.
  130085. // If they measure too early, they will be wrong results. In the widthModel.shrinkWrap
  130086. // case, the boxParent "crushes" the child. In the case of widthModel.natural, the
  130087. // boxParent's width is likely a key part of the child's width (e.g., "50%" or just
  130088. // normal block-level behavior of 100% width)
  130089. boxChildItem.measuresBox = widthModel.shrinkWrap ? boxChildItem.hasRawContent : widthModel.natural;
  130090. if (boxChildItem.measuresBox) {
  130091. children = me.boxChildren;
  130092. if (children) {
  130093. children.push(boxChildItem);
  130094. } else {
  130095. me.boxChildren = [ boxChildItem ];
  130096. }
  130097. }
  130098. },
  130099. /**
  130100. * Adds a trigger.
  130101. *
  130102. * @param {String} propName The property name that triggers the layout (e.g., 'width').
  130103. * @param {Boolean} inDom True if the trigger list is `domTriggers`, false if `triggers`.
  130104. * @private
  130105. */
  130106. addTrigger: function (propName, inDom) {
  130107. var me = this,
  130108. name = inDom ? 'domTriggers' : 'triggers',
  130109. collection = me[name] || (me[name] = {}),
  130110. context = me.context,
  130111. layout = context.currentLayout,
  130112. triggers = collection[propName] || (collection[propName] = {});
  130113. if (!triggers[layout.id]) {
  130114. triggers[layout.id] = layout;
  130115. ++layout.triggerCount;
  130116. triggers = context.triggers[inDom ? 'dom' : 'data'];
  130117. (triggers[layout.id] || (triggers[layout.id] = [])).push({
  130118. item: this,
  130119. prop: propName
  130120. });
  130121. if (me.props[propName] !== undefined) {
  130122. if (!inDom || !(me.dirty && (propName in me.dirty))) {
  130123. ++layout.firedTriggers;
  130124. }
  130125. }
  130126. }
  130127. },
  130128. boxChildMeasured: function () {
  130129. var me = this,
  130130. state = me.state,
  130131. count = (state.boxesMeasured = (state.boxesMeasured || 0) + 1);
  130132. if (count == me.boxChildren.length) {
  130133. // all of our children have measured themselves, so we can clear the width
  130134. // and resume layouts for this component...
  130135. state.clearBoxWidth = 1;
  130136. ++me.context.progressCount;
  130137. me.markDirty();
  130138. }
  130139. },
  130140. borderNames: [ 'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'],
  130141. marginNames: [ 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' ],
  130142. paddingNames: [ 'padding-top', 'padding-right', 'padding-bottom', 'padding-left' ],
  130143. trblNames: [ 'top', 'right', 'bottom', 'left' ],
  130144. cacheMissHandlers: {
  130145. borderInfo: function (me) {
  130146. var info = me.getStyles(me.borderNames, me.trblNames);
  130147. info.width = info.left + info.right;
  130148. info.height = info.top + info.bottom;
  130149. return info;
  130150. },
  130151. marginInfo: function (me) {
  130152. var info = me.getStyles(me.marginNames, me.trblNames);
  130153. info.width = info.left + info.right;
  130154. info.height = info.top + info.bottom;
  130155. return info;
  130156. },
  130157. paddingInfo: function (me) {
  130158. // if this context item's target is a framed component the padding is on the frameBody, not on the main el
  130159. var item = me.frameBodyContext || me,
  130160. info = item.getStyles(me.paddingNames, me.trblNames);
  130161. info.width = info.left + info.right;
  130162. info.height = info.top + info.bottom;
  130163. return info;
  130164. }
  130165. },
  130166. checkCache: function (entry) {
  130167. return this.cacheMissHandlers[entry](this);
  130168. },
  130169. clearAllBlocks: function (name) {
  130170. var collection = this[name],
  130171. propName;
  130172. if (collection) {
  130173. for (propName in collection) {
  130174. this.clearBlocks(name, propName);
  130175. }
  130176. }
  130177. },
  130178. /**
  130179. * Removes any blocks on a property in the specified set. Any layouts that were blocked
  130180. * by this property and are not still blocked (by other properties) will be rescheduled.
  130181. *
  130182. * @param {String} name The name of the block list ('blocks' or 'domBlocks').
  130183. * @param {String} propName The property name that blocked the layout (e.g., 'width').
  130184. * @private
  130185. */
  130186. clearBlocks: function (name, propName) {
  130187. var collection = this[name],
  130188. blockedLayouts = collection && collection[propName],
  130189. context, layout, layoutId;
  130190. if (blockedLayouts) {
  130191. delete collection[propName];
  130192. context = this.context;
  130193. for (layoutId in blockedLayouts) {
  130194. layout = blockedLayouts[layoutId];
  130195. --context.blockCount;
  130196. if (! --layout.blockCount && !layout.pending && !layout.done) {
  130197. context.queueLayout(layout);
  130198. }
  130199. }
  130200. }
  130201. },
  130202. /**
  130203. * Registers a layout in the block list for the given property. Once the property is
  130204. * set in the {@link Ext.layout.Context}, the layout is unblocked.
  130205. *
  130206. * @param {Ext.layout.Layout} layout
  130207. * @param {String} propName The property name that blocked the layout (e.g., 'width').
  130208. */
  130209. block: function (layout, propName) {
  130210. this.addBlock('blocks', layout, propName);
  130211. },
  130212. /**
  130213. * Registers a layout in the DOM block list for the given property. Once the property
  130214. * flushed to the DOM by the {@link Ext.layout.Context}, the layout is unblocked.
  130215. *
  130216. * @param {Ext.layout.Layout} layout
  130217. * @param {String} propName The property name that blocked the layout (e.g., 'width').
  130218. */
  130219. domBlock: function (layout, propName) {
  130220. this.addBlock('domBlocks', layout, propName);
  130221. },
  130222. /**
  130223. * Reschedules any layouts associated with a given trigger.
  130224. *
  130225. * @param {String} name The name of the trigger list ('triggers' or 'domTriggers').
  130226. * @param {String} propName The property name that triggers the layout (e.g., 'width').
  130227. * @private
  130228. */
  130229. fireTriggers: function (name, propName) {
  130230. var collection = this[name],
  130231. triggers = collection && collection[propName],
  130232. context = this.context,
  130233. layout, layoutId;
  130234. if (triggers) {
  130235. for (layoutId in triggers) {
  130236. layout = triggers[layoutId];
  130237. ++layout.firedTriggers;
  130238. if (!layout.done && !layout.blockCount && !layout.pending) {
  130239. context.queueLayout(layout);
  130240. }
  130241. }
  130242. }
  130243. },
  130244. /**
  130245. * Flushes any updates in the dirty collection to the DOM. This is only called if there
  130246. * are dirty entries because this object is only added to the flushQueue of the
  130247. * {@link Ext.layout.Context} when entries become dirty.
  130248. */
  130249. flush: function () {
  130250. var me = this,
  130251. dirty = me.dirty,
  130252. state = me.state,
  130253. targetEl = me.el;
  130254. me.dirtyCount = 0;
  130255. // Flush added/removed classes
  130256. if (me.classList && me.classList.dirty) {
  130257. me.classList.flush();
  130258. }
  130259. // Set any queued DOM attributes
  130260. if ('attributes' in me) {
  130261. targetEl.set(me.attributes);
  130262. delete me.attributes;
  130263. }
  130264. // Set any queued DOM HTML content
  130265. if ('innerHTML' in me) {
  130266. targetEl.innerHTML = me.innerHTML;
  130267. delete me.innerHTML;
  130268. }
  130269. if (state && state.clearBoxWidth) {
  130270. state.clearBoxWidth = 0;
  130271. me.el.setStyle('width', null);
  130272. if (! --state.blocks) {
  130273. me.context.queueItemLayouts(me);
  130274. }
  130275. }
  130276. if (dirty) {
  130277. delete me.dirty;
  130278. me.writeProps(dirty, true);
  130279. }
  130280. },
  130281. /**
  130282. * @private
  130283. */
  130284. flushAnimations: function() {
  130285. var me = this,
  130286. animateFrom = me.lastBox,
  130287. target, targetAnim, duration, animateProps, anim,
  130288. changeCount, j, propsLen, propName, oldValue, newValue;
  130289. // Only animate if the Component has been previously layed out: first layout should not animate
  130290. if (animateFrom) {
  130291. target = me.target;
  130292. targetAnim = target.layout && target.layout.animate;
  130293. if (targetAnim) {
  130294. duration = Ext.isNumber(targetAnim) ? targetAnim : targetAnim.duration;
  130295. }
  130296. animateProps = Ext.Object.getKeys(me.animatePolicy);
  130297. // Create an animation block using the targetAnim configuration to provide defaults.
  130298. // They may want custom duration, or easing, or listeners.
  130299. anim = Ext.apply({}, {
  130300. from: {},
  130301. to: {},
  130302. duration: duration || Ext.fx.Anim.prototype.duration
  130303. }, targetAnim);
  130304. for (changeCount = 0, j = 0, propsLen = animateProps.length; j < propsLen; j++) {
  130305. propName = animateProps[j];
  130306. oldValue = animateFrom[propName];
  130307. newValue = me.peek(propName);
  130308. if (oldValue != newValue) {
  130309. propName = me.translateProps[propName]||propName;
  130310. anim.from[propName] = oldValue;
  130311. anim.to[propName] = newValue;
  130312. ++changeCount;
  130313. }
  130314. }
  130315. // If any values have changed, kick off animation from the cached old values to the new values
  130316. if (changeCount) {
  130317. // It'a Panel being collapsed. rollback, and then fix the class name string
  130318. if (me.isCollapsingOrExpanding === 1) {
  130319. target.componentLayout.undoLayout(me);
  130320. }
  130321. // Otherwise, undo just the animated properties so the animation can proceed from the old layout.
  130322. else {
  130323. me.writeProps(anim.from);
  130324. }
  130325. me.el.animate(anim);
  130326. Ext.fx.Manager.getFxQueue(me.el.id)[0].on({
  130327. afteranimate: function() {
  130328. if (me.isCollapsingOrExpanding === 1) {
  130329. target.componentLayout.redoLayout(me);
  130330. target.afterCollapse(true);
  130331. } else if (me.isCollapsingOrExpanding === 2) {
  130332. target.afterExpand(true);
  130333. }
  130334. }
  130335. });
  130336. }
  130337. }
  130338. },
  130339. /**
  130340. * Gets the border information for the element as an object with left, top, right and
  130341. * bottom properties holding border size in pixels. This object is only read from the
  130342. * DOM on first request and is cached.
  130343. * @return {Object}
  130344. */
  130345. getBorderInfo: function () {
  130346. var me = this,
  130347. info = me.borderInfo;
  130348. if (!info) {
  130349. me.borderInfo = info = me.checkCache('borderInfo');
  130350. }
  130351. return info;
  130352. },
  130353. /**
  130354. * Returns a ClassList-like object to buffer access to this item's element's classes.
  130355. */
  130356. getClassList: function () {
  130357. return this.classList || (this.classList = new Ext.layout.ClassList(this));
  130358. },
  130359. /**
  130360. * Returns the context item for an owned element. This should only be called on a
  130361. * component's item. The list of child items is used to manage invalidating calculated
  130362. * results.
  130363. */
  130364. getEl: function (nameOrEl, owner) {
  130365. var me = this,
  130366. src, el, elContext;
  130367. if (nameOrEl) {
  130368. if (nameOrEl.dom) {
  130369. el = nameOrEl;
  130370. } else {
  130371. src = me.target;
  130372. if (owner) {
  130373. src = owner;
  130374. }
  130375. el = src[nameOrEl];
  130376. if (typeof el == 'function') { // ex 'getTarget'
  130377. el = el.call(src);
  130378. if (el === me.el) {
  130379. return this; // comp.getTarget() often returns comp.el
  130380. }
  130381. }
  130382. }
  130383. if (el) {
  130384. elContext = me.context.getEl(me, el);
  130385. }
  130386. }
  130387. return elContext || null;
  130388. },
  130389. getFraming: function () {
  130390. var me = this;
  130391. if (!me.framingInfo) {
  130392. me.framingInfo = me.target.frameSize || me.noFraming;
  130393. }
  130394. return me.framingInfo;
  130395. },
  130396. /**
  130397. * Gets the "frame" information for the element as an object with left, top, right and
  130398. * bottom properties holding border+framing size in pixels. This object is calculated
  130399. * on first request and is cached.
  130400. * @return {Object}
  130401. */
  130402. getFrameInfo: function () {
  130403. var me = this,
  130404. info = me.frameInfo,
  130405. frame, border;
  130406. if (!info) {
  130407. frame = me.getFraming();
  130408. border = me.getBorderInfo();
  130409. me.frameInfo = info = {
  130410. top : frame.top + border.top,
  130411. right : frame.right + border.right,
  130412. bottom: frame.bottom + border.bottom,
  130413. left : frame.left + border.left,
  130414. width : frame.width + border.width,
  130415. height: frame.height + border.height
  130416. };
  130417. }
  130418. return info;
  130419. },
  130420. /**
  130421. * Gets the margin information for the element as an object with left, top, right and
  130422. * bottom properties holding margin size in pixels. This object is only read from the
  130423. * DOM on first request and is cached.
  130424. * @return {Object}
  130425. */
  130426. getMarginInfo: function () {
  130427. var me = this,
  130428. info = me.marginInfo,
  130429. comp, manageMargins, margins, ownerLayout, ownerLayoutId;
  130430. if (!info) {
  130431. if (!me.wrapsComponent) {
  130432. info = me.checkCache('marginInfo');
  130433. } else {
  130434. comp = me.target;
  130435. ownerLayout = comp.ownerLayout;
  130436. ownerLayoutId = ownerLayout ? ownerLayout.id : null;
  130437. manageMargins = ownerLayout && ownerLayout.manageMargins;
  130438. // Option #1 for configuring margins on components is the "margin" config
  130439. // property. When supplied, this config is written to the DOM during the
  130440. // render process (see AbstractComponent#initStyles).
  130441. //
  130442. // Option #2 is available to some layouts (e.g., Box, Border, Fit) that
  130443. // handle margin calculations themselves. These layouts support a "margins"
  130444. // config property on their items and they have a "defaultMargins" config
  130445. // property. These margin values are added to the "natural" margins read
  130446. // from the DOM and 0's are written to the DOM after they are added.
  130447. // To avoid having to do all this on every layout, we cache the results on
  130448. // the component in the (private) "margin$" property. We identify the cache
  130449. // results as belonging to the appropriate ownerLayout in case items are
  130450. // moved around.
  130451. info = comp.margin$;
  130452. if (info && info.ownerId !== ownerLayoutId) {
  130453. // got one but from the wrong owner
  130454. info = null;
  130455. // if (manageMargins) {
  130456. // TODO: clear inline margins (the 0's we wrote last time)???
  130457. // }
  130458. }
  130459. if (!info) { // if (no cache)
  130460. // CSS margins are only checked if there isn't a margin property on the component
  130461. info = me.parseMargins(comp.margin) || me.checkCache('marginInfo');
  130462. // Some layouts also support margins and defaultMargins, e.g. Fit, Border, Box
  130463. if (manageMargins) {
  130464. margins = me.parseMargins(comp.margins, ownerLayout.defaultMargins);
  130465. if (margins) { // if (using 'margins' and/or 'defaultMargins')
  130466. // margin and margins can both be present at the same time and must be combined
  130467. info = {
  130468. top: info.top + margins.top,
  130469. right: info.right + margins.right,
  130470. bottom: info.bottom + margins.bottom,
  130471. left: info.left + margins.left
  130472. };
  130473. }
  130474. me.setProp('margin-top', 0);
  130475. me.setProp('margin-right', 0);
  130476. me.setProp('margin-bottom', 0);
  130477. me.setProp('margin-left', 0);
  130478. }
  130479. // cache the layout margins and tag them with the layout id:
  130480. info.ownerId = ownerLayoutId;
  130481. comp.margin$ = info;
  130482. }
  130483. info.width = info.left + info.right;
  130484. info.height = info.top + info.bottom;
  130485. }
  130486. me.marginInfo = info;
  130487. }
  130488. return info;
  130489. },
  130490. /**
  130491. * clears the margin cache so that marginInfo get re-read from the dom on the next call to getMarginInfo()
  130492. * This is needed in some special cases where the margins have changed since the last layout, making the cached
  130493. * values invalid. For example collapsed window headers have different margin than expanded ones.
  130494. */
  130495. clearMarginCache: function() {
  130496. delete this.marginInfo;
  130497. delete this.target.margin$;
  130498. },
  130499. /**
  130500. * Gets the padding information for the element as an object with left, top, right and
  130501. * bottom properties holding padding size in pixels. This object is only read from the
  130502. * DOM on first request and is cached.
  130503. * @return {Object}
  130504. */
  130505. getPaddingInfo: function () {
  130506. var me = this,
  130507. info = me.paddingInfo;
  130508. if (!info) {
  130509. me.paddingInfo = info = me.checkCache('paddingInfo');
  130510. }
  130511. return info;
  130512. },
  130513. /**
  130514. * Gets a property of this object. Also tracks the current layout as dependent on this
  130515. * property so that changes to it will trigger the layout to be recalculated.
  130516. * @param {String} propName The property name that blocked the layout (e.g., 'width').
  130517. * @return {Object} The property value or undefined if not yet set.
  130518. */
  130519. getProp: function (propName) {
  130520. var me = this,
  130521. result = me.props[propName];
  130522. me.addTrigger(propName);
  130523. return result;
  130524. },
  130525. /**
  130526. * Gets a property of this object if it is correct in the DOM. Also tracks the current
  130527. * layout as dependent on this property so that DOM writes of it will trigger the
  130528. * layout to be recalculated.
  130529. * @param {String} propName The property name (e.g., 'width').
  130530. * @return {Object} The property value or undefined if not yet set or is dirty.
  130531. */
  130532. getDomProp: function (propName) {
  130533. var me = this,
  130534. result = (me.dirty && (propName in me.dirty)) ? undefined : me.props[propName];
  130535. me.addTrigger(propName, true);
  130536. return result;
  130537. },
  130538. /**
  130539. * Returns a style for this item. Each style is read from the DOM only once on first
  130540. * request and is then cached. If the value is an integer, it is parsed automatically
  130541. * (so '5px' is not returned, but rather 5).
  130542. *
  130543. * @param {String} styleName The CSS style name.
  130544. * @return {Object} The value of the DOM style (parsed as necessary).
  130545. */
  130546. getStyle: function (styleName) {
  130547. var me = this,
  130548. styles = me.styles,
  130549. info, value;
  130550. if (styleName in styles) {
  130551. value = styles[styleName];
  130552. } else {
  130553. info = me.styleInfo[styleName];
  130554. value = me.el.getStyle(styleName);
  130555. if (info && info.parseInt) {
  130556. value = parseInt(value, 10) || 0;
  130557. }
  130558. styles[styleName] = value;
  130559. }
  130560. return value;
  130561. },
  130562. /**
  130563. * Returns styles for this item. Each style is read from the DOM only once on first
  130564. * request and is then cached. If the value is an integer, it is parsed automatically
  130565. * (so '5px' is not returned, but rather 5).
  130566. *
  130567. * @param {String[]} styleNames The CSS style names.
  130568. * @param {String[]} [altNames] The alternate names for the returned styles. If given,
  130569. * these names must correspond one-for-one to the `styleNames`.
  130570. * @return {Object} The values of the DOM styles (parsed as necessary).
  130571. */
  130572. getStyles: function (styleNames, altNames) {
  130573. var me = this,
  130574. styleCache = me.styles,
  130575. values = {},
  130576. hits = 0,
  130577. n = styleNames.length,
  130578. i, missing, missingAltNames, name, info, styleInfo, styles, value;
  130579. altNames = altNames || styleNames;
  130580. // We are optimizing this for all hits or all misses. If we hit on all styles, we
  130581. // don't create a missing[]. If we miss on all styles, we also don't create one.
  130582. for (i = 0; i < n; ++i) {
  130583. name = styleNames[i];
  130584. if (name in styleCache) {
  130585. values[altNames[i]] = styleCache[name];
  130586. ++hits;
  130587. if (i && hits==1) { // if (first hit was after some misses)
  130588. missing = styleNames.slice(0, i);
  130589. missingAltNames = altNames.slice(0, i);
  130590. }
  130591. } else if (hits) {
  130592. (missing || (missing = [])).push(name);
  130593. (missingAltNames || (missingAltNames = [])).push(altNames[i]);
  130594. }
  130595. }
  130596. if (hits < n) {
  130597. missing = missing || styleNames;
  130598. missingAltNames = missingAltNames || altNames;
  130599. styleInfo = me.styleInfo;
  130600. styles = me.el.getStyle(missing);
  130601. for (i = missing.length; i--; ) {
  130602. name = missing[i];
  130603. info = styleInfo[name];
  130604. value = styles[name];
  130605. if (info && info.parseInt) {
  130606. value = parseInt(value, 10) || 0;
  130607. }
  130608. values[missingAltNames[i]] = value;
  130609. styleCache[name] = value;
  130610. }
  130611. }
  130612. return values;
  130613. },
  130614. /**
  130615. * Returns true if the given property has been set. This is equivalent to calling
  130616. * {@link #getProp} and not getting an undefined result. In particular, this call
  130617. * registers the current layout to be triggered by changes to this property.
  130618. *
  130619. * @param {String} propName The property name (e.g., 'width').
  130620. * @return {Boolean}
  130621. */
  130622. hasProp: function (propName) {
  130623. var value = this.getProp(propName);
  130624. return typeof value != 'undefined';
  130625. },
  130626. /**
  130627. * Returns true if the given property is correct in the DOM. This is equivalent to
  130628. * calling {@link #getDomProp} and not getting an undefined result. In particular,
  130629. * this call registers the current layout to be triggered by flushes of this property.
  130630. *
  130631. * @param {String} propName The property name (e.g., 'width').
  130632. * @return {Boolean}
  130633. */
  130634. hasDomProp: function (propName) {
  130635. var value = this.getDomProp(propName);
  130636. return typeof value != 'undefined';
  130637. },
  130638. /**
  130639. * Invalidates the component associated with this item. The layouts for this component
  130640. * and all of its contained items will be re-run after first clearing any computed
  130641. * values.
  130642. *
  130643. * If state needs to be carried forward beyond the invalidation, the `options` parameter
  130644. * can be used.
  130645. *
  130646. * @param {Object} options An object describing how to handle the invalidation.
  130647. * @param {Object} options.state An object to {@link Ext#apply} to the {@link #state}
  130648. * of this item after invalidation clears all other properties.
  130649. * @param {Function} options.before A function to call after the context data is cleared
  130650. * and before the {@link Ext.layout.Layout#beginLayoutCycle} methods are called.
  130651. * @param {Ext.layout.ContextItem} options.before.item This ContextItem.
  130652. * @param {Object} options.before.options The options object passed to {@link #invalidate}.
  130653. * @param {Function} options.after A function to call after the context data is cleared
  130654. * and after the {@link Ext.layout.Layout#beginLayoutCycle} methods are called.
  130655. * @param {Ext.layout.ContextItem} options.after.item This ContextItem.
  130656. * @param {Object} options.after.options The options object passed to {@link #invalidate}.
  130657. * @param {Object} options.scope The scope to use when calling the callback functions.
  130658. */
  130659. invalidate: function (options) {
  130660. this.context.queueInvalidate(this, options);
  130661. },
  130662. markDirty: function () {
  130663. if (++this.dirtyCount == 1) {
  130664. // our first dirty property... queue us for flush
  130665. this.context.queueFlush(this);
  130666. }
  130667. },
  130668. onBoxMeasured: function () {
  130669. var boxParent = this.boxParent,
  130670. state = this.state;
  130671. if (boxParent && boxParent.widthModel.shrinkWrap && !state.boxMeasured && this.measuresBox) {
  130672. // since an autoWidth boxParent is holding a width on itself to allow each
  130673. // child to measure
  130674. state.boxMeasured = 1; // best to only call once per child
  130675. boxParent.boxChildMeasured();
  130676. }
  130677. },
  130678. parseMargins: function (margins, defaultMargins) {
  130679. if (margins === true) {
  130680. margins = 5;
  130681. }
  130682. var type = typeof margins,
  130683. ret;
  130684. if (type == 'string' || type == 'number') {
  130685. ret = Ext.util.Format.parseBox(margins);
  130686. } else if (margins || defaultMargins) {
  130687. ret = { top: 0, right: 0, bottom: 0, left: 0 }; // base defaults
  130688. if (defaultMargins) {
  130689. Ext.apply(ret, this.parseMargins(defaultMargins)); // + layout defaults
  130690. }
  130691. Ext.apply(ret, margins); // + config
  130692. }
  130693. return ret;
  130694. },
  130695. peek: function (propName) {
  130696. return this.props[propName];
  130697. },
  130698. /**
  130699. * Recovers a property value from the last computation and restores its value and
  130700. * dirty state.
  130701. *
  130702. * @param {String} propName The name of the property to recover.
  130703. * @param {Object} oldProps The old "props" object from which to recover values.
  130704. * @param {Object} oldDirty The old "dirty" object from which to recover state.
  130705. */
  130706. recoverProp: function (propName, oldProps, oldDirty) {
  130707. var me = this,
  130708. props = me.props,
  130709. dirty;
  130710. if (propName in oldProps) {
  130711. props[propName] = oldProps[propName];
  130712. if (oldDirty && propName in oldDirty) {
  130713. dirty = me.dirty || (me.dirty = {});
  130714. dirty[propName] = oldDirty[propName];
  130715. }
  130716. }
  130717. },
  130718. redo: function(deep) {
  130719. var me = this,
  130720. items, len, i;
  130721. me.revertProps(me.props);
  130722. if (deep && me.wrapsComponent) {
  130723. // Rollback the state of child Components
  130724. if (me.childItems) {
  130725. for (i = 0, items = me.childItems, len = items.length; i < len; i++) {
  130726. items[i].redo(deep);
  130727. }
  130728. }
  130729. // Rollback the state of child Elements
  130730. for (i = 0, items = me.children, len = items.length; i < len; i++) {
  130731. items[i].redo();
  130732. }
  130733. }
  130734. },
  130735. revertProps: function (props) {
  130736. var name,
  130737. flushed = this.flushedProps,
  130738. reverted = {};
  130739. for (name in props) {
  130740. if (flushed.hasOwnProperty(name)) {
  130741. reverted[name] = props[name];
  130742. }
  130743. }
  130744. this.writeProps(reverted);
  130745. },
  130746. /**
  130747. * Queue the setting of a DOM attribute on this ContextItem's target when next flushed.
  130748. */
  130749. setAttribute: function(name, value) {
  130750. var me = this;
  130751. if (!me.attributes) {
  130752. me.attributes = {};
  130753. }
  130754. me.attributes[name] = value;
  130755. me.markDirty();
  130756. },
  130757. setBox: function (box) {
  130758. var me = this;
  130759. if ('left' in box) {
  130760. me.setProp('x', box.left);
  130761. }
  130762. if ('top' in box) {
  130763. me.setProp('y', box.top);
  130764. }
  130765. // if sizeModel says we should not be setting these, the appropriate calls will be
  130766. // null operations... otherwise, we must set these values, so what we have in box
  130767. // is what we go with (undefined, NaN and no change are handled at a lower level):
  130768. me.setSize(box.width, box.height);
  130769. },
  130770. /**
  130771. * Sets the contentHeight property. If the component uses raw content, then only the
  130772. * measured height is acceptable.
  130773. *
  130774. * Calculated values can sometimes be NaN or undefined, which generally mean the
  130775. * calculation is not done. To indicate that such as value was passed, 0 is returned.
  130776. * Otherwise, 1 is returned.
  130777. *
  130778. * If the caller is not measuring (i.e., they are calculating) and the component has raw
  130779. * content, 1 is returned indicating that the caller is done.
  130780. */
  130781. setContentHeight: function (height, measured) {
  130782. if (!measured && this.hasRawContent) {
  130783. return 1;
  130784. }
  130785. return this.setProp('contentHeight', height);
  130786. },
  130787. /**
  130788. * Sets the contentWidth property. If the component uses raw content, then only the
  130789. * measured width is acceptable.
  130790. *
  130791. * Calculated values can sometimes be NaN or undefined, which generally means that the
  130792. * calculation is not done. To indicate that such as value was passed, 0 is returned.
  130793. * Otherwise, 1 is returned.
  130794. *
  130795. * If the caller is not measuring (i.e., they are calculating) and the component has raw
  130796. * content, 1 is returned indicating that the caller is done.
  130797. */
  130798. setContentWidth: function (width, measured) {
  130799. if (!measured && this.hasRawContent) {
  130800. return 1;
  130801. }
  130802. return this.setProp('contentWidth', width);
  130803. },
  130804. /**
  130805. * Sets the contentWidth and contentHeight properties. If the component uses raw content,
  130806. * then only the measured values are acceptable.
  130807. *
  130808. * Calculated values can sometimes be NaN or undefined, which generally means that the
  130809. * calculation is not done. To indicate that either passed value was such a value, false
  130810. * returned. Otherwise, true is returned.
  130811. *
  130812. * If the caller is not measuring (i.e., they are calculating) and the component has raw
  130813. * content, true is returned indicating that the caller is done.
  130814. */
  130815. setContentSize: function (width, height, measured) {
  130816. return this.setContentWidth(width, measured) +
  130817. this.setContentHeight(height, measured) == 2;
  130818. },
  130819. /**
  130820. * Sets a property value. This will unblock and/or trigger dependent layouts if the
  130821. * property value is being changed. Values of NaN and undefined are not accepted by
  130822. * this method.
  130823. *
  130824. * @param {String} propName The property name (e.g., 'width').
  130825. * @param {Object} value The new value of the property.
  130826. * @param {Boolean} dirty Optionally specifies if the value is currently in the DOM
  130827. * (default is `true` which indicates the value is not in the DOM and must be flushed
  130828. * at some point).
  130829. * @return {Number} 1 if this call specified the property value, 0 if not.
  130830. */
  130831. setProp: function (propName, value, dirty) {
  130832. var me = this,
  130833. valueType = typeof value,
  130834. borderBox, info;
  130835. if (valueType == 'undefined' || (valueType === 'number' && isNaN(value))) {
  130836. return 0;
  130837. }
  130838. if (me.props[propName] === value) {
  130839. return 1;
  130840. }
  130841. me.props[propName] = value;
  130842. ++me.context.progressCount;
  130843. if (dirty === false) {
  130844. // if the prop is equivalent to what is in the DOM (we won't be writing it),
  130845. // we need to clear hard blocks (domBlocks) on that property.
  130846. me.fireTriggers('domTriggers', propName);
  130847. me.clearBlocks('domBlocks', propName);
  130848. } else {
  130849. info = me.styleInfo[propName];
  130850. if (info) {
  130851. if (!me.dirty) {
  130852. me.dirty = {};
  130853. }
  130854. if (propName == 'width' || propName == 'height') {
  130855. borderBox = me.isBorderBoxValue;
  130856. if (borderBox == null) {
  130857. me.isBorderBoxValue = borderBox = !!me.el.isBorderBox();
  130858. }
  130859. if (!borderBox) {
  130860. me.borderInfo || me.getBorderInfo();
  130861. me.paddingInfo || me.getPaddingInfo();
  130862. }
  130863. }
  130864. me.dirty[propName] = value;
  130865. me.markDirty();
  130866. }
  130867. }
  130868. // we always clear soft blocks on set
  130869. me.fireTriggers('triggers', propName);
  130870. me.clearBlocks('blocks', propName);
  130871. return 1;
  130872. },
  130873. /**
  130874. * Sets the height and constrains the height to min/maxHeight range.
  130875. *
  130876. * @param {Number} height The height.
  130877. * @param {Boolean} [dirty=true] Specifies if the value is currently in the DOM. A
  130878. * value of `false` indicates that the value is already in the DOM.
  130879. * @return {Number} The actual height after constraining.
  130880. */
  130881. setHeight: function (height, dirty /*, private {Boolean} force */) {
  130882. var me = this,
  130883. comp = me.target,
  130884. frameBody, frameInfo, padding;
  130885. if (height < 0) {
  130886. height = 0;
  130887. }
  130888. if (!me.wrapsComponent) {
  130889. if (!me.setProp('height', height, dirty)) {
  130890. return NaN;
  130891. }
  130892. } else {
  130893. height = Ext.Number.constrain(height, comp.minHeight || 0, comp.maxHeight);
  130894. if (!me.setProp('height', height, dirty)) {
  130895. return NaN;
  130896. }
  130897. frameBody = me.frameBodyContext;
  130898. if (frameBody){
  130899. frameInfo = me.getFrameInfo();
  130900. frameBody.setHeight(height - frameInfo.height, dirty);
  130901. }
  130902. }
  130903. return height;
  130904. },
  130905. /**
  130906. * Sets the height and constrains the width to min/maxWidth range.
  130907. *
  130908. * @param {Number} width The width.
  130909. * @param {Boolean} [dirty=true] Specifies if the value is currently in the DOM. A
  130910. * value of `false` indicates that the value is already in the DOM.
  130911. * @return {Number} The actual width after constraining.
  130912. */
  130913. setWidth: function (width, dirty /*, private {Boolean} force */) {
  130914. var me = this,
  130915. comp = me.target,
  130916. frameBody, frameInfo, padding;
  130917. if (width < 0) {
  130918. width = 0;
  130919. }
  130920. if (!me.wrapsComponent) {
  130921. if (!me.setProp('width', width, dirty)) {
  130922. return NaN;
  130923. }
  130924. } else {
  130925. width = Ext.Number.constrain(width, comp.minWidth || 0, comp.maxWidth);
  130926. if (!me.setProp('width', width, dirty)) {
  130927. return NaN;
  130928. }
  130929. //if ((frameBody = me.target.frameBody) && (frameBody = me.getEl(frameBody))){
  130930. frameBody = me.frameBodyContext;
  130931. if (frameBody) {
  130932. frameInfo = me.getFrameInfo();
  130933. frameBody.setWidth(width - frameInfo.width, dirty);
  130934. }
  130935. /*if (owner.frameMC) {
  130936. frameContext = ownerContext.frameContext ||
  130937. (ownerContext.frameContext = ownerContext.getEl('frameMC'));
  130938. width += (frameContext.paddingInfo || frameContext.getPaddingInfo()).width;
  130939. }*/
  130940. }
  130941. return width;
  130942. },
  130943. setSize: function (width, height, dirty) {
  130944. this.setWidth(width, dirty);
  130945. this.setHeight(height, dirty);
  130946. },
  130947. translateProps: {
  130948. x: 'left',
  130949. y: 'top'
  130950. },
  130951. undo: function(deep) {
  130952. var me = this,
  130953. items, len, i;
  130954. me.revertProps(me.lastBox);
  130955. if (deep && me.wrapsComponent) {
  130956. // Rollback the state of child Components
  130957. if (me.childItems) {
  130958. for (i = 0, items = me.childItems, len = items.length; i < len; i++) {
  130959. items[i].undo(deep);
  130960. }
  130961. }
  130962. // Rollback the state of child Elements
  130963. for (i = 0, items = me.children, len = items.length; i < len; i++) {
  130964. items[i].undo();
  130965. }
  130966. }
  130967. },
  130968. unsetProp: function (propName) {
  130969. var dirty = this.dirty;
  130970. delete this.props[propName];
  130971. if (dirty) {
  130972. delete dirty[propName];
  130973. }
  130974. },
  130975. writeProps: function(dirtyProps, flushing) {
  130976. if (!(dirtyProps && typeof dirtyProps == 'object')) {
  130977. Ext.Logger.warn('writeProps expected dirtyProps to be an object');
  130978. return;
  130979. }
  130980. var me = this,
  130981. el = me.el,
  130982. styles = {},
  130983. styleCount = 0, // used as a boolean, the exact count doesn't matter
  130984. styleInfo = me.styleInfo,
  130985. info,
  130986. propName,
  130987. numericValue,
  130988. dirtyX = 'x' in dirtyProps,
  130989. dirtyY = 'y' in dirtyProps,
  130990. x = dirtyProps.x,
  130991. y = dirtyProps.y,
  130992. width = dirtyProps.width,
  130993. height = dirtyProps.height,
  130994. isBorderBox = me.isBorderBoxValue,
  130995. target = me.target,
  130996. max = Math.max,
  130997. paddingWidth = 0,
  130998. paddingHeight = 0,
  130999. hasWidth, hasHeight, isAbsolute, scrollbarSize, style, targetEl;
  131000. // Process non-style properties:
  131001. if ('displayed' in dirtyProps) {
  131002. el.setDisplayed(dirtyProps.displayed);
  131003. }
  131004. // Unblock any hard blocks (domBlocks) and copy dom styles into 'styles'
  131005. for (propName in dirtyProps) {
  131006. if (flushing) {
  131007. me.fireTriggers('domTriggers', propName);
  131008. me.clearBlocks('domBlocks', propName);
  131009. me.flushedProps[propName] = 1;
  131010. }
  131011. info = styleInfo[propName];
  131012. if (info && info.dom) {
  131013. // Numeric dirty values should have their associated suffix added
  131014. if (info.suffix && (numericValue = parseInt(dirtyProps[propName], 10))) {
  131015. styles[propName] = numericValue + info.suffix;
  131016. }
  131017. // Non-numeric (eg "auto") go in unchanged.
  131018. else {
  131019. styles[propName] = dirtyProps[propName];
  131020. }
  131021. ++styleCount;
  131022. }
  131023. }
  131024. // convert x/y into setPosition (for a component) or left/top styles (for an el)
  131025. if (dirtyX || dirtyY) {
  131026. if (target.isComponent) {
  131027. // Ensure we always pass the current coordinate in if one coordinate has not been dirtied by a calculation cycle.
  131028. target.setPosition(x||me.props.x, y||me.props.y);
  131029. } else {
  131030. // we wrap an element, so convert x/y to styles:
  131031. if (dirtyX) {
  131032. styles.left = x + 'px';
  131033. ++styleCount;
  131034. }
  131035. if (dirtyY) {
  131036. styles.top = y + 'px';
  131037. ++styleCount;
  131038. }
  131039. }
  131040. }
  131041. // Support for the content-box box model...
  131042. if (!isBorderBox && (width > 0 || height > 0)) { // no need to subtract from 0
  131043. // The width and height values assume the border-box box model,
  131044. // so we must remove the padding & border to calculate the content-box.
  131045. if (!(me.borderInfo && me.paddingInfo)) {
  131046. throw Error("Needed to have gotten the borderInfo and paddingInfo when the width or height was setProp'd");
  131047. }
  131048. if(!me.frameBodyContext) {
  131049. // Padding needs to be removed only if the element is not framed.
  131050. paddingWidth = me.paddingInfo.width;
  131051. paddingHeight = me.paddingInfo.height;
  131052. }
  131053. if (width) {
  131054. width = max(parseInt(width, 10) - (me.borderInfo.width + paddingWidth), 0);
  131055. styles.width = width + 'px';
  131056. ++styleCount;
  131057. }
  131058. if (height) {
  131059. height = max(parseInt(height, 10) - (me.borderInfo.height + paddingHeight), 0);
  131060. styles.height = height + 'px';
  131061. ++styleCount;
  131062. }
  131063. }
  131064. // IE9 strict subtracts the scrollbar size from the element size when the element
  131065. // is absolutely positioned and uses box-sizing: border-box. To workaround this
  131066. // issue we have to add the the scrollbar size.
  131067. //
  131068. // See http://social.msdn.microsoft.com/Forums/da-DK/iewebdevelopment/thread/47c5148f-a142-4a99-9542-5f230c78cb3b
  131069. //
  131070. if (me.wrapsComponent && Ext.isIE9 && Ext.isStrict) {
  131071. // when we set a width and we have a vertical scrollbar (overflowY), we need
  131072. // to add the scrollbar width... conversely for the height and overflowX
  131073. if ((hasWidth = width !== undefined && me.hasOverflowY) ||
  131074. (hasHeight = height !== undefined && me.hasOverflowX)) {
  131075. // check that the component is absolute positioned and border-box:
  131076. isAbsolute = me.isAbsolute;
  131077. if (isAbsolute === undefined) {
  131078. isAbsolute = false;
  131079. targetEl = me.target.getTargetEl();
  131080. style = targetEl.getStyle('position');
  131081. if (style == 'absolute') {
  131082. style = targetEl.getStyle('box-sizing');
  131083. isAbsolute = (style == 'border-box');
  131084. }
  131085. me.isAbsolute = isAbsolute; // cache it
  131086. }
  131087. if (isAbsolute) {
  131088. scrollbarSize = Ext.getScrollbarSize();
  131089. if (hasWidth) {
  131090. width = parseInt(width, 10) + scrollbarSize.width;
  131091. styles.width = width + 'px';
  131092. ++styleCount;
  131093. }
  131094. if (hasHeight) {
  131095. height = parseInt(height, 10) + scrollbarSize.height;
  131096. styles.height = height + 'px';
  131097. ++styleCount;
  131098. }
  131099. }
  131100. }
  131101. }
  131102. // we make only one call to setStyle to allow it to optimize itself:
  131103. if (styleCount) {
  131104. el.setStyle(styles);
  131105. }
  131106. }
  131107. }, function () {
  131108. var px = { dom: true, parseInt: true, suffix: 'px' },
  131109. isDom = { dom: true },
  131110. faux = { dom: false };
  131111. // If a property exists in styleInfo, it participates in some way with the DOM. It may
  131112. // be virtualized (like 'x' and y') and be indirect, but still requires a flush cycle
  131113. // to reach the DOM. Properties (like 'contentWidth' and 'contentHeight') have no real
  131114. // presence in the DOM and hence have no flush intanglements.
  131115. //
  131116. // For simple styles, the object value on the right contains properties that help in
  131117. // decoding values read by getStyle and preparing values to pass to setStyle.
  131118. //
  131119. this.prototype.styleInfo = {
  131120. childrenDone: faux,
  131121. componentChildrenDone: faux,
  131122. containerChildrenDone: faux,
  131123. containerLayoutDone: faux,
  131124. displayed: faux,
  131125. done: faux,
  131126. x: faux,
  131127. y: faux,
  131128. // For Ext.grid.ColumnLayout
  131129. columnWidthsDone: faux,
  131130. left: px,
  131131. top: px,
  131132. right: px,
  131133. bottom: px,
  131134. width: px,
  131135. height: px,
  131136. 'border-top-width': px,
  131137. 'border-right-width': px,
  131138. 'border-bottom-width': px,
  131139. 'border-left-width': px,
  131140. 'margin-top': px,
  131141. 'margin-right': px,
  131142. 'margin-bottom': px,
  131143. 'margin-left': px,
  131144. 'padding-top': px,
  131145. 'padding-right': px,
  131146. 'padding-bottom': px,
  131147. 'padding-left': px,
  131148. 'line-height': isDom,
  131149. display: isDom
  131150. };
  131151. });
  131152. /**
  131153. * Manages context information during a layout.
  131154. *
  131155. * # Algorithm
  131156. *
  131157. * This class performs the following jobs:
  131158. *
  131159. * - Cache DOM reads to avoid reading the same values repeatedly.
  131160. * - Buffer DOM writes and flush them as a block to avoid read/write interleaving.
  131161. * - Track layout dependencies so each layout does not have to figure out the source of
  131162. * its dependent values.
  131163. * - Intelligently run layouts when the values on which they depend change (a trigger).
  131164. * - Allow layouts to avoid processing when required values are unavailable (a block).
  131165. *
  131166. * Work done during layout falls into either a "read phase" or a "write phase" and it is
  131167. * essential to always be aware of the current phase. Most methods in
  131168. * {@link Ext.layout.Layout Layout} are called during a read phase:
  131169. * {@link Ext.layout.Layout#calculate calculate},
  131170. * {@link Ext.layout.Layout#completeLayout completeLayout} and
  131171. * {@link Ext.layout.Layout#finalizeLayout finalizeLayout}. The exceptions to this are
  131172. * {@link Ext.layout.Layout#beginLayout beginLayout},
  131173. * {@link Ext.layout.Layout#beginLayoutCycle beginLayoutCycle} and
  131174. * {@link Ext.layout.Layout#finishedLayout finishedLayout} which are called during
  131175. * a write phase. While {@link Ext.layout.Layout#finishedLayout finishedLayout} is called
  131176. * a write phase, it is really intended to be a catch-all for post-processing after a
  131177. * layout run.
  131178. *
  131179. * In a read phase, it is OK to read the DOM but this should be done using the appropriate
  131180. * {@link Ext.layout.ContextItem ContextItem} where possible since that provides a cache
  131181. * to avoid redundant reads. No writes should be made to the DOM in a read phase! Instead,
  131182. * the values should be written to the proper ContextItem for later write-back.
  131183. *
  131184. * The rules flip-flop in a write phase. The only difference is that ContextItem methods
  131185. * like {@link Ext.layout.ContextItem#getStyle getStyle} will still read the DOM unless the
  131186. * value was previously read. This detail is unknowable from the outside of ContextItem, so
  131187. * read calls to ContextItem should also be avoided in a write phase.
  131188. *
  131189. * Calculating interdependent layouts requires a certain amount of iteration. In a given
  131190. * cycle, some layouts will contribute results that allow other layouts to proceed. The
  131191. * general flow then is to gather all of the layouts (both component and container) in a
  131192. * component tree and queue them all for processing. The initial queue order is bottom-up
  131193. * and component layout first, then container layout (if applicable) for each component.
  131194. *
  131195. * This initial step also calls the beginLayout method on all layouts to clear any values
  131196. * from the DOM that might interfere with calculations and measurements. In other words,
  131197. * this is a "write phase" and reads from the DOM should be strictly avoided.
  131198. *
  131199. * Next the layout enters into its iterations or "cycles". Each cycle consists of calling
  131200. * the {@link Ext.layout.Layout#calculate calculate} method on all layouts in the
  131201. * {@link #layoutQueue}. These calls are part of a "read phase" and writes to the DOM should
  131202. * be strictly avoided.
  131203. *
  131204. * # Considerations
  131205. *
  131206. * **RULE 1**: Respect the read/write cycles. Always use the {@link Ext.layout.ContextItem#getProp getProp}
  131207. * or {@link Ext.layout.ContextItem#getDomProp getDomProp} methods to get calculated values;
  131208. * only use the {@link Ext.layout.ContextItem#getStyle getStyle} method to read styles; use
  131209. * {@link Ext.layout.ContextItem#setProp setProp} to set DOM values. Some reads will, of
  131210. * course, still go directly to the DOM, but if there is a method in
  131211. * {@link Ext.layout.ContextItem ContextItem} to do a certain job, it should be used instead
  131212. * of a lower-level equivalent.
  131213. *
  131214. * The basic logic flow in {@link Ext.layout.Layout#calculate calculate} consists of gathering
  131215. * values by calling {@link Ext.layout.ContextItem#getProp getProp} or
  131216. * {@link Ext.layout.ContextItem#getDomProp getDomProp}, calculating results and publishing
  131217. * them by calling {@link Ext.layout.ContextItem#setProp setProp}. It is important to realize
  131218. * that {@link Ext.layout.ContextItem#getProp getProp} will return `undefined` if the value
  131219. * is not yet known. But the act of calling the method is enough to track the fact that the
  131220. * calling layout depends (in some way) on this value. In other words, the calling layout is
  131221. * "triggered" by the properties it requests.
  131222. *
  131223. * **RULE 2**: Avoid calling {@link Ext.layout.ContextItem#getProp getProp} unless the value
  131224. * is needed. Gratuitous calls cause inefficiency because the layout will appear to depend on
  131225. * values that it never actually uses. This applies equally to
  131226. * {@link Ext.layout.ContextItem#getDomProp getDomProp} and the test-only methods
  131227. * {@link Ext.layout.ContextItem#hasProp hasProp} and {@link Ext.layout.ContextItem#hasDomProp hasDomProp}.
  131228. *
  131229. * Because {@link Ext.layout.ContextItem#getProp getProp} can return `undefined`, it is often
  131230. * the case that subsequent math will produce NaN's. This is usually not a problem as the
  131231. * NaN's simply propagate along and result in final results that are NaN. Both `undefined`
  131232. * and NaN are ignored by {@link Ext.layout.ContextItem#setProp}, so it is often not necessary
  131233. * to even know that this is happening. It does become important for determining if a layout
  131234. * is not done or if it might lead to publishing an incorrect (but not NaN or `undefined`)
  131235. * value.
  131236. *
  131237. * **RULE 3**: If a layout has not calculated all the values it is required to calculate, it
  131238. * must set {@link Ext.layout.Layout#done done} to `false` before returning from
  131239. * {@link Ext.layout.Layout#calculate calculate}. This value is always `true` on entry because
  131240. * it is simpler to detect the incomplete state rather than the complete state (especially up
  131241. * and down a class hierarchy).
  131242. *
  131243. * **RULE 4**: A layout must never publish an incomplete (wrong) result. Doing so would cause
  131244. * dependent layouts to run their calculations on those wrong values, producing more wrong
  131245. * values and some layouts may even incorrectly flag themselves as {@link Ext.layout.Layout#done done}
  131246. * before the correct values are determined and republished. Doing this will poison the
  131247. * calculations.
  131248. *
  131249. * **RULE 5**: Each value should only be published by one layout. If multiple layouts attempt
  131250. * to publish the same values, it would be nearly impossible to avoid breaking **RULE 4**. To
  131251. * help detect this problem, the layout diagnostics will trap on an attempt to set a value
  131252. * from different layouts.
  131253. *
  131254. * Complex layouts can produce many results as part of their calculations. These values are
  131255. * important for other layouts to proceed and need to be published by the earliest possible
  131256. * call to {@link Ext.layout.Layout#calculate} to avoid unnecessary cycles and poor performance. It is
  131257. * also possible, however, for some results to be related in a way such that publishing them
  131258. * may be an all-or-none proposition (typically to avoid breaking *RULE 4*).
  131259. *
  131260. * **RULE 6**: Publish results as soon as they are known to be correct rather than wait for
  131261. * all values to be calculated. Waiting for everything to be complete can lead to deadlock.
  131262. * The key here is not to forget **RULE 4** in the process.
  131263. *
  131264. * Some layouts depend on certain critical values as part of their calculations. For example,
  131265. * HBox depends on width and cannot do anything until the width is known. In these cases, it
  131266. * is best to use {@link Ext.layout.ContextItem#block block} or
  131267. * {@link Ext.layout.ContextItem#domBlock domBlock} and thereby avoid processing the layout
  131268. * until the needed value is available.
  131269. *
  131270. * **RULE 7**: Use {@link Ext.layout.ContextItem#block block} or
  131271. * {@link Ext.layout.ContextItem#domBlock domBlock} when values are required to make progress.
  131272. * This will mimize wasted recalculations.
  131273. *
  131274. * **RULE 8**: Blocks should only be used when no forward progress can be made. If even one
  131275. * value could still be calculated, a block could result in a deadlock.
  131276. *
  131277. * Historically, layouts have been invoked directly by component code, sometimes in places
  131278. * like an `afterLayout` method for a child component. With the flexibility now available
  131279. * to solve complex, iterative issues, such things should be done in a responsible layout
  131280. * (be it component or container).
  131281. *
  131282. * **RULE 9**: Use layouts to solve layout issues and don't wait for the layout to finish to
  131283. * perform further layouts. This is especially important now that layouts process entire
  131284. * component trees and not each layout in isolation.
  131285. *
  131286. * # Sequence Diagram
  131287. *
  131288. * The simplest sequence diagram for a layout run looks roughly like this:
  131289. *
  131290. * Context Layout 1 Item 1 Layout 2 Item 2
  131291. * | | | | |
  131292. * ---->X-------------->X | | |
  131293. * run X---------------|-----------|---------->X |
  131294. * X beginLayout | | | |
  131295. * X | | | |
  131296. * A X-------------->X | | |
  131297. * X calculate X---------->X | |
  131298. * X C X getProp | | |
  131299. * B X X---------->X | |
  131300. * X | setProp | | |
  131301. * X | | | |
  131302. * D X---------------|-----------|---------->X |
  131303. * X calculate | | X---------->X
  131304. * X | | | setProp |
  131305. * E X | | | |
  131306. * X---------------|-----------|---------->X |
  131307. * X completeLayout| | F | |
  131308. * X | | | |
  131309. * G X | | | |
  131310. * H X-------------->X | | |
  131311. * X calculate X---------->X | |
  131312. * X I X getProp | | |
  131313. * X X---------->X | |
  131314. * X | setProp | | |
  131315. * J X-------------->X | | |
  131316. * X completeLayout| | | |
  131317. * X | | | |
  131318. * K X-------------->X | | |
  131319. * X---------------|-----------|---------->X |
  131320. * X finalizeLayout| | | |
  131321. * X | | | |
  131322. * L X-------------->X | | |
  131323. * X---------------|-----------|---------->X |
  131324. * X finishedLayout| | | |
  131325. * X | | | |
  131326. * M X-------------->X | | |
  131327. * X---------------|-----------|---------->X |
  131328. * X notifyOwner | | | |
  131329. * N | | | | |
  131330. * - - - - -
  131331. *
  131332. *
  131333. * Notes:
  131334. *
  131335. * **A.** This is a call from the {@link #run} method to the {@link #runCycle} method.
  131336. * Each layout in the queue will have its {@link Ext.layout.Layout#calculate calculate}
  131337. * method called.
  131338. *
  131339. * **B.** After each {@link Ext.layout.Layout#calculate calculate} method is called the
  131340. * {@link Ext.layout.Layout#done done} flag is checked to see if the Layout has completed.
  131341. * If it has completed and that layout object implements a
  131342. * {@link Ext.layout.Layout#completeLayout completeLayout} method, this layout is queued to
  131343. * receive its call. Otherwise, the layout will be queued again unless there are blocks or
  131344. * triggers that govern its requeueing.
  131345. *
  131346. * **C.** The call to {@link Ext.layout.ContextItem#getProp getProp} is made to the Item
  131347. * and that will be tracked as a trigger (keyed by the name of the property being requested).
  131348. * Changes to this property will cause this layout to be requeued. The call to
  131349. * {@link Ext.layout.ContextItem#setProp setProp} will place a value in the item and not
  131350. * directly into the DOM.
  131351. *
  131352. * **D.** Call the other layouts now in the first cycle (repeat **B** and **C** for each
  131353. * layout).
  131354. *
  131355. * **E.** After completing a cycle, if progress was made (new properties were written to
  131356. * the context) and if the {@link #layoutQueue} is not empty, the next cycle is run. If no
  131357. * progress was made or no layouts are ready to run, all buffered values are written to
  131358. * the DOM (a flush).
  131359. *
  131360. * **F.** After flushing, any layouts that were marked as {@link Ext.layout.Layout#done done}
  131361. * that also have a {@link Ext.layout.Layout#completeLayout completeLayout} method are called.
  131362. * This can cause them to become no longer done (see {@link #invalidate}). As with
  131363. * {@link Ext.layout.Layout#calculate calculate}, this is considered a "read phase" and
  131364. * direct DOM writes should be avoided.
  131365. *
  131366. * **G.** Flushing and calling any pending {@link Ext.layout.Layout#completeLayout completeLayout}
  131367. * methods will likely trigger layouts that called {@link Ext.layout.ContextItem#getDomProp getDomProp}
  131368. * and unblock layouts that have called {@link Ext.layout.ContextItem#domBlock domBlock}.
  131369. * These variants are used when a layout needs the value to be correct in the DOM and not
  131370. * simply known. If this does not cause at least one layout to enter the queue, we have a
  131371. * layout FAILURE. Otherwise, we continue with the next cycle.
  131372. *
  131373. * **H.** Call {@link Ext.layout.Layout#calculate calculate} on any layouts in the queue
  131374. * at the start of this cycle. Just a repeat of **B** through **G**.
  131375. *
  131376. * **I.** Once the layout has calculated all that it is resposible for, it can leave itself
  131377. * in the {@link Ext.layout.Layout#done done} state. This is the value on entry to
  131378. * {@link Ext.layout.Layout#calculate calculate} and must be cleared in that call if the
  131379. * layout has more work to do.
  131380. *
  131381. * **J.** Now that all layouts are done, flush any DOM values and
  131382. * {@link Ext.layout.Layout#completeLayout completeLayout} calls. This can again cause
  131383. * layouts to become not done, and so we will be back on another cycle if that happens.
  131384. *
  131385. * **K.** After all layouts are done, call the {@link Ext.layout.Layout#finalizeLayout finalizeLayout}
  131386. * method on any layouts that have one. As with {@link Ext.layout.Layout#completeLayout completeLayout},
  131387. * this can cause layouts to become no longer done. This is less desirable than using
  131388. * {@link Ext.layout.Layout#completeLayout completeLayout} because it will cause all
  131389. * {@link Ext.layout.Layout#finalizeLayout finalizeLayout} methods to be called again
  131390. * when we think things are all wrapped up.
  131391. *
  131392. * **L.** After finishing the last iteration, layouts that have a
  131393. * {@link Ext.layout.Layout#finishedLayout finishedLayout} method will be called. This
  131394. * call will only happen once per run and cannot cause layouts to be run further.
  131395. *
  131396. * **M.** After calling finahedLayout, layouts that have a
  131397. * {@link Ext.layout.Layout#notifyOwner notifyOwner} method will be called. This
  131398. * call will only happen once per run and cannot cause layouts to be run further.
  131399. *
  131400. * **N.** One last flush to make sure everything has been written to the DOM.
  131401. *
  131402. * # Inter-Layout Collaboration
  131403. *
  131404. * Many layout problems require collaboration between multiple layouts. In some cases, this
  131405. * is as simple as a component's container layout providing results used by its component
  131406. * layout or vise-versa. A slightly more distant collaboration occurs in a box layout when
  131407. * stretchmax is used: the child item's component layout provides results that are consumed
  131408. * by the ownerCt's box layout to determine the size of the children.
  131409. *
  131410. * The various forms of interdependence between a container and its children are described by
  131411. * each components' {@link Ext.AbstractComponent#getSizeModel size model}.
  131412. *
  131413. * To facilitate this collaboration, the following pairs of properties are published to the
  131414. * component's {@link Ext.layout.ContextItem ContextItem}:
  131415. *
  131416. * - width/height: These hold the final size of the component. The layout indicated by the
  131417. * {@link Ext.AbstractComponent#getSizeModel size model} is responsible for setting these.
  131418. * - contentWidth/contentHeight: These hold size information published by the container
  131419. * layout or from DOM measurement. These describe the content only. These values are
  131420. * used by the component layout to determine the outer width/height when that component
  131421. * is {@link Ext.AbstractComponent#shrinkWrap shrink-wrapped}. They are also used to
  131422. * determine overflow. All container layouts must publish these values for dimensions
  131423. * that are shrink-wrapped. If a component has raw content (not container items), the
  131424. * componentLayout must publish these values instead.
  131425. *
  131426. * @protected
  131427. */
  131428. Ext.define('Ext.layout.Context', {
  131429. requires: [
  131430. 'Ext.util.Queue',
  131431. 'Ext.layout.ContextItem',
  131432. 'Ext.layout.Layout',
  131433. 'Ext.fx.Anim',
  131434. 'Ext.fx.Manager'
  131435. ],
  131436. remainingLayouts: 0,
  131437. /**
  131438. * @property {Number} state One of these values:
  131439. *
  131440. * - 0 - Before run
  131441. * - 1 - Running
  131442. * - 2 - Run complete
  131443. */
  131444. state: 0,
  131445. constructor: function (config) {
  131446. var me = this;
  131447. Ext.apply(me, config);
  131448. // holds the ContextItem collection, keyed by element id
  131449. me.items = {};
  131450. // a collection of layouts keyed by layout id
  131451. me.layouts = {};
  131452. // the number of blocks of any kind:
  131453. me.blockCount = 0;
  131454. // the number of cycles that have been run:
  131455. me.cycleCount = 0;
  131456. // the number of flushes to the DOM:
  131457. me.flushCount = 0;
  131458. // the number of layout calculate calls:
  131459. me.calcCount = 0;
  131460. me.animateQueue = me.newQueue();
  131461. me.completionQueue = me.newQueue();
  131462. me.finalizeQueue = me.newQueue();
  131463. me.finishQueue = me.newQueue();
  131464. me.flushQueue = me.newQueue();
  131465. me.invalidateData = {};
  131466. /**
  131467. * @property {Ext.util.Queue} layoutQueue
  131468. * List of layouts to perform.
  131469. */
  131470. me.layoutQueue = me.newQueue();
  131471. // this collection is special because we ensure that there are no parent/child pairs
  131472. // present, only distinct top-level components
  131473. me.invalidQueue = [];
  131474. me.triggers = {
  131475. data: {
  131476. /*
  131477. layoutId: [
  131478. { item: contextItem, prop: propertyName }
  131479. ]
  131480. */
  131481. },
  131482. dom: {}
  131483. };
  131484. },
  131485. callLayout: function (layout, methodName) {
  131486. this.currentLayout = layout;
  131487. layout[methodName](this.getCmp(layout.owner));
  131488. },
  131489. cancelComponent: function (comp, isChild, isDestroying) {
  131490. var me = this,
  131491. components = comp,
  131492. isArray = !comp.isComponent,
  131493. length = isArray ? components.length : 1,
  131494. i, k, klen, items, layout, newQueue, oldQueue, entry, temp,
  131495. ownerCtContext;
  131496. for (i = 0; i < length; ++i) {
  131497. if (isArray) {
  131498. comp = components[i];
  131499. }
  131500. // If the component is being destroyed, remove the component's ContextItem from its parent's contextItem.childItems array
  131501. if (isDestroying && comp.ownerCt) {
  131502. ownerCtContext = this.items[comp.ownerCt.el.id];
  131503. if (ownerCtContext) {
  131504. Ext.Array.remove(ownerCtContext.childItems, me.getCmp(comp));
  131505. }
  131506. }
  131507. if (!isChild) {
  131508. oldQueue = me.invalidQueue;
  131509. klen = oldQueue.length;
  131510. if (klen) {
  131511. me.invalidQueue = newQueue = [];
  131512. for (k = 0; k < klen; ++k) {
  131513. entry = oldQueue[k];
  131514. temp = entry.item.target;
  131515. if (temp != comp && !temp.isDescendant(comp)) {
  131516. newQueue.push(entry);
  131517. }
  131518. }
  131519. }
  131520. }
  131521. layout = comp.componentLayout;
  131522. me.cancelLayout(layout);
  131523. if (layout.getLayoutItems) {
  131524. items = layout.getLayoutItems();
  131525. if (items.length) {
  131526. me.cancelComponent(items, true);
  131527. }
  131528. }
  131529. if (comp.isContainer && !comp.collapsed) {
  131530. layout = comp.layout;
  131531. me.cancelLayout(layout);
  131532. items = layout.getVisibleItems();
  131533. if (items.length) {
  131534. me.cancelComponent(items, true);
  131535. }
  131536. }
  131537. }
  131538. },
  131539. cancelLayout: function (layout) {
  131540. var me = this;
  131541. me.completionQueue.remove(layout);
  131542. me.finalizeQueue.remove(layout);
  131543. me.finishQueue.remove(layout);
  131544. me.layoutQueue.remove(layout);
  131545. if (layout.running) {
  131546. me.layoutDone(layout);
  131547. }
  131548. layout.ownerContext = null;
  131549. },
  131550. clearTriggers: function (layout, inDom) {
  131551. var id = layout.id,
  131552. collection = this.triggers[inDom ? 'dom' : 'data'],
  131553. triggers = collection && collection[id],
  131554. length = (triggers && triggers.length) || 0,
  131555. collection, i, item, trigger;
  131556. for (i = 0; i < length; ++i) {
  131557. trigger = triggers[i];
  131558. item = trigger.item;
  131559. collection = inDom ? item.domTriggers : item.triggers;
  131560. delete collection[trigger.prop][id];
  131561. }
  131562. },
  131563. /**
  131564. * Flushes any pending writes to the DOM by calling each ContextItem in the flushQueue.
  131565. */
  131566. flush: function () {
  131567. var me = this,
  131568. items = me.flushQueue.clear(),
  131569. length = items.length, i;
  131570. if (length) {
  131571. ++me.flushCount;
  131572. for (i = 0; i < length; ++i) {
  131573. items[i].flush();
  131574. }
  131575. }
  131576. },
  131577. flushAnimations: function() {
  131578. var me = this,
  131579. items = me.animateQueue.clear(),
  131580. len = items.length,
  131581. i;
  131582. if (len) {
  131583. for (i = 0; i < len; i++) {
  131584. // Each Component may refuse to participate in animations.
  131585. // This is used by the BoxReorder plugin which drags a Component,
  131586. // during which that Component must be exempted from layout positioning.
  131587. if (items[i].target.animate !== false) {
  131588. items[i].flushAnimations();
  131589. }
  131590. }
  131591. // Ensure the first frame fires now to avoid a browser repaint with the elements in the "to" state
  131592. // before they are returned to their "from" state by the animation.
  131593. Ext.fx.Manager.runner();
  131594. }
  131595. },
  131596. flushInvalidates: function () {
  131597. var me = this,
  131598. queue = me.invalidQueue,
  131599. length = queue && queue.length,
  131600. comp, components, entry, i;
  131601. me.invalidQueue = [];
  131602. if (length) {
  131603. components = [];
  131604. for (i = 0; i < length; ++i) {
  131605. comp = (entry = queue[i]).item.target;
  131606. // we filter out-of-body components here but allow them into the queue to
  131607. // ensure that their child components are coalesced out (w/no additional
  131608. // cost beyond our normal effort to avoid parent/child components in the
  131609. // queue)
  131610. if (!comp.container.isDetachedBody) {
  131611. components.push(comp);
  131612. if (entry.options) {
  131613. me.invalidateData[comp.id] = entry.options;
  131614. }
  131615. }
  131616. }
  131617. me.invalidate(components, null);
  131618. }
  131619. },
  131620. flushLayouts: function (queueName, methodName, dontClear) {
  131621. var me = this,
  131622. layouts = dontClear ? me[queueName].items : me[queueName].clear(),
  131623. length = layouts.length,
  131624. i, layout;
  131625. if (length) {
  131626. for (i = 0; i < length; ++i) {
  131627. layout = layouts[i];
  131628. if (!layout.running) {
  131629. me.callLayout(layout, methodName);
  131630. }
  131631. }
  131632. me.currentLayout = null;
  131633. }
  131634. },
  131635. /**
  131636. * Returns the ContextItem for a component.
  131637. * @param {Ext.Component} cmp
  131638. */
  131639. getCmp: function (cmp) {
  131640. return this.getItem(cmp, cmp.el);
  131641. },
  131642. /**
  131643. * Returns the ContextItem for an element.
  131644. * @param {Ext.layout.ContextItem} parent
  131645. * @param {Ext.dom.Element} el
  131646. */
  131647. getEl: function (parent, el) {
  131648. var item = this.getItem(el, el);
  131649. if (!item.parent) {
  131650. item.parent = parent;
  131651. // all items share an empty children array (to avoid null checks), so we can
  131652. // only push on to the children array if there is already something there (we
  131653. // copy-on-write):
  131654. if (parent.children.length) {
  131655. parent.children.push(item);
  131656. } else {
  131657. parent.children = [ item ]; // now parent has its own children[] (length=1)
  131658. }
  131659. }
  131660. return item;
  131661. },
  131662. getItem: function (target, el) {
  131663. var id = el.id,
  131664. items = this.items,
  131665. item = items[id] ||
  131666. (items[id] = new Ext.layout.ContextItem({
  131667. context: this,
  131668. target: target,
  131669. el: el
  131670. }));
  131671. return item;
  131672. },
  131673. handleFailure: function () {
  131674. // This method should never be called, but is need when layouts fail (hence the
  131675. // "should never"). We just disconnect any of the layouts from the run and return
  131676. // them to the state they would be in had the layout completed properly.
  131677. var layouts = this.layouts,
  131678. layout, key;
  131679. Ext.failedLayouts = (Ext.failedLayouts || 0) + 1;
  131680. Ext.log('Layout run failed');
  131681. for (key in layouts) {
  131682. layout = layouts[key];
  131683. if (layouts.hasOwnProperty(key)) {
  131684. layout.running = false;
  131685. layout.ownerContext = null;
  131686. }
  131687. }
  131688. },
  131689. /**
  131690. * Invalidates one or more components' layouts (component and container). This can be
  131691. * called before run to identify the components that need layout or during the run to
  131692. * restart the layout of a component. This is called internally to flush any queued
  131693. * invalidations at the start of a cycle. If called during a run, it is not expected
  131694. * that new components will be introduced to the layout.
  131695. *
  131696. * @param {Ext.Component/Array} components An array of Components or a single Component.
  131697. * @param {Ext.layout.ContextItem} ownerCtContext The ownerCt's ContextItem.
  131698. * @param {Boolean} full True if all properties should be invalidated, otherwise only
  131699. * those calculated by the component should be invalidated.
  131700. */
  131701. invalidate: function (components, full) {
  131702. var me = this,
  131703. isArray = !components.isComponent,
  131704. componentChildrenDone, containerChildrenDone, containerLayoutDone,
  131705. firstTime, i, comp, item, items, length, componentLayout, layout,
  131706. invalidateOptions, token;
  131707. for (i = 0, length = isArray ? components.length : 1; i < length; ++i) {
  131708. comp = isArray ? components[i] : components;
  131709. if (comp.rendered && !comp.hidden) {
  131710. item = me.getCmp(comp);
  131711. componentLayout = comp.componentLayout;
  131712. firstTime = !componentLayout.ownerContext;
  131713. layout = (comp.isContainer && !comp.collapsed) ? comp.layout : null;
  131714. // Extract any invalidate() options for this item.
  131715. invalidateOptions = me.invalidateData[item.id];
  131716. delete me.invalidateData[item.id];
  131717. // We invalidate the contextItem's in a top-down manner so that SizeModel
  131718. // info for containers is available to their children. This is a critical
  131719. // optimization since sizeModel determination often requires knowing the
  131720. // sizeModel of the ownerCt. If this weren't cached as we descend, this
  131721. // would be an O(N^2) operation! (where N=number of components, or 300+/-
  131722. // in Themes)
  131723. token = item.init(full, invalidateOptions);
  131724. if (invalidateOptions) {
  131725. me.processInvalidate(invalidateOptions, item, 'before');
  131726. }
  131727. // Allow the component layout a chance to effect its size model before we
  131728. // recurse down the component hierarchy (since children need to know the
  131729. // size model of their ownerCt).
  131730. if (componentLayout.beforeLayoutCycle) {
  131731. componentLayout.beforeLayoutCycle(item);
  131732. }
  131733. // Finish up the item-level processing that is based on the size model of
  131734. // the component.
  131735. token = item.initContinue(token);
  131736. // Start these state variables at true, since that is the value we want if
  131737. // they do not apply (i.e., no work of this kind on which to wait).
  131738. componentChildrenDone = containerChildrenDone = containerLayoutDone = true;
  131739. // A ComponentLayout MUST implement getLayoutItems to allow its children
  131740. // to be collected. Ext.container.Container does this, but non-Container
  131741. // Components which manage Components as part of their structure (e.g.,
  131742. // HtmlEditor) must still return child Components via getLayoutItems.
  131743. if (componentLayout.getLayoutItems) {
  131744. componentLayout.renderChildren();
  131745. items = componentLayout.getLayoutItems();
  131746. if (items.length) {
  131747. me.invalidate(items, true);
  131748. componentChildrenDone = false;
  131749. }
  131750. }
  131751. if (layout) {
  131752. containerLayoutDone = false;
  131753. layout.renderChildren();
  131754. items = layout.getVisibleItems();
  131755. if (items.length) {
  131756. me.invalidate(items, true);
  131757. containerChildrenDone = false;
  131758. }
  131759. }
  131760. // Finish the processing that requires the size models of child items to
  131761. // be determined (and some misc other stuff).
  131762. item.initDone(token, componentChildrenDone, containerChildrenDone,
  131763. containerLayoutDone);
  131764. // Inform the layouts that we are about to begin (or begin again) now that
  131765. // the size models of the component and its children are setup.
  131766. me.resetLayout(componentLayout, item, firstTime);
  131767. if (layout) {
  131768. me.resetLayout(layout, item, firstTime);
  131769. }
  131770. // This has to occur after the component layout has had a chance to begin
  131771. // so that we can determine what kind of animation might be needed. TODO-
  131772. // move this determination into the layout itself.
  131773. item.initAnimation();
  131774. if (invalidateOptions) {
  131775. me.processInvalidate(invalidateOptions, item, 'after');
  131776. }
  131777. }
  131778. }
  131779. me.currentLayout = null;
  131780. },
  131781. layoutDone: function (layout) {
  131782. var ownerContext = layout.ownerContext,
  131783. ownerCtContext;
  131784. layout.running = false;
  131785. // Once a component layout completes, we can mark it as "done" but we can also
  131786. // decrement the remainingChildLayouts property on the ownerCtContext. When that
  131787. // goes to 0, we can mark the ownerCtContext as "childrenDone".
  131788. if (layout.isComponentLayout) {
  131789. if (ownerContext.measuresBox) {
  131790. ownerContext.onBoxMeasured(); // be sure to release our boxParent
  131791. }
  131792. ownerContext.setProp('done', true);
  131793. ownerCtContext = ownerContext.ownerCtContext;
  131794. if (ownerCtContext) {
  131795. if (ownerContext.target.ownerLayout.isComponentLayout) {
  131796. if (! --ownerCtContext.remainingComponentChildLayouts) {
  131797. ownerCtContext.setProp('componentChildrenDone', true);
  131798. }
  131799. } else {
  131800. if (! --ownerCtContext.remainingContainerChildLayouts) {
  131801. ownerCtContext.setProp('containerChildrenDone', true);
  131802. }
  131803. }
  131804. if (! --ownerCtContext.remainingChildLayouts) {
  131805. ownerCtContext.setProp('childrenDone', true);
  131806. }
  131807. }
  131808. } else {
  131809. ownerContext.setProp('containerLayoutDone', true);
  131810. }
  131811. --this.remainingLayouts;
  131812. ++this.progressCount; // a layout completion is progress
  131813. },
  131814. newQueue: function () {
  131815. return new Ext.util.Queue();
  131816. },
  131817. processInvalidate: function (options, item, name) {
  131818. // When calling a callback, the currentLayout needs to be adjusted so
  131819. // that whichever layout caused the invalidate is the currentLayout...
  131820. if (options[name]) {
  131821. var me = this,
  131822. currentLayout = me.currentLayout;
  131823. me.currentLayout = options.layout || null;
  131824. options[name](item, options);
  131825. me.currentLayout = currentLayout;
  131826. }
  131827. },
  131828. /**
  131829. * Queues a ContextItem to have its {@link Ext.layout.ContextItem#flushAnimations} method called.
  131830. *
  131831. * @param {Ext.layout.ContextItem} item
  131832. * @private
  131833. */
  131834. queueAnimation: function (item) {
  131835. this.animateQueue.add(item);
  131836. },
  131837. /**
  131838. * Queues a layout to have its {@link Ext.layout.Layout#completeLayout} method called.
  131839. *
  131840. * @param {Ext.layout.Layout} layout
  131841. * @private
  131842. */
  131843. queueCompletion: function (layout) {
  131844. this.completionQueue.add(layout);
  131845. },
  131846. /**
  131847. * Queues a layout to have its {@link Ext.layout.Layout#finalizeLayout} method called.
  131848. *
  131849. * @param {Ext.layout.Layout} layout
  131850. * @private
  131851. */
  131852. queueFinalize: function (layout) {
  131853. this.finalizeQueue.add(layout);
  131854. },
  131855. /**
  131856. * Queues a ContextItem for the next flush to the DOM. This should only be called by
  131857. * the {@link Ext.layout.ContextItem} class.
  131858. *
  131859. * @param {Ext.layout.ContextItem} item
  131860. * @private
  131861. */
  131862. queueFlush: function (item) {
  131863. this.flushQueue.add(item);
  131864. },
  131865. chainFns: function (oldOptions, newOptions, funcName) {
  131866. var me = this,
  131867. oldLayout = oldOptions.layout,
  131868. newLayout = newOptions.layout,
  131869. oldFn = oldOptions[funcName],
  131870. newFn = newOptions[funcName];
  131871. // Call newFn last so it can get the final word on things... also, the "this"
  131872. // pointer will be passed correctly by createSequence with oldFn first.
  131873. return function (contextItem) {
  131874. var prev = me.currentLayout;
  131875. if (oldFn) {
  131876. me.currentLayout = oldLayout;
  131877. oldFn.call(oldOptions.scope || oldOptions, contextItem, oldOptions);
  131878. }
  131879. me.currentLayout = newLayout;
  131880. newFn.call(newOptions.scope || newOptions, contextItem, newOptions);
  131881. me.currentLayout = prev;
  131882. };
  131883. },
  131884. /**
  131885. * Queue a component (and its tree) to be invalidated on the next cycle.
  131886. *
  131887. * @param {Ext.Component/Ext.layout.ContextItem} item The component or ContextItem to invalidate.
  131888. * @param {Object} options An object describing how to handle the invalidation (see
  131889. * {@link Ext.layout.ContextItem#invalidate} for details).
  131890. * @private
  131891. */
  131892. queueInvalidate: function (item, options) {
  131893. var me = this,
  131894. newQueue = [],
  131895. oldQueue = me.invalidQueue,
  131896. index = oldQueue.length,
  131897. comp, old, oldComp, oldOptions, oldState;
  131898. if (item.isComponent) {
  131899. item = me.getCmp(comp = item);
  131900. } else {
  131901. comp = item.target;
  131902. }
  131903. item.invalid = true;
  131904. // See if comp is contained by any component already in the queue (ignore comp if
  131905. // that is the case). Eliminate any components in the queue that are contained by
  131906. // comp (by not adding them to newQueue).
  131907. while (index--) {
  131908. old = oldQueue[index];
  131909. oldComp = old.item.target;
  131910. if (comp.isDescendant(oldComp)) {
  131911. return; // oldComp contains comp, so this invalidate is redundant
  131912. }
  131913. if (oldComp == comp) {
  131914. // if already in the queue, update the options...
  131915. if (!(oldOptions = old.options)) {
  131916. old.options = options;
  131917. } else if (options) {
  131918. if (options.widthModel) {
  131919. oldOptions.widthModel = options.widthModel;
  131920. }
  131921. if (options.heightModel) {
  131922. oldOptions.heightModel = options.heightModel;
  131923. }
  131924. if (!(oldState = oldOptions.state)) {
  131925. oldOptions.state = options.state;
  131926. } else if (options.state) {
  131927. Ext.apply(oldState, options.state);
  131928. }
  131929. if (options.before) {
  131930. oldOptions.before = me.chainFns(oldOptions, options, 'before');
  131931. }
  131932. if (options.after) {
  131933. oldOptions.after = me.chainFns(oldOptions, options, 'after');
  131934. }
  131935. }
  131936. // leave the old queue alone now that we've update this comp's entry...
  131937. return;
  131938. }
  131939. if (!oldComp.isDescendant(comp)) {
  131940. newQueue.push(old); // comp does not contain oldComp
  131941. }
  131942. // else if (oldComp isDescendant of comp) skip
  131943. }
  131944. // newQueue contains only those components not a descendant of comp
  131945. // to get here, comp must not be a child of anything already in the queue, so it
  131946. // needs to be added along with its "options":
  131947. newQueue.push({ item: item, options: options });
  131948. me.invalidQueue = newQueue;
  131949. },
  131950. queueItemLayouts: function (item) {
  131951. var comp = item.isComponent ? item : item.target,
  131952. layout = comp.componentLayout;
  131953. if (!layout.pending && !layout.invalid && !layout.done) {
  131954. this.queueLayout(layout);
  131955. }
  131956. layout = comp.layout;
  131957. if (layout && !layout.pending && !layout.invalid && !layout.done) {
  131958. this.queueLayout(layout);
  131959. }
  131960. },
  131961. /**
  131962. * Queues a layout for the next calculation cycle. This should not be called if the
  131963. * layout is done, blocked or already in the queue. The only classes that should call
  131964. * this method are this class and {@link Ext.layout.ContextItem}.
  131965. *
  131966. * @param {Ext.layout.Layout} layout The layout to add to the queue.
  131967. * @private
  131968. */
  131969. queueLayout: function (layout) {
  131970. this.layoutQueue.add(layout);
  131971. layout.pending = true;
  131972. },
  131973. /**
  131974. * Resets the given layout object. This is called at the start of the run and can also
  131975. * be called during the run by calling {@link #invalidate}.
  131976. */
  131977. resetLayout: function (layout, ownerContext, firstTime) {
  131978. var me = this,
  131979. ownerCtContext;
  131980. me.currentLayout = layout;
  131981. layout.done = false;
  131982. layout.pending = true;
  131983. layout.firedTriggers = 0;
  131984. me.layoutQueue.add(layout);
  131985. if (firstTime) {
  131986. me.layouts[layout.id] = layout; // track the layout for this run by its id
  131987. layout.running = true;
  131988. if (layout.finishedLayout) {
  131989. me.finishQueue.add(layout);
  131990. }
  131991. // reset or update per-run counters:
  131992. ++me.remainingLayouts;
  131993. ++layout.layoutCount; // the number of whole layouts run for the layout
  131994. layout.ownerContext = ownerContext;
  131995. layout.beginCount = 0; // the number of beginLayout calls
  131996. layout.blockCount = 0; // the number of blocks set for the layout
  131997. layout.calcCount = 0; // the number of times calculate is called
  131998. layout.triggerCount = 0; // the number of triggers set for the layout
  131999. // Count the children of each ownerCt so we can tell when they are all done:
  132000. if (layout.isComponentLayout && (ownerCtContext = ownerContext.ownerCtContext)) {
  132001. // This layout's ownerCt is in this run... The component associated with
  132002. // this layout (the "target") could be owned by the ownerCt's container
  132003. // layout or component layout (e.g. docked items)! To manage this, we keep
  132004. // two counters for these and one for the combined total:
  132005. if (ownerContext.target.ownerLayout.isComponentLayout) {
  132006. ++ownerCtContext.remainingComponentChildLayouts;
  132007. } else {
  132008. ++ownerCtContext.remainingContainerChildLayouts;
  132009. }
  132010. ++ownerCtContext.remainingChildLayouts;
  132011. }
  132012. if (!layout.initialized) {
  132013. layout.initLayout();
  132014. }
  132015. layout.beginLayout(ownerContext);
  132016. } else {
  132017. ++layout.beginCount;
  132018. if (!layout.running) {
  132019. // back into the mahem with this one:
  132020. ++me.remainingLayouts;
  132021. layout.running = true;
  132022. if (layout.isComponentLayout) {
  132023. // this one is fun... if we call setProp('done', false) that would still
  132024. // trigger/unblock layouts, but what layouts are really looking for with
  132025. // this property is for it to go to true, not just be set to a value...
  132026. ownerContext.unsetProp('done');
  132027. // On subsequent resets we increment the child layout count properties
  132028. // on ownerCtContext and clear 'childrenDone' and the appropriate other
  132029. // indicator as we transition to 1:
  132030. ownerCtContext = ownerContext.ownerCtContext;
  132031. if (ownerCtContext) {
  132032. if (ownerContext.target.ownerLayout.isComponentLayout) {
  132033. if (++ownerCtContext.remainingComponentChildLayouts == 1) {
  132034. ownerCtContext.unsetProp('componentChildrenDone');
  132035. }
  132036. } else {
  132037. if (++ownerCtContext.remainingContainerChildLayouts == 1) {
  132038. ownerCtContext.unsetProp('containerChildrenDone');
  132039. }
  132040. }
  132041. if (++ownerCtContext.remainingChildLayouts == 1) {
  132042. ownerCtContext.unsetProp('childrenDone');
  132043. }
  132044. }
  132045. }
  132046. // and it needs to be removed from the completion and/or finalize queues...
  132047. me.completionQueue.remove(layout);
  132048. me.finalizeQueue.remove(layout);
  132049. }
  132050. }
  132051. layout.beginLayoutCycle(ownerContext, firstTime);
  132052. },
  132053. /**
  132054. * Runs the layout calculations. This can be called only once on this object.
  132055. * @return {Boolean} True if all layouts were completed, false if not.
  132056. */
  132057. run: function () {
  132058. var me = this,
  132059. flushed = false,
  132060. watchDog = 100;
  132061. me.flushInvalidates();
  132062. me.state = 1;
  132063. me.totalCount = me.layoutQueue.getCount();
  132064. // We may start with unflushed data placed by beginLayout calls. Since layouts may
  132065. // use setProp as a convenience, even in a write phase, we don't want to transition
  132066. // to a read phase with unflushed data since we can write it now "cheaply". Also,
  132067. // these value could easily be needed in the DOM in order to really get going with
  132068. // the calculations. In particular, fixed (configured) dimensions fall into this
  132069. // category.
  132070. me.flush();
  132071. // While we have layouts that have not completed...
  132072. while ((me.remainingLayouts || me.invalidQueue.length) && watchDog--) {
  132073. if (me.invalidQueue.length) {
  132074. me.flushInvalidates();
  132075. }
  132076. // if any of them can run right now, run them
  132077. if (me.runCycle()) {
  132078. flushed = false; // progress means we probably need to flush something
  132079. // but not all progress appears in the flushQueue (e.g. 'contentHeight')
  132080. } else if (!flushed) {
  132081. // as long as we are making progress, flush updates to the DOM and see if
  132082. // that triggers or unblocks any layouts...
  132083. me.flush();
  132084. flushed = true; // all flushed now, so more progress is required
  132085. me.flushLayouts('completionQueue', 'completeLayout');
  132086. } else {
  132087. // after a flush, we must make progress or something is WRONG
  132088. me.state = 2;
  132089. break;
  132090. }
  132091. if (!(me.remainingLayouts || me.invalidQueue.length)) {
  132092. me.flush();
  132093. me.flushLayouts('completionQueue', 'completeLayout');
  132094. me.flushLayouts('finalizeQueue', 'finalizeLayout');
  132095. }
  132096. }
  132097. return me.runComplete();
  132098. },
  132099. runComplete: function () {
  132100. var me = this;
  132101. me.state = 2;
  132102. if (me.remainingLayouts) {
  132103. me.handleFailure();
  132104. return false;
  132105. }
  132106. me.flush();
  132107. // Call finishedLayout on all layouts, but do not clear the queue.
  132108. me.flushLayouts('finishQueue', 'finishedLayout', true);
  132109. // Call notifyOwner on all layouts and then clear the queue.
  132110. me.flushLayouts('finishQueue', 'notifyOwner');
  132111. me.flush(); // in case any setProp calls were made
  132112. me.flushAnimations();
  132113. return true;
  132114. },
  132115. /**
  132116. * Performs one layout cycle by calling each layout in the layout queue.
  132117. * @return {Boolean} True if some progress was made, false if not.
  132118. * @protected
  132119. */
  132120. runCycle: function () {
  132121. var me = this,
  132122. layouts = me.layoutQueue.clear(),
  132123. length = layouts.length,
  132124. i;
  132125. ++me.cycleCount;
  132126. // This value is incremented by ContextItem#setProp whenever new values are set
  132127. // (thereby detecting forward progress):
  132128. me.progressCount = 0;
  132129. for (i = 0; i < length; ++i) {
  132130. me.runLayout(me.currentLayout = layouts[i]);
  132131. }
  132132. me.currentLayout = null;
  132133. return me.progressCount > 0;
  132134. },
  132135. /**
  132136. * Runs one layout as part of a cycle.
  132137. * @private
  132138. */
  132139. runLayout: function (layout) {
  132140. var me = this,
  132141. ownerContext = me.getCmp(layout.owner);
  132142. layout.pending = false;
  132143. if (ownerContext.state.blocks) {
  132144. return;
  132145. }
  132146. // We start with the assumption that the layout will finish and if it does not, it
  132147. // must clear this flag. It turns out this is much simpler than knowing when a layout
  132148. // is done (100% correctly) when base classes and derived classes are collaborating.
  132149. // Knowing that some part of the layout is not done is much more obvious.
  132150. layout.done = true;
  132151. ++layout.calcCount;
  132152. ++me.calcCount;
  132153. layout.calculate(ownerContext);
  132154. if (layout.done) {
  132155. me.layoutDone(layout);
  132156. if (layout.completeLayout) {
  132157. me.queueCompletion(layout);
  132158. }
  132159. if (layout.finalizeLayout) {
  132160. me.queueFinalize(layout);
  132161. }
  132162. } else if (!layout.pending && !layout.invalid && !(layout.blockCount + layout.triggerCount - layout.firedTriggers)) {
  132163. // A layout that is not done and has no blocks or triggers that will queue it
  132164. // automatically, must be queued now:
  132165. me.queueLayout(layout);
  132166. }
  132167. },
  132168. /**
  132169. * Set the size of a component, element or composite or an array of components or elements.
  132170. * @param {Ext.Component/Ext.Component[]/Ext.dom.Element/Ext.dom.Element[]/Ext.dom.CompositeElement}
  132171. * The item(s) to size.
  132172. * @param {Number} width The new width to set (ignored if undefined or NaN).
  132173. * @param {Number} height The new height to set (ignored if undefined or NaN).
  132174. */
  132175. setItemSize: function(item, width, height) {
  132176. var items = item,
  132177. len = 1,
  132178. contextItem, i;
  132179. // NOTE: we don't pre-check for validity because:
  132180. // - maybe only one dimension is valid
  132181. // - the diagnostics layer will track the setProp call to help find who is trying
  132182. // (but failing) to set a property
  132183. // - setProp already checks this anyway
  132184. if (item.isComposite) {
  132185. items = item.elements;
  132186. len = items.length;
  132187. item = items[0];
  132188. } else if (!item.dom && !item.el) { // array by process of elimination
  132189. len = items.length;
  132190. item = items[0];
  132191. }
  132192. // else len = 1 and items = item (to avoid error on "items[++i]")
  132193. for (i = 0; i < len; ) {
  132194. contextItem = this.get(item);
  132195. contextItem.setSize(width, height);
  132196. item = items[++i]; // this accomodation avoids making an array of 1
  132197. }
  132198. }
  132199. });
  132200. /**
  132201. * Component layout for tabs
  132202. * @private
  132203. */
  132204. Ext.define('Ext.layout.component.Tab', {
  132205. extend: 'Ext.layout.component.Button',
  132206. alias: 'layout.tab',
  132207. beginLayout: function(ownerContext) {
  132208. var me = this,
  132209. closable = me.owner.closable;
  132210. // Changing the close button visibility causes our cached measurements to be wrong,
  132211. // so we must convince our base class to re-cache those adjustments...
  132212. if (me.lastClosable !== closable) {
  132213. me.lastClosable = closable;
  132214. me.clearTargetCache();
  132215. }
  132216. me.callParent(arguments);
  132217. }
  132218. });
  132219. /**
  132220. * @private
  132221. */
  132222. Ext.define('Ext.layout.component.field.Slider', {
  132223. /* Begin Definitions */
  132224. alias: ['layout.sliderfield'],
  132225. extend: 'Ext.layout.component.field.Field',
  132226. /* End Definitions */
  132227. type: 'sliderfield',
  132228. beginLayout: function(ownerContext) {
  132229. this.callParent(arguments);
  132230. ownerContext.endElContext = ownerContext.getEl('endEl');
  132231. ownerContext.innerElContext = ownerContext.getEl('innerEl');
  132232. ownerContext.bodyElContext = ownerContext.getEl('bodyEl');
  132233. },
  132234. publishInnerHeight: function (ownerContext, height) {
  132235. var innerHeight = height - this.measureLabelErrorHeight(ownerContext),
  132236. endElPad,
  132237. inputPad;
  132238. if (this.owner.vertical) {
  132239. endElPad = ownerContext.endElContext.getPaddingInfo();
  132240. inputPad = ownerContext.inputContext.getPaddingInfo();
  132241. ownerContext.innerElContext.setHeight(innerHeight - inputPad.height - endElPad.height);
  132242. } else {
  132243. ownerContext.bodyElContext.setHeight(innerHeight);
  132244. }
  132245. },
  132246. publishInnerWidth: function (ownerContext, width) {
  132247. if (!this.owner.vertical) {
  132248. var endElPad = ownerContext.endElContext.getPaddingInfo(),
  132249. inputPad = ownerContext.inputContext.getPaddingInfo();
  132250. ownerContext.innerElContext.setWidth(width - inputPad.left - endElPad.right - ownerContext.labelContext.getProp('width'));
  132251. }
  132252. },
  132253. beginLayoutFixed: function(ownerContext, width, suffix) {
  132254. var me = this,
  132255. ieInputWidthAdjustment = me.ieInputWidthAdjustment;
  132256. if (ieInputWidthAdjustment) {
  132257. // adjust for IE 6/7 strict content-box model
  132258. // RTL: This might have to be padding-left unless the senses of the padding styles switch when in RTL mode.
  132259. me.owner.bodyEl.setStyle('padding-right', ieInputWidthAdjustment + 'px');
  132260. }
  132261. me.callParent(arguments);
  132262. }
  132263. });
  132264. /**
  132265. * This is a layout that inherits the anchoring of {@link Ext.layout.container.Anchor} and adds the
  132266. * ability for x/y positioning using the standard x and y component config options.
  132267. *
  132268. * This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
  132269. * configuration property. See {@link Ext.container.Container#layout} for additional details.
  132270. *
  132271. * @example
  132272. * Ext.create('Ext.form.Panel', {
  132273. * title: 'Absolute Layout',
  132274. * width: 300,
  132275. * height: 275,
  132276. * layout: {
  132277. * type: 'absolute'
  132278. * // layout-specific configs go here
  132279. * //itemCls: 'x-abs-layout-item',
  132280. * },
  132281. * url:'save-form.php',
  132282. * defaultType: 'textfield',
  132283. * items: [{
  132284. * x: 10,
  132285. * y: 10,
  132286. * xtype:'label',
  132287. * text: 'Send To:'
  132288. * },{
  132289. * x: 80,
  132290. * y: 10,
  132291. * name: 'to',
  132292. * anchor:'90%' // anchor width by percentage
  132293. * },{
  132294. * x: 10,
  132295. * y: 40,
  132296. * xtype:'label',
  132297. * text: 'Subject:'
  132298. * },{
  132299. * x: 80,
  132300. * y: 40,
  132301. * name: 'subject',
  132302. * anchor: '90%' // anchor width by percentage
  132303. * },{
  132304. * x:0,
  132305. * y: 80,
  132306. * xtype: 'textareafield',
  132307. * name: 'msg',
  132308. * anchor: '100% 100%' // anchor width and height
  132309. * }],
  132310. * renderTo: Ext.getBody()
  132311. * });
  132312. */
  132313. Ext.define('Ext.layout.container.Absolute', {
  132314. /* Begin Definitions */
  132315. alias: 'layout.absolute',
  132316. extend: 'Ext.layout.container.Anchor',
  132317. alternateClassName: 'Ext.layout.AbsoluteLayout',
  132318. /* End Definitions */
  132319. targetCls: Ext.baseCSSPrefix + 'abs-layout-ct',
  132320. itemCls: Ext.baseCSSPrefix + 'abs-layout-item',
  132321. /**
  132322. * @cfg {Boolean} ignoreOnContentChange
  132323. * True indicates that changes to one item in this layout do not effect the layout in
  132324. * general. This may need to be set to false if {@link Ext.Component#autoScroll}
  132325. * is enabled for the container.
  132326. */
  132327. ignoreOnContentChange: true,
  132328. type: 'absolute',
  132329. // private
  132330. adjustWidthAnchor: function(value, childContext) {
  132331. var padding = this.targetPadding,
  132332. x = childContext.getStyle('left');
  132333. return value - x + padding.left;
  132334. },
  132335. // private
  132336. adjustHeightAnchor: function(value, childContext) {
  132337. var padding = this.targetPadding,
  132338. y = childContext.getStyle('top');
  132339. return value - y + padding.top;
  132340. },
  132341. isItemLayoutRoot: function (item) {
  132342. return this.ignoreOnContentChange || this.callParent(arguments);
  132343. },
  132344. isItemShrinkWrap: function (item) {
  132345. return true;
  132346. },
  132347. beginLayout: function (ownerContext) {
  132348. var me = this,
  132349. target = me.getTarget();
  132350. me.callParent(arguments);
  132351. // Do not set position: relative; when the absolute layout target is the body
  132352. if (target.dom !== document.body) {
  132353. target.position();
  132354. }
  132355. me.targetPadding = ownerContext.targetContext.getPaddingInfo();
  132356. },
  132357. isItemBoxParent: function (itemContext) {
  132358. return true;
  132359. },
  132360. onContentChange: function () {
  132361. if (this.ignoreOnContentChange) {
  132362. return false;
  132363. }
  132364. return this.callParent(arguments);
  132365. }
  132366. });
  132367. /**
  132368. * This is a layout that manages multiple Panels in an expandable accordion style such that by default only
  132369. * one Panel can be expanded at any given time (set {@link #multi} config to have more open). Each Panel has
  132370. * built-in support for expanding and collapsing.
  132371. *
  132372. * Note: Only Ext Panels and all subclasses of Ext.panel.Panel may be used in an accordion layout Container.
  132373. *
  132374. * @example
  132375. * Ext.create('Ext.panel.Panel', {
  132376. * title: 'Accordion Layout',
  132377. * width: 300,
  132378. * height: 300,
  132379. * defaults: {
  132380. * // applied to each contained panel
  132381. * bodyStyle: 'padding:15px'
  132382. * },
  132383. * layout: {
  132384. * // layout-specific configs go here
  132385. * type: 'accordion',
  132386. * titleCollapse: false,
  132387. * animate: true,
  132388. * activeOnTop: true
  132389. * },
  132390. * items: [{
  132391. * title: 'Panel 1',
  132392. * html: 'Panel content!'
  132393. * },{
  132394. * title: 'Panel 2',
  132395. * html: 'Panel content!'
  132396. * },{
  132397. * title: 'Panel 3',
  132398. * html: 'Panel content!'
  132399. * }],
  132400. * renderTo: Ext.getBody()
  132401. * });
  132402. */
  132403. Ext.define('Ext.layout.container.Accordion', {
  132404. extend: 'Ext.layout.container.VBox',
  132405. alias: ['layout.accordion'],
  132406. alternateClassName: 'Ext.layout.AccordionLayout',
  132407. itemCls: [Ext.baseCSSPrefix + 'box-item', Ext.baseCSSPrefix + 'accordion-item'],
  132408. align: 'stretch',
  132409. /**
  132410. * @cfg {Boolean} fill
  132411. * True to adjust the active item's height to fill the available space in the container, false to use the
  132412. * item's current height, or auto height if not explicitly set.
  132413. */
  132414. fill : true,
  132415. /**
  132416. * @cfg {Boolean} autoWidth
  132417. * Child Panels have their width actively managed to fit within the accordion's width.
  132418. * @removed This config is ignored in ExtJS 4
  132419. */
  132420. /**
  132421. * @cfg {Boolean} titleCollapse
  132422. * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
  132423. * expand/collapse only when the toggle tool button is clicked. When set to false,
  132424. * {@link #hideCollapseTool} should be false also.
  132425. */
  132426. titleCollapse : true,
  132427. /**
  132428. * @cfg {Boolean} hideCollapseTool
  132429. * True to hide the contained Panels' collapse/expand toggle buttons, false to display them.
  132430. * When set to true, {@link #titleCollapse} is automatically set to true.
  132431. */
  132432. hideCollapseTool : false,
  132433. /**
  132434. * @cfg {Boolean} collapseFirst
  132435. * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
  132436. * in the contained Panels' title bars, false to render it last.
  132437. */
  132438. collapseFirst : false,
  132439. /**
  132440. * @cfg {Boolean} animate
  132441. * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
  132442. * close directly with no animation. Note: The layout performs animated collapsing
  132443. * and expanding, *not* the child Panels.
  132444. */
  132445. animate : true,
  132446. /**
  132447. * @cfg {Boolean} activeOnTop
  132448. * Only valid when {@link #multi} is `false` and {@link #animate} is `false`.
  132449. *
  132450. * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
  132451. * false to keep the panels in the rendered order.
  132452. */
  132453. activeOnTop : false,
  132454. /**
  132455. * @cfg {Boolean} multi
  132456. * Set to true to enable multiple accordion items to be open at once.
  132457. */
  132458. multi: false,
  132459. defaultAnimatePolicy: {
  132460. y: true,
  132461. height: true
  132462. },
  132463. constructor: function() {
  132464. var me = this;
  132465. me.callParent(arguments);
  132466. if (me.animate) {
  132467. me.animatePolicy = Ext.apply({}, me.defaultAnimatePolicy);
  132468. } else {
  132469. me.animatePolicy = null;
  132470. }
  132471. },
  132472. beforeRenderItems: function (items) {
  132473. var me = this,
  132474. ln = items.length,
  132475. i = 0,
  132476. comp;
  132477. for (; i < ln; i++) {
  132478. comp = items[i];
  132479. if (!comp.rendered) {
  132480. // Set up initial properties for Panels in an accordion.
  132481. if (me.collapseFirst) {
  132482. comp.collapseFirst = me.collapseFirst;
  132483. }
  132484. if (me.hideCollapseTool) {
  132485. comp.hideCollapseTool = me.hideCollapseTool;
  132486. comp.titleCollapse = true;
  132487. }
  132488. else if (me.titleCollapse) {
  132489. comp.titleCollapse = me.titleCollapse;
  132490. }
  132491. delete comp.hideHeader;
  132492. delete comp.width;
  132493. comp.collapsible = true;
  132494. comp.title = comp.title || '&#160;';
  132495. comp.addBodyCls(Ext.baseCSSPrefix + 'accordion-body');
  132496. // If only one child Panel is allowed to be expanded
  132497. // then collapse all except the first one found with collapsed:false
  132498. if (!me.multi) {
  132499. // If there is an expanded item, all others must be rendered collapsed.
  132500. if (me.expandedItem !== undefined) {
  132501. comp.collapsed = true;
  132502. }
  132503. // Otherwise expand the first item with collapsed explicitly configured as false
  132504. else if (comp.hasOwnProperty('collapsed') && comp.collapsed === false) {
  132505. me.expandedItem = i;
  132506. } else {
  132507. comp.collapsed = true;
  132508. }
  132509. // If only one child Panel may be expanded, then intercept expand/show requests.
  132510. me.owner.mon(comp, {
  132511. show: me.onComponentShow,
  132512. beforeexpand: me.onComponentExpand,
  132513. scope: me
  132514. });
  132515. }
  132516. // If we must fill available space, a collapse must be listened for and a sibling must
  132517. // be expanded.
  132518. if (me.fill) {
  132519. me.owner.mon(comp, {
  132520. beforecollapse: me.onComponentCollapse,
  132521. scope: me
  132522. });
  132523. }
  132524. }
  132525. }
  132526. // If no collapsed:false Panels found, make the first one expanded.
  132527. if (ln && me.expandedItem === undefined) {
  132528. me.expandedItem = 0;
  132529. items[0].collapsed = false;
  132530. }
  132531. },
  132532. getItemsRenderTree: function(items) {
  132533. this.beforeRenderItems(items);
  132534. return this.callParent(arguments);
  132535. },
  132536. renderItems : function(items, target) {
  132537. this.beforeRenderItems(items);
  132538. this.callParent(arguments);
  132539. },
  132540. configureItem: function(item) {
  132541. this.callParent(arguments);
  132542. // We handle animations for the expand/collapse of items.
  132543. // Items do not have individual borders
  132544. item.animCollapse = item.border = false;
  132545. // If filling available space, all Panels flex.
  132546. if (this.fill) {
  132547. item.flex = 1;
  132548. }
  132549. },
  132550. onChildPanelRender: function(panel) {
  132551. panel.header.addCls(Ext.baseCSSPrefix + 'accordion-hd');
  132552. },
  132553. beginLayout: function (ownerContext) {
  132554. this.callParent(arguments);
  132555. this.updatePanelClasses(ownerContext);
  132556. },
  132557. updatePanelClasses: function(ownerContext) {
  132558. var children = ownerContext.visibleItems,
  132559. ln = children.length,
  132560. siblingCollapsed = true,
  132561. i, child, header;
  132562. for (i = 0; i < ln; i++) {
  132563. child = children[i];
  132564. header = child.header;
  132565. header.addCls(Ext.baseCSSPrefix + 'accordion-hd');
  132566. if (siblingCollapsed) {
  132567. header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
  132568. } else {
  132569. header.addCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
  132570. }
  132571. if (i + 1 == ln && child.collapsed) {
  132572. header.addCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
  132573. } else {
  132574. header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
  132575. }
  132576. siblingCollapsed = child.collapsed;
  132577. }
  132578. },
  132579. // When a Component expands, adjust the heights of the other Components to be just enough to accommodate
  132580. // their headers.
  132581. // The expanded Component receives the only flex value, and so gets all remaining space.
  132582. onComponentExpand: function(toExpand) {
  132583. var me = this,
  132584. owner = me.owner,
  132585. expanded,
  132586. expandedCount, i,
  132587. previousValue;
  132588. if (!me.processing) {
  132589. me.processing = true;
  132590. previousValue = owner.deferLayouts;
  132591. owner.deferLayouts = true;
  132592. expanded = me.multi ? [] : owner.query('>panel:not([collapsed])');
  132593. expandedCount = expanded.length;
  132594. // Collapse all other expanded child items (Won't loop if multi is true)
  132595. for (i = 0; i < expandedCount; i++) {
  132596. expanded[i].collapse();
  132597. }
  132598. owner.deferLayouts = previousValue;
  132599. me.processing = false;
  132600. }
  132601. },
  132602. onComponentCollapse: function(comp) {
  132603. var me = this,
  132604. owner = me.owner,
  132605. toExpand,
  132606. expanded,
  132607. previousValue;
  132608. if (me.owner.items.getCount() === 1) {
  132609. // do not allow collapse if there is only one item
  132610. return false;
  132611. }
  132612. if (!me.processing) {
  132613. me.processing = true;
  132614. previousValue = owner.deferLayouts;
  132615. owner.deferLayouts = true;
  132616. toExpand = comp.next() || comp.prev();
  132617. // If we are allowing multi, and the "toCollapse" component is NOT the only expanded Component,
  132618. // then ask the box layout to collapse it to its header.
  132619. if (me.multi) {
  132620. expanded = me.owner.query('>panel:not([collapsed])');
  132621. // If the collapsing Panel is the only expanded one, expand the following Component.
  132622. // All this is handling fill: true, so there must be at least one expanded,
  132623. if (expanded.length === 1) {
  132624. toExpand.expand();
  132625. }
  132626. } else if (toExpand) {
  132627. toExpand.expand();
  132628. }
  132629. owner.deferLayouts = previousValue;
  132630. me.processing = false;
  132631. }
  132632. },
  132633. onComponentShow: function(comp) {
  132634. // Showing a Component means that you want to see it, so expand it.
  132635. this.onComponentExpand(comp);
  132636. }
  132637. });
  132638. /**
  132639. * This class functions **between siblings of a {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox}
  132640. * layout** to resize both immediate siblings.
  132641. *
  132642. * A Splitter will preserve the flex ratio of any flexed siblings it is required to resize. It does this by setting the `flex` property of *all* flexed siblings
  132643. * to equal their pixel size. The actual numerical `flex` property in the Components will change, but the **ratio** to the total flex value will be preserved.
  132644. *
  132645. * A Splitter may be configured to show a centered mini-collapse tool orientated to collapse the {@link #collapseTarget}.
  132646. * The Splitter will then call that sibling Panel's {@link Ext.panel.Panel#method-collapse collapse} or {@link Ext.panel.Panel#method-expand expand} method
  132647. * to perform the appropriate operation (depending on the sibling collapse state). To create the mini-collapse tool but take care
  132648. * of collapsing yourself, configure the splitter with `{@link #performCollapse}: false`.
  132649. */
  132650. Ext.define('Ext.resizer.Splitter', {
  132651. extend: 'Ext.Component',
  132652. requires: ['Ext.XTemplate'],
  132653. uses: ['Ext.resizer.SplitterTracker'],
  132654. alias: 'widget.splitter',
  132655. childEls: [
  132656. 'collapseEl'
  132657. ],
  132658. renderTpl: [
  132659. '<tpl if="collapsible===true">',
  132660. '<div id="{id}-collapseEl" class="', Ext.baseCSSPrefix, 'collapse-el ',
  132661. Ext.baseCSSPrefix, 'layout-split-{collapseDir}">&#160;</div>',
  132662. '</tpl>'
  132663. ],
  132664. baseCls: Ext.baseCSSPrefix + 'splitter',
  132665. collapsedClsInternal: Ext.baseCSSPrefix + 'splitter-collapsed',
  132666. // Default to tree, allow internal classes to disable resizing
  132667. canResize: true,
  132668. /**
  132669. * @cfg {Boolean} collapsible
  132670. * True to show a mini-collapse tool in the Splitter to toggle expand and collapse on the {@link #collapseTarget} Panel.
  132671. * Defaults to the {@link Ext.panel.Panel#collapsible collapsible} setting of the Panel.
  132672. */
  132673. collapsible: false,
  132674. /**
  132675. * @cfg {Boolean} performCollapse
  132676. * Set to false to prevent this Splitter's mini-collapse tool from managing the collapse
  132677. * state of the {@link #collapseTarget}.
  132678. */
  132679. /**
  132680. * @cfg {Boolean} collapseOnDblClick
  132681. * True to enable dblclick to toggle expand and collapse on the {@link #collapseTarget} Panel.
  132682. */
  132683. collapseOnDblClick: true,
  132684. /**
  132685. * @cfg {Number} defaultSplitMin
  132686. * Provides a default minimum width or height for the two components
  132687. * that the splitter is between.
  132688. */
  132689. defaultSplitMin: 40,
  132690. /**
  132691. * @cfg {Number} defaultSplitMax
  132692. * Provides a default maximum width or height for the two components
  132693. * that the splitter is between.
  132694. */
  132695. defaultSplitMax: 1000,
  132696. /**
  132697. * @cfg {String} collapsedCls
  132698. * A class to add to the splitter when it is collapsed. See {@link #collapsible}.
  132699. */
  132700. /**
  132701. * @cfg {String/Ext.panel.Panel} collapseTarget
  132702. * A string describing the relative position of the immediate sibling Panel to collapse. May be 'prev' or 'next'.
  132703. *
  132704. * Or the immediate sibling Panel to collapse.
  132705. *
  132706. * The orientation of the mini-collapse tool will be inferred from this setting.
  132707. *
  132708. * **Note that only Panels may be collapsed.**
  132709. */
  132710. collapseTarget: 'next',
  132711. /**
  132712. * @property {String} orientation
  132713. * Orientation of this Splitter. `'vertical'` when used in an hbox layout, `'horizontal'`
  132714. * when used in a vbox layout.
  132715. */
  132716. horizontal: false,
  132717. vertical: false,
  132718. /**
  132719. * Returns the config object (with an `xclass` property) for the splitter tracker. This
  132720. * is overridden by {@link Ext.resizer.BorderSplitter BorderSplitter} to create a
  132721. * {@link Ext.resizer.BorderSplitterTracker BorderSplitterTracker}.
  132722. * @protected
  132723. */
  132724. getTrackerConfig: function () {
  132725. return {
  132726. xclass: 'Ext.resizer.SplitterTracker',
  132727. el: this.el,
  132728. splitter: this
  132729. };
  132730. },
  132731. beforeRender: function() {
  132732. var me = this,
  132733. target = me.getCollapseTarget(),
  132734. collapseDir = me.getCollapseDirection(),
  132735. vertical = me.vertical,
  132736. fixedSizeProp = vertical ? 'width' : 'height',
  132737. stretchSizeProp = vertical ? 'height' : 'width',
  132738. cls;
  132739. me.callParent();
  132740. if (!me.hasOwnProperty(stretchSizeProp)) {
  132741. me[stretchSizeProp] = '100%';
  132742. }
  132743. if (!me.hasOwnProperty(fixedSizeProp)) {
  132744. me[fixedSizeProp] = 5;
  132745. }
  132746. if (target.collapsed) {
  132747. me.addCls(me.collapsedClsInternal);
  132748. }
  132749. cls = me.baseCls + '-' + me.orientation;
  132750. me.addCls(cls);
  132751. if (!me.canResize) {
  132752. me.addCls(cls + '-noresize');
  132753. }
  132754. Ext.applyIf(me.renderData, {
  132755. collapseDir: collapseDir,
  132756. collapsible: me.collapsible || target.collapsible
  132757. });
  132758. },
  132759. onRender: function() {
  132760. var me = this;
  132761. me.callParent(arguments);
  132762. // Add listeners on the mini-collapse tool unless performCollapse is set to false
  132763. if (me.performCollapse !== false) {
  132764. if (me.renderData.collapsible) {
  132765. me.mon(me.collapseEl, 'click', me.toggleTargetCmp, me);
  132766. }
  132767. if (me.collapseOnDblClick) {
  132768. me.mon(me.el, 'dblclick', me.toggleTargetCmp, me);
  132769. }
  132770. }
  132771. // Ensure the mini collapse icon is set to the correct direction when the target is collapsed/expanded by any means
  132772. me.mon(me.getCollapseTarget(), {
  132773. collapse: me.onTargetCollapse,
  132774. expand: me.onTargetExpand,
  132775. scope: me
  132776. });
  132777. me.el.unselectable();
  132778. if (me.canResize) {
  132779. me.tracker = Ext.create(me.getTrackerConfig());
  132780. // Relay the most important events to our owner (could open wider later):
  132781. me.relayEvents(me.tracker, [ 'beforedragstart', 'dragstart', 'dragend' ]);
  132782. }
  132783. },
  132784. getCollapseDirection: function() {
  132785. var me = this,
  132786. dir = me.collapseDirection,
  132787. collapseTarget, idx, items, type;
  132788. if (!dir) {
  132789. collapseTarget = me.collapseTarget;
  132790. if (collapseTarget.isComponent) {
  132791. dir = collapseTarget.collapseDirection;
  132792. }
  132793. if (!dir) {
  132794. // Avoid duplication of string tests.
  132795. // Create a two bit truth table of the configuration of the Splitter:
  132796. // Collapse Target | orientation
  132797. // 0 0 = next, horizontal
  132798. // 0 1 = next, vertical
  132799. // 1 0 = prev, horizontal
  132800. // 1 1 = prev, vertical
  132801. type = me.ownerCt.layout.type;
  132802. if (collapseTarget.isComponent) {
  132803. items = me.ownerCt.items;
  132804. idx = Number(items.indexOf(collapseTarget) == items.indexOf(me) - 1) << 1 | Number(type == 'hbox');
  132805. } else {
  132806. idx = Number(me.collapseTarget == 'prev') << 1 | Number(type == 'hbox');
  132807. }
  132808. // Read the data out the truth table
  132809. dir = ['bottom', 'right', 'top', 'left'][idx];
  132810. }
  132811. me.collapseDirection = dir;
  132812. }
  132813. me.orientation = (dir == 'top' || dir == 'bottom') ? 'horizontal' : 'vertical';
  132814. me[me.orientation] = true;
  132815. return dir;
  132816. },
  132817. getCollapseTarget: function() {
  132818. var me = this;
  132819. return me.collapseTarget.isComponent ? me.collapseTarget : me.collapseTarget == 'prev' ? me.previousSibling() : me.nextSibling();
  132820. },
  132821. onTargetCollapse: function(target) {
  132822. this.el.addCls([this.collapsedClsInternal, this.collapsedCls]);
  132823. },
  132824. onTargetExpand: function(target) {
  132825. this.el.removeCls([this.collapsedClsInternal, this.collapsedCls]);
  132826. },
  132827. toggleTargetCmp: function(e, t) {
  132828. var cmp = this.getCollapseTarget(),
  132829. placeholder = cmp.placeholder,
  132830. toggle;
  132831. if (placeholder && !placeholder.hidden) {
  132832. toggle = true;
  132833. } else {
  132834. toggle = !cmp.hidden;
  132835. }
  132836. if (toggle) {
  132837. if (cmp.collapsed) {
  132838. cmp.expand();
  132839. } else if (cmp.collapseDirection) {
  132840. cmp.collapse();
  132841. } else {
  132842. cmp.collapse(this.renderData.collapseDir);
  132843. }
  132844. }
  132845. },
  132846. /*
  132847. * Work around IE bug. %age margins do not get recalculated on element resize unless repaint called.
  132848. */
  132849. setSize: function() {
  132850. var me = this;
  132851. me.callParent(arguments);
  132852. if (Ext.isIE && me.el) {
  132853. me.el.repaint();
  132854. }
  132855. },
  132856. beforeDestroy: function(){
  132857. Ext.destroy(this.tracker);
  132858. this.callParent();
  132859. }
  132860. });
  132861. /**
  132862. * Private utility class for Ext.layout.container.Border.
  132863. * @private
  132864. */
  132865. Ext.define('Ext.resizer.BorderSplitter', {
  132866. extend: 'Ext.resizer.Splitter',
  132867. uses: ['Ext.resizer.BorderSplitterTracker'],
  132868. alias: 'widget.bordersplitter',
  132869. // must be configured in by the border layout:
  132870. collapseTarget: null,
  132871. getTrackerConfig: function () {
  132872. var trackerConfig = this.callParent();
  132873. trackerConfig.xclass = 'Ext.resizer.BorderSplitterTracker';
  132874. return trackerConfig;
  132875. }
  132876. });
  132877. /**
  132878. * This is a multi-pane, application-oriented UI layout style that supports multiple nested panels, automatic bars
  132879. * between regions and built-in {@link Ext.panel.Panel#collapsible expanding and collapsing} of regions.
  132880. *
  132881. * This class is intended to be extended or created via the `layout:'border'` {@link Ext.container.Container#layout}
  132882. * config, and should generally not need to be created directly via the new keyword.
  132883. *
  132884. * @example
  132885. * Ext.create('Ext.panel.Panel', {
  132886. * width: 500,
  132887. * height: 300,
  132888. * title: 'Border Layout',
  132889. * layout: 'border',
  132890. * items: [{
  132891. * title: 'South Region is resizable',
  132892. * region: 'south', // position for region
  132893. * xtype: 'panel',
  132894. * height: 100,
  132895. * split: true, // enable resizing
  132896. * margins: '0 5 5 5'
  132897. * },{
  132898. * // xtype: 'panel' implied by default
  132899. * title: 'West Region is collapsible',
  132900. * region:'west',
  132901. * xtype: 'panel',
  132902. * margins: '5 0 0 5',
  132903. * width: 200,
  132904. * collapsible: true, // make collapsible
  132905. * id: 'west-region-container',
  132906. * layout: 'fit'
  132907. * },{
  132908. * title: 'Center Region',
  132909. * region: 'center', // center region is required, no width/height specified
  132910. * xtype: 'panel',
  132911. * layout: 'fit',
  132912. * margins: '5 5 0 0'
  132913. * }],
  132914. * renderTo: Ext.getBody()
  132915. * });
  132916. *
  132917. * # Notes
  132918. *
  132919. * - When using the split option, the layout will automatically insert a {@link Ext.resizer.Splitter}
  132920. * into the appropriate place. This will modify the underlying
  132921. * {@link Ext.container.Container#property-items items} collection in the container.
  132922. *
  132923. * - Any Container using the Border layout **must** have a child item with `region:'center'`.
  132924. * The child item in the center region will always be resized to fill the remaining space
  132925. * not used by the other regions in the layout.
  132926. *
  132927. * - Any child items with a region of `west` or `east` may be configured with either an initial
  132928. * `width`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage width
  132929. * **string** (Which is simply divided by 100 and used as a flex value).
  132930. * The 'center' region has a flex value of `1`.
  132931. *
  132932. * - Any child items with a region of `north` or `south` may be configured with either an initial
  132933. * `height`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage height
  132934. * **string** (Which is simply divided by 100 and used as a flex value).
  132935. * The 'center' region has a flex value of `1`.
  132936. *
  132937. * - **There is no BorderLayout.Region class in ExtJS 4.0+**
  132938. */
  132939. Ext.define('Ext.layout.container.Border', {
  132940. alias: 'layout.border',
  132941. extend: 'Ext.layout.container.Container',
  132942. requires: ['Ext.resizer.BorderSplitter', 'Ext.Component', 'Ext.fx.Anim'],
  132943. alternateClassName: 'Ext.layout.BorderLayout',
  132944. targetCls: Ext.baseCSSPrefix + 'border-layout-ct',
  132945. itemCls: [Ext.baseCSSPrefix + 'border-item', Ext.baseCSSPrefix + 'box-item'],
  132946. type: 'border',
  132947. /**
  132948. * @cfg {Boolean} split
  132949. * This configuration option is to be applied to the **child `items`** managed by this layout.
  132950. * Each region with `split:true` will get a {@link Ext.resizer.BorderSplitter Splitter} that
  132951. * allows for manual resizing of the container. Except for the `center` region.
  132952. */
  132953. /**
  132954. * @cfg {Boolean} [splitterResize=true]
  132955. * This configuration option is to be applied to the **child `items`** managed by this layout and
  132956. * is used in conjunction with {@link #split}. By default, when specifying {@link #split}, the region
  132957. * can be dragged to be resized. Set this option to false to show the split bar but prevent resizing.
  132958. */
  132959. /**
  132960. * @cfg {Number/String/Object} padding
  132961. * Sets the padding to be applied to all child items managed by this layout.
  132962. *
  132963. * This property can be specified as a string containing space-separated, numeric
  132964. * padding values. The order of the sides associated with each value matches the way
  132965. * CSS processes padding values:
  132966. *
  132967. * - If there is only one value, it applies to all sides.
  132968. * - If there are two values, the top and bottom borders are set to the first value
  132969. * and the right and left are set to the second.
  132970. * - If there are three values, the top is set to the first value, the left and right
  132971. * are set to the second, and the bottom is set to the third.
  132972. * - If there are four values, they apply to the top, right, bottom, and left,
  132973. * respectively.
  132974. *
  132975. */
  132976. padding: undefined,
  132977. percentageRe: /(\d+)%/,
  132978. /**
  132979. * Reused meta-data objects that describe axis properties.
  132980. * @private
  132981. */
  132982. axisProps: {
  132983. horz: {
  132984. borderBegin: 'west',
  132985. borderEnd: 'east',
  132986. horizontal: true,
  132987. posProp: 'x',
  132988. sizeProp: 'width',
  132989. sizePropCap: 'Width'
  132990. },
  132991. vert: {
  132992. borderBegin: 'north',
  132993. borderEnd: 'south',
  132994. horizontal: false,
  132995. posProp: 'y',
  132996. sizeProp: 'height',
  132997. sizePropCap: 'Height'
  132998. }
  132999. },
  133000. // @private
  133001. centerRegion: null,
  133002. /**
  133003. * Maps from region name to collapseDirection for panel.
  133004. * @private
  133005. */
  133006. collapseDirections: {
  133007. north: 'top',
  133008. south: 'bottom',
  133009. east: 'right',
  133010. west: 'left'
  133011. },
  133012. manageMargins: true,
  133013. panelCollapseAnimate: true,
  133014. panelCollapseMode: 'placeholder',
  133015. /**
  133016. * @cfg {Object} regionWeights
  133017. * The default weights to assign to regions in the border layout. These values are
  133018. * used when a region does not contain a `weight` property. This object must have
  133019. * properties for all regions ("north", "south", "east" and "west").
  133020. *
  133021. * **IMPORTANT:** Since this is an object, changing its properties will impact ALL
  133022. * instances of Border layout. If this is not desired, provide a replacement object as
  133023. * a config option instead:
  133024. *
  133025. * layout: {
  133026. * type: 'border',
  133027. * regionWeights: {
  133028. * west: 20,
  133029. * north: 10,
  133030. * south: -10,
  133031. * east: -20
  133032. * }
  133033. * }
  133034. *
  133035. * The region with the highest weight is assigned space from the border before other
  133036. * regions. Regions of equal weight are assigned space based on their position in the
  133037. * owner's items list (first come, first served).
  133038. */
  133039. regionWeights: {
  133040. north: 20,
  133041. south: 10,
  133042. center: 0,
  133043. west: -10,
  133044. east: -20
  133045. },
  133046. //----------------------------------
  133047. // Layout processing
  133048. /**
  133049. * Creates the axis objects for the layout. These are only missing size information
  133050. * which is added during {@link #calculate}.
  133051. * @private
  133052. */
  133053. beginAxis: function (ownerContext, regions, name) {
  133054. var me = this,
  133055. props = me.axisProps[name],
  133056. isVert = !props.horizontal,
  133057. sizeProp = props.sizeProp,
  133058. totalFlex = 0,
  133059. childItems = ownerContext.childItems,
  133060. length = childItems.length,
  133061. center, i, childContext, centerFlex, comp, region, match, size, type, target, placeholder;
  133062. for (i = 0; i < length; ++i) {
  133063. childContext = childItems[i];
  133064. comp = childContext.target;
  133065. childContext.layoutPos = {};
  133066. if (comp.region) {
  133067. childContext.region = region = comp.region;
  133068. childContext.isCenter = comp.isCenter;
  133069. childContext.isHorz = comp.isHorz;
  133070. childContext.isVert = comp.isVert;
  133071. childContext.weight = comp.weight || me.regionWeights[region] || 0;
  133072. regions[comp.id] = childContext;
  133073. if (comp.isCenter) {
  133074. center = childContext;
  133075. centerFlex = comp.flex;
  133076. ownerContext.centerRegion = center;
  133077. continue;
  133078. }
  133079. if (isVert !== childContext.isVert) {
  133080. continue;
  133081. }
  133082. // process regions "isVert ? north||south : east||center||west"
  133083. childContext.reverseWeighting = (region == props.borderEnd);
  133084. size = comp[sizeProp];
  133085. type = typeof size;
  133086. if (!comp.collapsed) {
  133087. if (type == 'string' && (match = me.percentageRe.exec(size))) {
  133088. childContext.percentage = parseInt(match[1], 10);
  133089. } else if (comp.flex) {
  133090. totalFlex += childContext.flex = comp.flex;
  133091. }
  133092. }
  133093. }
  133094. }
  133095. // Special cases for a collapsed center region
  133096. if (center) {
  133097. target = center.target;
  133098. if (placeholder = target.placeholderFor) {
  133099. if (!centerFlex && isVert === placeholder.collapsedVertical()) {
  133100. // The center region is a placeholder, collapsed in this axis
  133101. centerFlex = 0;
  133102. center.collapseAxis = name;
  133103. }
  133104. } else if (target.collapsed && (isVert === target.collapsedVertical())) {
  133105. // The center region is a collapsed header, collapsed in this axis
  133106. centerFlex = 0;
  133107. center.collapseAxis = name;
  133108. }
  133109. }
  133110. if (centerFlex == null) {
  133111. // If we still don't have a center flex, default to 1
  133112. centerFlex = 1;
  133113. }
  133114. totalFlex += centerFlex;
  133115. return Ext.apply({
  133116. before : isVert ? 'top' : 'left',
  133117. totalFlex : totalFlex
  133118. }, props);
  133119. },
  133120. beginLayout: function (ownerContext) {
  133121. var me = this,
  133122. items = me.getLayoutItems(),
  133123. pad = me.padding,
  133124. type = typeof pad,
  133125. padOnContainer = false,
  133126. childContext, item, length, i, regions, collapseTarget,
  133127. doShow, hidden, region;
  133128. // We sync the visibility state of splitters with their region:
  133129. if (pad) {
  133130. if (type == 'string' || type == 'number') {
  133131. pad = Ext.util.Format.parseBox(pad);
  133132. }
  133133. } else {
  133134. pad = ownerContext.getEl('getTargetEl').getPaddingInfo();
  133135. padOnContainer = true;
  133136. }
  133137. ownerContext.outerPad = pad;
  133138. ownerContext.padOnContainer = padOnContainer;
  133139. for (i = 0, length = items.length; i < length; ++i) {
  133140. item = items[i];
  133141. collapseTarget = me.getSplitterTarget(item);
  133142. if (collapseTarget) {
  133143. hidden = !!item.hidden;
  133144. if (!collapseTarget.split) {
  133145. if (collapseTarget.isCollapsingOrExpanding) {
  133146. doShow = !!collapseTarget.collapsed;
  133147. }
  133148. } else if (hidden !== collapseTarget.hidden) {
  133149. doShow = !collapseTarget.hidden;
  133150. }
  133151. if (doShow === true) {
  133152. item.show();
  133153. } else if (doShow === false) {
  133154. item.hide();
  133155. }
  133156. }
  133157. }
  133158. // The above synchronized visibility of splitters with their regions, so we need
  133159. // to make this call after that so that childItems and visibleItems are correct:
  133160. //
  133161. me.callParent(arguments);
  133162. items = ownerContext.childItems;
  133163. length = items.length;
  133164. regions = {};
  133165. ownerContext.borderAxisHorz = me.beginAxis(ownerContext, regions, 'horz');
  133166. ownerContext.borderAxisVert = me.beginAxis(ownerContext, regions, 'vert');
  133167. // Now that weights are assigned to the region's contextItems, we assign those
  133168. // same weights to the contextItem for the splitters. We also cross link the
  133169. // contextItems for the collapseTarget and its splitter.
  133170. for (i = 0; i < length; ++i) {
  133171. childContext = items[i];
  133172. collapseTarget = me.getSplitterTarget(childContext.target);
  133173. if (collapseTarget) { // if (splitter)
  133174. region = regions[collapseTarget.id]
  133175. if (!region) {
  133176. // if the region was hidden it will not be part of childItems, and
  133177. // so beginAxis() won't add it to the regions object, so we have
  133178. // to create the context item here.
  133179. region = ownerContext.getEl(collapseTarget.el, me);
  133180. region.region = collapseTarget.region;
  133181. }
  133182. childContext.collapseTarget = collapseTarget = region;
  133183. childContext.weight = collapseTarget.weight;
  133184. childContext.reverseWeighting = collapseTarget.reverseWeighting;
  133185. collapseTarget.splitter = childContext;
  133186. childContext.isHorz = collapseTarget.isHorz;
  133187. childContext.isVert = collapseTarget.isVert;
  133188. }
  133189. }
  133190. // Now we want to sort the childItems by their weight.
  133191. me.sortWeightedItems(items, 'reverseWeighting');
  133192. me.setupSplitterNeighbors(items);
  133193. },
  133194. calculate: function (ownerContext) {
  133195. var me = this,
  133196. containerSize = me.getContainerSize(ownerContext),
  133197. childItems = ownerContext.childItems,
  133198. length = childItems.length,
  133199. horz = ownerContext.borderAxisHorz,
  133200. vert = ownerContext.borderAxisVert,
  133201. pad = ownerContext.outerPad,
  133202. padOnContainer = ownerContext.padOnContainer,
  133203. i, childContext, childMargins, size, horzPercentTotal, vertPercentTotal;
  133204. horz.begin = pad.left;
  133205. vert.begin = pad.top;
  133206. // If the padding is already on the container we need to add it to the space
  133207. // If not on the container, it's "virtual" padding.
  133208. horzPercentTotal = horz.end = horz.flexSpace = containerSize.width + (padOnContainer ? pad.left : -pad.right);
  133209. vertPercentTotal = vert.end = vert.flexSpace = containerSize.height + (padOnContainer ? pad.top : -pad.bottom);
  133210. // Reduce flexSpace on each axis by the fixed/auto sized dimensions of items that
  133211. // aren't flexed along that axis.
  133212. for (i = 0; i < length; ++i) {
  133213. childContext = childItems[i];
  133214. childMargins = childContext.getMarginInfo();
  133215. // Margins are always fixed size and must be removed from the space used for percentages and flexes
  133216. if (childContext.isHorz || childContext.isCenter) {
  133217. horz.addUnflexed(childMargins.width);
  133218. horzPercentTotal -= childMargins.width;
  133219. }
  133220. if (childContext.isVert || childContext.isCenter) {
  133221. vert.addUnflexed(childMargins.height);
  133222. vertPercentTotal -= childMargins.height;
  133223. }
  133224. // Fixed size components must have their sizes removed from the space used for flex
  133225. if (!childContext.flex && !childContext.percentage) {
  133226. if (childContext.isHorz || (childContext.isCenter && childContext.collapseAxis === 'horz')) {
  133227. size = childContext.getProp('width');
  133228. horz.addUnflexed(size);
  133229. // splitters should not count towards percentages
  133230. if (childContext.collapseTarget) {
  133231. horzPercentTotal -= size;
  133232. }
  133233. } else if (childContext.isVert || (childContext.isCenter && childContext.collapseAxis === 'vert')) {
  133234. size = childContext.getProp('height');
  133235. vert.addUnflexed(size);
  133236. // splitters should not count towards percentages
  133237. if (childContext.collapseTarget) {
  133238. vertPercentTotal -= size;
  133239. }
  133240. }
  133241. // else ignore center since it is fully flexed
  133242. }
  133243. }
  133244. for (i = 0; i < length; ++i) {
  133245. childContext = childItems[i];
  133246. childMargins = childContext.getMarginInfo();
  133247. // Calculate the percentage sizes. After this calculation percentages are very similar to fixed sizes
  133248. if (childContext.percentage) {
  133249. if (childContext.isHorz) {
  133250. size = Math.ceil(horzPercentTotal * childContext.percentage / 100);
  133251. size = childContext.setWidth(size);
  133252. horz.addUnflexed(size);
  133253. } else if (childContext.isVert) {
  133254. size = Math.ceil(vertPercentTotal * childContext.percentage / 100);
  133255. size = childContext.setHeight(size);
  133256. vert.addUnflexed(size);
  133257. }
  133258. // center shouldn't have a percentage but if it does it should be ignored
  133259. }
  133260. }
  133261. // If we haven't gotten sizes for all unflexed dimensions on an axis, the flexSpace
  133262. // will be NaN so we won't be calculating flexed dimensions until that is resolved.
  133263. for (i = 0; i < length; ++i) {
  133264. childContext = childItems[i];
  133265. if (!childContext.isCenter) {
  133266. me.calculateChildAxis(childContext, horz);
  133267. me.calculateChildAxis(childContext, vert);
  133268. }
  133269. }
  133270. // Once all items are placed, the final size of the center can be determined. If we
  133271. // can determine both width and height, we are done. We use '+' instead of '&&' to
  133272. // avoid short-circuiting (we want to call both):
  133273. if (me.finishAxis(ownerContext, vert) + me.finishAxis(ownerContext, horz) < 2) {
  133274. me.done = false;
  133275. } else {
  133276. // Size information is published as we place regions but position is hard to do
  133277. // that way (while avoiding published multiple times) so we publish all the
  133278. // positions at the end.
  133279. me.finishPositions(childItems);
  133280. }
  133281. },
  133282. /**
  133283. * Performs the calculations for a region on a specified axis.
  133284. * @private
  133285. */
  133286. calculateChildAxis: function (childContext, axis) {
  133287. var collapseTarget = childContext.collapseTarget,
  133288. setSizeMethod = 'set' + axis.sizePropCap,
  133289. sizeProp = axis.sizeProp,
  133290. childMarginSize = childContext.getMarginInfo()[sizeProp],
  133291. region, isBegin, flex, pos, size;
  133292. if (collapseTarget) { // if (splitter)
  133293. region = collapseTarget.region;
  133294. } else {
  133295. region = childContext.region;
  133296. flex = childContext.flex;
  133297. }
  133298. isBegin = region == axis.borderBegin;
  133299. if (!isBegin && region != axis.borderEnd) {
  133300. // a north/south region on the horizontal axis or an east/west region on the
  133301. // vertical axis: stretch to fill remaining space:
  133302. childContext[setSizeMethod](axis.end - axis.begin - childMarginSize);
  133303. pos = axis.begin;
  133304. } else {
  133305. if (flex) {
  133306. size = Math.ceil(axis.flexSpace * (flex / axis.totalFlex));
  133307. size = childContext[setSizeMethod](size);
  133308. } else if (childContext.percentage) {
  133309. // Like getProp but without registering a dependency - we calculated the size, we don't depend on it
  133310. size = childContext.peek(sizeProp);
  133311. } else {
  133312. size = childContext.getProp(sizeProp);
  133313. }
  133314. size += childMarginSize;
  133315. if (isBegin) {
  133316. pos = axis.begin;
  133317. axis.begin += size;
  133318. } else {
  133319. axis.end = pos = axis.end - size;
  133320. }
  133321. }
  133322. childContext.layoutPos[axis.posProp] = pos;
  133323. },
  133324. /**
  133325. * Finishes the calculations on an axis. This basically just assigns the remaining
  133326. * space to the center region.
  133327. * @private
  133328. */
  133329. finishAxis: function (ownerContext, axis) {
  133330. var size = axis.end - axis.begin,
  133331. center = ownerContext.centerRegion;
  133332. if (center) {
  133333. center['set' + axis.sizePropCap](size - center.getMarginInfo()[axis.sizeProp]);
  133334. center.layoutPos[axis.posProp] = axis.begin;
  133335. }
  133336. return Ext.isNumber(size) ? 1 : 0;
  133337. },
  133338. /**
  133339. * Finishes by setting the positions on the child items.
  133340. * @private
  133341. */
  133342. finishPositions: function (childItems) {
  133343. var length = childItems.length,
  133344. index, childContext;
  133345. for (index = 0; index < length; ++index) {
  133346. childContext = childItems[index];
  133347. childContext.setProp('x', childContext.layoutPos.x + childContext.marginInfo.left);
  133348. childContext.setProp('y', childContext.layoutPos.y + childContext.marginInfo.top);
  133349. }
  133350. },
  133351. getPlaceholder: function (comp) {
  133352. return comp.getPlaceholder && comp.getPlaceholder();
  133353. },
  133354. getSplitterTarget: function (splitter) {
  133355. var collapseTarget = splitter.collapseTarget;
  133356. if (collapseTarget && collapseTarget.collapsed) {
  133357. return collapseTarget.placeholder || collapseTarget;
  133358. }
  133359. return collapseTarget;
  133360. },
  133361. isItemBoxParent: function (itemContext) {
  133362. return true;
  133363. },
  133364. isItemShrinkWrap: function (item) {
  133365. return true;
  133366. },
  133367. //----------------------------------
  133368. // Event handlers
  133369. /**
  133370. * Inserts the splitter for a given region. A reference to the splitter is also stored
  133371. * on the component as "splitter".
  133372. * @private
  133373. */
  133374. insertSplitter: function (item, index, hidden) {
  133375. var region = item.region,
  133376. splitter = {
  133377. xtype: 'bordersplitter',
  133378. collapseTarget: item,
  133379. id: item.id + '-splitter',
  133380. hidden: hidden,
  133381. canResize: item.splitterResize !== false
  133382. },
  133383. at = index + ((region == 'south' || region == 'east') ? 0 : 1);
  133384. // remove the default fixed width or height depending on orientation:
  133385. if (item.isHorz) {
  133386. splitter.height = null;
  133387. } else {
  133388. splitter.width = null;
  133389. }
  133390. if (item.collapseMode == 'mini') {
  133391. splitter.collapsedCls = item.collapsedCls;
  133392. }
  133393. item.splitter = this.owner.add(at, splitter);
  133394. },
  133395. /**
  133396. * Called when a region (actually when any component) is added to the container. The
  133397. * region is decorated with some helpful properties (isCenter, isHorz, isVert) and its
  133398. * splitter is added if its "split" property is true.
  133399. * @private
  133400. */
  133401. onAdd: function (item, index) {
  133402. var me = this,
  133403. placeholderFor = item.placeholderFor,
  133404. region = item.region,
  133405. split,
  133406. hidden;
  133407. me.callParent(arguments);
  133408. if (region) {
  133409. Ext.apply(item, me.regionFlags[region]);
  133410. if (region == 'center') {
  133411. if (me.centerRegion) {
  133412. Ext.Error.raise("Cannot have multiple center regions in a BorderLayout.");
  133413. }
  133414. me.centerRegion = item;
  133415. } else {
  133416. item.collapseDirection = this.collapseDirections[region];
  133417. split = item.split;
  133418. hidden = !!item.hidden;
  133419. if ((item.isHorz || item.isVert) && (split || item.collapseMode == 'mini')) {
  133420. me.insertSplitter(item, index, hidden || !split);
  133421. }
  133422. }
  133423. if (!item.hasOwnProperty('collapseMode')) {
  133424. item.collapseMode = me.panelCollapseMode;
  133425. }
  133426. if (!item.hasOwnProperty('animCollapse')) {
  133427. if (item.collapseMode != 'placeholder') {
  133428. // other collapse modes do not animate nicely in a border layout, so
  133429. // default them to off:
  133430. item.animCollapse = false;
  133431. } else {
  133432. item.animCollapse = me.panelCollapseAnimate;
  133433. }
  133434. }
  133435. } else if (placeholderFor) {
  133436. Ext.apply(item, me.regionFlags[placeholderFor.region]);
  133437. item.region = placeholderFor.region;
  133438. item.weight = placeholderFor.weight;
  133439. }
  133440. },
  133441. onDestroy: function() {
  133442. this.centerRegion = null;
  133443. this.callParent();
  133444. },
  133445. onRemove: function (item) {
  133446. var me = this,
  133447. region = item.region,
  133448. splitter = item.splitter;
  133449. if (region) {
  133450. if (item.isCenter) {
  133451. me.centerRegion = null;
  133452. }
  133453. delete item.isCenter;
  133454. delete item.isHorz;
  133455. delete item.isVert;
  133456. if (splitter) {
  133457. me.owner.doRemove(splitter, true); // avoid another layout
  133458. delete item.splitter;
  133459. }
  133460. }
  133461. me.callParent(arguments);
  133462. },
  133463. //----------------------------------
  133464. // Misc
  133465. regionFlags: {
  133466. center: { isCenter: true, isHorz: false, isVert: false },
  133467. north: { isCenter: false, isHorz: false, isVert: true },
  133468. south: { isCenter: false, isHorz: false, isVert: true },
  133469. west: { isCenter: false, isHorz: true, isVert: false },
  133470. east: { isCenter: false, isHorz: true, isVert: false }
  133471. },
  133472. setupSplitterNeighbors: function (items) {
  133473. var edgeRegions = {
  133474. //north: null,
  133475. //south: null,
  133476. //east: null,
  133477. //west: null
  133478. },
  133479. length = items.length,
  133480. touchedRegions = this.touchedRegions,
  133481. i, j, center, count, edge, comp, region, splitter, touched;
  133482. for (i = 0; i < length; ++i) {
  133483. comp = items[i].target;
  133484. region = comp.region;
  133485. if (comp.isCenter) {
  133486. center = comp;
  133487. } else if (region) {
  133488. touched = touchedRegions[region];
  133489. for (j = 0, count = touched.length; j < count; ++j) {
  133490. edge = edgeRegions[touched[j]];
  133491. if (edge) {
  133492. edge.neighbors.push(comp);
  133493. }
  133494. }
  133495. if (comp.placeholderFor) {
  133496. // placeholder, so grab the splitter for the actual panel
  133497. splitter = comp.placeholderFor.splitter;
  133498. } else {
  133499. splitter = comp.splitter;
  133500. }
  133501. if (splitter) {
  133502. splitter.neighbors = [];
  133503. }
  133504. edgeRegions[region] = splitter;
  133505. }
  133506. }
  133507. if (center) {
  133508. touched = touchedRegions.center;
  133509. for (j = 0, count = touched.length; j < count; ++j) {
  133510. edge = edgeRegions[touched[j]];
  133511. if (edge) {
  133512. edge.neighbors.push(center);
  133513. }
  133514. }
  133515. }
  133516. },
  133517. /**
  133518. * Lists the regions that would consider an interior region a neighbor. For example,
  133519. * a north region would consider an east or west region its neighbords (as well as
  133520. * an inner north region).
  133521. * @private
  133522. */
  133523. touchedRegions: {
  133524. center: [ 'north', 'south', 'east', 'west' ],
  133525. north: [ 'north', 'east', 'west' ],
  133526. south: [ 'south', 'east', 'west' ],
  133527. east: [ 'east', 'north', 'south' ],
  133528. west: [ 'west', 'north', 'south' ]
  133529. },
  133530. sizePolicies: {
  133531. vert: {
  133532. setsWidth: 1,
  133533. setsHeight: 0
  133534. },
  133535. horz: {
  133536. setsWidth: 0,
  133537. setsHeight: 1
  133538. },
  133539. flexAll: {
  133540. setsWidth: 1,
  133541. setsHeight: 1
  133542. }
  133543. },
  133544. getItemSizePolicy: function (item) {
  133545. var me = this,
  133546. policies = this.sizePolicies,
  133547. collapseTarget, size, policy, placeholderFor;
  133548. if (item.isCenter) {
  133549. placeholderFor = item.placeholderFor;
  133550. if (placeholderFor) {
  133551. if (placeholderFor.collapsedVertical()) {
  133552. return policies.vert;
  133553. }
  133554. return policies.horz;
  133555. }
  133556. if (item.collapsed) {
  133557. if (item.collapsedVertical()) {
  133558. return policies.vert;
  133559. }
  133560. return policies.horz;
  133561. }
  133562. return policies.flexAll;
  133563. }
  133564. collapseTarget = item.collapseTarget;
  133565. if (collapseTarget) {
  133566. return collapseTarget.isVert ? policies.vert : policies.horz;
  133567. }
  133568. if (item.region) {
  133569. if (item.isVert) {
  133570. size = item.height;
  133571. policy = policies.vert;
  133572. } else {
  133573. size = item.width;
  133574. policy = policies.horz;
  133575. }
  133576. if (item.flex || (typeof size == 'string' && me.percentageRe.test(size))) {
  133577. return policies.flexAll;
  133578. }
  133579. return policy;
  133580. }
  133581. return me.autoSizePolicy;
  133582. }
  133583. }, function () {
  133584. var methods = {
  133585. addUnflexed: function (px) {
  133586. this.flexSpace = Math.max(this.flexSpace - px, 0);
  133587. }
  133588. },
  133589. props = this.prototype.axisProps;
  133590. Ext.apply(props.horz, methods);
  133591. Ext.apply(props.vert, methods);
  133592. });
  133593. /**
  133594. * This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be
  133595. * visible at any given time. This layout style is most commonly used for wizards, tab implementations, etc.
  133596. * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config,
  133597. * and should generally not need to be created directly via the new keyword.
  133598. *
  133599. * The CardLayout's focal method is {@link #setActiveItem}. Since only one panel is displayed at a time,
  133600. * the only way to move from one Component to the next is by calling setActiveItem, passing the next panel to display
  133601. * (or its id or index). The layout itself does not provide a user interface for handling this navigation,
  133602. * so that functionality must be provided by the developer.
  133603. *
  133604. * To change the active card of a container, call the setActiveItem method of its layout:
  133605. *
  133606. * @example
  133607. * var p = Ext.create('Ext.panel.Panel', {
  133608. * layout: 'card',
  133609. * items: [
  133610. * { html: 'Card 1' },
  133611. * { html: 'Card 2' }
  133612. * ],
  133613. * renderTo: Ext.getBody()
  133614. * });
  133615. *
  133616. * p.getLayout().setActiveItem(1);
  133617. *
  133618. * In the following example, a simplistic wizard setup is demonstrated. A button bar is added
  133619. * to the footer of the containing panel to provide navigation buttons. The buttons will be handled by a
  133620. * common navigation routine. Note that other uses of a CardLayout (like a tab control) would require a
  133621. * completely different implementation. For serious implementations, a better approach would be to extend
  133622. * CardLayout to provide the custom functionality needed.
  133623. *
  133624. * @example
  133625. * var navigate = function(panel, direction){
  133626. * // This routine could contain business logic required to manage the navigation steps.
  133627. * // It would call setActiveItem as needed, manage navigation button state, handle any
  133628. * // branching logic that might be required, handle alternate actions like cancellation
  133629. * // or finalization, etc. A complete wizard implementation could get pretty
  133630. * // sophisticated depending on the complexity required, and should probably be
  133631. * // done as a subclass of CardLayout in a real-world implementation.
  133632. * var layout = panel.getLayout();
  133633. * layout[direction]();
  133634. * Ext.getCmp('move-prev').setDisabled(!layout.getPrev());
  133635. * Ext.getCmp('move-next').setDisabled(!layout.getNext());
  133636. * };
  133637. *
  133638. * Ext.create('Ext.panel.Panel', {
  133639. * title: 'Example Wizard',
  133640. * width: 300,
  133641. * height: 200,
  133642. * layout: 'card',
  133643. * bodyStyle: 'padding:15px',
  133644. * defaults: {
  133645. * // applied to each contained panel
  133646. * border: false
  133647. * },
  133648. * // just an example of one possible navigation scheme, using buttons
  133649. * bbar: [
  133650. * {
  133651. * id: 'move-prev',
  133652. * text: 'Back',
  133653. * handler: function(btn) {
  133654. * navigate(btn.up("panel"), "prev");
  133655. * },
  133656. * disabled: true
  133657. * },
  133658. * '->', // greedy spacer so that the buttons are aligned to each side
  133659. * {
  133660. * id: 'move-next',
  133661. * text: 'Next',
  133662. * handler: function(btn) {
  133663. * navigate(btn.up("panel"), "next");
  133664. * }
  133665. * }
  133666. * ],
  133667. * // the panels (or "cards") within the layout
  133668. * items: [{
  133669. * id: 'card-0',
  133670. * html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
  133671. * },{
  133672. * id: 'card-1',
  133673. * html: '<p>Step 2 of 3</p>'
  133674. * },{
  133675. * id: 'card-2',
  133676. * html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
  133677. * }],
  133678. * renderTo: Ext.getBody()
  133679. * });
  133680. */
  133681. Ext.define('Ext.layout.container.Card', {
  133682. /* Begin Definitions */
  133683. extend: 'Ext.layout.container.Fit',
  133684. alternateClassName: 'Ext.layout.CardLayout',
  133685. alias: 'layout.card',
  133686. /* End Definitions */
  133687. type: 'card',
  133688. hideInactive: true,
  133689. /**
  133690. * @cfg {Boolean} deferredRender
  133691. * True to render each contained item at the time it becomes active, false to render all contained items
  133692. * as soon as the layout is rendered (defaults to false). If there is a significant amount of content or
  133693. * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
  133694. * true might improve performance.
  133695. */
  133696. deferredRender : false,
  133697. getRenderTree: function () {
  133698. var me = this,
  133699. activeItem = me.getActiveItem();
  133700. if (activeItem) {
  133701. // If they veto the activate, we have no active item
  133702. if (activeItem.hasListeners.beforeactivate && activeItem.fireEvent('beforeactivate', activeItem) === false) {
  133703. // We must null our activeItem reference, AND the one in our owning Container.
  133704. // Because upon layout invalidation, renderChildren will use this.getActiveItem which
  133705. // uses this.activeItem || this.owner.activeItem
  133706. activeItem = me.activeItem = me.owner.activeItem = null;
  133707. }
  133708. // Item is to be the active one. Fire event after it is first layed out
  133709. else if (activeItem.hasListeners.activate) {
  133710. activeItem.on({
  133711. boxready: function() {
  133712. activeItem.fireEvent('activate', activeItem);
  133713. },
  133714. single: true
  133715. });
  133716. }
  133717. if (me.deferredRender) {
  133718. if (activeItem) {
  133719. return me.getItemsRenderTree([activeItem]);
  133720. }
  133721. } else {
  133722. return me.callParent(arguments);
  133723. }
  133724. }
  133725. },
  133726. renderChildren: function () {
  133727. var me = this,
  133728. active = me.getActiveItem();
  133729. if (!me.deferredRender) {
  133730. me.callParent();
  133731. } else if (active) {
  133732. // ensure the active item is configured for the layout
  133733. me.renderItems([active], me.getRenderTarget());
  133734. }
  133735. },
  133736. isValidParent : function(item, target, position) {
  133737. // Note: Card layout does not care about order within the target because only one is ever visible.
  133738. // We only care whether the item is a direct child of the target.
  133739. var itemEl = item.el ? item.el.dom : Ext.getDom(item);
  133740. return (itemEl && itemEl.parentNode === (target.dom || target)) || false;
  133741. },
  133742. /**
  133743. * Return the active (visible) component in the layout.
  133744. * @returns {Ext.Component}
  133745. */
  133746. getActiveItem: function() {
  133747. var me = this,
  133748. // Ensure the calculated result references a Component
  133749. result = me.parseActiveItem(me.activeItem || (me.owner && me.owner.activeItem));
  133750. // Sanitize the result in case the active item is no longer there.
  133751. if (result && me.owner.items.indexOf(result) != -1) {
  133752. me.activeItem = result;
  133753. } else {
  133754. me.activeItem = null;
  133755. }
  133756. return me.activeItem;
  133757. },
  133758. // @private
  133759. parseActiveItem: function(item) {
  133760. if (item && item.isComponent) {
  133761. return item;
  133762. } else if (typeof item == 'number' || item === undefined) {
  133763. return this.getLayoutItems()[item || 0];
  133764. } else {
  133765. return this.owner.getComponent(item);
  133766. }
  133767. },
  133768. // @private. Called before both dynamic render, and bulk render.
  133769. // Ensure that the active item starts visible, and inactive ones start invisible
  133770. configureItem: function(item) {
  133771. if (item === this.getActiveItem()) {
  133772. item.hidden = false;
  133773. } else {
  133774. item.hidden = true;
  133775. }
  133776. this.callParent(arguments);
  133777. },
  133778. onRemove: function(component) {
  133779. var me = this;
  133780. if (component === me.activeItem) {
  133781. me.activeItem = null;
  133782. }
  133783. },
  133784. // @private
  133785. getAnimation: function(newCard, owner) {
  133786. var newAnim = (newCard || {}).cardSwitchAnimation;
  133787. if (newAnim === false) {
  133788. return false;
  133789. }
  133790. return newAnim || owner.cardSwitchAnimation;
  133791. },
  133792. /**
  133793. * Return the active (visible) component in the layout to the next card
  133794. * @returns {Ext.Component} The next component or false.
  133795. */
  133796. getNext: function() {
  133797. //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
  133798. //should come back in 4.1
  133799. var wrap = arguments[0],
  133800. items = this.getLayoutItems(),
  133801. index = Ext.Array.indexOf(items, this.activeItem);
  133802. return items[index + 1] || (wrap ? items[0] : false);
  133803. },
  133804. /**
  133805. * Sets the active (visible) component in the layout to the next card
  133806. * @return {Ext.Component} the activated component or false when nothing activated.
  133807. */
  133808. next: function() {
  133809. //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
  133810. //should come back in 4.1
  133811. var anim = arguments[0],
  133812. wrap = arguments[1];
  133813. return this.setActiveItem(this.getNext(wrap), anim);
  133814. },
  133815. /**
  133816. * Return the active (visible) component in the layout to the previous card
  133817. * @returns {Ext.Component} The previous component or false.
  133818. */
  133819. getPrev: function() {
  133820. //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
  133821. //should come back in 4.1
  133822. var wrap = arguments[0],
  133823. items = this.getLayoutItems(),
  133824. index = Ext.Array.indexOf(items, this.activeItem);
  133825. return items[index - 1] || (wrap ? items[items.length - 1] : false);
  133826. },
  133827. /**
  133828. * Sets the active (visible) component in the layout to the previous card
  133829. * @return {Ext.Component} the activated component or false when nothing activated.
  133830. */
  133831. prev: function() {
  133832. //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
  133833. //should come back in 4.1
  133834. var anim = arguments[0],
  133835. wrap = arguments[1];
  133836. return this.setActiveItem(this.getPrev(wrap), anim);
  133837. },
  133838. /**
  133839. * Makes the given card active.
  133840. *
  133841. * var card1 = Ext.create('Ext.panel.Panel', {itemId: 'card-1'});
  133842. * var card2 = Ext.create('Ext.panel.Panel', {itemId: 'card-2'});
  133843. * var panel = Ext.create('Ext.panel.Panel', {
  133844. * layout: 'card',
  133845. * activeItem: 0,
  133846. * items: [card1, card2]
  133847. * });
  133848. * // These are all equivalent
  133849. * panel.getLayout().setActiveItem(card2);
  133850. * panel.getLayout().setActiveItem('card-2');
  133851. * panel.getLayout().setActiveItem(1);
  133852. *
  133853. * @param {Ext.Component/Number/String} newCard The component, component {@link Ext.Component#id id},
  133854. * {@link Ext.Component#itemId itemId}, or index of component.
  133855. * @return {Ext.Component} the activated component or false when nothing activated.
  133856. * False is returned also when trying to activate an already active card.
  133857. */
  133858. setActiveItem: function(newCard) {
  133859. var me = this,
  133860. owner = me.owner,
  133861. oldCard = me.activeItem,
  133862. rendered = owner.rendered,
  133863. newIndex;
  133864. newCard = me.parseActiveItem(newCard);
  133865. newIndex = owner.items.indexOf(newCard);
  133866. // If the card is not a child of the owner, then add it.
  133867. // Without doing a layout!
  133868. if (newIndex == -1) {
  133869. newIndex = owner.items.items.length;
  133870. Ext.suspendLayouts();
  133871. newCard = owner.add(newCard);
  133872. Ext.resumeLayouts();
  133873. }
  133874. // Is this a valid, different card?
  133875. if (newCard && oldCard != newCard) {
  133876. // Fire the beforeactivate and beforedeactivate events on the cards
  133877. if (newCard.fireEvent('beforeactivate', newCard, oldCard) === false) {
  133878. return false;
  133879. }
  133880. if (oldCard && oldCard.fireEvent('beforedeactivate', oldCard, newCard) === false) {
  133881. return false;
  133882. }
  133883. if (rendered) {
  133884. Ext.suspendLayouts();
  133885. // If the card has not been rendered yet, now is the time to do so.
  133886. if (!newCard.rendered) {
  133887. me.renderItem(newCard, me.getRenderTarget(), owner.items.length);
  133888. }
  133889. if (oldCard) {
  133890. if (me.hideInactive) {
  133891. oldCard.hide();
  133892. oldCard.hiddenByLayout = true;
  133893. }
  133894. oldCard.fireEvent('deactivate', oldCard, newCard);
  133895. }
  133896. // Make sure the new card is shown
  133897. if (newCard.hidden) {
  133898. newCard.show();
  133899. }
  133900. // Layout needs activeItem to be correct, so set it if the show has not been vetoed
  133901. if (!newCard.hidden) {
  133902. me.activeItem = newCard;
  133903. }
  133904. Ext.resumeLayouts(true);
  133905. } else {
  133906. me.activeItem = newCard;
  133907. }
  133908. newCard.fireEvent('activate', newCard, oldCard);
  133909. return me.activeItem;
  133910. }
  133911. return false;
  133912. }
  133913. });
  133914. /**
  133915. * This is the layout style of choice for creating structural layouts in a multi-column format where the width of each
  133916. * column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content. This
  133917. * class is intended to be extended or created via the layout:'column' {@link Ext.container.Container#layout} config,
  133918. * and should generally not need to be created directly via the new keyword.
  133919. *
  133920. * ColumnLayout does not have any direct config options (other than inherited ones), but it does support a specific
  133921. * config property of `columnWidth` that can be included in the config of any panel added to it. The layout will use
  133922. * the columnWidth (if present) or width of each panel during layout to determine how to size each panel. If width or
  133923. * columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).
  133924. *
  133925. * The width property is always evaluated as pixels, and must be a number greater than or equal to 1. The columnWidth
  133926. * property is always evaluated as a percentage, and must be a decimal value greater than 0 and less than 1 (e.g., .25).
  133927. *
  133928. * The basic rules for specifying column widths are pretty simple. The logic makes two passes through the set of
  133929. * contained panels. During the first layout pass, all panels that either have a fixed width or none specified (auto)
  133930. * are skipped, but their widths are subtracted from the overall container width.
  133931. *
  133932. * During the second pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages
  133933. * based on the total **remaining** container width. In other words, percentage width panels are designed to fill
  133934. * the space left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any
  133935. * number of columns with different percentages, the columnWidths must always add up to 1 (or 100%) when added
  133936. * together, otherwise your layout may not render as expected.
  133937. *
  133938. * @example
  133939. * // All columns are percentages -- they must add up to 1
  133940. * Ext.create('Ext.panel.Panel', {
  133941. * title: 'Column Layout - Percentage Only',
  133942. * width: 350,
  133943. * height: 250,
  133944. * layout:'column',
  133945. * items: [{
  133946. * title: 'Column 1',
  133947. * columnWidth: 0.25
  133948. * },{
  133949. * title: 'Column 2',
  133950. * columnWidth: 0.55
  133951. * },{
  133952. * title: 'Column 3',
  133953. * columnWidth: 0.20
  133954. * }],
  133955. * renderTo: Ext.getBody()
  133956. * });
  133957. *
  133958. * // Mix of width and columnWidth -- all columnWidth values must add up
  133959. * // to 1. The first column will take up exactly 120px, and the last two
  133960. * // columns will fill the remaining container width.
  133961. *
  133962. * Ext.create('Ext.Panel', {
  133963. * title: 'Column Layout - Mixed',
  133964. * width: 350,
  133965. * height: 250,
  133966. * layout:'column',
  133967. * items: [{
  133968. * title: 'Column 1',
  133969. * width: 120
  133970. * },{
  133971. * title: 'Column 2',
  133972. * columnWidth: 0.7
  133973. * },{
  133974. * title: 'Column 3',
  133975. * columnWidth: 0.3
  133976. * }],
  133977. * renderTo: Ext.getBody()
  133978. * });
  133979. */
  133980. Ext.define('Ext.layout.container.Column', {
  133981. extend: 'Ext.layout.container.Container',
  133982. alias: ['layout.column'],
  133983. alternateClassName: 'Ext.layout.ColumnLayout',
  133984. type: 'column',
  133985. itemCls: Ext.baseCSSPrefix + 'column',
  133986. targetCls: Ext.baseCSSPrefix + 'column-layout-ct',
  133987. // Columns with a columnWidth have their width managed.
  133988. columnWidthSizePolicy: {
  133989. setsWidth: 1,
  133990. setsHeight: 0
  133991. },
  133992. childEls: [
  133993. 'innerCt'
  133994. ],
  133995. manageOverflow: 2,
  133996. renderTpl: [
  133997. '<div id="{ownerId}-innerCt" class="',Ext.baseCSSPrefix,'column-inner">',
  133998. '{%this.renderBody(out,values)%}',
  133999. '<div class="',Ext.baseCSSPrefix,'clear"></div>',
  134000. '</div>',
  134001. '{%this.renderPadder(out,values)%}'
  134002. ],
  134003. getItemSizePolicy: function (item) {
  134004. if (item.columnWidth) {
  134005. return this.columnWidthSizePolicy;
  134006. }
  134007. return this.autoSizePolicy;
  134008. },
  134009. beginLayout: function() {
  134010. this.callParent(arguments);
  134011. this.innerCt.dom.style.width = '';
  134012. },
  134013. calculate: function (ownerContext) {
  134014. var me = this,
  134015. containerSize = me.getContainerSize(ownerContext),
  134016. state = ownerContext.state;
  134017. if (state.calculatedColumns || (state.calculatedColumns = me.calculateColumns(ownerContext))) {
  134018. if (me.calculateHeights(ownerContext)) {
  134019. me.calculateOverflow(ownerContext, containerSize);
  134020. return;
  134021. }
  134022. }
  134023. me.done = false;
  134024. },
  134025. calculateColumns: function (ownerContext) {
  134026. var me = this,
  134027. containerSize = me.getContainerSize(ownerContext),
  134028. innerCtContext = ownerContext.getEl('innerCt', me),
  134029. items = ownerContext.childItems,
  134030. len = items.length,
  134031. contentWidth = 0,
  134032. blocked, availableWidth, i, itemContext, itemMarginWidth, itemWidth;
  134033. // Can never decide upon necessity of vertical scrollbar (and therefore, narrower
  134034. // content width) until the component layout has published a height for the target
  134035. // element.
  134036. if (!ownerContext.heightModel.shrinkWrap && !ownerContext.targetContext.hasProp('height')) {
  134037. return false;
  134038. }
  134039. // No parallel measurement, cannot lay out boxes.
  134040. if (!containerSize.gotWidth) { //\\ TODO: Deal with target padding width
  134041. ownerContext.targetContext.block(me, 'width');
  134042. blocked = true;
  134043. } else {
  134044. availableWidth = containerSize.width;
  134045. innerCtContext.setWidth(availableWidth);
  134046. }
  134047. // we need the widths of the columns we don't manage to proceed so we block on them
  134048. // if they are not ready...
  134049. for (i = 0; i < len; ++i) {
  134050. itemContext = items[i];
  134051. // this is needed below for non-calculated columns, but is also needed in the
  134052. // next loop for calculated columns... this way we only call getMarginInfo in
  134053. // this loop and use the marginInfo property in the next...
  134054. itemMarginWidth = itemContext.getMarginInfo().width;
  134055. if (!itemContext.widthModel.calculated) {
  134056. itemWidth = itemContext.getProp('width');
  134057. if (typeof itemWidth != 'number') {
  134058. itemContext.block(me, 'width');
  134059. blocked = true;
  134060. }
  134061. contentWidth += itemWidth + itemMarginWidth;
  134062. }
  134063. }
  134064. if (!blocked) {
  134065. availableWidth = (availableWidth < contentWidth) ? 0 : availableWidth - contentWidth;
  134066. for (i = 0; i < len; ++i) {
  134067. itemContext = items[i];
  134068. if (itemContext.widthModel.calculated) {
  134069. itemMarginWidth = itemContext.marginInfo.width; // always set by above loop
  134070. itemWidth = itemContext.target.columnWidth;
  134071. itemWidth = Math.floor(itemWidth * availableWidth) - itemMarginWidth;
  134072. itemWidth = itemContext.setWidth(itemWidth); // constrains to min/maxWidth
  134073. contentWidth += itemWidth + itemMarginWidth;
  134074. }
  134075. }
  134076. ownerContext.setContentWidth(contentWidth);
  134077. }
  134078. // we registered all the values that block this calculation, so abort now if blocked...
  134079. return !blocked;
  134080. },
  134081. calculateHeights: function (ownerContext) {
  134082. var me = this,
  134083. items = ownerContext.childItems,
  134084. len = items.length,
  134085. blocked, i, itemContext;
  134086. // in order for innerCt to have the proper height, all the items must have height
  134087. // correct in the DOM...
  134088. blocked = false;
  134089. for (i = 0; i < len; ++i) {
  134090. itemContext = items[i];
  134091. if (!itemContext.hasDomProp('height')) {
  134092. itemContext.domBlock(me, 'height');
  134093. blocked = true;
  134094. }
  134095. }
  134096. if (!blocked) {
  134097. ownerContext.setContentHeight(me.innerCt.getHeight() + ownerContext.targetContext.getPaddingInfo().height);
  134098. }
  134099. return !blocked;
  134100. },
  134101. finishedLayout: function (ownerContext) {
  134102. var bc = ownerContext.bodyContext;
  134103. // Owner may not have a body - this seems to only be needed for Panels.
  134104. if (bc && (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks)) {
  134105. // Fix for https://sencha.jira.com/browse/EXTJSIV-4979
  134106. bc.el.repaint();
  134107. }
  134108. this.callParent(arguments);
  134109. },
  134110. getRenderTarget : function() {
  134111. return this.innerCt;
  134112. }
  134113. });
  134114. /**
  134115. * This is a layout that will render form Fields, one under the other all stretched to the Container width.
  134116. *
  134117. * @example
  134118. * Ext.create('Ext.Panel', {
  134119. * width: 500,
  134120. * height: 300,
  134121. * title: "FormLayout Panel",
  134122. * layout: 'form',
  134123. * renderTo: Ext.getBody(),
  134124. * bodyPadding: 5,
  134125. * defaultType: 'textfield',
  134126. * items: [{
  134127. * fieldLabel: 'First Name',
  134128. * name: 'first',
  134129. * allowBlank:false
  134130. * },{
  134131. * fieldLabel: 'Last Name',
  134132. * name: 'last'
  134133. * },{
  134134. * fieldLabel: 'Company',
  134135. * name: 'company'
  134136. * }, {
  134137. * fieldLabel: 'Email',
  134138. * name: 'email',
  134139. * vtype:'email'
  134140. * }, {
  134141. * fieldLabel: 'DOB',
  134142. * name: 'dob',
  134143. * xtype: 'datefield'
  134144. * }, {
  134145. * fieldLabel: 'Age',
  134146. * name: 'age',
  134147. * xtype: 'numberfield',
  134148. * minValue: 0,
  134149. * maxValue: 100
  134150. * }, {
  134151. * xtype: 'timefield',
  134152. * fieldLabel: 'Time',
  134153. * name: 'time',
  134154. * minValue: '8:00am',
  134155. * maxValue: '6:00pm'
  134156. * }]
  134157. * });
  134158. *
  134159. * Note that any configured {@link Ext.Component#padding padding} will be ignored on items within a Form layout.
  134160. */
  134161. Ext.define('Ext.layout.container.Form', {
  134162. /* Begin Definitions */
  134163. alias: 'layout.form',
  134164. extend: 'Ext.layout.container.Auto',
  134165. alternateClassName: 'Ext.layout.FormLayout',
  134166. /* End Definitions */
  134167. tableCls: Ext.baseCSSPrefix + 'form-layout-table',
  134168. type: 'form',
  134169. manageOverflow: 2,
  134170. childEls: ['formTable'],
  134171. padRow: '<tr><td class="' + Ext.baseCSSPrefix + 'form-item-pad" colspan="3"></td></tr>',
  134172. renderTpl: [
  134173. '<table id="{ownerId}-formTable" class="{tableCls}" style="width:100%" cellpadding="0">',
  134174. '{%this.renderBody(out,values)%}',
  134175. '</table>',
  134176. '{%this.renderPadder(out,values)%}'
  134177. ],
  134178. getRenderData: function(){
  134179. var data = this.callParent();
  134180. data.tableCls = this.tableCls;
  134181. return data;
  134182. },
  134183. calculate : function (ownerContext) {
  134184. var me = this,
  134185. containerSize = me.getContainerSize(ownerContext, true),
  134186. tableWidth,
  134187. childItems,
  134188. i = 0, length;
  134189. // Once we have been widthed, we can impose that width (in a non-dirty setting) upon all children at once
  134190. if (containerSize.gotWidth) {
  134191. this.callParent(arguments);
  134192. tableWidth = me.formTable.dom.offsetWidth;
  134193. childItems = ownerContext.childItems;
  134194. for (length = childItems.length; i < length; ++i) {
  134195. childItems[i].setWidth(tableWidth, false);
  134196. }
  134197. } else {
  134198. me.done = false;
  134199. }
  134200. },
  134201. getRenderTarget: function() {
  134202. return this.formTable;
  134203. },
  134204. getRenderTree: function() {
  134205. var me = this,
  134206. result = me.callParent(arguments),
  134207. i, len;
  134208. for (i = 0, len = result.length; i < len; i++) {
  134209. result[i] = me.transformItemRenderTree(result[i]);
  134210. }
  134211. return result;
  134212. },
  134213. transformItemRenderTree: function(item) {
  134214. if (item.tag && item.tag == 'table') {
  134215. item.tag = 'tbody';
  134216. delete item.cellspacing;
  134217. delete item.cellpadding;
  134218. // IE6 doesn't separate cells nicely to provide input field
  134219. // vertical separation. It also does not support transparent borders
  134220. // which is how the extra 1px is added to the 2px each side cell spacing.
  134221. // So it needs a 5px high pad row.
  134222. if (Ext.isIE6) {
  134223. item.cn = this.padRow;
  134224. }
  134225. return item;
  134226. }
  134227. return {
  134228. tag: 'tbody',
  134229. cn: {
  134230. tag: 'tr',
  134231. cn: {
  134232. tag: 'td',
  134233. colspan: 3,
  134234. style: 'width:100%',
  134235. cn: item
  134236. }
  134237. }
  134238. };
  134239. },
  134240. isValidParent: function(item, target, position) {
  134241. return true;
  134242. },
  134243. isItemShrinkWrap: function(item) {
  134244. return ((item.shrinkWrap === true) ? 3 : item.shrinkWrap||0) & 2;
  134245. },
  134246. getItemSizePolicy: function(item) {
  134247. return {
  134248. setsWidth: 1,
  134249. setsHeight: 0
  134250. };
  134251. }
  134252. });
  134253. /**
  134254. * A base class for all menu items that require menu-related functionality such as click handling,
  134255. * sub-menus, icons, etc.
  134256. *
  134257. * @example
  134258. * Ext.create('Ext.menu.Menu', {
  134259. * width: 100,
  134260. * height: 100,
  134261. * floating: false, // usually you want this set to True (default)
  134262. * renderTo: Ext.getBody(), // usually rendered by it's containing component
  134263. * items: [{
  134264. * text: 'icon item',
  134265. * iconCls: 'add16'
  134266. * },{
  134267. * text: 'text item'
  134268. * },{
  134269. * text: 'plain item',
  134270. * plain: true
  134271. * }]
  134272. * });
  134273. */
  134274. Ext.define('Ext.menu.Item', {
  134275. extend: 'Ext.Component',
  134276. alias: 'widget.menuitem',
  134277. alternateClassName: 'Ext.menu.TextItem',
  134278. /**
  134279. * @property {Boolean} activated
  134280. * Whether or not this item is currently activated
  134281. */
  134282. /**
  134283. * @property {Ext.menu.Menu} parentMenu
  134284. * The parent Menu of this item.
  134285. */
  134286. /**
  134287. * @cfg {String} activeCls
  134288. * The CSS class added to the menu item when the item is activated (focused/mouseover).
  134289. */
  134290. activeCls: Ext.baseCSSPrefix + 'menu-item-active',
  134291. /**
  134292. * @cfg {String} ariaRole
  134293. * @private
  134294. */
  134295. ariaRole: 'menuitem',
  134296. /**
  134297. * @cfg {Boolean} canActivate
  134298. * Whether or not this menu item can be activated when focused/mouseovered.
  134299. */
  134300. canActivate: true,
  134301. /**
  134302. * @cfg {Number} clickHideDelay
  134303. * The delay in milliseconds to wait before hiding the menu after clicking the menu item.
  134304. * This only has an effect when `hideOnClick: true`.
  134305. */
  134306. clickHideDelay: 1,
  134307. /**
  134308. * @cfg {Boolean} destroyMenu
  134309. * Whether or not to destroy any associated sub-menu when this item is destroyed.
  134310. */
  134311. destroyMenu: true,
  134312. /**
  134313. * @cfg {String} disabledCls
  134314. * The CSS class added to the menu item when the item is disabled.
  134315. */
  134316. disabledCls: Ext.baseCSSPrefix + 'menu-item-disabled',
  134317. /**
  134318. * @cfg {String} [href='#']
  134319. * The href attribute to use for the underlying anchor link.
  134320. */
  134321. /**
  134322. * @cfg {String} hrefTarget
  134323. * The target attribute to use for the underlying anchor link.
  134324. */
  134325. /**
  134326. * @cfg {Boolean} hideOnClick
  134327. * Whether to not to hide the owning menu when this item is clicked.
  134328. */
  134329. hideOnClick: true,
  134330. /**
  134331. * @cfg {String} icon
  134332. * The path to an icon to display in this item.
  134333. *
  134334. * Defaults to `Ext.BLANK_IMAGE_URL`.
  134335. */
  134336. /**
  134337. * @cfg {String} iconCls
  134338. * A CSS class that specifies a `background-image` to use as the icon for this item.
  134339. */
  134340. isMenuItem: true,
  134341. /**
  134342. * @cfg {Ext.menu.Menu/Object} menu
  134343. * Either an instance of {@link Ext.menu.Menu} or a config object for an {@link Ext.menu.Menu}
  134344. * which will act as a sub-menu to this item.
  134345. */
  134346. /**
  134347. * @property {Ext.menu.Menu} menu The sub-menu associated with this item, if one was configured.
  134348. */
  134349. /**
  134350. * @cfg {String} menuAlign
  134351. * The default {@link Ext.Element#getAlignToXY Ext.Element.getAlignToXY} anchor position value for this
  134352. * item's sub-menu relative to this item's position.
  134353. */
  134354. menuAlign: 'tl-tr?',
  134355. /**
  134356. * @cfg {Number} menuExpandDelay
  134357. * The delay in milliseconds before this item's sub-menu expands after this item is moused over.
  134358. */
  134359. menuExpandDelay: 200,
  134360. /**
  134361. * @cfg {Number} menuHideDelay
  134362. * The delay in milliseconds before this item's sub-menu hides after this item is moused out.
  134363. */
  134364. menuHideDelay: 200,
  134365. /**
  134366. * @cfg {Boolean} plain
  134367. * Whether or not this item is plain text/html with no icon or visual activation.
  134368. */
  134369. /**
  134370. * @cfg {String/Object} tooltip
  134371. * The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or
  134372. * QuickTips config object.
  134373. */
  134374. /**
  134375. * @cfg {String} tooltipType
  134376. * The type of tooltip to use. Either 'qtip' for QuickTips or 'title' for title attribute.
  134377. */
  134378. tooltipType: 'qtip',
  134379. arrowCls: Ext.baseCSSPrefix + 'menu-item-arrow',
  134380. childEls: [
  134381. 'itemEl', 'iconEl', 'textEl', 'arrowEl'
  134382. ],
  134383. renderTpl: [
  134384. '<tpl if="plain">',
  134385. '{text}',
  134386. '<tpl else>',
  134387. '<a id="{id}-itemEl" class="' + Ext.baseCSSPrefix + 'menu-item-link" href="{href}" <tpl if="hrefTarget">target="{hrefTarget}"</tpl> hidefocus="true" unselectable="on">',
  134388. '<img id="{id}-iconEl" src="{icon}" class="' + Ext.baseCSSPrefix + 'menu-item-icon {iconCls}" />',
  134389. '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'menu-item-text" <tpl if="arrowCls">style="margin-right: 17px;"</tpl> >{text}</span>',
  134390. '<img id="{id}-arrowEl" src="{blank}" class="{arrowCls}" />',
  134391. '</a>',
  134392. '</tpl>'
  134393. ],
  134394. maskOnDisable: false,
  134395. /**
  134396. * @cfg {String} text
  134397. * The text/html to display in this item.
  134398. */
  134399. /**
  134400. * @cfg {Function} handler
  134401. * A function called when the menu item is clicked (can be used instead of {@link #click} event).
  134402. * @cfg {Ext.menu.Item} handler.item The item that was clicked
  134403. * @cfg {Ext.EventObject} handler.e The underyling {@link Ext.EventObject}.
  134404. */
  134405. activate: function() {
  134406. var me = this;
  134407. if (!me.activated && me.canActivate && me.rendered && !me.isDisabled() && me.isVisible()) {
  134408. me.el.addCls(me.activeCls);
  134409. me.focus();
  134410. me.activated = true;
  134411. me.fireEvent('activate', me);
  134412. }
  134413. },
  134414. getFocusEl: function() {
  134415. return this.itemEl;
  134416. },
  134417. deactivate: function() {
  134418. var me = this;
  134419. if (me.activated) {
  134420. me.el.removeCls(me.activeCls);
  134421. me.blur();
  134422. me.hideMenu();
  134423. me.activated = false;
  134424. me.fireEvent('deactivate', me);
  134425. }
  134426. },
  134427. deferExpandMenu: function() {
  134428. var me = this;
  134429. if (me.activated && (!me.menu.rendered || !me.menu.isVisible())) {
  134430. me.parentMenu.activeChild = me.menu;
  134431. me.menu.parentItem = me;
  134432. me.menu.parentMenu = me.menu.ownerCt = me.parentMenu;
  134433. me.menu.showBy(me, me.menuAlign);
  134434. }
  134435. },
  134436. deferHideMenu: function() {
  134437. if (this.menu.isVisible()) {
  134438. this.menu.hide();
  134439. }
  134440. },
  134441. cancelDeferHide: function(){
  134442. clearTimeout(this.hideMenuTimer);
  134443. },
  134444. deferHideParentMenus: function() {
  134445. var ancestor;
  134446. Ext.menu.Manager.hideAll();
  134447. if (!Ext.Element.getActiveElement()) {
  134448. // If we have just hidden all Menus, and there is no currently focused element in the dom, transfer focus to the first visible ancestor if any.
  134449. ancestor = this.up(':not([hidden])');
  134450. if (ancestor) {
  134451. ancestor.focus();
  134452. }
  134453. }
  134454. },
  134455. expandMenu: function(delay) {
  134456. var me = this;
  134457. if (me.menu) {
  134458. me.cancelDeferHide();
  134459. if (delay === 0) {
  134460. me.deferExpandMenu();
  134461. } else {
  134462. me.expandMenuTimer = Ext.defer(me.deferExpandMenu, Ext.isNumber(delay) ? delay : me.menuExpandDelay, me);
  134463. }
  134464. }
  134465. },
  134466. getRefItems: function(deep){
  134467. var menu = this.menu,
  134468. items;
  134469. if (menu) {
  134470. items = menu.getRefItems(deep);
  134471. items.unshift(menu);
  134472. }
  134473. return items || [];
  134474. },
  134475. hideMenu: function(delay) {
  134476. var me = this;
  134477. if (me.menu) {
  134478. clearTimeout(me.expandMenuTimer);
  134479. me.hideMenuTimer = Ext.defer(me.deferHideMenu, Ext.isNumber(delay) ? delay : me.menuHideDelay, me);
  134480. }
  134481. },
  134482. initComponent: function() {
  134483. var me = this,
  134484. prefix = Ext.baseCSSPrefix,
  134485. cls = [prefix + 'menu-item'],
  134486. menu;
  134487. me.addEvents(
  134488. /**
  134489. * @event activate
  134490. * Fires when this item is activated
  134491. * @param {Ext.menu.Item} item The activated item
  134492. */
  134493. 'activate',
  134494. /**
  134495. * @event click
  134496. * Fires when this item is clicked
  134497. * @param {Ext.menu.Item} item The item that was clicked
  134498. * @param {Ext.EventObject} e The underyling {@link Ext.EventObject}.
  134499. */
  134500. 'click',
  134501. /**
  134502. * @event deactivate
  134503. * Fires when this tiem is deactivated
  134504. * @param {Ext.menu.Item} item The deactivated item
  134505. */
  134506. 'deactivate'
  134507. );
  134508. if (me.plain) {
  134509. cls.push(prefix + 'menu-item-plain');
  134510. }
  134511. if (me.cls) {
  134512. cls.push(me.cls);
  134513. }
  134514. me.cls = cls.join(' ');
  134515. if (me.menu) {
  134516. menu = me.menu;
  134517. delete me.menu;
  134518. me.setMenu(menu);
  134519. }
  134520. me.callParent(arguments);
  134521. },
  134522. onClick: function(e) {
  134523. var me = this;
  134524. if (!me.href) {
  134525. e.stopEvent();
  134526. }
  134527. if (me.disabled) {
  134528. return;
  134529. }
  134530. if (me.hideOnClick) {
  134531. me.deferHideParentMenusTimer = Ext.defer(me.deferHideParentMenus, me.clickHideDelay, me);
  134532. }
  134533. Ext.callback(me.handler, me.scope || me, [me, e]);
  134534. me.fireEvent('click', me, e);
  134535. if (!me.hideOnClick) {
  134536. me.focus();
  134537. }
  134538. },
  134539. onRemoved: function() {
  134540. var me = this;
  134541. // Removing the active item, must deactivate it.
  134542. if (me.activated && me.parentMenu.activeItem === me) {
  134543. me.parentMenu.deactivateActiveItem();
  134544. }
  134545. me.callParent(arguments);
  134546. delete me.parentMenu;
  134547. delete me.ownerButton;
  134548. },
  134549. // private
  134550. beforeDestroy: function() {
  134551. var me = this;
  134552. if (me.rendered) {
  134553. me.clearTip();
  134554. }
  134555. me.callParent();
  134556. },
  134557. onDestroy: function() {
  134558. var me = this;
  134559. clearTimeout(me.expandMenuTimer);
  134560. me.cancelDeferHide();
  134561. clearTimeout(me.deferHideParentMenusTimer);
  134562. me.setMenu(null);
  134563. me.callParent(arguments);
  134564. },
  134565. beforeRender: function() {
  134566. var me = this,
  134567. blank = Ext.BLANK_IMAGE_URL,
  134568. iconCls,
  134569. arrowCls;
  134570. me.callParent();
  134571. if (me.iconAlign === 'right') {
  134572. iconCls = me.checkChangeDisabled ? me.disabledCls : '';
  134573. arrowCls = Ext.baseCSSPrefix + 'menu-item-icon-right ' + me.iconCls;
  134574. } else {
  134575. iconCls = me.iconCls + (me.checkChangeDisabled ? ' ' + me.disabledCls : '');
  134576. arrowCls = me.menu ? me.arrowCls : '';
  134577. }
  134578. Ext.applyIf(me.renderData, {
  134579. href: me.href || '#',
  134580. hrefTarget: me.hrefTarget,
  134581. icon: me.icon || blank,
  134582. iconCls: iconCls,
  134583. plain: me.plain,
  134584. text: me.text,
  134585. arrowCls: arrowCls,
  134586. blank: blank
  134587. });
  134588. },
  134589. onRender: function() {
  134590. var me = this;
  134591. me.callParent(arguments);
  134592. if (me.tooltip) {
  134593. me.setTooltip(me.tooltip, true);
  134594. }
  134595. },
  134596. /**
  134597. * Set a child menu for this item. See the {@link #cfg-menu} configuration.
  134598. * @param {Ext.menu.Menu/Object} menu A menu, or menu configuration. null may be
  134599. * passed to remove the menu.
  134600. * @param {Boolean} [destroyMenu] True to destroy any existing menu. False to
  134601. * prevent destruction. If not specified, the {@link #destroyMenu} configuration
  134602. * will be used.
  134603. */
  134604. setMenu: function(menu, destroyMenu) {
  134605. var me = this,
  134606. oldMenu = me.menu,
  134607. arrowEl = me.arrowEl;
  134608. if (oldMenu) {
  134609. delete oldMenu.parentItem;
  134610. delete oldMenu.parentMenu;
  134611. delete oldMenu.ownerCt;
  134612. delete oldMenu.ownerItem;
  134613. if (destroyMenu === true || (destroyMenu !== false && me.destroyMenu)) {
  134614. Ext.destroy(oldMenu);
  134615. }
  134616. }
  134617. if (menu) {
  134618. me.menu = Ext.menu.Manager.get(menu);
  134619. me.menu.ownerItem = me;
  134620. } else {
  134621. me.menu = null;
  134622. }
  134623. if (me.rendered && !me.destroying && arrowEl) {
  134624. arrowEl[me.menu ? 'addCls' : 'removeCls'](me.arrowCls);
  134625. }
  134626. },
  134627. /**
  134628. * Sets the {@link #click} handler of this item
  134629. * @param {Function} fn The handler function
  134630. * @param {Object} [scope] The scope of the handler function
  134631. */
  134632. setHandler: function(fn, scope) {
  134633. this.handler = fn || null;
  134634. this.scope = scope;
  134635. },
  134636. /**
  134637. * Sets the {@link #icon} on this item.
  134638. * @param {String} icon The new icon
  134639. */
  134640. setIcon: function(icon){
  134641. var iconEl = this.iconEl;
  134642. if (iconEl) {
  134643. iconEl.src = icon || Ext.BLANK_IMAGE_URL;
  134644. }
  134645. this.icon = icon;
  134646. },
  134647. /**
  134648. * Sets the {@link #iconCls} of this item
  134649. * @param {String} iconCls The CSS class to set to {@link #iconCls}
  134650. */
  134651. setIconCls: function(iconCls) {
  134652. var me = this,
  134653. iconEl = me.iconEl;
  134654. if (iconEl) {
  134655. if (me.iconCls) {
  134656. iconEl.removeCls(me.iconCls);
  134657. }
  134658. if (iconCls) {
  134659. iconEl.addCls(iconCls);
  134660. }
  134661. }
  134662. me.iconCls = iconCls;
  134663. },
  134664. /**
  134665. * Sets the {@link #text} of this item
  134666. * @param {String} text The {@link #text}
  134667. */
  134668. setText: function(text) {
  134669. var me = this,
  134670. el = me.textEl || me.el;
  134671. me.text = text;
  134672. if (me.rendered) {
  134673. el.update(text || '');
  134674. // cannot just call layout on the component due to stretchmax
  134675. me.ownerCt.updateLayout();
  134676. }
  134677. },
  134678. getTipAttr: function(){
  134679. return this.tooltipType == 'qtip' ? 'data-qtip' : 'title';
  134680. },
  134681. //private
  134682. clearTip: function() {
  134683. if (Ext.isObject(this.tooltip)) {
  134684. Ext.tip.QuickTipManager.unregister(this.itemEl);
  134685. }
  134686. },
  134687. /**
  134688. * Sets the tooltip for this menu item.
  134689. *
  134690. * @param {String/Object} tooltip This may be:
  134691. *
  134692. * - **String** : A string to be used as innerHTML (html tags are accepted) to show in a tooltip
  134693. * - **Object** : A configuration object for {@link Ext.tip.QuickTipManager#register}.
  134694. *
  134695. * @return {Ext.menu.Item} this
  134696. */
  134697. setTooltip: function(tooltip, initial) {
  134698. var me = this;
  134699. if (me.rendered) {
  134700. if (!initial) {
  134701. me.clearTip();
  134702. }
  134703. if (Ext.isObject(tooltip)) {
  134704. Ext.tip.QuickTipManager.register(Ext.apply({
  134705. target: me.itemEl.id
  134706. },
  134707. tooltip));
  134708. me.tooltip = tooltip;
  134709. } else {
  134710. me.itemEl.dom.setAttribute(me.getTipAttr(), tooltip);
  134711. }
  134712. } else {
  134713. me.tooltip = tooltip;
  134714. }
  134715. return me;
  134716. }
  134717. });
  134718. /**
  134719. * A menu item that contains a togglable checkbox by default, but that can also be a part of a radio group.
  134720. *
  134721. * @example
  134722. * Ext.create('Ext.menu.Menu', {
  134723. * width: 100,
  134724. * height: 110,
  134725. * floating: false, // usually you want this set to True (default)
  134726. * renderTo: Ext.getBody(), // usually rendered by it's containing component
  134727. * items: [{
  134728. * xtype: 'menucheckitem',
  134729. * text: 'select all'
  134730. * },{
  134731. * xtype: 'menucheckitem',
  134732. * text: 'select specific'
  134733. * },{
  134734. * iconCls: 'add16',
  134735. * text: 'icon item'
  134736. * },{
  134737. * text: 'regular item'
  134738. * }]
  134739. * });
  134740. */
  134741. Ext.define('Ext.menu.CheckItem', {
  134742. extend: 'Ext.menu.Item',
  134743. alias: 'widget.menucheckitem',
  134744. /**
  134745. * @cfg {Boolean} [checked=false]
  134746. * True to render the menuitem initially checked.
  134747. */
  134748. /**
  134749. * @cfg {Function} checkHandler
  134750. * Alternative for the {@link #checkchange} event. Gets called with the same parameters.
  134751. */
  134752. /**
  134753. * @cfg {Object} scope
  134754. * Scope for the {@link #checkHandler} callback.
  134755. */
  134756. /**
  134757. * @cfg {String} group
  134758. * Name of a radio group that the item belongs.
  134759. *
  134760. * Specifying this option will turn check item into a radio item.
  134761. *
  134762. * Note that the group name must be globally unique.
  134763. */
  134764. /**
  134765. * @cfg {String} checkedCls
  134766. * The CSS class used by {@link #cls} to show the checked state.
  134767. * Defaults to `Ext.baseCSSPrefix + 'menu-item-checked'`.
  134768. */
  134769. checkedCls: Ext.baseCSSPrefix + 'menu-item-checked',
  134770. /**
  134771. * @cfg {String} uncheckedCls
  134772. * The CSS class used by {@link #cls} to show the unchecked state.
  134773. * Defaults to `Ext.baseCSSPrefix + 'menu-item-unchecked'`.
  134774. */
  134775. uncheckedCls: Ext.baseCSSPrefix + 'menu-item-unchecked',
  134776. /**
  134777. * @cfg {String} groupCls
  134778. * The CSS class applied to this item's icon image to denote being a part of a radio group.
  134779. * Defaults to `Ext.baseCSSClass + 'menu-group-icon'`.
  134780. * Any specified {@link #iconCls} overrides this.
  134781. */
  134782. groupCls: Ext.baseCSSPrefix + 'menu-group-icon',
  134783. /**
  134784. * @cfg {Boolean} [hideOnClick=false]
  134785. * Whether to not to hide the owning menu when this item is clicked.
  134786. * Defaults to `false` for checkbox items, and to `true` for radio group items.
  134787. */
  134788. hideOnClick: false,
  134789. /**
  134790. * @cfg {Boolean} [checkChangeDisabled=false]
  134791. * True to prevent the checked item from being toggled. Any submenu will still be accessible.
  134792. */
  134793. checkChangeDisabled: false,
  134794. afterRender: function() {
  134795. var me = this;
  134796. me.callParent();
  134797. me.checked = !me.checked;
  134798. me.setChecked(!me.checked, true);
  134799. if (me.checkChangeDisabled) {
  134800. me.disableCheckChange();
  134801. }
  134802. },
  134803. initComponent: function() {
  134804. var me = this;
  134805. me.addEvents(
  134806. /**
  134807. * @event beforecheckchange
  134808. * Fires before a change event. Return false to cancel.
  134809. * @param {Ext.menu.CheckItem} this
  134810. * @param {Boolean} checked
  134811. */
  134812. 'beforecheckchange',
  134813. /**
  134814. * @event checkchange
  134815. * Fires after a change event.
  134816. * @param {Ext.menu.CheckItem} this
  134817. * @param {Boolean} checked
  134818. */
  134819. 'checkchange'
  134820. );
  134821. me.callParent(arguments);
  134822. Ext.menu.Manager.registerCheckable(me);
  134823. if (me.group) {
  134824. if (!me.iconCls) {
  134825. me.iconCls = me.groupCls;
  134826. }
  134827. if (me.initialConfig.hideOnClick !== false) {
  134828. me.hideOnClick = true;
  134829. }
  134830. }
  134831. },
  134832. /**
  134833. * Disables just the checkbox functionality of this menu Item. If this menu item has a submenu, that submenu
  134834. * will still be accessible
  134835. */
  134836. disableCheckChange: function() {
  134837. var me = this,
  134838. iconEl = me.iconEl;
  134839. if (iconEl) {
  134840. iconEl.addCls(me.disabledCls);
  134841. }
  134842. // In some cases the checkbox will disappear until repainted
  134843. // Happens in everything except IE9 strict, see: EXTJSIV-6412
  134844. if (!(Ext.isIE9 && Ext.isStrict) && me.rendered) {
  134845. me.el.repaint();
  134846. }
  134847. me.checkChangeDisabled = true;
  134848. },
  134849. /**
  134850. * Reenables the checkbox functionality of this menu item after having been disabled by {@link #disableCheckChange}
  134851. */
  134852. enableCheckChange: function() {
  134853. var me = this,
  134854. iconEl = me.iconEl;
  134855. if (iconEl) {
  134856. iconEl.removeCls(me.disabledCls);
  134857. }
  134858. me.checkChangeDisabled = false;
  134859. },
  134860. onClick: function(e) {
  134861. var me = this;
  134862. if(!me.disabled && !me.checkChangeDisabled && !(me.checked && me.group)) {
  134863. me.setChecked(!me.checked);
  134864. }
  134865. this.callParent([e]);
  134866. },
  134867. onDestroy: function() {
  134868. Ext.menu.Manager.unregisterCheckable(this);
  134869. this.callParent(arguments);
  134870. },
  134871. /**
  134872. * Sets the checked state of the item
  134873. * @param {Boolean} checked True to check, false to uncheck
  134874. * @param {Boolean} [suppressEvents=false] True to prevent firing the checkchange events.
  134875. */
  134876. setChecked: function(checked, suppressEvents) {
  134877. var me = this;
  134878. if (me.checked !== checked && (suppressEvents || me.fireEvent('beforecheckchange', me, checked) !== false)) {
  134879. if (me.el) {
  134880. me.el[checked ? 'addCls' : 'removeCls'](me.checkedCls)[!checked ? 'addCls' : 'removeCls'](me.uncheckedCls);
  134881. }
  134882. me.checked = checked;
  134883. Ext.menu.Manager.onCheckChange(me, checked);
  134884. if (!suppressEvents) {
  134885. Ext.callback(me.checkHandler, me.scope, [me, checked]);
  134886. me.fireEvent('checkchange', me, checked);
  134887. }
  134888. }
  134889. }
  134890. });
  134891. /**
  134892. * @private
  134893. */
  134894. Ext.define('Ext.menu.KeyNav', {
  134895. extend: 'Ext.util.KeyNav',
  134896. requires: ['Ext.FocusManager'],
  134897. constructor: function(menu) {
  134898. var me = this;
  134899. me.menu = menu;
  134900. me.callParent([menu.el, {
  134901. down: me.down,
  134902. enter: me.enter,
  134903. esc: me.escape,
  134904. left: me.left,
  134905. right: me.right,
  134906. space: me.enter,
  134907. tab: me.tab,
  134908. up: me.up
  134909. }]);
  134910. },
  134911. down: function(e) {
  134912. var me = this,
  134913. fi = me.menu.focusedItem;
  134914. if (fi && e.getKey() == Ext.EventObject.DOWN && me.isWhitelisted(fi)) {
  134915. return true;
  134916. }
  134917. me.focusNextItem(1);
  134918. },
  134919. enter: function(e) {
  134920. var menu = this.menu,
  134921. focused = menu.focusedItem;
  134922. if (menu.activeItem) {
  134923. menu.onClick(e);
  134924. } else if (focused && focused.isFormField) {
  134925. // prevent stopEvent being called
  134926. return true;
  134927. }
  134928. },
  134929. escape: function(e) {
  134930. Ext.menu.Manager.hideAll();
  134931. },
  134932. focusNextItem: function(step) {
  134933. var menu = this.menu,
  134934. items = menu.items,
  134935. focusedItem = menu.focusedItem,
  134936. startIdx = focusedItem ? items.indexOf(focusedItem) : -1,
  134937. idx = startIdx + step,
  134938. item;
  134939. while (idx != startIdx) {
  134940. if (idx < 0) {
  134941. idx = items.length - 1;
  134942. } else if (idx >= items.length) {
  134943. idx = 0;
  134944. }
  134945. item = items.getAt(idx);
  134946. if (menu.canActivateItem(item)) {
  134947. menu.setActiveItem(item);
  134948. break;
  134949. }
  134950. idx += step;
  134951. }
  134952. },
  134953. isWhitelisted: function(item) {
  134954. return Ext.FocusManager.isWhitelisted(item);
  134955. },
  134956. left: function(e) {
  134957. var menu = this.menu,
  134958. fi = menu.focusedItem,
  134959. ai = menu.activeItem;
  134960. if (fi && this.isWhitelisted(fi)) {
  134961. return true;
  134962. }
  134963. menu.hide();
  134964. if (menu.parentMenu) {
  134965. menu.parentMenu.focus();
  134966. }
  134967. },
  134968. right: function(e) {
  134969. var menu = this.menu,
  134970. fi = menu.focusedItem,
  134971. ai = menu.activeItem,
  134972. am;
  134973. if (fi && this.isWhitelisted(fi)) {
  134974. return true;
  134975. }
  134976. if (ai) {
  134977. am = menu.activeItem.menu;
  134978. if (am) {
  134979. ai.expandMenu(0);
  134980. Ext.defer(function() {
  134981. am.setActiveItem(am.items.getAt(0));
  134982. }, 25);
  134983. }
  134984. }
  134985. },
  134986. tab: function(e) {
  134987. var me = this;
  134988. if (e.shiftKey) {
  134989. me.up(e);
  134990. } else {
  134991. me.down(e);
  134992. }
  134993. },
  134994. up: function(e) {
  134995. var me = this,
  134996. fi = me.menu.focusedItem;
  134997. if (fi && e.getKey() == Ext.EventObject.UP && me.isWhitelisted(fi)) {
  134998. return true;
  134999. }
  135000. me.focusNextItem(-1);
  135001. }
  135002. });
  135003. /**
  135004. * Adds a separator bar to a menu, used to divide logical groups of menu items. Generally you will
  135005. * add one of these by using "-" in your call to add() or in your items config rather than creating one directly.
  135006. *
  135007. * @example
  135008. * Ext.create('Ext.menu.Menu', {
  135009. * width: 100,
  135010. * height: 100,
  135011. * floating: false, // usually you want this set to True (default)
  135012. * renderTo: Ext.getBody(), // usually rendered by it's containing component
  135013. * items: [{
  135014. * text: 'icon item',
  135015. * iconCls: 'add16'
  135016. * },{
  135017. * xtype: 'menuseparator'
  135018. * },{
  135019. * text: 'separator above'
  135020. * },{
  135021. * text: 'regular item'
  135022. * }]
  135023. * });
  135024. */
  135025. Ext.define('Ext.menu.Separator', {
  135026. extend: 'Ext.menu.Item',
  135027. alias: 'widget.menuseparator',
  135028. /**
  135029. * @cfg {String} activeCls
  135030. * @private
  135031. */
  135032. /**
  135033. * @cfg {Boolean} canActivate
  135034. * @private
  135035. */
  135036. canActivate: false,
  135037. /**
  135038. * @cfg {Boolean} clickHideDelay
  135039. * @private
  135040. */
  135041. /**
  135042. * @cfg {Boolean} destroyMenu
  135043. * @private
  135044. */
  135045. /**
  135046. * @cfg {Boolean} disabledCls
  135047. * @private
  135048. */
  135049. focusable: false,
  135050. /**
  135051. * @cfg {String} href
  135052. * @private
  135053. */
  135054. /**
  135055. * @cfg {String} hrefTarget
  135056. * @private
  135057. */
  135058. /**
  135059. * @cfg {Boolean} hideOnClick
  135060. * @private
  135061. */
  135062. hideOnClick: false,
  135063. /**
  135064. * @cfg {String} icon
  135065. * @private
  135066. */
  135067. /**
  135068. * @cfg {String} iconCls
  135069. * @private
  135070. */
  135071. /**
  135072. * @cfg {Object} menu
  135073. * @private
  135074. */
  135075. /**
  135076. * @cfg {String} menuAlign
  135077. * @private
  135078. */
  135079. /**
  135080. * @cfg {Number} menuExpandDelay
  135081. * @private
  135082. */
  135083. /**
  135084. * @cfg {Number} menuHideDelay
  135085. * @private
  135086. */
  135087. /**
  135088. * @cfg {Boolean} plain
  135089. * @private
  135090. */
  135091. plain: true,
  135092. /**
  135093. * @cfg {String} separatorCls
  135094. * The CSS class used by the separator item to show the incised line.
  135095. */
  135096. separatorCls: Ext.baseCSSPrefix + 'menu-item-separator',
  135097. /**
  135098. * @cfg {String} text
  135099. * @private
  135100. */
  135101. text: '&#160;',
  135102. beforeRender: function(ct, pos) {
  135103. var me = this;
  135104. me.callParent();
  135105. me.addCls(me.separatorCls);
  135106. }
  135107. });
  135108. /**
  135109. * A menu object. This is the container to which you may add {@link Ext.menu.Item menu items}.
  135110. *
  135111. * Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Components}.
  135112. * Menus may also contain {@link Ext.panel.AbstractPanel#dockedItems docked items} because it extends {@link Ext.panel.Panel}.
  135113. *
  135114. * To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items},
  135115. * specify `{@link Ext.menu.Item#plain plain}: true`. This reserves a space for an icon, and indents the Component
  135116. * in line with the other menu items.
  135117. *
  135118. * By default, Menus are absolutely positioned, floating Components. By configuring a Menu with `{@link #floating}: false`,
  135119. * a Menu may be used as a child of a {@link Ext.container.Container Container}.
  135120. *
  135121. * @example
  135122. * Ext.create('Ext.menu.Menu', {
  135123. * width: 100,
  135124. * margin: '0 0 10 0',
  135125. * floating: false, // usually you want this set to True (default)
  135126. * renderTo: Ext.getBody(), // usually rendered by it's containing component
  135127. * items: [{
  135128. * text: 'regular item 1'
  135129. * },{
  135130. * text: 'regular item 2'
  135131. * },{
  135132. * text: 'regular item 3'
  135133. * }]
  135134. * });
  135135. *
  135136. * Ext.create('Ext.menu.Menu', {
  135137. * width: 100,
  135138. * plain: true,
  135139. * floating: false, // usually you want this set to True (default)
  135140. * renderTo: Ext.getBody(), // usually rendered by it's containing component
  135141. * items: [{
  135142. * text: 'plain item 1'
  135143. * },{
  135144. * text: 'plain item 2'
  135145. * },{
  135146. * text: 'plain item 3'
  135147. * }]
  135148. * });
  135149. */
  135150. Ext.define('Ext.menu.Menu', {
  135151. extend: 'Ext.panel.Panel',
  135152. alias: 'widget.menu',
  135153. requires: [
  135154. 'Ext.layout.container.Fit',
  135155. 'Ext.layout.container.VBox',
  135156. 'Ext.menu.CheckItem',
  135157. 'Ext.menu.Item',
  135158. 'Ext.menu.KeyNav',
  135159. 'Ext.menu.Manager',
  135160. 'Ext.menu.Separator'
  135161. ],
  135162. /**
  135163. * @property {Ext.menu.Menu} parentMenu
  135164. * The parent Menu of this Menu.
  135165. */
  135166. /**
  135167. * @cfg {Boolean} [enableKeyNav=true]
  135168. * True to enable keyboard navigation for controlling the menu.
  135169. * This option should generally be disabled when form fields are
  135170. * being used inside the menu.
  135171. */
  135172. enableKeyNav: true,
  135173. /**
  135174. * @cfg {Boolean} [allowOtherMenus=false]
  135175. * True to allow multiple menus to be displayed at the same time.
  135176. */
  135177. allowOtherMenus: false,
  135178. /**
  135179. * @cfg {String} ariaRole
  135180. * @private
  135181. */
  135182. ariaRole: 'menu',
  135183. /**
  135184. * @cfg {Boolean} autoRender
  135185. * Floating is true, so autoRender always happens.
  135186. * @private
  135187. */
  135188. /**
  135189. * @cfg {String} [defaultAlign="tl-bl?"]
  135190. * The default {@link Ext.Element#getAlignToXY Ext.Element#getAlignToXY} anchor position value for this menu
  135191. * relative to its element of origin.
  135192. */
  135193. defaultAlign: 'tl-bl?',
  135194. /**
  135195. * @cfg {Boolean} [floating=true]
  135196. * A Menu configured as `floating: true` (the default) will be rendered as an absolutely positioned,
  135197. * {@link Ext.Component#floating floating} {@link Ext.Component Component}. If configured as `floating: false`, the Menu may be
  135198. * used as a child item of another {@link Ext.container.Container Container}.
  135199. */
  135200. floating: true,
  135201. /**
  135202. * @cfg {Boolean} constrain
  135203. * Menus are constrained to the document body by default.
  135204. * @private
  135205. */
  135206. constrain: true,
  135207. /**
  135208. * @cfg {Boolean} [hidden=undefined]
  135209. * True to initially render the Menu as hidden, requiring to be shown manually.
  135210. *
  135211. * Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.
  135212. */
  135213. hidden: true,
  135214. hideMode: 'visibility',
  135215. /**
  135216. * @cfg {Boolean} [ignoreParentClicks=false]
  135217. * True to ignore clicks on any item in this menu that is a parent item (displays a submenu)
  135218. * so that the submenu is not dismissed when clicking the parent item.
  135219. */
  135220. ignoreParentClicks: false,
  135221. /**
  135222. * @property {Boolean} isMenu
  135223. * `true` in this class to identify an object as an instantiated Menu, or subclass thereof.
  135224. */
  135225. isMenu: true,
  135226. /**
  135227. * @cfg {String/Object} layout
  135228. * @private
  135229. */
  135230. /**
  135231. * @cfg {Boolean} [showSeparator=true]
  135232. * True to show the icon separator.
  135233. */
  135234. showSeparator : true,
  135235. /**
  135236. * @cfg {Number} [minWidth=120]
  135237. * The minimum width of the Menu. The default minWidth only applies when the {@link #floating} config is true.
  135238. */
  135239. minWidth: undefined,
  135240. defaultMinWidth: 120,
  135241. /**
  135242. * @cfg {Boolean} [plain=false]
  135243. * True to remove the incised line down the left side of the menu and to not indent general Component items.
  135244. */
  135245. initComponent: function() {
  135246. var me = this,
  135247. prefix = Ext.baseCSSPrefix,
  135248. cls = [prefix + 'menu'],
  135249. bodyCls = me.bodyCls ? [me.bodyCls] : [],
  135250. isFloating = me.floating !== false;
  135251. me.addEvents(
  135252. /**
  135253. * @event click
  135254. * Fires when this menu is clicked
  135255. * @param {Ext.menu.Menu} menu The menu which has been clicked
  135256. * @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.
  135257. * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}.
  135258. */
  135259. 'click',
  135260. /**
  135261. * @event mouseenter
  135262. * Fires when the mouse enters this menu
  135263. * @param {Ext.menu.Menu} menu The menu
  135264. * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
  135265. */
  135266. 'mouseenter',
  135267. /**
  135268. * @event mouseleave
  135269. * Fires when the mouse leaves this menu
  135270. * @param {Ext.menu.Menu} menu The menu
  135271. * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
  135272. */
  135273. 'mouseleave',
  135274. /**
  135275. * @event mouseover
  135276. * Fires when the mouse is hovering over this menu
  135277. * @param {Ext.menu.Menu} menu The menu
  135278. * @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.
  135279. * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
  135280. */
  135281. 'mouseover'
  135282. );
  135283. Ext.menu.Manager.register(me);
  135284. // Menu classes
  135285. if (me.plain) {
  135286. cls.push(prefix + 'menu-plain');
  135287. }
  135288. me.cls = cls.join(' ');
  135289. // Menu body classes
  135290. bodyCls.unshift(prefix + 'menu-body');
  135291. me.bodyCls = bodyCls.join(' ');
  135292. // Internal vbox layout, with scrolling overflow
  135293. // Placed in initComponent (rather than prototype) in order to support dynamic layout/scroller
  135294. // options if we wish to allow for such configurations on the Menu.
  135295. // e.g., scrolling speed, vbox align stretch, etc.
  135296. if (!me.layout) {
  135297. me.layout = {
  135298. type: 'vbox',
  135299. align: 'stretchmax',
  135300. overflowHandler: 'Scroller'
  135301. };
  135302. }
  135303. // only apply the minWidth when we're floating & one hasn't already been set
  135304. if (isFloating && me.minWidth === undefined) {
  135305. me.minWidth = me.defaultMinWidth;
  135306. }
  135307. // hidden defaults to false if floating is configured as false
  135308. if (!isFloating && me.initialConfig.hidden !== true) {
  135309. me.hidden = false;
  135310. }
  135311. me.callParent(arguments);
  135312. me.on('beforeshow', function() {
  135313. var hasItems = !!me.items.length;
  135314. // FIXME: When a menu has its show cancelled because of no items, it
  135315. // gets a visibility: hidden applied to it (instead of the default display: none)
  135316. // Not sure why, but we remove this style when we want to show again.
  135317. if (hasItems && me.rendered) {
  135318. me.el.setStyle('visibility', null);
  135319. }
  135320. return hasItems;
  135321. });
  135322. },
  135323. beforeRender: function() {
  135324. this.callParent(arguments);
  135325. // Menus are usually floating: true, which means they shrink wrap their items.
  135326. // However, when they are contained, and not auto sized, we must stretch the items.
  135327. if (!this.getSizeModel().width.shrinkWrap) {
  135328. this.layout.align = 'stretch';
  135329. }
  135330. },
  135331. onBoxReady: function() {
  135332. var me = this,
  135333. separatorSpec;
  135334. me.callParent(arguments);
  135335. // TODO: Move this to a subTemplate When we support them in the future
  135336. if (me.showSeparator) {
  135337. separatorSpec = {
  135338. cls: Ext.baseCSSPrefix + 'menu-icon-separator',
  135339. html: '&#160;'
  135340. };
  135341. if ((!Ext.isStrict && Ext.isIE) || Ext.isIE6) {
  135342. separatorSpec.style = 'height:' + me.el.getHeight() + 'px';
  135343. }
  135344. me.iconSepEl = me.layout.getElementTarget().insertFirst(separatorSpec);
  135345. }
  135346. me.mon(me.el, {
  135347. click: me.onClick,
  135348. mouseover: me.onMouseOver,
  135349. scope: me
  135350. });
  135351. me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);
  135352. if (me.enableKeyNav) {
  135353. me.keyNav = new Ext.menu.KeyNav(me);
  135354. }
  135355. },
  135356. getBubbleTarget: function() {
  135357. // If a submenu, this will have a parentMenu property
  135358. // If a menu of a Button, it will have an ownerButton property
  135359. // Else use the default method.
  135360. return this.parentMenu || this.ownerButton || this.callParent(arguments);
  135361. },
  135362. /**
  135363. * Returns whether a menu item can be activated or not.
  135364. * @return {Boolean}
  135365. */
  135366. canActivateItem: function(item) {
  135367. return item && !item.isDisabled() && item.isVisible() && (item.canActivate || item.getXTypes().indexOf('menuitem') < 0);
  135368. },
  135369. /**
  135370. * Deactivates the current active item on the menu, if one exists.
  135371. */
  135372. deactivateActiveItem: function(andBlurFocusedItem) {
  135373. var me = this,
  135374. activeItem = me.activeItem,
  135375. focusedItem = me.focusedItem;
  135376. if (activeItem) {
  135377. activeItem.deactivate();
  135378. if (!activeItem.activated) {
  135379. delete me.activeItem;
  135380. }
  135381. }
  135382. // Blur the focused item if we are being asked to do that too
  135383. // Only needed if we are being hidden - mouseout does not blur.
  135384. if (focusedItem && andBlurFocusedItem) {
  135385. focusedItem.blur();
  135386. delete me.focusedItem;
  135387. }
  135388. },
  135389. // inherit docs
  135390. getFocusEl: function() {
  135391. return this.focusedItem || this.el;
  135392. },
  135393. // inherit docs
  135394. hide: function() {
  135395. this.deactivateActiveItem(true);
  135396. this.callParent(arguments);
  135397. },
  135398. // private
  135399. getItemFromEvent: function(e) {
  135400. return this.getChildByElement(e.getTarget());
  135401. },
  135402. lookupComponent: function(cmp) {
  135403. var me = this;
  135404. if (typeof cmp == 'string') {
  135405. cmp = me.lookupItemFromString(cmp);
  135406. } else if (Ext.isObject(cmp)) {
  135407. cmp = me.lookupItemFromObject(cmp);
  135408. }
  135409. // Apply our minWidth to all of our child components so it's accounted
  135410. // for in our VBox layout
  135411. cmp.minWidth = cmp.minWidth || me.minWidth;
  135412. return cmp;
  135413. },
  135414. // private
  135415. lookupItemFromObject: function(cmp) {
  135416. var me = this,
  135417. prefix = Ext.baseCSSPrefix,
  135418. cls;
  135419. if (!cmp.isComponent) {
  135420. if (!cmp.xtype) {
  135421. cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
  135422. } else {
  135423. cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
  135424. }
  135425. }
  135426. if (cmp.isMenuItem) {
  135427. cmp.parentMenu = me;
  135428. }
  135429. if (!cmp.isMenuItem && !cmp.dock) {
  135430. cls = [prefix + 'menu-item', prefix + 'menu-item-cmp'];
  135431. if (!me.plain && (cmp.indent === true || cmp.iconCls === 'no-icon')) {
  135432. cls.push(prefix + 'menu-item-indent');
  135433. }
  135434. if (cmp.rendered) {
  135435. cmp.el.addCls(cls);
  135436. } else {
  135437. cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');
  135438. }
  135439. }
  135440. return cmp;
  135441. },
  135442. // private
  135443. lookupItemFromString: function(cmp) {
  135444. return (cmp == 'separator' || cmp == '-') ?
  135445. new Ext.menu.Separator()
  135446. : new Ext.menu.Item({
  135447. canActivate: false,
  135448. hideOnClick: false,
  135449. plain: true,
  135450. text: cmp
  135451. });
  135452. },
  135453. onClick: function(e) {
  135454. var me = this,
  135455. item;
  135456. if (me.disabled) {
  135457. e.stopEvent();
  135458. return;
  135459. }
  135460. item = (e.type === 'click') ? me.getItemFromEvent(e) : me.activeItem;
  135461. if (item && item.isMenuItem) {
  135462. if (!item.menu || !me.ignoreParentClicks) {
  135463. item.onClick(e);
  135464. } else {
  135465. e.stopEvent();
  135466. }
  135467. }
  135468. // Click event may be fired without an item, so we need a second check
  135469. if (!item || item.disabled) {
  135470. item = undefined;
  135471. }
  135472. me.fireEvent('click', me, item, e);
  135473. },
  135474. onDestroy: function() {
  135475. var me = this;
  135476. Ext.menu.Manager.unregister(me);
  135477. delete me.parentMenu;
  135478. delete me.ownerButton;
  135479. if (me.rendered) {
  135480. me.el.un(me.mouseMonitor);
  135481. Ext.destroy(me.keyNav);
  135482. delete me.keyNav;
  135483. }
  135484. me.callParent(arguments);
  135485. },
  135486. onMouseLeave: function(e) {
  135487. var me = this;
  135488. me.deactivateActiveItem();
  135489. if (me.disabled) {
  135490. return;
  135491. }
  135492. me.fireEvent('mouseleave', me, e);
  135493. },
  135494. onMouseOver: function(e) {
  135495. var me = this,
  135496. fromEl = e.getRelatedTarget(),
  135497. mouseEnter = !me.el.contains(fromEl),
  135498. item = me.getItemFromEvent(e),
  135499. parentMenu = me.parentMenu,
  135500. parentItem = me.parentItem;
  135501. if (mouseEnter && parentMenu) {
  135502. parentMenu.setActiveItem(parentItem);
  135503. parentItem.cancelDeferHide();
  135504. parentMenu.mouseMonitor.mouseenter();
  135505. }
  135506. if (me.disabled) {
  135507. return;
  135508. }
  135509. // Do not activate the item if the mouseover was within the item, and it's already active
  135510. if (item && !item.activated) {
  135511. me.setActiveItem(item);
  135512. if (item.activated && item.expandMenu) {
  135513. item.expandMenu();
  135514. }
  135515. }
  135516. if (mouseEnter) {
  135517. me.fireEvent('mouseenter', me, e);
  135518. }
  135519. me.fireEvent('mouseover', me, item, e);
  135520. },
  135521. setActiveItem: function(item) {
  135522. var me = this;
  135523. if (item && (item != me.activeItem)) {
  135524. me.deactivateActiveItem();
  135525. if (me.canActivateItem(item)) {
  135526. if (item.activate) {
  135527. item.activate();
  135528. if (item.activated) {
  135529. me.activeItem = item;
  135530. me.focusedItem = item;
  135531. me.focus();
  135532. }
  135533. } else {
  135534. item.focus();
  135535. me.focusedItem = item;
  135536. }
  135537. }
  135538. item.el.scrollIntoView(me.layout.getRenderTarget());
  135539. }
  135540. },
  135541. /**
  135542. * Shows the floating menu by the specified {@link Ext.Component Component} or {@link Ext.Element Element}.
  135543. * @param {Ext.Component/Ext.Element} component The {@link Ext.Component} or {@link Ext.Element} to show the menu by.
  135544. * @param {String} [position] Alignment position as used by {@link Ext.Element#getAlignToXY}.
  135545. * Defaults to `{@link #defaultAlign}`.
  135546. * @param {Number[]} [offsets] Alignment offsets as used by {@link Ext.Element#getAlignToXY}.
  135547. * @return {Ext.menu.Menu} This Menu.
  135548. */
  135549. showBy: function(cmp, pos, off) {
  135550. var me = this;
  135551. if (me.floating && cmp) {
  135552. me.show();
  135553. // Align to Component or Element using setPagePosition because normal show
  135554. // methods are container-relative, and we must align to the requested element
  135555. // or Component:
  135556. me.setPagePosition(me.el.getAlignToXY(cmp.el || cmp, pos || me.defaultAlign, off));
  135557. me.setVerticalPosition();
  135558. }
  135559. return me;
  135560. },
  135561. show: function() {
  135562. var me = this,
  135563. parentEl, viewHeight, result,
  135564. maxWas = me.maxHeight;
  135565. // we need to get scope parent for height constraint
  135566. if (!me.rendered){
  135567. me.doAutoRender();
  135568. }
  135569. // constrain the height to the curren viewable area
  135570. if (me.floating) {
  135571. //if our reset css is scoped, there will be a x-reset wrapper on this menu which we need to skip
  135572. parentEl = Ext.fly(me.el.getScopeParent());
  135573. viewHeight = parentEl.getViewSize().height;
  135574. me.maxHeight = Math.min(maxWas || viewHeight, viewHeight);
  135575. }
  135576. result = me.callParent(arguments);
  135577. me.maxHeight = maxWas;
  135578. return result;
  135579. },
  135580. afterComponentLayout: function(width, height, oldWidth, oldHeight){
  135581. var me = this;
  135582. me.callParent(arguments);
  135583. // fixup the separator
  135584. if (me.showSeparator){
  135585. me.iconSepEl.setHeight(me.componentLayout.lastComponentSize.contentHeight);
  135586. }
  135587. },
  135588. // private
  135589. // adjust the vertical position of the menu if the height of the
  135590. // menu is equal (or greater than) the viewport size
  135591. setVerticalPosition: function(){
  135592. var me = this,
  135593. max,
  135594. y = me.el.getY(),
  135595. returnY = y,
  135596. height = me.getHeight(),
  135597. viewportHeight = Ext.Element.getViewportHeight().height,
  135598. parentEl = Ext.fly(me.el.getScopeParent()),
  135599. viewHeight = parentEl.getViewSize().height,
  135600. normalY = y - parentEl.getScroll().top; // factor in scrollTop of parent
  135601. parentEl = null;
  135602. if (me.floating) {
  135603. max = me.maxHeight ? me.maxHeight : viewHeight - normalY;
  135604. if (height > viewHeight) {
  135605. returnY = y - normalY;
  135606. } else if (max < height) {
  135607. returnY = y - (height - max);
  135608. } else if((y + height) > viewportHeight){ // keep the document from scrolling
  135609. returnY = viewportHeight - height;
  135610. }
  135611. }
  135612. me.el.setY(returnY);
  135613. }
  135614. });
  135615. /**
  135616. * A menu containing a Ext.picker.Color Component.
  135617. *
  135618. * Notes:
  135619. *
  135620. * - Although not listed here, the **constructor** for this class accepts all of the
  135621. * configuration options of {@link Ext.picker.Color}.
  135622. * - If subclassing ColorMenu, any configuration options for the ColorPicker must be
  135623. * applied to the **initialConfig** property of the ColorMenu. Applying
  135624. * {@link Ext.picker.Color ColorPicker} configuration settings to `this` will **not**
  135625. * affect the ColorPicker's configuration.
  135626. *
  135627. * Example:
  135628. *
  135629. * @example
  135630. * var colorPicker = Ext.create('Ext.menu.ColorPicker', {
  135631. * value: '000000'
  135632. * });
  135633. *
  135634. * Ext.create('Ext.menu.Menu', {
  135635. * width: 100,
  135636. * height: 90,
  135637. * floating: false, // usually you want this set to True (default)
  135638. * renderTo: Ext.getBody(), // usually rendered by it's containing component
  135639. * items: [{
  135640. * text: 'choose a color',
  135641. * menu: colorPicker
  135642. * },{
  135643. * iconCls: 'add16',
  135644. * text: 'icon item'
  135645. * },{
  135646. * text: 'regular item'
  135647. * }]
  135648. * });
  135649. */
  135650. Ext.define('Ext.menu.ColorPicker', {
  135651. extend: 'Ext.menu.Menu',
  135652. alias: 'widget.colormenu',
  135653. requires: [
  135654. 'Ext.picker.Color'
  135655. ],
  135656. /**
  135657. * @cfg {Boolean} hideOnClick
  135658. * False to continue showing the menu after a date is selected.
  135659. */
  135660. hideOnClick : true,
  135661. /**
  135662. * @cfg {String} pickerId
  135663. * An id to assign to the underlying color picker.
  135664. */
  135665. pickerId : null,
  135666. /**
  135667. * @cfg {Number} maxHeight
  135668. * @private
  135669. */
  135670. /**
  135671. * @property {Ext.picker.Color} picker
  135672. * The {@link Ext.picker.Color} instance for this ColorMenu
  135673. */
  135674. /**
  135675. * @event click
  135676. * @private
  135677. */
  135678. initComponent : function(){
  135679. var me = this,
  135680. cfg = Ext.apply({}, me.initialConfig);
  135681. // Ensure we don't get duplicate listeners
  135682. delete cfg.listeners;
  135683. Ext.apply(me, {
  135684. plain: true,
  135685. showSeparator: false,
  135686. items: Ext.applyIf({
  135687. cls: Ext.baseCSSPrefix + 'menu-color-item',
  135688. id: me.pickerId,
  135689. xtype: 'colorpicker'
  135690. }, cfg)
  135691. });
  135692. me.callParent(arguments);
  135693. me.picker = me.down('colorpicker');
  135694. /**
  135695. * @event select
  135696. * @inheritdoc Ext.picker.Color#select
  135697. */
  135698. me.relayEvents(me.picker, ['select']);
  135699. if (me.hideOnClick) {
  135700. me.on('select', me.hidePickerOnSelect, me);
  135701. }
  135702. },
  135703. /**
  135704. * Hides picker on select if hideOnClick is true
  135705. * @private
  135706. */
  135707. hidePickerOnSelect: function() {
  135708. Ext.menu.Manager.hideAll();
  135709. }
  135710. });
  135711. /**
  135712. * A menu containing an Ext.picker.Date Component.
  135713. *
  135714. * Notes:
  135715. *
  135716. * - Although not listed here, the **constructor** for this class accepts all of the
  135717. * configuration options of **{@link Ext.picker.Date}**.
  135718. * - If subclassing DateMenu, any configuration options for the DatePicker must be applied
  135719. * to the **initialConfig** property of the DateMenu. Applying {@link Ext.picker.Date Date Picker}
  135720. * configuration settings to **this** will **not** affect the Date Picker's configuration.
  135721. *
  135722. * Example:
  135723. *
  135724. * @example
  135725. * var dateMenu = Ext.create('Ext.menu.DatePicker', {
  135726. * handler: function(dp, date){
  135727. * Ext.Msg.alert('Date Selected', 'You selected ' + Ext.Date.format(date, 'M j, Y'));
  135728. * }
  135729. * });
  135730. *
  135731. * Ext.create('Ext.menu.Menu', {
  135732. * width: 100,
  135733. * height: 90,
  135734. * floating: false, // usually you want this set to True (default)
  135735. * renderTo: Ext.getBody(), // usually rendered by it's containing component
  135736. * items: [{
  135737. * text: 'choose a date',
  135738. * menu: dateMenu
  135739. * },{
  135740. * iconCls: 'add16',
  135741. * text: 'icon item'
  135742. * },{
  135743. * text: 'regular item'
  135744. * }]
  135745. * });
  135746. */
  135747. Ext.define('Ext.menu.DatePicker', {
  135748. extend: 'Ext.menu.Menu',
  135749. alias: 'widget.datemenu',
  135750. requires: [
  135751. 'Ext.picker.Date'
  135752. ],
  135753. /**
  135754. * @cfg {Boolean} hideOnClick
  135755. * False to continue showing the menu after a date is selected.
  135756. */
  135757. hideOnClick : true,
  135758. /**
  135759. * @cfg {String} pickerId
  135760. * An id to assign to the underlying date picker.
  135761. */
  135762. pickerId : null,
  135763. /**
  135764. * @cfg {Number} maxHeight
  135765. * @private
  135766. */
  135767. /**
  135768. * @property {Ext.picker.Date} picker
  135769. * The {@link Ext.picker.Date} instance for this DateMenu
  135770. */
  135771. initComponent : function(){
  135772. var me = this,
  135773. cfg = Ext.apply({}, me.initialConfig);
  135774. // Ensure we clear any listeners so they aren't duplicated
  135775. delete cfg.listeners;
  135776. Ext.apply(me, {
  135777. showSeparator: false,
  135778. plain: true,
  135779. border: false,
  135780. bodyPadding: 0, // remove the body padding from the datepicker menu item so it looks like 3.3
  135781. items: Ext.applyIf({
  135782. cls: Ext.baseCSSPrefix + 'menu-date-item',
  135783. id: me.pickerId,
  135784. xtype: 'datepicker'
  135785. }, cfg)
  135786. });
  135787. me.callParent(arguments);
  135788. me.picker = me.down('datepicker');
  135789. /**
  135790. * @event select
  135791. * @inheritdoc Ext.picker.Date#select
  135792. */
  135793. me.relayEvents(me.picker, ['select']);
  135794. if (me.hideOnClick) {
  135795. me.on('select', me.hidePickerOnSelect, me);
  135796. }
  135797. },
  135798. hidePickerOnSelect: function() {
  135799. Ext.menu.Manager.hideAll();
  135800. }
  135801. });
  135802. /**
  135803. * This class is used to display small visual icons in the header of a panel. There are a set of
  135804. * 25 icons that can be specified by using the {@link #type} config. The {@link #handler} config
  135805. * can be used to provide a function that will respond to any click events. In general, this class
  135806. * will not be instantiated directly, rather it will be created by specifying the {@link Ext.panel.Panel#tools}
  135807. * configuration on the Panel itself.
  135808. *
  135809. * @example
  135810. * Ext.create('Ext.panel.Panel', {
  135811. * width: 200,
  135812. * height: 200,
  135813. * renderTo: document.body,
  135814. * title: 'A Panel',
  135815. * tools: [{
  135816. * type: 'help',
  135817. * handler: function(){
  135818. * // show help here
  135819. * }
  135820. * }, {
  135821. * itemId: 'refresh',
  135822. * type: 'refresh',
  135823. * hidden: true,
  135824. * handler: function(){
  135825. * // do refresh
  135826. * }
  135827. * }, {
  135828. * type: 'search',
  135829. * handler: function(event, target, owner, tool){
  135830. * // do search
  135831. * owner.child('#refresh').show();
  135832. * }
  135833. * }]
  135834. * });
  135835. */
  135836. Ext.define('Ext.panel.Tool', {
  135837. extend: 'Ext.Component',
  135838. requires: ['Ext.tip.QuickTipManager'],
  135839. alias: 'widget.tool',
  135840. baseCls: Ext.baseCSSPrefix + 'tool',
  135841. disabledCls: Ext.baseCSSPrefix + 'tool-disabled',
  135842. /**
  135843. * @cfg
  135844. * @private
  135845. */
  135846. toolPressedCls: Ext.baseCSSPrefix + 'tool-pressed',
  135847. /**
  135848. * @cfg
  135849. * @private
  135850. */
  135851. toolOverCls: Ext.baseCSSPrefix + 'tool-over',
  135852. ariaRole: 'button',
  135853. childEls: [
  135854. 'toolEl'
  135855. ],
  135856. renderTpl: [
  135857. '<img id="{id}-toolEl" src="{blank}" class="{baseCls}-{type}" role="presentation"/>'
  135858. ],
  135859. /**
  135860. * @cfg {Function} handler
  135861. * A function to execute when the tool is clicked. Arguments passed are:
  135862. *
  135863. * - **event** : Ext.EventObject - The click event.
  135864. * - **toolEl** : Ext.Element - The tool Element.
  135865. * - **owner** : Ext.panel.Header - The host panel header.
  135866. * - **tool** : Ext.panel.Tool - The tool object
  135867. */
  135868. /**
  135869. * @cfg {Object} scope
  135870. * The scope to execute the {@link #handler} function. Defaults to the tool.
  135871. */
  135872. /**
  135873. * @cfg {String} type
  135874. * The type of tool to render. The following types are available:
  135875. *
  135876. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-close"></span> close
  135877. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minimize"></span> minimize
  135878. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-maximize"></span> maximize
  135879. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-restore"></span> restore
  135880. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-toggle"></span> toggle
  135881. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-gear"></span> gear
  135882. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-prev"></span> prev
  135883. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-next"></span> next
  135884. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-pin"></span> pin
  135885. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-unpin"></span> unpin
  135886. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-right"></span> right
  135887. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-left"></span> left
  135888. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-down"></span> down
  135889. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-up"></span> up
  135890. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-refresh"></span> refresh
  135891. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-plus"></span> plus
  135892. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minus"></span> minus
  135893. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-search"></span> search
  135894. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-save"></span> save
  135895. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-help"></span> help
  135896. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-print"></span> print
  135897. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-expand"></span> expand
  135898. * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-collapse"></span> collapse
  135899. */
  135900. /**
  135901. * @cfg {String/Object} tooltip
  135902. * The tooltip for the tool - can be a string to be used as innerHTML (html tags are accepted) or QuickTips config
  135903. * object
  135904. */
  135905. /**
  135906. * @cfg {String} tooltipType
  135907. * The type of tooltip to use. Either 'qtip' (default) for QuickTips or 'title' for title attribute.
  135908. */
  135909. tooltipType: 'qtip',
  135910. /**
  135911. * @cfg {Boolean} stopEvent
  135912. * Specify as false to allow click event to propagate.
  135913. */
  135914. stopEvent: true,
  135915. height: 15,
  135916. width: 15,
  135917. _toolTypes: {
  135918. close:1,
  135919. collapse:1,
  135920. down:1,
  135921. expand:1,
  135922. gear:1,
  135923. help:1,
  135924. left:1,
  135925. maximize:1,
  135926. minimize:1,
  135927. minus:1,
  135928. //move:1,
  135929. next:1,
  135930. pin:1,
  135931. plus:1,
  135932. prev:1,
  135933. print:1,
  135934. refresh:1,
  135935. //resize:1,
  135936. restore:1,
  135937. right:1,
  135938. save:1,
  135939. search:1,
  135940. toggle:1,
  135941. unpin:1,
  135942. up:1
  135943. },
  135944. initComponent: function() {
  135945. var me = this;
  135946. me.addEvents(
  135947. /**
  135948. * @event click
  135949. * Fires when the tool is clicked
  135950. * @param {Ext.panel.Tool} this
  135951. * @param {Ext.EventObject} e The event object
  135952. */
  135953. 'click'
  135954. );
  135955. if (me.id && me._toolTypes[me.id] && Ext.global.console) {
  135956. Ext.global.console.warn('When specifying a tool you should use the type option, the id can conflict now that tool is a Component');
  135957. }
  135958. me.type = me.type || me.id;
  135959. Ext.applyIf(me.renderData, {
  135960. baseCls: me.baseCls,
  135961. blank: Ext.BLANK_IMAGE_URL,
  135962. type: me.type
  135963. });
  135964. // alias qtip, should use tooltip since it's what we have in the docs
  135965. me.tooltip = me.tooltip || me.qtip;
  135966. me.callParent();
  135967. me.on({
  135968. element: 'toolEl',
  135969. click: me.onClick,
  135970. mousedown: me.onMouseDown,
  135971. mouseover: me.onMouseOver,
  135972. mouseout: me.onMouseOut,
  135973. scope: me
  135974. });
  135975. },
  135976. // inherit docs
  135977. afterRender: function() {
  135978. var me = this,
  135979. attr;
  135980. me.callParent(arguments);
  135981. if (me.tooltip) {
  135982. if (Ext.isObject(me.tooltip)) {
  135983. Ext.tip.QuickTipManager.register(Ext.apply({
  135984. target: me.id
  135985. }, me.tooltip));
  135986. }
  135987. else {
  135988. attr = me.tooltipType == 'qtip' ? 'data-qtip' : 'title';
  135989. me.toolEl.dom.setAttribute(attr, me.tooltip);
  135990. }
  135991. }
  135992. },
  135993. getFocusEl: function() {
  135994. return this.el;
  135995. },
  135996. /**
  135997. * Sets the type of the tool. Allows the icon to be changed.
  135998. * @param {String} type The new type. See the {@link #type} config.
  135999. * @return {Ext.panel.Tool} this
  136000. */
  136001. setType: function(type) {
  136002. var me = this;
  136003. me.type = type;
  136004. if (me.rendered) {
  136005. me.toolEl.dom.className = me.baseCls + '-' + type;
  136006. }
  136007. return me;
  136008. },
  136009. /**
  136010. * Binds this tool to a component.
  136011. * @private
  136012. * @param {Ext.Component} component The component
  136013. */
  136014. bindTo: function(component) {
  136015. this.owner = component;
  136016. },
  136017. /**
  136018. * Called when the tool element is clicked
  136019. * @private
  136020. * @param {Ext.EventObject} e
  136021. * @param {HTMLElement} target The target element
  136022. */
  136023. onClick: function(e, target) {
  136024. var me = this,
  136025. owner;
  136026. if (me.disabled) {
  136027. return false;
  136028. }
  136029. owner = me.owner || me.ownerCt;
  136030. //remove the pressed + over class
  136031. me.el.removeCls(me.toolPressedCls);
  136032. me.el.removeCls(me.toolOverCls);
  136033. if (me.stopEvent !== false) {
  136034. e.stopEvent();
  136035. }
  136036. Ext.callback(me.handler, me.scope || me, [e, target, owner, me]);
  136037. me.fireEvent('click', me, e);
  136038. return true;
  136039. },
  136040. // inherit docs
  136041. onDestroy: function(){
  136042. if (Ext.isObject(this.tooltip)) {
  136043. Ext.tip.QuickTipManager.unregister(this.id);
  136044. }
  136045. this.callParent();
  136046. },
  136047. /**
  136048. * Called when the user presses their mouse button down on a tool
  136049. * Adds the press class ({@link #toolPressedCls})
  136050. * @private
  136051. */
  136052. onMouseDown: function() {
  136053. if (this.disabled) {
  136054. return false;
  136055. }
  136056. this.el.addCls(this.toolPressedCls);
  136057. },
  136058. /**
  136059. * Called when the user rolls over a tool
  136060. * Adds the over class ({@link #toolOverCls})
  136061. * @private
  136062. */
  136063. onMouseOver: function() {
  136064. if (this.disabled) {
  136065. return false;
  136066. }
  136067. this.el.addCls(this.toolOverCls);
  136068. },
  136069. /**
  136070. * Called when the user rolls out from a tool.
  136071. * Removes the over class ({@link #toolOverCls})
  136072. * @private
  136073. */
  136074. onMouseOut: function() {
  136075. this.el.removeCls(this.toolOverCls);
  136076. }
  136077. });
  136078. /**
  136079. * Private utility class for Ext.Splitter.
  136080. * @private
  136081. */
  136082. Ext.define('Ext.resizer.SplitterTracker', {
  136083. extend: 'Ext.dd.DragTracker',
  136084. requires: ['Ext.util.Region'],
  136085. enabled: true,
  136086. overlayCls: Ext.baseCSSPrefix + 'resizable-overlay',
  136087. createDragOverlay: function () {
  136088. var overlay;
  136089. overlay = this.overlay = Ext.getBody().createChild({
  136090. cls: this.overlayCls,
  136091. html: '&#160;'
  136092. });
  136093. overlay.unselectable();
  136094. overlay.setSize(Ext.Element.getViewWidth(true), Ext.Element.getViewHeight(true));
  136095. overlay.show();
  136096. },
  136097. getPrevCmp: function() {
  136098. var splitter = this.getSplitter();
  136099. return splitter.previousSibling();
  136100. },
  136101. getNextCmp: function() {
  136102. var splitter = this.getSplitter();
  136103. return splitter.nextSibling();
  136104. },
  136105. // ensure the tracker is enabled, store boxes of previous and next
  136106. // components and calculate the constrain region
  136107. onBeforeStart: function(e) {
  136108. var me = this,
  136109. prevCmp = me.getPrevCmp(),
  136110. nextCmp = me.getNextCmp(),
  136111. collapseEl = me.getSplitter().collapseEl,
  136112. target = e.getTarget(),
  136113. box;
  136114. if (collapseEl && target === me.getSplitter().collapseEl.dom) {
  136115. return false;
  136116. }
  136117. // SplitterTracker is disabled if any of its adjacents are collapsed.
  136118. if (nextCmp.collapsed || prevCmp.collapsed) {
  136119. return false;
  136120. }
  136121. // store boxes of previous and next
  136122. me.prevBox = prevCmp.getEl().getBox();
  136123. me.nextBox = nextCmp.getEl().getBox();
  136124. me.constrainTo = box = me.calculateConstrainRegion();
  136125. if (!box) {
  136126. return false;
  136127. }
  136128. me.createDragOverlay();
  136129. return box;
  136130. },
  136131. // We move the splitter el. Add the proxy class.
  136132. onStart: function(e) {
  136133. var splitter = this.getSplitter();
  136134. splitter.addCls(splitter.baseCls + '-active');
  136135. },
  136136. // calculate the constrain Region in which the splitter el may be moved.
  136137. calculateConstrainRegion: function() {
  136138. var me = this,
  136139. splitter = me.getSplitter(),
  136140. splitWidth = splitter.getWidth(),
  136141. defaultMin = splitter.defaultSplitMin,
  136142. orient = splitter.orientation,
  136143. prevBox = me.prevBox,
  136144. prevCmp = me.getPrevCmp(),
  136145. nextBox = me.nextBox,
  136146. nextCmp = me.getNextCmp(),
  136147. // prev and nextConstrainRegions are the maximumBoxes minus the
  136148. // minimumBoxes. The result is always the intersection
  136149. // of these two boxes.
  136150. prevConstrainRegion, nextConstrainRegion;
  136151. // vertical splitters, so resizing left to right
  136152. if (orient === 'vertical') {
  136153. // Region constructor accepts (top, right, bottom, left)
  136154. // anchored/calculated from the left
  136155. prevConstrainRegion = new Ext.util.Region(
  136156. prevBox.y,
  136157. // Right boundary is x + maxWidth if there IS a maxWidth.
  136158. // Otherwise it is calculated based upon the minWidth of the next Component
  136159. (prevCmp.maxWidth ? prevBox.x + prevCmp.maxWidth : nextBox.right - (nextCmp.minWidth || defaultMin)) + splitWidth,
  136160. prevBox.bottom,
  136161. prevBox.x + (prevCmp.minWidth || defaultMin)
  136162. );
  136163. // anchored/calculated from the right
  136164. nextConstrainRegion = new Ext.util.Region(
  136165. nextBox.y,
  136166. nextBox.right - (nextCmp.minWidth || defaultMin),
  136167. nextBox.bottom,
  136168. // Left boundary is right - maxWidth if there IS a maxWidth.
  136169. // Otherwise it is calculated based upon the minWidth of the previous Component
  136170. (nextCmp.maxWidth ? nextBox.right - nextCmp.maxWidth : prevBox.x + (prevBox.minWidth || defaultMin)) - splitWidth
  136171. );
  136172. } else {
  136173. // anchored/calculated from the top
  136174. prevConstrainRegion = new Ext.util.Region(
  136175. prevBox.y + (prevCmp.minHeight || defaultMin),
  136176. prevBox.right,
  136177. // Bottom boundary is y + maxHeight if there IS a maxHeight.
  136178. // Otherwise it is calculated based upon the minWidth of the next Component
  136179. (prevCmp.maxHeight ? prevBox.y + prevCmp.maxHeight : nextBox.bottom - (nextCmp.minHeight || defaultMin)) + splitWidth,
  136180. prevBox.x
  136181. );
  136182. // anchored/calculated from the bottom
  136183. nextConstrainRegion = new Ext.util.Region(
  136184. // Top boundary is bottom - maxHeight if there IS a maxHeight.
  136185. // Otherwise it is calculated based upon the minHeight of the previous Component
  136186. (nextCmp.maxHeight ? nextBox.bottom - nextCmp.maxHeight : prevBox.y + (prevCmp.minHeight || defaultMin)) - splitWidth,
  136187. nextBox.right,
  136188. nextBox.bottom - (nextCmp.minHeight || defaultMin),
  136189. nextBox.x
  136190. );
  136191. }
  136192. // intersection of the two regions to provide region draggable
  136193. return prevConstrainRegion.intersect(nextConstrainRegion);
  136194. },
  136195. // Performs the actual resizing of the previous and next components
  136196. performResize: function(e, offset) {
  136197. var me = this,
  136198. splitter = me.getSplitter(),
  136199. orient = splitter.orientation,
  136200. prevCmp = me.getPrevCmp(),
  136201. nextCmp = me.getNextCmp(),
  136202. owner = splitter.ownerCt,
  136203. flexedSiblings = owner.query('>[flex]'),
  136204. len = flexedSiblings.length,
  136205. i = 0,
  136206. dimension,
  136207. size,
  136208. totalFlex = 0;
  136209. // Convert flexes to pixel values proportional to the total pixel width of all flexes.
  136210. for (; i < len; i++) {
  136211. size = flexedSiblings[i].getWidth();
  136212. totalFlex += size;
  136213. flexedSiblings[i].flex = size;
  136214. }
  136215. offset = offset || me.getOffset('dragTarget');
  136216. if (orient === 'vertical') {
  136217. offset = offset[0];
  136218. dimension = 'width';
  136219. } else {
  136220. dimension = 'height';
  136221. offset = offset[1];
  136222. }
  136223. if (prevCmp) {
  136224. size = me.prevBox[dimension] + offset;
  136225. if (prevCmp.flex) {
  136226. prevCmp.flex = size;
  136227. } else {
  136228. prevCmp[dimension] = size;
  136229. }
  136230. }
  136231. if (nextCmp) {
  136232. size = me.nextBox[dimension] - offset;
  136233. if (nextCmp.flex) {
  136234. nextCmp.flex = size;
  136235. } else {
  136236. nextCmp[dimension] = size;
  136237. }
  136238. }
  136239. owner.updateLayout();
  136240. },
  136241. // Cleans up the overlay (if we have one) and calls the base. This cannot be done in
  136242. // onEnd, because onEnd is only called if a drag is detected but the overlay is created
  136243. // regardless (by onBeforeStart).
  136244. endDrag: function () {
  136245. var me = this;
  136246. if (me.overlay) {
  136247. me.overlay.remove();
  136248. delete me.overlay;
  136249. }
  136250. me.callParent(arguments); // this calls onEnd
  136251. },
  136252. // perform the resize and remove the proxy class from the splitter el
  136253. onEnd: function(e) {
  136254. var me = this,
  136255. splitter = me.getSplitter();
  136256. splitter.removeCls(splitter.baseCls + '-active');
  136257. me.performResize(e, me.getOffset('dragTarget'));
  136258. },
  136259. // Track the proxy and set the proper XY coordinates
  136260. // while constraining the drag
  136261. onDrag: function(e) {
  136262. var me = this,
  136263. offset = me.getOffset('dragTarget'),
  136264. splitter = me.getSplitter(),
  136265. splitEl = splitter.getEl(),
  136266. orient = splitter.orientation;
  136267. if (orient === "vertical") {
  136268. splitEl.setX(me.startRegion.left + offset[0]);
  136269. } else {
  136270. splitEl.setY(me.startRegion.top + offset[1]);
  136271. }
  136272. },
  136273. getSplitter: function() {
  136274. return this.splitter;
  136275. }
  136276. });
  136277. /**
  136278. * Private utility class for Ext.BorderSplitter.
  136279. * @private
  136280. */
  136281. Ext.define('Ext.resizer.BorderSplitterTracker', {
  136282. extend: 'Ext.resizer.SplitterTracker',
  136283. requires: ['Ext.util.Region'],
  136284. getPrevCmp: null,
  136285. getNextCmp: null,
  136286. // calculate the constrain Region in which the splitter el may be moved.
  136287. calculateConstrainRegion: function() {
  136288. var me = this,
  136289. splitter = me.splitter,
  136290. collapseTarget = splitter.collapseTarget,
  136291. defaultSplitMin = splitter.defaultSplitMin,
  136292. sizePropCap = splitter.vertical ? 'Width' : 'Height',
  136293. minSizeProp = 'min' + sizePropCap,
  136294. maxSizeProp = 'max' + sizePropCap,
  136295. getSizeMethod = 'get' + sizePropCap,
  136296. neighbors = splitter.neighbors,
  136297. length = neighbors.length,
  136298. box = collapseTarget.el.getBox(),
  136299. left = box.x,
  136300. top = box.y,
  136301. right = box.right,
  136302. bottom = box.bottom,
  136303. size = splitter.vertical ? (right - left) : (bottom - top),
  136304. //neighborSizes = [],
  136305. i, neighbor, minRange, maxRange, maxGrowth, maxShrink, targetSize;
  136306. // if size=100 and minSize=80, we can reduce by 20 so minRange = minSize-size = -20
  136307. minRange = (collapseTarget[minSizeProp] || Math.min(size,defaultSplitMin)) - size;
  136308. // if maxSize=150, maxRange = maxSize - size = 50
  136309. maxRange = collapseTarget[maxSizeProp];
  136310. if (!maxRange) {
  136311. maxRange = 1e9;
  136312. } else {
  136313. maxRange -= size;
  136314. }
  136315. targetSize = size;
  136316. for (i = 0; i < length; ++i) {
  136317. neighbor = neighbors[i];
  136318. size = neighbor[getSizeMethod]();
  136319. //neighborSizes.push(size);
  136320. maxGrowth = size - neighbor[maxSizeProp]; // NaN if no maxSize or negative
  136321. maxShrink = size - (neighbor[minSizeProp] || Math.min(size,defaultSplitMin));
  136322. if (!isNaN(maxGrowth)) {
  136323. // if neighbor can only grow by 10 (maxGrowth = -10), minRange cannot be
  136324. // -20 anymore, but now only -10:
  136325. if (minRange < maxGrowth) {
  136326. minRange = maxGrowth;
  136327. }
  136328. }
  136329. // if neighbor can shrink by 20 (maxShrink=20), maxRange cannot be 50 anymore,
  136330. // but now only 20:
  136331. if (maxRange > maxShrink) {
  136332. maxRange = maxShrink;
  136333. }
  136334. }
  136335. if (maxRange - minRange < 2) {
  136336. return null;
  136337. }
  136338. box = new Ext.util.Region(top, right, bottom, left);
  136339. me.constraintAdjusters[splitter.collapseDirection](box, minRange, maxRange, splitter);
  136340. me.dragInfo = {
  136341. minRange: minRange,
  136342. maxRange: maxRange,
  136343. //neighborSizes: neighborSizes,
  136344. targetSize: targetSize
  136345. };
  136346. return box;
  136347. },
  136348. constraintAdjusters: {
  136349. // splitter is to the right of the box
  136350. left: function (box, minRange, maxRange, splitter) {
  136351. box[0] = box.x = box.left = box.right + minRange;
  136352. box.right += maxRange + splitter.getWidth();
  136353. },
  136354. // splitter is below the box
  136355. top: function (box, minRange, maxRange, splitter) {
  136356. box[1] = box.y = box.top = box.bottom + minRange;
  136357. box.bottom += maxRange + splitter.getHeight();
  136358. },
  136359. // splitter is above the box
  136360. bottom: function (box, minRange, maxRange, splitter) {
  136361. box.bottom = box.top - minRange;
  136362. box.top -= maxRange + splitter.getHeight();
  136363. },
  136364. // splitter is to the left of the box
  136365. right: function (box, minRange, maxRange, splitter) {
  136366. box.right = box.left - minRange;
  136367. box.left -= maxRange + splitter.getWidth();
  136368. }
  136369. },
  136370. onBeforeStart: function(e) {
  136371. var me = this,
  136372. splitter = me.splitter,
  136373. collapseTarget = splitter.collapseTarget,
  136374. neighbors = splitter.neighbors,
  136375. collapseEl = me.getSplitter().collapseEl,
  136376. target = e.getTarget(),
  136377. length = neighbors.length,
  136378. i, neighbor;
  136379. if (collapseEl && target === splitter.collapseEl.dom) {
  136380. return false;
  136381. }
  136382. if (collapseTarget.collapsed) {
  136383. return false;
  136384. }
  136385. // disabled if any neighbors are collapsed in parallel direction.
  136386. for (i = 0; i < length; ++i) {
  136387. neighbor = neighbors[i];
  136388. if (neighbor.collapsed && neighbor.isHorz === collapseTarget.isHorz) {
  136389. return false;
  136390. }
  136391. }
  136392. if (!(me.constrainTo = me.calculateConstrainRegion())) {
  136393. return false;
  136394. }
  136395. me.createDragOverlay();
  136396. return true;
  136397. },
  136398. performResize: function(e, offset) {
  136399. var me = this,
  136400. splitter = me.splitter,
  136401. collapseDirection = splitter.collapseDirection,
  136402. collapseTarget = splitter.collapseTarget,
  136403. // a vertical splitter adjusts horizontal dimensions
  136404. adjusters = me.splitAdjusters[splitter.vertical ? 'horz' : 'vert'],
  136405. delta = offset[adjusters.index],
  136406. dragInfo = me.dragInfo,
  136407. //neighbors = splitter.neighbors,
  136408. //length = neighbors.length,
  136409. //neighborSizes = dragInfo.neighborSizes,
  136410. //isVert = collapseTarget.isVert,
  136411. //i, neighbor,
  136412. owner;
  136413. if (collapseDirection == 'right' || collapseDirection == 'bottom') {
  136414. // these splitters grow by moving left/up, so flip the sign of delta...
  136415. delta = -delta;
  136416. }
  136417. // now constrain delta to our computed range:
  136418. delta = Math.min(Math.max(dragInfo.minRange, delta), dragInfo.maxRange);
  136419. if (delta) {
  136420. (owner = splitter.ownerCt).suspendLayouts();
  136421. adjusters.adjustTarget(collapseTarget, dragInfo.targetSize, delta);
  136422. //for (i = 0; i < length; ++i) {
  136423. // neighbor = neighbors[i];
  136424. // if (!neighbor.isCenter && !neighbor.maintainFlex && neighbor.isVert == isVert) {
  136425. // delete neighbor.flex;
  136426. // adjusters.adjustNeighbor(neighbor, neighborSizes[i], delta);
  136427. // }
  136428. //}
  136429. owner.resumeLayouts(true);
  136430. }
  136431. },
  136432. splitAdjusters: {
  136433. horz: {
  136434. index: 0,
  136435. //adjustNeighbor: function (neighbor, size, delta) {
  136436. // neighbor.setSize(size - delta);
  136437. //},
  136438. adjustTarget: function (target, size, delta) {
  136439. target.flex = null;
  136440. target.setSize(size + delta);
  136441. }
  136442. },
  136443. vert: {
  136444. index: 1,
  136445. //adjustNeighbor: function (neighbor, size, delta) {
  136446. // neighbor.setSize(undefined, size - delta);
  136447. //},
  136448. adjustTarget: function (target, targetSize, delta) {
  136449. target.flex = null;
  136450. target.setSize(undefined, targetSize + delta);
  136451. }
  136452. }
  136453. }
  136454. });
  136455. /**
  136456. * Provides a handle for 9-point resizing of Elements or Components.
  136457. */
  136458. Ext.define('Ext.resizer.Handle', {
  136459. extend: 'Ext.Component',
  136460. handleCls: '',
  136461. baseHandleCls: Ext.baseCSSPrefix + 'resizable-handle',
  136462. // Ext.resizer.Resizer.prototype.possiblePositions define the regions
  136463. // which will be passed in as a region configuration.
  136464. region: '',
  136465. beforeRender: function() {
  136466. var me = this;
  136467. me.callParent();
  136468. me.addCls(
  136469. me.baseHandleCls,
  136470. me.baseHandleCls + '-' + me.region,
  136471. me.handleCls
  136472. );
  136473. },
  136474. onRender: function() {
  136475. this.callParent(arguments);
  136476. this.el.unselectable();
  136477. }
  136478. });
  136479. /**
  136480. * Private utility class for Ext.resizer.Resizer.
  136481. * @private
  136482. */
  136483. Ext.define('Ext.resizer.ResizeTracker', {
  136484. extend: 'Ext.dd.DragTracker',
  136485. dynamic: true,
  136486. preserveRatio: false,
  136487. // Default to no constraint
  136488. constrainTo: null,
  136489. proxyCls: Ext.baseCSSPrefix + 'resizable-proxy',
  136490. constructor: function(config) {
  136491. var me = this,
  136492. widthRatio, heightRatio,
  136493. throttledResizeFn;
  136494. if (!config.el) {
  136495. if (config.target.isComponent) {
  136496. me.el = config.target.getEl();
  136497. } else {
  136498. me.el = config.target;
  136499. }
  136500. }
  136501. this.callParent(arguments);
  136502. // Ensure that if we are preserving aspect ratio, the largest minimum is honoured
  136503. if (me.preserveRatio && me.minWidth && me.minHeight) {
  136504. widthRatio = me.minWidth / me.el.getWidth();
  136505. heightRatio = me.minHeight / me.el.getHeight();
  136506. // largest ratio of minimum:size must be preserved.
  136507. // So if a 400x200 pixel image has
  136508. // minWidth: 50, maxWidth: 50, the maxWidth will be 400 * (50/200)... that is 100
  136509. if (heightRatio > widthRatio) {
  136510. me.minWidth = me.el.getWidth() * heightRatio;
  136511. } else {
  136512. me.minHeight = me.el.getHeight() * widthRatio;
  136513. }
  136514. }
  136515. // If configured as throttled, create an instance version of resize which calls
  136516. // a throttled function to perform the resize operation.
  136517. if (me.throttle) {
  136518. throttledResizeFn = Ext.Function.createThrottled(function() {
  136519. Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
  136520. }, me.throttle);
  136521. me.resize = function(box, direction, atEnd) {
  136522. if (atEnd) {
  136523. Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
  136524. } else {
  136525. throttledResizeFn.apply(null, arguments);
  136526. }
  136527. };
  136528. }
  136529. },
  136530. onBeforeStart: function(e) {
  136531. // record the startBox
  136532. this.startBox = this.el.getBox();
  136533. },
  136534. /**
  136535. * @private
  136536. * Returns the object that will be resized on every mousemove event.
  136537. * If dynamic is false, this will be a proxy, otherwise it will be our actual target.
  136538. */
  136539. getDynamicTarget: function() {
  136540. var me = this,
  136541. target = me.target;
  136542. if (me.dynamic) {
  136543. return target;
  136544. } else if (!me.proxy) {
  136545. me.proxy = me.createProxy(target);
  136546. }
  136547. me.proxy.show();
  136548. return me.proxy;
  136549. },
  136550. /**
  136551. * Create a proxy for this resizer
  136552. * @param {Ext.Component/Ext.Element} target The target
  136553. * @return {Ext.Element} A proxy element
  136554. */
  136555. createProxy: function(target){
  136556. var proxy,
  136557. cls = this.proxyCls,
  136558. renderTo;
  136559. if (target.isComponent) {
  136560. proxy = target.getProxy().addCls(cls);
  136561. } else {
  136562. renderTo = Ext.getBody();
  136563. if (Ext.scopeResetCSS) {
  136564. renderTo = Ext.getBody().createChild({
  136565. cls: Ext.resetCls
  136566. });
  136567. }
  136568. proxy = target.createProxy({
  136569. tag: 'div',
  136570. cls: cls,
  136571. id: target.id + '-rzproxy'
  136572. }, renderTo);
  136573. }
  136574. proxy.removeCls(Ext.baseCSSPrefix + 'proxy-el');
  136575. return proxy;
  136576. },
  136577. onStart: function(e) {
  136578. // returns the Ext.ResizeHandle that the user started dragging
  136579. this.activeResizeHandle = Ext.get(this.getDragTarget().id);
  136580. // If we are using a proxy, ensure it is sized.
  136581. if (!this.dynamic) {
  136582. this.resize(this.startBox, {
  136583. horizontal: 'none',
  136584. vertical: 'none'
  136585. });
  136586. }
  136587. },
  136588. onDrag: function(e) {
  136589. // dynamic resizing, update dimensions during resize
  136590. if (this.dynamic || this.proxy) {
  136591. this.updateDimensions(e);
  136592. }
  136593. },
  136594. updateDimensions: function(e, atEnd) {
  136595. var me = this,
  136596. region = me.activeResizeHandle.region,
  136597. offset = me.getOffset(me.constrainTo ? 'dragTarget' : null),
  136598. box = me.startBox,
  136599. ratio,
  136600. widthAdjust = 0,
  136601. heightAdjust = 0,
  136602. snappedWidth,
  136603. snappedHeight,
  136604. adjustX = 0,
  136605. adjustY = 0,
  136606. dragRatio,
  136607. horizDir = offset[0] < 0 ? 'right' : 'left',
  136608. vertDir = offset[1] < 0 ? 'down' : 'up',
  136609. oppositeCorner,
  136610. axis, // 1 = x, 2 = y, 3 = x and y.
  136611. newBox,
  136612. newHeight, newWidth;
  136613. switch (region) {
  136614. case 'south':
  136615. heightAdjust = offset[1];
  136616. axis = 2;
  136617. break;
  136618. case 'north':
  136619. heightAdjust = -offset[1];
  136620. adjustY = -heightAdjust;
  136621. axis = 2;
  136622. break;
  136623. case 'east':
  136624. widthAdjust = offset[0];
  136625. axis = 1;
  136626. break;
  136627. case 'west':
  136628. widthAdjust = -offset[0];
  136629. adjustX = -widthAdjust;
  136630. axis = 1;
  136631. break;
  136632. case 'northeast':
  136633. heightAdjust = -offset[1];
  136634. adjustY = -heightAdjust;
  136635. widthAdjust = offset[0];
  136636. oppositeCorner = [box.x, box.y + box.height];
  136637. axis = 3;
  136638. break;
  136639. case 'southeast':
  136640. heightAdjust = offset[1];
  136641. widthAdjust = offset[0];
  136642. oppositeCorner = [box.x, box.y];
  136643. axis = 3;
  136644. break;
  136645. case 'southwest':
  136646. widthAdjust = -offset[0];
  136647. adjustX = -widthAdjust;
  136648. heightAdjust = offset[1];
  136649. oppositeCorner = [box.x + box.width, box.y];
  136650. axis = 3;
  136651. break;
  136652. case 'northwest':
  136653. heightAdjust = -offset[1];
  136654. adjustY = -heightAdjust;
  136655. widthAdjust = -offset[0];
  136656. adjustX = -widthAdjust;
  136657. oppositeCorner = [box.x + box.width, box.y + box.height];
  136658. axis = 3;
  136659. break;
  136660. }
  136661. newBox = {
  136662. width: box.width + widthAdjust,
  136663. height: box.height + heightAdjust,
  136664. x: box.x + adjustX,
  136665. y: box.y + adjustY
  136666. };
  136667. // Snap value between stops according to configured increments
  136668. snappedWidth = Ext.Number.snap(newBox.width, me.widthIncrement);
  136669. snappedHeight = Ext.Number.snap(newBox.height, me.heightIncrement);
  136670. if (snappedWidth != newBox.width || snappedHeight != newBox.height){
  136671. switch (region) {
  136672. case 'northeast':
  136673. newBox.y -= snappedHeight - newBox.height;
  136674. break;
  136675. case 'north':
  136676. newBox.y -= snappedHeight - newBox.height;
  136677. break;
  136678. case 'southwest':
  136679. newBox.x -= snappedWidth - newBox.width;
  136680. break;
  136681. case 'west':
  136682. newBox.x -= snappedWidth - newBox.width;
  136683. break;
  136684. case 'northwest':
  136685. newBox.x -= snappedWidth - newBox.width;
  136686. newBox.y -= snappedHeight - newBox.height;
  136687. }
  136688. newBox.width = snappedWidth;
  136689. newBox.height = snappedHeight;
  136690. }
  136691. // out of bounds
  136692. if (newBox.width < me.minWidth || newBox.width > me.maxWidth) {
  136693. newBox.width = Ext.Number.constrain(newBox.width, me.minWidth, me.maxWidth);
  136694. // Re-adjust the X position if we were dragging the west side
  136695. if (adjustX) {
  136696. newBox.x = box.x + (box.width - newBox.width);
  136697. }
  136698. } else {
  136699. me.lastX = newBox.x;
  136700. }
  136701. if (newBox.height < me.minHeight || newBox.height > me.maxHeight) {
  136702. newBox.height = Ext.Number.constrain(newBox.height, me.minHeight, me.maxHeight);
  136703. // Re-adjust the Y position if we were dragging the north side
  136704. if (adjustY) {
  136705. newBox.y = box.y + (box.height - newBox.height);
  136706. }
  136707. } else {
  136708. me.lastY = newBox.y;
  136709. }
  136710. // If this is configured to preserve the aspect ratio, or they are dragging using the shift key
  136711. if (me.preserveRatio || e.shiftKey) {
  136712. ratio = me.startBox.width / me.startBox.height;
  136713. // Calculate aspect ratio constrained values.
  136714. newHeight = Math.min(Math.max(me.minHeight, newBox.width / ratio), me.maxHeight);
  136715. newWidth = Math.min(Math.max(me.minWidth, newBox.height * ratio), me.maxWidth);
  136716. // X axis: width-only change, height must obey
  136717. if (axis == 1) {
  136718. newBox.height = newHeight;
  136719. }
  136720. // Y axis: height-only change, width must obey
  136721. else if (axis == 2) {
  136722. newBox.width = newWidth;
  136723. }
  136724. // Corner drag.
  136725. else {
  136726. // Drag ratio is the ratio of the mouse point from the opposite corner.
  136727. // Basically what edge we are dragging, a horizontal edge or a vertical edge.
  136728. dragRatio = Math.abs(oppositeCorner[0] - this.lastXY[0]) / Math.abs(oppositeCorner[1] - this.lastXY[1]);
  136729. // If drag ratio > aspect ratio then width is dominant and height must obey
  136730. if (dragRatio > ratio) {
  136731. newBox.height = newHeight;
  136732. } else {
  136733. newBox.width = newWidth;
  136734. }
  136735. // Handle dragging start coordinates
  136736. if (region == 'northeast') {
  136737. newBox.y = box.y - (newBox.height - box.height);
  136738. } else if (region == 'northwest') {
  136739. newBox.y = box.y - (newBox.height - box.height);
  136740. newBox.x = box.x - (newBox.width - box.width);
  136741. } else if (region == 'southwest') {
  136742. newBox.x = box.x - (newBox.width - box.width);
  136743. }
  136744. }
  136745. }
  136746. if (heightAdjust === 0) {
  136747. vertDir = 'none';
  136748. }
  136749. if (widthAdjust === 0) {
  136750. horizDir = 'none';
  136751. }
  136752. me.resize(newBox, {
  136753. horizontal: horizDir,
  136754. vertical: vertDir
  136755. }, atEnd);
  136756. },
  136757. getResizeTarget: function(atEnd) {
  136758. return atEnd ? this.target : this.getDynamicTarget();
  136759. },
  136760. resize: function(box, direction, atEnd) {
  136761. var target = this.getResizeTarget(atEnd);
  136762. if (target.isComponent) {
  136763. target.setSize(box.width, box.height);
  136764. if (target.floating) {
  136765. target.setPagePosition(box.x, box.y);
  136766. }
  136767. } else {
  136768. target.setBox(box);
  136769. }
  136770. // update the originalTarget if it was wrapped, and the target passed in was the wrap el.
  136771. target = this.originalTarget;
  136772. if (target && (this.dynamic || atEnd)) {
  136773. if (target.isComponent) {
  136774. target.setSize(box.width, box.height);
  136775. if (target.floating) {
  136776. target.setPagePosition(box.x, box.y);
  136777. }
  136778. } else {
  136779. target.setBox(box);
  136780. }
  136781. }
  136782. },
  136783. onEnd: function(e) {
  136784. this.updateDimensions(e, true);
  136785. if (this.proxy) {
  136786. this.proxy.hide();
  136787. }
  136788. }
  136789. });
  136790. /**
  136791. * Applies drag handles to an element or component to make it resizable. The drag handles are inserted into the element
  136792. * (or component's element) and positioned absolute.
  136793. *
  136794. * Textarea and img elements will be wrapped with an additional div because these elements do not support child nodes.
  136795. * The original element can be accessed through the originalTarget property.
  136796. *
  136797. * Here is the list of valid resize handles:
  136798. *
  136799. * Value Description
  136800. * ------ -------------------
  136801. * 'n' north
  136802. * 's' south
  136803. * 'e' east
  136804. * 'w' west
  136805. * 'nw' northwest
  136806. * 'sw' southwest
  136807. * 'se' southeast
  136808. * 'ne' northeast
  136809. * 'all' all
  136810. *
  136811. * {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component}
  136812. *
  136813. * Here's an example showing the creation of a typical Resizer:
  136814. *
  136815. * Ext.create('Ext.resizer.Resizer', {
  136816. * el: 'elToResize',
  136817. * handles: 'all',
  136818. * minWidth: 200,
  136819. * minHeight: 100,
  136820. * maxWidth: 500,
  136821. * maxHeight: 400,
  136822. * pinned: true
  136823. * });
  136824. */
  136825. Ext.define('Ext.resizer.Resizer', {
  136826. mixins: {
  136827. observable: 'Ext.util.Observable'
  136828. },
  136829. uses: ['Ext.resizer.ResizeTracker', 'Ext.Component'],
  136830. alternateClassName: 'Ext.Resizable',
  136831. handleCls: Ext.baseCSSPrefix + 'resizable-handle',
  136832. pinnedCls: Ext.baseCSSPrefix + 'resizable-pinned',
  136833. overCls: Ext.baseCSSPrefix + 'resizable-over',
  136834. wrapCls: Ext.baseCSSPrefix + 'resizable-wrap',
  136835. /**
  136836. * @cfg {Boolean} dynamic
  136837. * Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during
  136838. * dragging. This is `true` by default, but the {@link Ext.Component Component} class passes `false` when it is
  136839. * configured as {@link Ext.Component#resizable}.
  136840. *
  136841. * If specified as `false`, a proxy element is displayed during the resize operation, and the {@link #target} is
  136842. * updated on mouseup.
  136843. */
  136844. dynamic: true,
  136845. /**
  136846. * @cfg {String} handles
  136847. * String consisting of the resize handles to display. Defaults to 's e se' for Elements and fixed position
  136848. * Components. Defaults to 8 point resizing for floating Components (such as Windows). Specify either `'all'` or any
  136849. * of `'n s e w ne nw se sw'`.
  136850. */
  136851. handles: 's e se',
  136852. /**
  136853. * @cfg {Number} height
  136854. * Optional. The height to set target to in pixels
  136855. */
  136856. height : null,
  136857. /**
  136858. * @cfg {Number} width
  136859. * Optional. The width to set the target to in pixels
  136860. */
  136861. width : null,
  136862. /**
  136863. * @cfg {Number} heightIncrement
  136864. * The increment to snap the height resize in pixels.
  136865. */
  136866. heightIncrement : 0,
  136867. /**
  136868. * @cfg {Number} widthIncrement
  136869. * The increment to snap the width resize in pixels.
  136870. */
  136871. widthIncrement : 0,
  136872. /**
  136873. * @cfg {Number} minHeight
  136874. * The minimum height for the element
  136875. */
  136876. minHeight : 20,
  136877. /**
  136878. * @cfg {Number} minWidth
  136879. * The minimum width for the element
  136880. */
  136881. minWidth : 20,
  136882. /**
  136883. * @cfg {Number} maxHeight
  136884. * The maximum height for the element
  136885. */
  136886. maxHeight : 10000,
  136887. /**
  136888. * @cfg {Number} maxWidth
  136889. * The maximum width for the element
  136890. */
  136891. maxWidth : 10000,
  136892. /**
  136893. * @cfg {Boolean} pinned
  136894. * True to ensure that the resize handles are always visible, false indicates resizing by cursor changes only
  136895. */
  136896. pinned: false,
  136897. /**
  136898. * @cfg {Boolean} preserveRatio
  136899. * True to preserve the original ratio between height and width during resize
  136900. */
  136901. preserveRatio: false,
  136902. /**
  136903. * @cfg {Boolean} transparent
  136904. * True for transparent handles. This is only applied at config time.
  136905. */
  136906. transparent: false,
  136907. /**
  136908. * @cfg {Ext.Element/Ext.util.Region} constrainTo
  136909. * An element, or a {@link Ext.util.Region Region} into which the resize operation must be constrained.
  136910. */
  136911. possiblePositions: {
  136912. n: 'north',
  136913. s: 'south',
  136914. e: 'east',
  136915. w: 'west',
  136916. se: 'southeast',
  136917. sw: 'southwest',
  136918. nw: 'northwest',
  136919. ne: 'northeast'
  136920. },
  136921. /**
  136922. * @cfg {Ext.Element/Ext.Component} target
  136923. * The Element or Component to resize.
  136924. */
  136925. /**
  136926. * @property {Ext.Element} el
  136927. * Outer element for resizing behavior.
  136928. */
  136929. constructor: function(config) {
  136930. var me = this,
  136931. target,
  136932. targetEl,
  136933. tag,
  136934. handles = me.handles,
  136935. handleCls,
  136936. possibles,
  136937. len,
  136938. i = 0,
  136939. pos,
  136940. handleEls = [],
  136941. eastWestStyle, style,
  136942. box;
  136943. me.addEvents(
  136944. /**
  136945. * @event beforeresize
  136946. * Fired before resize is allowed. Return false to cancel resize.
  136947. * @param {Ext.resizer.Resizer} this
  136948. * @param {Number} width The start width
  136949. * @param {Number} height The start height
  136950. * @param {Ext.EventObject} e The mousedown event
  136951. */
  136952. 'beforeresize',
  136953. /**
  136954. * @event resizedrag
  136955. * Fires during resizing. Return false to cancel resize.
  136956. * @param {Ext.resizer.Resizer} this
  136957. * @param {Number} width The new width
  136958. * @param {Number} height The new height
  136959. * @param {Ext.EventObject} e The mousedown event
  136960. */
  136961. 'resizedrag',
  136962. /**
  136963. * @event resize
  136964. * Fired after a resize.
  136965. * @param {Ext.resizer.Resizer} this
  136966. * @param {Number} width The new width
  136967. * @param {Number} height The new height
  136968. * @param {Ext.EventObject} e The mouseup event
  136969. */
  136970. 'resize'
  136971. );
  136972. if (Ext.isString(config) || Ext.isElement(config) || config.dom) {
  136973. target = config;
  136974. config = arguments[1] || {};
  136975. config.target = target;
  136976. }
  136977. // will apply config to this
  136978. me.mixins.observable.constructor.call(me, config);
  136979. // If target is a Component, ensure that we pull the element out.
  136980. // Resizer must examine the underlying Element.
  136981. target = me.target;
  136982. if (target) {
  136983. if (target.isComponent) {
  136984. me.el = target.getEl();
  136985. if (target.minWidth) {
  136986. me.minWidth = target.minWidth;
  136987. }
  136988. if (target.minHeight) {
  136989. me.minHeight = target.minHeight;
  136990. }
  136991. if (target.maxWidth) {
  136992. me.maxWidth = target.maxWidth;
  136993. }
  136994. if (target.maxHeight) {
  136995. me.maxHeight = target.maxHeight;
  136996. }
  136997. if (target.floating) {
  136998. if (!me.hasOwnProperty('handles')) {
  136999. me.handles = 'n ne e se s sw w nw';
  137000. }
  137001. }
  137002. } else {
  137003. me.el = me.target = Ext.get(target);
  137004. }
  137005. }
  137006. // Backwards compatibility with Ext3.x's Resizable which used el as a config.
  137007. else {
  137008. me.target = me.el = Ext.get(me.el);
  137009. }
  137010. // Tags like textarea and img cannot
  137011. // have children and therefore must
  137012. // be wrapped
  137013. tag = me.el.dom.tagName.toUpperCase();
  137014. if (tag == 'TEXTAREA' || tag == 'IMG' || tag == 'TABLE') {
  137015. /**
  137016. * @property {Ext.Element/Ext.Component} originalTarget
  137017. * Reference to the original resize target if the element of the original resize target was a
  137018. * {@link Ext.form.field.Field Field}, or an IMG or a TEXTAREA which must be wrapped in a DIV.
  137019. */
  137020. me.originalTarget = me.target;
  137021. targetEl = me.el;
  137022. box = targetEl.getBox();
  137023. me.target = me.el = me.el.wrap({
  137024. cls: me.wrapCls,
  137025. id: me.el.id + '-rzwrap',
  137026. style: targetEl.getStyles('margin-top', 'margin-bottom')
  137027. });
  137028. // Transfer originalTarget's positioning+sizing+margins
  137029. me.el.setPositioning(targetEl.getPositioning());
  137030. targetEl.clearPositioning();
  137031. me.el.setBox(box);
  137032. // Position the wrapped element absolute so that it does not stretch the wrapper
  137033. targetEl.setStyle('position', 'absolute');
  137034. }
  137035. // Position the element, this enables us to absolute position
  137036. // the handles within this.el
  137037. me.el.position();
  137038. if (me.pinned) {
  137039. me.el.addCls(me.pinnedCls);
  137040. }
  137041. /**
  137042. * @property {Ext.resizer.ResizeTracker} resizeTracker
  137043. */
  137044. me.resizeTracker = new Ext.resizer.ResizeTracker({
  137045. disabled: me.disabled,
  137046. target: me.target,
  137047. constrainTo: me.constrainTo,
  137048. overCls: me.overCls,
  137049. throttle: me.throttle,
  137050. originalTarget: me.originalTarget,
  137051. delegate: '.' + me.handleCls,
  137052. dynamic: me.dynamic,
  137053. preserveRatio: me.preserveRatio,
  137054. heightIncrement: me.heightIncrement,
  137055. widthIncrement: me.widthIncrement,
  137056. minHeight: me.minHeight,
  137057. maxHeight: me.maxHeight,
  137058. minWidth: me.minWidth,
  137059. maxWidth: me.maxWidth
  137060. });
  137061. // Relay the ResizeTracker's superclass events as our own resize events
  137062. me.resizeTracker.on({
  137063. mousedown: me.onBeforeResize,
  137064. drag: me.onResize,
  137065. dragend: me.onResizeEnd,
  137066. scope: me
  137067. });
  137068. if (me.handles == 'all') {
  137069. me.handles = 'n s e w ne nw se sw';
  137070. }
  137071. handles = me.handles = me.handles.split(/ |\s*?[,;]\s*?/);
  137072. possibles = me.possiblePositions;
  137073. len = handles.length;
  137074. handleCls = me.handleCls + ' ' + (me.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-';
  137075. // Needs heighting on IE6!
  137076. eastWestStyle = Ext.isIE6 ? ' style="height:' + me.el.getHeight() + 'px"' : '';
  137077. for (; i < len; i++){
  137078. // if specified and possible, create
  137079. if (handles[i] && possibles[handles[i]]) {
  137080. pos = possibles[handles[i]];
  137081. if (pos === 'east' || pos === 'west') {
  137082. style = eastWestStyle;
  137083. } else {
  137084. style = '';
  137085. }
  137086. handleEls.push('<div id="' + me.el.id + '-' + pos + '-handle" class="' + handleCls + pos + ' ' + Ext.baseCSSPrefix + 'unselectable"' + style + '></div>');
  137087. }
  137088. }
  137089. Ext.DomHelper.append(me.el, handleEls.join(''));
  137090. // store a reference to each handle elelemtn in this.east, this.west, etc
  137091. for (i = 0; i < len; i++){
  137092. // if specified and possible, create
  137093. if (handles[i] && possibles[handles[i]]) {
  137094. pos = possibles[handles[i]];
  137095. me[pos] = me.el.getById(me.el.id + '-' + pos + '-handle');
  137096. me[pos].region = pos;
  137097. me[pos].unselectable();
  137098. if (me.transparent) {
  137099. me[pos].setOpacity(0);
  137100. }
  137101. }
  137102. }
  137103. // Constrain within configured maxima
  137104. if (Ext.isNumber(me.width)) {
  137105. me.width = Ext.Number.constrain(me.width, me.minWidth, me.maxWidth);
  137106. }
  137107. if (Ext.isNumber(me.height)) {
  137108. me.height = Ext.Number.constrain(me.height, me.minHeight, me.maxHeight);
  137109. }
  137110. // Size the target (and originalTarget)
  137111. if (me.width !== null || me.height !== null) {
  137112. if (me.originalTarget) {
  137113. me.originalTarget.setWidth(me.width);
  137114. me.originalTarget.setHeight(me.height);
  137115. }
  137116. me.resizeTo(me.width, me.height);
  137117. }
  137118. me.forceHandlesHeight();
  137119. },
  137120. disable: function() {
  137121. this.resizeTracker.disable();
  137122. },
  137123. enable: function() {
  137124. this.resizeTracker.enable();
  137125. },
  137126. /**
  137127. * @private Relay the Tracker's mousedown event as beforeresize
  137128. * @param tracker The Resizer
  137129. * @param e The Event
  137130. */
  137131. onBeforeResize: function(tracker, e) {
  137132. var box = this.el.getBox();
  137133. return this.fireEvent('beforeresize', this, box.width, box.height, e);
  137134. },
  137135. /**
  137136. * @private Relay the Tracker's drag event as resizedrag
  137137. * @param tracker The Resizer
  137138. * @param e The Event
  137139. */
  137140. onResize: function(tracker, e) {
  137141. var me = this,
  137142. box = me.el.getBox();
  137143. me.forceHandlesHeight();
  137144. return me.fireEvent('resizedrag', me, box.width, box.height, e);
  137145. },
  137146. /**
  137147. * @private Relay the Tracker's dragend event as resize
  137148. * @param tracker The Resizer
  137149. * @param e The Event
  137150. */
  137151. onResizeEnd: function(tracker, e) {
  137152. var me = this,
  137153. box = me.el.getBox();
  137154. me.forceHandlesHeight();
  137155. return me.fireEvent('resize', me, box.width, box.height, e);
  137156. },
  137157. /**
  137158. * Perform a manual resize and fires the 'resize' event.
  137159. * @param {Number} width
  137160. * @param {Number} height
  137161. */
  137162. resizeTo : function(width, height) {
  137163. var me = this;
  137164. me.target.setSize(width, height);
  137165. me.fireEvent('resize', me, width, height, null);
  137166. },
  137167. /**
  137168. * Returns the element that was configured with the el or target config property. If a component was configured with
  137169. * the target property then this will return the element of this component.
  137170. *
  137171. * Textarea and img elements will be wrapped with an additional div because these elements do not support child
  137172. * nodes. The original element can be accessed through the originalTarget property.
  137173. * @return {Ext.Element} element
  137174. */
  137175. getEl : function() {
  137176. return this.el;
  137177. },
  137178. /**
  137179. * Returns the element or component that was configured with the target config property.
  137180. *
  137181. * Textarea and img elements will be wrapped with an additional div because these elements do not support child
  137182. * nodes. The original element can be accessed through the originalTarget property.
  137183. * @return {Ext.Element/Ext.Component}
  137184. */
  137185. getTarget: function() {
  137186. return this.target;
  137187. },
  137188. destroy: function() {
  137189. var i = 0,
  137190. handles = this.handles,
  137191. len = handles.length,
  137192. positions = this.possiblePositions;
  137193. for (; i < len; i++) {
  137194. this[positions[handles[i]]].remove();
  137195. }
  137196. },
  137197. /**
  137198. * @private
  137199. * Fix IE6 handle height issue.
  137200. */
  137201. forceHandlesHeight : function() {
  137202. var me = this,
  137203. handle;
  137204. if (Ext.isIE6) {
  137205. handle = me.east;
  137206. if (handle) {
  137207. handle.setHeight(me.el.getHeight());
  137208. }
  137209. handle = me.west;
  137210. if (handle) {
  137211. handle.setHeight(me.el.getHeight());
  137212. }
  137213. me.el.repaint();
  137214. }
  137215. }
  137216. });
  137217. /**
  137218. *
  137219. */
  137220. Ext.define('Ext.selection.CellModel', {
  137221. extend: 'Ext.selection.Model',
  137222. alias: 'selection.cellmodel',
  137223. requires: ['Ext.util.KeyNav'],
  137224. isCellModel: true,
  137225. /**
  137226. * @cfg {Boolean} enableKeyNav
  137227. * Turns on/off keyboard navigation within the grid.
  137228. */
  137229. enableKeyNav: true,
  137230. /**
  137231. * @cfg {Boolean} preventWrap
  137232. * Set this configuration to true to prevent wrapping around of selection as
  137233. * a user navigates to the first or last column.
  137234. */
  137235. preventWrap: false,
  137236. // private property to use when firing a deselect when no old selection exists.
  137237. noSelection: {
  137238. row: -1,
  137239. column: -1
  137240. },
  137241. constructor: function() {
  137242. this.addEvents(
  137243. /**
  137244. * @event deselect
  137245. * Fired after a cell is deselected
  137246. * @param {Ext.selection.CellModel} this
  137247. * @param {Ext.data.Model} record The record of the deselected cell
  137248. * @param {Number} row The row index deselected
  137249. * @param {Number} column The column index deselected
  137250. */
  137251. 'deselect',
  137252. /**
  137253. * @event select
  137254. * Fired after a cell is selected
  137255. * @param {Ext.selection.CellModel} this
  137256. * @param {Ext.data.Model} record The record of the selected cell
  137257. * @param {Number} row The row index selected
  137258. * @param {Number} column The column index selected
  137259. */
  137260. 'select'
  137261. );
  137262. this.callParent(arguments);
  137263. },
  137264. bindComponent: function(view) {
  137265. var me = this,
  137266. grid = view.ownerCt;
  137267. me.primaryView = view;
  137268. me.views = me.views || [];
  137269. me.views.push(view);
  137270. me.bindStore(view.getStore(), true);
  137271. view.on({
  137272. cellmousedown: me.onMouseDown,
  137273. refresh: me.onViewRefresh,
  137274. scope: me
  137275. });
  137276. if (grid.optimizedColumnMove !== false) {
  137277. grid.on('columnmove', me.onColumnMove, me);
  137278. }
  137279. if (me.enableKeyNav) {
  137280. me.initKeyNav(view);
  137281. }
  137282. },
  137283. initKeyNav: function(view) {
  137284. var me = this;
  137285. if (!view.rendered) {
  137286. view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
  137287. return;
  137288. }
  137289. view.el.set({
  137290. tabIndex: -1
  137291. });
  137292. // view.el has tabIndex -1 to allow for
  137293. // keyboard events to be passed to it.
  137294. me.keyNav = new Ext.util.KeyNav({
  137295. target: view.el,
  137296. ignoreInputFields: true,
  137297. up: me.onKeyUp,
  137298. down: me.onKeyDown,
  137299. right: me.onKeyRight,
  137300. left: me.onKeyLeft,
  137301. tab: me.onKeyTab,
  137302. scope: me
  137303. });
  137304. },
  137305. getHeaderCt: function() {
  137306. var selection = this.getCurrentPosition(),
  137307. view = selection ? selection.view : this.primaryView;
  137308. return view.headerCt;
  137309. },
  137310. onKeyUp: function(e, t) {
  137311. this.keyNavigation = true;
  137312. this.move('up', e);
  137313. this.keyNavigation = false;
  137314. },
  137315. onKeyDown: function(e, t) {
  137316. this.keyNavigation = true;
  137317. this.move('down', e);
  137318. this.keyNavigation = false;
  137319. },
  137320. onKeyLeft: function(e, t) {
  137321. this.keyNavigation = true;
  137322. this.move('left', e);
  137323. this.keyNavigation = false;
  137324. },
  137325. onKeyRight: function(e, t) {
  137326. this.keyNavigation = true;
  137327. this.move('right', e);
  137328. this.keyNavigation = false;
  137329. },
  137330. move: function(dir, e) {
  137331. var me = this,
  137332. pos = me.getCurrentPosition(),
  137333. // Calculate the new row and column position
  137334. newPos = pos.view.walkCells(pos, dir, e, me.preventWrap);
  137335. // If walk was successful, select new Position
  137336. if (newPos) {
  137337. newPos.view = pos.view;
  137338. return me.setCurrentPosition(newPos);
  137339. }
  137340. // Enforce code correctness in unbuilt source.
  137341. return null;
  137342. },
  137343. /**
  137344. * Returns the current position in the format {row: row, column: column}
  137345. */
  137346. getCurrentPosition: function() {
  137347. return this.selection;
  137348. },
  137349. /**
  137350. * Sets the current position
  137351. * @param {Object} position The position to set.
  137352. */
  137353. setCurrentPosition: function(pos) {
  137354. var me = this;
  137355. // onSelectChange uses lastSelection and nextSelection
  137356. me.lastSelection = me.selection;
  137357. if (me.selection) {
  137358. me.onCellDeselect(me.selection);
  137359. }
  137360. if (pos) {
  137361. me.nextSelection = new me.Selection(me);
  137362. me.nextSelection.setPosition(pos);
  137363. me.onCellSelect(me.nextSelection);
  137364. // Deselect triggered by new selection will kill the selection property, so restore it here.
  137365. return me.selection = me.nextSelection;
  137366. }
  137367. // Enforce code correctness in unbuilt source.
  137368. return null;
  137369. },
  137370. // Keep selection model in consistent state upon record deletion.
  137371. onStoreRemove: function(store, record, index) {
  137372. var me = this,
  137373. pos = me.getCurrentPosition();
  137374. me.callParent(arguments);
  137375. if (pos) {
  137376. // Deleting the row containing the selection.
  137377. // Attempt to reselect the same cell which has moved up if there is one
  137378. if (pos.row == index) {
  137379. if (index < store.getCount() - 1) {
  137380. pos.setPosition(index, pos.column);
  137381. me.setCurrentPosition(pos);
  137382. } else {
  137383. delete me.selection;
  137384. }
  137385. }
  137386. // Deleting a row before the selection.
  137387. // Move the selection up by one row
  137388. else if (index < pos.row) {
  137389. pos.setPosition(pos.row - 1, pos.column);
  137390. me.setCurrentPosition(pos);
  137391. }
  137392. }
  137393. },
  137394. /**
  137395. * Set the current position based on where the user clicks.
  137396. * @private
  137397. */
  137398. onMouseDown: function(view, cell, cellIndex, record, row, rowIndex, e) {
  137399. this.setCurrentPosition({
  137400. view: view,
  137401. row: rowIndex,
  137402. column: cellIndex
  137403. });
  137404. },
  137405. // notify the view that the cell has been selected to update the ui
  137406. // appropriately and bring the cell into focus
  137407. onCellSelect: function(position, supressEvent) {
  137408. if (position && position.row !== undefined && position.row > -1) {
  137409. this.doSelect(position.view.getStore().getAt(position.row), /*keepExisting*/false, supressEvent);
  137410. }
  137411. },
  137412. // notify view that the cell has been deselected to update the ui
  137413. // appropriately
  137414. onCellDeselect: function(position, supressEvent) {
  137415. if (position && position.row !== undefined) {
  137416. this.doDeselect(position.view.getStore().getAt(position.row), supressEvent);
  137417. }
  137418. },
  137419. onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
  137420. var me = this,
  137421. pos,
  137422. eventName,
  137423. view;
  137424. if (isSelected) {
  137425. pos = me.nextSelection;
  137426. eventName = 'select';
  137427. } else {
  137428. pos = me.lastSelection || me.noSelection;
  137429. eventName = 'deselect';
  137430. }
  137431. // CellModel may be shared between two sides of a Lockable.
  137432. // The position must include a reference to the view in which the selection is current.
  137433. // Ensure we use the view specifiied by the position.
  137434. view = pos.view || me.primaryView;
  137435. if ((suppressEvent || me.fireEvent('before' + eventName, me, record, pos.row, pos.column)) !== false &&
  137436. commitFn() !== false) {
  137437. if (isSelected) {
  137438. view.onCellSelect(pos);
  137439. view.onCellFocus(pos);
  137440. } else {
  137441. view.onCellDeselect(pos);
  137442. delete me.selection;
  137443. }
  137444. if (!suppressEvent) {
  137445. me.fireEvent(eventName, me, record, pos.row, pos.column);
  137446. }
  137447. }
  137448. },
  137449. // Tab key from the View's KeyNav, *not* from an editor.
  137450. onKeyTab: function(e, t) {
  137451. var me = this,
  137452. editingPlugin = me.getCurrentPosition().view.editingPlugin;
  137453. // If we were in editing mode, but just focused on a non-editable cell, behave as if we tabbed off an editable field
  137454. if (editingPlugin && me.wasEditing) {
  137455. me.onEditorTab(editingPlugin, e)
  137456. } else {
  137457. me.move(e.shiftKey ? 'left' : 'right', e);
  137458. }
  137459. },
  137460. onEditorTab: function(editingPlugin, e) {
  137461. var me = this,
  137462. direction = e.shiftKey ? 'left' : 'right',
  137463. position = me.move(direction, e);
  137464. // Navigation had somewhere to go.... not hit the buffers.
  137465. if (position) {
  137466. // If we were able to begin editing clear the wasEditing flag. It gets set during navigation off an active edit.
  137467. if (editingPlugin.startEditByPosition(position)) {
  137468. me.wasEditing = false;
  137469. }
  137470. // If we could not continue editing...
  137471. // Set a flag that we should go back into editing mode upon next onKeyTab call
  137472. else {
  137473. me.wasEditing = true;
  137474. if (!position.columnHeader.dataIndex) {
  137475. me.onEditorTab(editingPlugin, e);
  137476. }
  137477. }
  137478. }
  137479. },
  137480. refresh: function() {
  137481. var pos = this.getCurrentPosition(),
  137482. selRowIdx;
  137483. // Synchronize the current position's row with the row of the last selected record.
  137484. if (pos && (selRowIdx = this.store.indexOf(this.selected.last())) !== -1) {
  137485. pos.row = selRowIdx;
  137486. }
  137487. },
  137488. /**
  137489. * @private
  137490. * When grid uses {@link Ext.panel.Table#optimizedColumnMove optimizedColumnMove} (the default), this is added as a
  137491. * {@link Ext.panel.Table#columnmove columnmove} handler to correctly maintain the
  137492. * selected column using the same column header.
  137493. *
  137494. * If optimizedColumnMove === false, (which some grid Features set) then the view is refreshed,
  137495. * so this is not added as a handler because the selected column.
  137496. */
  137497. onColumnMove: function(headerCt, header, fromIdx, toIdx) {
  137498. var grid = headerCt.up('tablepanel');
  137499. if (grid) {
  137500. this.onViewRefresh(grid.view);
  137501. }
  137502. },
  137503. onViewRefresh: function(view) {
  137504. var me = this,
  137505. pos = me.getCurrentPosition(),
  137506. headerCt = view.headerCt,
  137507. record, columnHeader;
  137508. // Re-establish selection of the same cell coordinate.
  137509. // DO NOT fire events because the selected
  137510. if (pos && pos.view === view) {
  137511. record = pos.record;
  137512. columnHeader = pos.columnHeader;
  137513. // After a refresh, recreate the selection using the same record and grid column as before
  137514. if (!columnHeader.isDescendantOf(headerCt)) {
  137515. // column header is not a child of the header container
  137516. // this happens when the grid is reconfigured with new columns
  137517. // make a best effor to select something by matching on id, then text, then dataIndex
  137518. columnHeader = headerCt.queryById(columnHeader.id) ||
  137519. headerCt.down('[text="' + columnHeader.text + '"]') ||
  137520. headerCt.down('[dataIndex="' + columnHeader.dataIndex + '"]');
  137521. }
  137522. // If we have a columnHeader (either the column header that already exists in
  137523. // the headerCt, or a suitable match that was found after reconfiguration)
  137524. // AND the record still exists in the store (or a record matching the id of
  137525. // the previously selected record) We are ok to go ahead and set the selection
  137526. if (columnHeader && (view.store.indexOfId(record.getId()) !== -1)) {
  137527. me.setCurrentPosition({
  137528. row: record,
  137529. column: columnHeader,
  137530. view: view
  137531. });
  137532. }
  137533. }
  137534. },
  137535. selectByPosition: function(position) {
  137536. this.setCurrentPosition(position);
  137537. }
  137538. }, function() {
  137539. // Encapsulate a single selection position.
  137540. // Maintains { row: n, column: n, record: r, columnHeader: c}
  137541. var Selection = this.prototype.Selection = function(model) {
  137542. this.model = model;
  137543. };
  137544. // Selection row/record & column/columnHeader
  137545. Selection.prototype.setPosition = function(row, col) {
  137546. var me = this,
  137547. view;
  137548. // We were passed {row: 1, column: 2, view: myView}
  137549. if (arguments.length === 1) {
  137550. // SelectionModel is shared between both sides of a locking grid.
  137551. // It can be positioned on either view.
  137552. if (row.view) {
  137553. me.view = view = row.view;
  137554. }
  137555. col = row.column;
  137556. row = row.row;
  137557. }
  137558. // If setting the position without specifying a view, and the position is already without a view
  137559. // use the owning Model's primary view
  137560. if (!view) {
  137561. me.view = view = me.model.primaryView;
  137562. }
  137563. // Row index passed
  137564. if (typeof row === 'number') {
  137565. me.row = row;
  137566. me.record = view.store.getAt(row);
  137567. }
  137568. // row is a Record
  137569. else if (row.isModel) {
  137570. me.record = row;
  137571. me.row = view.indexOf(row);
  137572. }
  137573. // row is a grid row
  137574. else if (row.tagName) {
  137575. me.record = view.getRecord(row);
  137576. me.row = view.indexOf(me.record);
  137577. }
  137578. // column index passed
  137579. if (typeof col === 'number') {
  137580. me.column = col;
  137581. me.columnHeader = view.getHeaderAtIndex(col);
  137582. }
  137583. // col is a column Header
  137584. else {
  137585. me.columnHeader = col;
  137586. me.column = col.getIndex();
  137587. }
  137588. return me;
  137589. }
  137590. });
  137591. /**
  137592. * Implements row based navigation via keyboard.
  137593. *
  137594. * Must synchronize across grid sections.
  137595. */
  137596. Ext.define('Ext.selection.RowModel', {
  137597. extend: 'Ext.selection.Model',
  137598. alias: 'selection.rowmodel',
  137599. requires: ['Ext.util.KeyNav'],
  137600. /**
  137601. * @private
  137602. * Number of pixels to scroll to the left/right when pressing
  137603. * left/right keys.
  137604. */
  137605. deltaScroll: 5,
  137606. /**
  137607. * @cfg {Boolean} enableKeyNav
  137608. *
  137609. * Turns on/off keyboard navigation within the grid.
  137610. */
  137611. enableKeyNav: true,
  137612. /**
  137613. * @cfg {Boolean} [ignoreRightMouseSelection=false]
  137614. * True to ignore selections that are made when using the right mouse button if there are
  137615. * records that are already selected. If no records are selected, selection will continue
  137616. * as normal
  137617. */
  137618. ignoreRightMouseSelection: false,
  137619. constructor: function() {
  137620. this.addEvents(
  137621. /**
  137622. * @event beforedeselect
  137623. * Fired before a record is deselected. If any listener returns false, the
  137624. * deselection is cancelled.
  137625. * @param {Ext.selection.RowModel} this
  137626. * @param {Ext.data.Model} record The deselected record
  137627. * @param {Number} index The row index deselected
  137628. */
  137629. 'beforedeselect',
  137630. /**
  137631. * @event beforeselect
  137632. * Fired before a record is selected. If any listener returns false, the
  137633. * selection is cancelled.
  137634. * @param {Ext.selection.RowModel} this
  137635. * @param {Ext.data.Model} record The selected record
  137636. * @param {Number} index The row index selected
  137637. */
  137638. 'beforeselect',
  137639. /**
  137640. * @event deselect
  137641. * Fired after a record is deselected
  137642. * @param {Ext.selection.RowModel} this
  137643. * @param {Ext.data.Model} record The deselected record
  137644. * @param {Number} index The row index deselected
  137645. */
  137646. 'deselect',
  137647. /**
  137648. * @event select
  137649. * Fired after a record is selected
  137650. * @param {Ext.selection.RowModel} this
  137651. * @param {Ext.data.Model} record The selected record
  137652. * @param {Number} index The row index selected
  137653. */
  137654. 'select'
  137655. );
  137656. this.views = [];
  137657. this.callParent(arguments);
  137658. },
  137659. bindComponent: function(view) {
  137660. var me = this;
  137661. me.views = me.views || [];
  137662. me.views.push(view);
  137663. me.bindStore(view.getStore(), true);
  137664. view.on({
  137665. itemmousedown: me.onRowMouseDown,
  137666. scope: me
  137667. });
  137668. if (me.enableKeyNav) {
  137669. me.initKeyNav(view);
  137670. }
  137671. },
  137672. initKeyNav: function(view) {
  137673. var me = this;
  137674. if (!view.rendered) {
  137675. view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
  137676. return;
  137677. }
  137678. // view.el has tabIndex -1 to allow for
  137679. // keyboard events to be passed to it.
  137680. view.el.set({
  137681. tabIndex: -1
  137682. });
  137683. // Drive the KeyNav off the View's itemkeydown event so that beforeitemkeydown listeners may veto
  137684. me.keyNav = new Ext.util.KeyNav({
  137685. target: view,
  137686. ignoreInputFields: true,
  137687. eventName: 'itemkeydown',
  137688. processEvent: function(view, record, node, index, event) {
  137689. event.record = record;
  137690. event.recordIndex = index;
  137691. return event;
  137692. },
  137693. up: me.onKeyUp,
  137694. down: me.onKeyDown,
  137695. right: me.onKeyRight,
  137696. left: me.onKeyLeft,
  137697. pageDown: me.onKeyPageDown,
  137698. pageUp: me.onKeyPageUp,
  137699. home: me.onKeyHome,
  137700. end: me.onKeyEnd,
  137701. space: me.onKeySpace,
  137702. enter: me.onKeyEnter,
  137703. scope: me
  137704. });
  137705. },
  137706. // Returns the number of rows currently visible on the screen or
  137707. // false if there were no rows. This assumes that all rows are
  137708. // of the same height and the first view is accurate.
  137709. getRowsVisible: function() {
  137710. var rowsVisible = false,
  137711. view = this.views[0],
  137712. row = view.getNode(0),
  137713. rowHeight, gridViewHeight;
  137714. if (row) {
  137715. rowHeight = Ext.fly(row).getHeight();
  137716. gridViewHeight = view.el.getHeight();
  137717. rowsVisible = Math.floor(gridViewHeight / rowHeight);
  137718. }
  137719. return rowsVisible;
  137720. },
  137721. // go to last visible record in grid.
  137722. onKeyEnd: function(e) {
  137723. var me = this,
  137724. last = me.store.getAt(me.store.getCount() - 1);
  137725. if (last) {
  137726. if (e.shiftKey) {
  137727. me.selectRange(last, me.lastFocused || 0);
  137728. me.setLastFocused(last);
  137729. } else if (e.ctrlKey) {
  137730. me.setLastFocused(last);
  137731. } else {
  137732. me.doSelect(last);
  137733. }
  137734. }
  137735. },
  137736. // go to first visible record in grid.
  137737. onKeyHome: function(e) {
  137738. var me = this,
  137739. first = me.store.getAt(0);
  137740. if (first) {
  137741. if (e.shiftKey) {
  137742. me.selectRange(first, me.lastFocused || 0);
  137743. me.setLastFocused(first);
  137744. } else if (e.ctrlKey) {
  137745. me.setLastFocused(first);
  137746. } else {
  137747. me.doSelect(first, false);
  137748. }
  137749. }
  137750. },
  137751. // Go one page up from the lastFocused record in the grid.
  137752. onKeyPageUp: function(e) {
  137753. var me = this,
  137754. rowsVisible = me.getRowsVisible(),
  137755. selIdx,
  137756. prevIdx,
  137757. prevRecord;
  137758. if (rowsVisible) {
  137759. selIdx = e.recordIndex;
  137760. prevIdx = selIdx - rowsVisible;
  137761. if (prevIdx < 0) {
  137762. prevIdx = 0;
  137763. }
  137764. prevRecord = me.store.getAt(prevIdx);
  137765. if (e.shiftKey) {
  137766. me.selectRange(prevRecord, e.record, e.ctrlKey, 'up');
  137767. me.setLastFocused(prevRecord);
  137768. } else if (e.ctrlKey) {
  137769. e.preventDefault();
  137770. me.setLastFocused(prevRecord);
  137771. } else {
  137772. me.doSelect(prevRecord);
  137773. }
  137774. }
  137775. },
  137776. // Go one page down from the lastFocused record in the grid.
  137777. onKeyPageDown: function(e) {
  137778. var me = this,
  137779. rowsVisible = me.getRowsVisible(),
  137780. selIdx,
  137781. nextIdx,
  137782. nextRecord;
  137783. if (rowsVisible) {
  137784. selIdx = e.recordIndex;
  137785. nextIdx = selIdx + rowsVisible;
  137786. if (nextIdx >= me.store.getCount()) {
  137787. nextIdx = me.store.getCount() - 1;
  137788. }
  137789. nextRecord = me.store.getAt(nextIdx);
  137790. if (e.shiftKey) {
  137791. me.selectRange(nextRecord, e.record, e.ctrlKey, 'down');
  137792. me.setLastFocused(nextRecord);
  137793. } else if (e.ctrlKey) {
  137794. // some browsers, this means go thru browser tabs
  137795. // attempt to stop.
  137796. e.preventDefault();
  137797. me.setLastFocused(nextRecord);
  137798. } else {
  137799. me.doSelect(nextRecord);
  137800. }
  137801. }
  137802. },
  137803. // Select/Deselect based on pressing Spacebar.
  137804. // Assumes a SIMPLE selectionmode style
  137805. onKeySpace: function(e) {
  137806. var me = this,
  137807. record = me.lastFocused;
  137808. if (record) {
  137809. if (me.isSelected(record)) {
  137810. me.doDeselect(record, false);
  137811. } else {
  137812. me.doSelect(record, true);
  137813. }
  137814. }
  137815. },
  137816. onKeyEnter: Ext.emptyFn,
  137817. // Navigate one record up. This could be a selection or
  137818. // could be simply focusing a record for discontiguous
  137819. // selection. Provides bounds checking.
  137820. onKeyUp: function(e) {
  137821. var me = this,
  137822. idx = me.store.indexOf(me.lastFocused),
  137823. record;
  137824. if (idx > 0) {
  137825. // needs to be the filtered count as thats what
  137826. // will be visible.
  137827. record = me.store.getAt(idx - 1);
  137828. if (e.shiftKey && me.lastFocused) {
  137829. if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
  137830. me.doDeselect(me.lastFocused, true);
  137831. me.setLastFocused(record);
  137832. } else if (!me.isSelected(me.lastFocused)) {
  137833. me.doSelect(me.lastFocused, true);
  137834. me.doSelect(record, true);
  137835. } else {
  137836. me.doSelect(record, true);
  137837. }
  137838. } else if (e.ctrlKey) {
  137839. me.setLastFocused(record);
  137840. } else {
  137841. me.doSelect(record);
  137842. //view.focusRow(idx - 1);
  137843. }
  137844. }
  137845. // There was no lastFocused record, and the user has pressed up
  137846. // Ignore??
  137847. //else if (this.selected.getCount() == 0) {
  137848. //
  137849. // this.doSelect(record);
  137850. // //view.focusRow(idx - 1);
  137851. //}
  137852. },
  137853. // Navigate one record down. This could be a selection or
  137854. // could be simply focusing a record for discontiguous
  137855. // selection. Provides bounds checking.
  137856. onKeyDown: function(e) {
  137857. var me = this,
  137858. idx = me.store.indexOf(me.lastFocused),
  137859. record;
  137860. // needs to be the filtered count as thats what
  137861. // will be visible.
  137862. if (idx + 1 < me.store.getCount()) {
  137863. record = me.store.getAt(idx + 1);
  137864. if (me.selected.getCount() === 0) {
  137865. if (!e.ctrlKey) {
  137866. me.doSelect(record);
  137867. } else {
  137868. me.setLastFocused(record);
  137869. }
  137870. //view.focusRow(idx + 1);
  137871. } else if (e.shiftKey && me.lastFocused) {
  137872. if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
  137873. me.doDeselect(me.lastFocused, true);
  137874. me.setLastFocused(record);
  137875. } else if (!me.isSelected(me.lastFocused)) {
  137876. me.doSelect(me.lastFocused, true);
  137877. me.doSelect(record, true);
  137878. } else {
  137879. me.doSelect(record, true);
  137880. }
  137881. } else if (e.ctrlKey) {
  137882. me.setLastFocused(record);
  137883. } else {
  137884. me.doSelect(record);
  137885. //view.focusRow(idx + 1);
  137886. }
  137887. }
  137888. },
  137889. scrollByDeltaX: function(delta) {
  137890. var view = this.views[0],
  137891. section = view.up(),
  137892. hScroll = section.horizontalScroller;
  137893. if (hScroll) {
  137894. hScroll.scrollByDeltaX(delta);
  137895. }
  137896. },
  137897. onKeyLeft: function(e) {
  137898. this.scrollByDeltaX(-this.deltaScroll);
  137899. },
  137900. onKeyRight: function(e) {
  137901. this.scrollByDeltaX(this.deltaScroll);
  137902. },
  137903. // Select the record with the event included so that
  137904. // we can take into account ctrlKey, shiftKey, etc
  137905. onRowMouseDown: function(view, record, item, index, e) {
  137906. if (!this.allowRightMouseSelection(e)) {
  137907. return;
  137908. }
  137909. if (e.button === 0 || !this.isSelected(record)) {
  137910. this.selectWithEvent(record, e);
  137911. }
  137912. },
  137913. /**
  137914. * Checks whether a selection should proceed based on the ignoreRightMouseSelection
  137915. * option.
  137916. * @private
  137917. * @param {Ext.EventObject} e The event
  137918. * @return {Boolean} False if the selection should not proceed
  137919. */
  137920. allowRightMouseSelection: function(e) {
  137921. var disallow = this.ignoreRightMouseSelection && e.button !== 0;
  137922. if (disallow) {
  137923. disallow = this.hasSelection();
  137924. }
  137925. return !disallow;
  137926. },
  137927. // Allow the GridView to update the UI by
  137928. // adding/removing a CSS class from the row.
  137929. onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
  137930. var me = this,
  137931. views = me.views,
  137932. viewsLn = views.length,
  137933. store = me.store,
  137934. rowIdx = store.indexOf(record),
  137935. eventName = isSelected ? 'select' : 'deselect',
  137936. i = 0;
  137937. if ((suppressEvent || me.fireEvent('before' + eventName, me, record, rowIdx)) !== false &&
  137938. commitFn() !== false) {
  137939. for (; i < viewsLn; i++) {
  137940. if (isSelected) {
  137941. views[i].onRowSelect(rowIdx, suppressEvent);
  137942. } else {
  137943. views[i].onRowDeselect(rowIdx, suppressEvent);
  137944. }
  137945. }
  137946. if (!suppressEvent) {
  137947. me.fireEvent(eventName, me, record, rowIdx);
  137948. }
  137949. }
  137950. },
  137951. // Provide indication of what row was last focused via
  137952. // the gridview.
  137953. onLastFocusChanged: function(oldFocused, newFocused, supressFocus) {
  137954. var views = this.views,
  137955. viewsLn = views.length,
  137956. store = this.store,
  137957. rowIdx,
  137958. i = 0;
  137959. if (oldFocused) {
  137960. rowIdx = store.indexOf(oldFocused);
  137961. if (rowIdx != -1) {
  137962. for (; i < viewsLn; i++) {
  137963. views[i].onRowFocus(rowIdx, false);
  137964. }
  137965. }
  137966. }
  137967. if (newFocused) {
  137968. rowIdx = store.indexOf(newFocused);
  137969. if (rowIdx != -1) {
  137970. for (i = 0; i < viewsLn; i++) {
  137971. views[i].onRowFocus(rowIdx, true, supressFocus);
  137972. }
  137973. }
  137974. }
  137975. this.callParent();
  137976. },
  137977. onEditorTab: function(editingPlugin, e) {
  137978. var me = this,
  137979. view = me.views[0],
  137980. record = editingPlugin.getActiveRecord(),
  137981. header = editingPlugin.getActiveColumn(),
  137982. position = view.getPosition(record, header),
  137983. direction = e.shiftKey ? 'left' : 'right';
  137984. do {
  137985. position = view.walkCells(position, direction, e, me.preventWrap);
  137986. } while(position && !view.headerCt.getHeaderAtIndex(position.column).getEditor());
  137987. if (position) {
  137988. editingPlugin.startEditByPosition(position);
  137989. }
  137990. },
  137991. /**
  137992. * Returns position of the first selected cell in the selection in the format {row: row, column: column}
  137993. */
  137994. getCurrentPosition: function() {
  137995. var firstSelection = this.selected.items[0];
  137996. if (firstSelection) {
  137997. return {
  137998. row: this.store.indexOf(firstSelection),
  137999. column: 0
  138000. };
  138001. }
  138002. },
  138003. selectByPosition: function(position) {
  138004. var record = this.store.getAt(position.row);
  138005. this.select(record);
  138006. },
  138007. /**
  138008. * Selects the record immediately following the currently selected record.
  138009. * @param {Boolean} [keepExisting] True to retain existing selections
  138010. * @param {Boolean} [suppressEvent] Set to false to not fire a select event
  138011. * @return {Boolean} `true` if there is a next record, else `false`
  138012. */
  138013. selectNext: function(keepExisting, suppressEvent) {
  138014. var me = this,
  138015. store = me.store,
  138016. selection = me.getSelection(),
  138017. record = selection[selection.length - 1],
  138018. index = store.indexOf(record) + 1,
  138019. success;
  138020. if(index === store.getCount() || index === 0) {
  138021. success = false;
  138022. } else {
  138023. me.doSelect(index, keepExisting, suppressEvent);
  138024. success = true;
  138025. }
  138026. return success;
  138027. },
  138028. /**
  138029. * Selects the record that precedes the currently selected record.
  138030. * @param {Boolean} [keepExisting] True to retain existing selections
  138031. * @param {Boolean} [suppressEvent] Set to false to not fire a select event
  138032. * @return {Boolean} `true` if there is a previous record, else `false`
  138033. */
  138034. selectPrevious: function(keepExisting, suppressEvent) {
  138035. var me = this,
  138036. selection = me.getSelection(),
  138037. record = selection[0],
  138038. index = me.store.indexOf(record) - 1,
  138039. success;
  138040. if (index < 0) {
  138041. success = false;
  138042. } else {
  138043. me.doSelect(index, keepExisting, suppressEvent);
  138044. success = true;
  138045. }
  138046. return success;
  138047. }
  138048. });
  138049. /**
  138050. * A selection model that renders a column of checkboxes that can be toggled to
  138051. * select or deselect rows. The default mode for this selection model is MULTI.
  138052. *
  138053. * The selection model will inject a header for the checkboxes in the first view
  138054. * and according to the 'injectCheckbox' configuration.
  138055. */
  138056. Ext.define('Ext.selection.CheckboxModel', {
  138057. alias: 'selection.checkboxmodel',
  138058. extend: 'Ext.selection.RowModel',
  138059. /**
  138060. * @cfg {String} mode
  138061. * Modes of selection.
  138062. * Valid values are SINGLE, SIMPLE, and MULTI.
  138063. */
  138064. mode: 'MULTI',
  138065. /**
  138066. * @cfg {Number/String} [injectCheckbox=0]
  138067. * The index at which to insert the checkbox column.
  138068. * Supported values are a numeric index, and the strings 'first' and 'last'.
  138069. */
  138070. injectCheckbox: 0,
  138071. /**
  138072. * @cfg {Boolean} checkOnly
  138073. * True if rows can only be selected by clicking on the checkbox column.
  138074. */
  138075. checkOnly: false,
  138076. /**
  138077. * @cfg {Boolean} showHeaderCheckbox
  138078. * Configure as `false` to not display the header checkbox at the top of the column.
  138079. */
  138080. showHeaderCheckbox: true,
  138081. headerWidth: 24,
  138082. // private
  138083. checkerOnCls: Ext.baseCSSPrefix + 'grid-hd-checker-on',
  138084. // private
  138085. refreshOnRemove: true,
  138086. beforeViewRender: function(view) {
  138087. var me = this;
  138088. me.callParent(arguments);
  138089. // if we have a locked header, only hook up to the first
  138090. if (!me.hasLockedHeader() || view.headerCt.lockedCt) {
  138091. if (me.showHeaderCheckbox !== false) {
  138092. view.headerCt.on('headerclick', me.onHeaderClick, me);
  138093. }
  138094. me.addCheckbox(view, true);
  138095. me.mon(view.ownerCt, 'reconfigure', me.onReconfigure, me);
  138096. }
  138097. },
  138098. bindComponent: function(view) {
  138099. var me = this;
  138100. me.sortable = false;
  138101. me.callParent(arguments);
  138102. },
  138103. hasLockedHeader: function(){
  138104. var views = this.views,
  138105. vLen = views.length,
  138106. v;
  138107. for (v = 0; v < vLen; v++) {
  138108. if (views[v].headerCt.lockedCt) {
  138109. return true;
  138110. }
  138111. }
  138112. return false;
  138113. },
  138114. /**
  138115. * Add the header checkbox to the header row
  138116. * @private
  138117. * @param {Boolean} initial True if we're binding for the first time.
  138118. */
  138119. addCheckbox: function(view, initial){
  138120. var me = this,
  138121. checkbox = me.injectCheckbox,
  138122. headerCt = view.headerCt;
  138123. // Preserve behaviour of false, but not clear why that would ever be done.
  138124. if (checkbox !== false) {
  138125. if (checkbox == 'first') {
  138126. checkbox = 0;
  138127. } else if (checkbox == 'last') {
  138128. checkbox = headerCt.getColumnCount();
  138129. }
  138130. Ext.suspendLayouts();
  138131. headerCt.add(checkbox, me.getHeaderConfig());
  138132. Ext.resumeLayouts();
  138133. }
  138134. if (initial !== true) {
  138135. view.refresh();
  138136. }
  138137. },
  138138. /**
  138139. * Handles the grid's reconfigure event. Adds the checkbox header if the columns have been reconfigured.
  138140. * @private
  138141. * @param {Ext.panel.Table} grid
  138142. * @param {Ext.data.Store} store
  138143. * @param {Object[]} columns
  138144. */
  138145. onReconfigure: function(grid, store, columns) {
  138146. if(columns) {
  138147. this.addCheckbox(this.views[0]);
  138148. }
  138149. },
  138150. /**
  138151. * Toggle the ui header between checked and unchecked state.
  138152. * @param {Boolean} isChecked
  138153. * @private
  138154. */
  138155. toggleUiHeader: function(isChecked) {
  138156. var view = this.views[0],
  138157. headerCt = view.headerCt,
  138158. checkHd = headerCt.child('gridcolumn[isCheckerHd]');
  138159. if (checkHd) {
  138160. if (isChecked) {
  138161. checkHd.el.addCls(this.checkerOnCls);
  138162. } else {
  138163. checkHd.el.removeCls(this.checkerOnCls);
  138164. }
  138165. }
  138166. },
  138167. /**
  138168. * Toggle between selecting all and deselecting all when clicking on
  138169. * a checkbox header.
  138170. */
  138171. onHeaderClick: function(headerCt, header, e) {
  138172. if (header.isCheckerHd) {
  138173. e.stopEvent();
  138174. var me = this,
  138175. isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
  138176. // Prevent focus changes on the view, since we're selecting/deselecting all records
  138177. me.preventFocus = true;
  138178. if (isChecked) {
  138179. me.deselectAll();
  138180. } else {
  138181. me.selectAll();
  138182. }
  138183. delete me.preventFocus;
  138184. }
  138185. },
  138186. /**
  138187. * Retrieve a configuration to be used in a HeaderContainer.
  138188. * This should be used when injectCheckbox is set to false.
  138189. */
  138190. getHeaderConfig: function() {
  138191. var me = this,
  138192. showCheck = me.showHeaderCheckbox !== false;
  138193. return {
  138194. isCheckerHd: showCheck,
  138195. text : '&#160;',
  138196. width: me.headerWidth,
  138197. sortable: false,
  138198. draggable: false,
  138199. resizable: false,
  138200. hideable: false,
  138201. menuDisabled: true,
  138202. dataIndex: '',
  138203. cls: showCheck ? Ext.baseCSSPrefix + 'column-header-checkbox ' : '',
  138204. renderer: Ext.Function.bind(me.renderer, me),
  138205. editRenderer: me.editRenderer || me.renderEmpty,
  138206. locked: me.hasLockedHeader()
  138207. };
  138208. },
  138209. renderEmpty: function(){
  138210. return '&#160;';
  138211. },
  138212. /**
  138213. * Generates the HTML to be rendered in the injected checkbox column for each row.
  138214. * Creates the standard checkbox markup by default; can be overridden to provide custom rendering.
  138215. * See {@link Ext.grid.column.Column#renderer} for description of allowed parameters.
  138216. */
  138217. renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
  138218. var baseCSSPrefix = Ext.baseCSSPrefix;
  138219. metaData.tdCls = baseCSSPrefix + 'grid-cell-special ' + baseCSSPrefix + 'grid-cell-row-checker';
  138220. return '<div class="' + baseCSSPrefix + 'grid-row-checker">&#160;</div>';
  138221. },
  138222. // override
  138223. onRowMouseDown: function(view, record, item, index, e) {
  138224. view.el.focus();
  138225. var me = this,
  138226. checker = e.getTarget('.' + Ext.baseCSSPrefix + 'grid-row-checker'),
  138227. mode;
  138228. if (!me.allowRightMouseSelection(e)) {
  138229. return;
  138230. }
  138231. // checkOnly set, but we didn't click on a checker.
  138232. if (me.checkOnly && !checker) {
  138233. return;
  138234. }
  138235. if (checker) {
  138236. mode = me.getSelectionMode();
  138237. // dont change the mode if its single otherwise
  138238. // we would get multiple selection
  138239. if (mode !== 'SINGLE') {
  138240. me.setSelectionMode('SIMPLE');
  138241. }
  138242. me.selectWithEvent(record, e);
  138243. me.setSelectionMode(mode);
  138244. } else {
  138245. me.selectWithEvent(record, e);
  138246. }
  138247. },
  138248. /**
  138249. * Synchronize header checker value as selection changes.
  138250. * @private
  138251. */
  138252. onSelectChange: function() {
  138253. var me = this;
  138254. me.callParent(arguments);
  138255. me.updateHeaderState();
  138256. },
  138257. /**
  138258. * @private
  138259. */
  138260. onStoreLoad: function() {
  138261. var me = this;
  138262. me.callParent(arguments);
  138263. me.updateHeaderState();
  138264. },
  138265. /**
  138266. * @private
  138267. */
  138268. updateHeaderState: function() {
  138269. // check to see if all records are selected
  138270. var hdSelectStatus = this.selected.getCount() === this.store.getCount();
  138271. this.toggleUiHeader(hdSelectStatus);
  138272. }
  138273. });
  138274. /**
  138275. * Adds custom behavior for left/right keyboard navigation for use with a tree.
  138276. * Depends on the view having an expand and collapse method which accepts a
  138277. * record.
  138278. *
  138279. * @private
  138280. */
  138281. Ext.define('Ext.selection.TreeModel', {
  138282. extend: 'Ext.selection.RowModel',
  138283. alias: 'selection.treemodel',
  138284. // typically selection models prune records from the selection
  138285. // model when they are removed, because the TreeView constantly
  138286. // adds/removes records as they are expanded/collapsed
  138287. pruneRemoved: false,
  138288. onKeyRight: function(e, t) {
  138289. var focused = this.getLastFocused(),
  138290. view = this.view;
  138291. if (focused) {
  138292. // tree node is already expanded, go down instead
  138293. // this handles both the case where we navigate to firstChild and if
  138294. // there are no children to the nextSibling
  138295. if (focused.isExpanded()) {
  138296. this.onKeyDown(e, t);
  138297. // if its not a leaf node, expand it
  138298. } else if (focused.isExpandable()) {
  138299. view.expand(focused);
  138300. }
  138301. }
  138302. },
  138303. onKeyLeft: function(e, t) {
  138304. var focused = this.getLastFocused(),
  138305. view = this.view,
  138306. viewSm = view.getSelectionModel(),
  138307. parentNode, parentRecord;
  138308. if (focused) {
  138309. parentNode = focused.parentNode;
  138310. // if focused node is already expanded, collapse it
  138311. if (focused.isExpanded()) {
  138312. view.collapse(focused);
  138313. // has a parentNode and its not root
  138314. // TODO: this needs to cover the case where the root isVisible
  138315. } else if (parentNode && !parentNode.isRoot()) {
  138316. // Select a range of records when doing multiple selection.
  138317. if (e.shiftKey) {
  138318. viewSm.selectRange(parentNode, focused, e.ctrlKey, 'up');
  138319. viewSm.setLastFocused(parentNode);
  138320. // just move focus, not selection
  138321. } else if (e.ctrlKey) {
  138322. viewSm.setLastFocused(parentNode);
  138323. // select it
  138324. } else {
  138325. viewSm.select(parentNode);
  138326. }
  138327. }
  138328. }
  138329. },
  138330. onKeySpace: function(e, t) {
  138331. this.toggleCheck(e);
  138332. },
  138333. onKeyEnter: function(e, t) {
  138334. this.toggleCheck(e);
  138335. },
  138336. toggleCheck: function(e){
  138337. e.stopEvent();
  138338. var selected = this.getLastSelected();
  138339. if (selected) {
  138340. this.view.onCheckChange(selected);
  138341. }
  138342. }
  138343. });
  138344. /**
  138345. * @class Ext.slider.Thumb
  138346. * @private
  138347. * Represents a single thumb element on a Slider. This would not usually be created manually and would instead
  138348. * be created internally by an {@link Ext.slider.Multi Multi slider}.
  138349. */
  138350. Ext.define('Ext.slider.Thumb', {
  138351. requires: ['Ext.dd.DragTracker', 'Ext.util.Format'],
  138352. /**
  138353. * @private
  138354. * @property {Number} topThumbZIndex
  138355. * The number used internally to set the z index of the top thumb (see promoteThumb for details)
  138356. */
  138357. topZIndex: 10000,
  138358. /**
  138359. * @cfg {Ext.slider.MultiSlider} slider (required)
  138360. * The Slider to render to.
  138361. */
  138362. /**
  138363. * Creates new slider thumb.
  138364. * @param {Object} [config] Config object.
  138365. */
  138366. constructor: function(config) {
  138367. var me = this;
  138368. /**
  138369. * @property {Ext.slider.MultiSlider} slider
  138370. * The slider this thumb is contained within
  138371. */
  138372. Ext.apply(me, config || {}, {
  138373. cls: Ext.baseCSSPrefix + 'slider-thumb',
  138374. /**
  138375. * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings
  138376. */
  138377. constrain: false
  138378. });
  138379. me.callParent([config]);
  138380. },
  138381. /**
  138382. * Renders the thumb into a slider
  138383. */
  138384. render: function() {
  138385. var me = this;
  138386. me.el = me.slider.innerEl.insertFirst(me.getElConfig());
  138387. me.onRender();
  138388. },
  138389. onRender: function() {
  138390. if (this.disabled) {
  138391. this.disable();
  138392. }
  138393. this.initEvents();
  138394. },
  138395. getElConfig: function() {
  138396. var me = this,
  138397. slider = me.slider,
  138398. style = {};
  138399. style[slider.vertical ? 'bottom' : 'left'] = slider.calculateThumbPosition(slider.normalizeValue(me.value)) + '%';
  138400. return {
  138401. style: style,
  138402. id : this.id,
  138403. cls : this.cls
  138404. };
  138405. },
  138406. /**
  138407. * @private
  138408. * move the thumb
  138409. */
  138410. move: function(v, animate) {
  138411. var el = this.el,
  138412. styleProp = this.slider.vertical ? 'bottom' : 'left',
  138413. to,
  138414. from;
  138415. v += '%';
  138416. if (!animate) {
  138417. el.dom.style[styleProp] = v;
  138418. } else {
  138419. to = {};
  138420. to[styleProp] = v;
  138421. if (!Ext.supports.GetPositionPercentage) {
  138422. from = {};
  138423. from[styleProp] = el.dom.style[styleProp];
  138424. }
  138425. new Ext.fx.Anim({
  138426. target: el,
  138427. duration: 350,
  138428. from: from,
  138429. to: to
  138430. });
  138431. }
  138432. },
  138433. /**
  138434. * @private
  138435. * Bring thumb dom element to front.
  138436. */
  138437. bringToFront: function() {
  138438. this.el.setStyle('zIndex', this.topZIndex);
  138439. },
  138440. /**
  138441. * @private
  138442. * Send thumb dom element to back.
  138443. */
  138444. sendToBack: function() {
  138445. this.el.setStyle('zIndex', '');
  138446. },
  138447. /**
  138448. * Enables the thumb if it is currently disabled
  138449. */
  138450. enable: function() {
  138451. var me = this;
  138452. me.disabled = false;
  138453. if (me.el) {
  138454. me.el.removeCls(me.slider.disabledCls);
  138455. }
  138456. },
  138457. /**
  138458. * Disables the thumb if it is currently enabled
  138459. */
  138460. disable: function() {
  138461. var me = this;
  138462. me.disabled = true;
  138463. if (me.el) {
  138464. me.el.addCls(me.slider.disabledCls);
  138465. }
  138466. },
  138467. /**
  138468. * Sets up an Ext.dd.DragTracker for this thumb
  138469. */
  138470. initEvents: function() {
  138471. var me = this,
  138472. el = me.el;
  138473. me.tracker = new Ext.dd.DragTracker({
  138474. onBeforeStart: Ext.Function.bind(me.onBeforeDragStart, me),
  138475. onStart : Ext.Function.bind(me.onDragStart, me),
  138476. onDrag : Ext.Function.bind(me.onDrag, me),
  138477. onEnd : Ext.Function.bind(me.onDragEnd, me),
  138478. tolerance : 3,
  138479. autoStart : 300,
  138480. overCls : Ext.baseCSSPrefix + 'slider-thumb-over'
  138481. });
  138482. me.tracker.initEl(el);
  138483. },
  138484. /**
  138485. * @private
  138486. * This is tied into the internal Ext.dd.DragTracker. If the slider is currently disabled,
  138487. * this returns false to disable the DragTracker too.
  138488. * @return {Boolean} False if the slider is currently disabled
  138489. */
  138490. onBeforeDragStart : function(e) {
  138491. if (this.disabled) {
  138492. return false;
  138493. } else {
  138494. this.slider.promoteThumb(this);
  138495. return true;
  138496. }
  138497. },
  138498. /**
  138499. * @private
  138500. * This is tied into the internal Ext.dd.DragTracker's onStart template method. Adds the drag CSS class
  138501. * to the thumb and fires the 'dragstart' event
  138502. */
  138503. onDragStart: function(e){
  138504. var me = this;
  138505. me.el.addCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
  138506. me.dragging = me.slider.dragging = true;
  138507. me.dragStartValue = me.value;
  138508. me.slider.fireEvent('dragstart', me.slider, e, me);
  138509. },
  138510. /**
  138511. * @private
  138512. * This is tied into the internal Ext.dd.DragTracker's onDrag template method. This is called every time
  138513. * the DragTracker detects a drag movement. It updates the Slider's value using the position of the drag
  138514. */
  138515. onDrag: function(e) {
  138516. var me = this,
  138517. slider = me.slider,
  138518. index = me.index,
  138519. newValue = me.getValueFromTracker(),
  138520. above,
  138521. below;
  138522. // If dragged out of range, value will be undefined
  138523. if (newValue !== undefined) {
  138524. if (me.constrain) {
  138525. above = slider.thumbs[index + 1];
  138526. below = slider.thumbs[index - 1];
  138527. if (below !== undefined && newValue <= below.value) {
  138528. newValue = below.value;
  138529. }
  138530. if (above !== undefined && newValue >= above.value) {
  138531. newValue = above.value;
  138532. }
  138533. }
  138534. slider.setValue(index, newValue, false);
  138535. slider.fireEvent('drag', slider, e, me);
  138536. }
  138537. },
  138538. getValueFromTracker: function() {
  138539. var slider = this.slider,
  138540. trackPoint = slider.getTrackpoint(this.tracker.getXY());
  138541. // If dragged out of range, value will be undefined
  138542. if (trackPoint !== undefined) {
  138543. return slider.reversePixelValue(trackPoint);
  138544. }
  138545. },
  138546. /**
  138547. * @private
  138548. * This is tied to the internal Ext.dd.DragTracker's onEnd template method. Removes the drag CSS class and
  138549. * fires the 'changecomplete' event with the new value
  138550. */
  138551. onDragEnd: function(e) {
  138552. var me = this,
  138553. slider = me.slider,
  138554. value = me.value;
  138555. me.el.removeCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
  138556. me.dragging = slider.dragging = false;
  138557. slider.fireEvent('dragend', slider, e);
  138558. if (me.dragStartValue != value) {
  138559. slider.fireEvent('changecomplete', slider, value, me);
  138560. }
  138561. },
  138562. destroy: function() {
  138563. Ext.destroy(this.tracker);
  138564. }
  138565. });
  138566. /**
  138567. * Simple plugin for using an Ext.tip.Tip with a slider to show the slider value. In general this class is not created
  138568. * directly, instead pass the {@link Ext.slider.Multi#useTips} and {@link Ext.slider.Multi#tipText} configuration
  138569. * options to the slider directly.
  138570. *
  138571. * @example
  138572. * Ext.create('Ext.slider.Single', {
  138573. * width: 214,
  138574. * minValue: 0,
  138575. * maxValue: 100,
  138576. * useTips: true,
  138577. * renderTo: Ext.getBody()
  138578. * });
  138579. *
  138580. * Optionally provide your own tip text by passing tipText:
  138581. *
  138582. * @example
  138583. * Ext.create('Ext.slider.Single', {
  138584. * width: 214,
  138585. * minValue: 0,
  138586. * maxValue: 100,
  138587. * useTips: true,
  138588. * tipText: function(thumb){
  138589. * return Ext.String.format('**{0}% complete**', thumb.value);
  138590. * },
  138591. * renderTo: Ext.getBody()
  138592. * });
  138593. */
  138594. Ext.define('Ext.slider.Tip', {
  138595. extend: 'Ext.tip.Tip',
  138596. minWidth: 10,
  138597. alias: 'widget.slidertip',
  138598. /**
  138599. * @cfg {Array} [offsets=null]
  138600. * Offsets for aligning the tip to the slider. See {@link Ext.dom.Element#alignTo}. Default values
  138601. * for offsets are provided by specifying the {@link #position} config.
  138602. */
  138603. offsets : null,
  138604. /**
  138605. * @cfg {String} [align=null]
  138606. * Alignment configuration for the tip to the slider. See {@link Ext.dom.Element#alignTo}. Default
  138607. * values for alignment are provided by specifying the {@link #position} config.
  138608. */
  138609. align: null,
  138610. /**
  138611. * @cfg {String} [position=For horizontal sliders, "top", for vertical sliders, "left"]
  138612. * Sets the position for where the tip will be displayed related to the thumb. This sets
  138613. * defaults for {@link #align} and {@link #offsets} configurations. If {@link #align} or
  138614. * {@link #offsets} configurations are specified, they will override the defaults defined
  138615. * by position.
  138616. */
  138617. position: '',
  138618. defaultVerticalPosition: 'left',
  138619. defaultHorizontalPosition: 'top',
  138620. isSliderTip: true,
  138621. init: function(slider) {
  138622. var me = this,
  138623. align,
  138624. offsets;
  138625. if (!me.position) {
  138626. me.position = slider.vertical ? me.defaultVerticalPosition : me.defaultHorizontalPosition;
  138627. }
  138628. switch (me.position) {
  138629. case 'top':
  138630. offsets = [0, -10];
  138631. align = 'b-t?';
  138632. break;
  138633. case 'bottom':
  138634. offsets = [0, 10];
  138635. align = 't-b?';
  138636. break;
  138637. case 'left':
  138638. offsets = [-10, 0];
  138639. align = 'r-l?';
  138640. break;
  138641. case 'right':
  138642. offsets = [10, 0];
  138643. align = 'l-r?';
  138644. }
  138645. if (!me.align) {
  138646. me.align = align;
  138647. }
  138648. if (!me.offsets) {
  138649. me.offsets = offsets;
  138650. }
  138651. slider.on({
  138652. scope : me,
  138653. dragstart: me.onSlide,
  138654. drag : me.onSlide,
  138655. dragend : me.hide,
  138656. destroy : me.destroy
  138657. });
  138658. },
  138659. /**
  138660. * @private
  138661. * Called whenever a dragstart or drag event is received on the associated Thumb.
  138662. * Aligns the Tip with the Thumb's new position.
  138663. * @param {Ext.slider.MultiSlider} slider The slider
  138664. * @param {Ext.EventObject} e The Event object
  138665. * @param {Ext.slider.Thumb} thumb The thumb that the Tip is attached to
  138666. */
  138667. onSlide : function(slider, e, thumb) {
  138668. var me = this;
  138669. me.show();
  138670. me.update(me.getText(thumb));
  138671. me.el.alignTo(thumb.el, me.align, me.offsets);
  138672. },
  138673. /**
  138674. * Used to create the text that appears in the Tip's body. By default this just returns the value of the Slider
  138675. * Thumb that the Tip is attached to. Override to customize.
  138676. * @param {Ext.slider.Thumb} thumb The Thumb that the Tip is attached to
  138677. * @return {String} The text to display in the tip
  138678. * @protected
  138679. * @template
  138680. */
  138681. getText : function(thumb) {
  138682. return String(thumb.value);
  138683. }
  138684. });
  138685. /**
  138686. * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
  138687. * and animation. Can be added as an item to any container.
  138688. *
  138689. * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
  138690. *
  138691. * @example
  138692. * Ext.create('Ext.slider.Multi', {
  138693. * width: 200,
  138694. * values: [25, 50, 75],
  138695. * increment: 5,
  138696. * minValue: 0,
  138697. * maxValue: 100,
  138698. *
  138699. * // this defaults to true, setting to false allows the thumbs to pass each other
  138700. * constrainThumbs: false,
  138701. * renderTo: Ext.getBody()
  138702. * });
  138703. */
  138704. Ext.define('Ext.slider.Multi', {
  138705. extend: 'Ext.form.field.Base',
  138706. alias: 'widget.multislider',
  138707. alternateClassName: 'Ext.slider.MultiSlider',
  138708. requires: [
  138709. 'Ext.slider.Thumb',
  138710. 'Ext.slider.Tip',
  138711. 'Ext.Number',
  138712. 'Ext.util.Format',
  138713. 'Ext.Template',
  138714. 'Ext.layout.component.field.Slider'
  138715. ],
  138716. childEls: [
  138717. 'endEl', 'innerEl'
  138718. ],
  138719. // note: {id} here is really {inputId}, but {cmpId} is available
  138720. fieldSubTpl: [
  138721. '<div id="{id}" class="' + Ext.baseCSSPrefix + 'slider {fieldCls} {vertical}" aria-valuemin="{minValue}" aria-valuemax="{maxValue}" aria-valuenow="{value}" aria-valuetext="{value}">',
  138722. '<div id="{cmpId}-endEl" class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',
  138723. '<div id="{cmpId}-innerEl" class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',
  138724. '{%this.renderThumbs(out, values)%}',
  138725. '</div>',
  138726. '</div>',
  138727. '</div>',
  138728. {
  138729. renderThumbs: function(out, values) {
  138730. var me = values.$comp,
  138731. i = 0,
  138732. thumbs = me.thumbs,
  138733. len = thumbs.length,
  138734. thumb,
  138735. thumbConfig;
  138736. for (; i < len; i++) {
  138737. thumb = thumbs[i];
  138738. thumbConfig = thumb.getElConfig();
  138739. thumbConfig.id = me.id + '-thumb-' + i;
  138740. Ext.DomHelper.generateMarkup(thumbConfig, out);
  138741. }
  138742. },
  138743. disableFormats: true
  138744. }
  138745. ],
  138746. /**
  138747. * @cfg {Number} value
  138748. * A value with which to initialize the slider. Setting this will only result in the creation
  138749. * of a single slider thumb; if you want multiple thumbs then use the {@link #values} config instead.
  138750. *
  138751. * Defaults to #minValue.
  138752. */
  138753. /**
  138754. * @cfg {Number[]} values
  138755. * Array of Number values with which to initalize the slider. A separate slider thumb will be created for each value
  138756. * in this array. This will take precedence over the single {@link #value} config.
  138757. */
  138758. /**
  138759. * @cfg {Boolean} vertical
  138760. * Orient the Slider vertically rather than horizontally.
  138761. */
  138762. vertical: false,
  138763. /**
  138764. * @cfg {Number} minValue
  138765. * The minimum value for the Slider.
  138766. */
  138767. minValue: 0,
  138768. /**
  138769. * @cfg {Number} maxValue
  138770. * The maximum value for the Slider.
  138771. */
  138772. maxValue: 100,
  138773. /**
  138774. * @cfg {Number/Boolean} decimalPrecision The number of decimal places to which to round the Slider's value.
  138775. *
  138776. * To disable rounding, configure as **false**.
  138777. */
  138778. decimalPrecision: 0,
  138779. /**
  138780. * @cfg {Number} keyIncrement
  138781. * How many units to change the Slider when adjusting with keyboard navigation. If the increment
  138782. * config is larger, it will be used instead.
  138783. */
  138784. keyIncrement: 1,
  138785. /**
  138786. * @cfg {Number} increment
  138787. * How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
  138788. */
  138789. increment: 0,
  138790. /**
  138791. * @cfg {Boolean} [zeroBasedSnapping=false]
  138792. * Set to `true` to calculate snap points based on {@link #increment}s from zero as opposed to
  138793. * from this Slider's {@link #minValue}.
  138794. *
  138795. * By Default, valid snap points are calculated starting {@link #increment}s from the {@link #minValue}
  138796. */
  138797. /**
  138798. * @private
  138799. * @property {Number[]} clickRange
  138800. * Determines whether or not a click to the slider component is considered to be a user request to change the value. Specified as an array of [top, bottom],
  138801. * the click event's 'top' property is compared to these numbers and the click only considered a change request if it falls within them. e.g. if the 'top'
  138802. * value of the click event is 4 or 16, the click is not considered a change request as it falls outside of the [5, 15] range
  138803. */
  138804. clickRange: [5,15],
  138805. /**
  138806. * @cfg {Boolean} clickToChange
  138807. * Determines whether or not clicking on the Slider axis will change the slider.
  138808. */
  138809. clickToChange : true,
  138810. /**
  138811. * @cfg {Boolean} animate
  138812. * Turn on or off animation.
  138813. */
  138814. animate: true,
  138815. /**
  138816. * @property {Boolean} dragging
  138817. * True while the thumb is in a drag operation
  138818. */
  138819. dragging: false,
  138820. /**
  138821. * @cfg {Boolean} constrainThumbs
  138822. * True to disallow thumbs from overlapping one another.
  138823. */
  138824. constrainThumbs: true,
  138825. componentLayout: 'sliderfield',
  138826. /**
  138827. * @cfg {Object/Boolean} useTips
  138828. * True to use an {@link Ext.slider.Tip} to display tips for the value. This option may also
  138829. * provide a configuration object for an {@link Ext.slider.Tip}.
  138830. */
  138831. useTips : true,
  138832. /**
  138833. * @cfg {Function} [tipText=undefined]
  138834. * A function used to display custom text for the slider tip.
  138835. *
  138836. * Defaults to null, which will use the default on the plugin.
  138837. *
  138838. * @cfg {Ext.slider.Thumb} tipText.thumb The Thumb that the Tip is attached to
  138839. * @cfg {String} tipText.return The text to display in the tip
  138840. */
  138841. tipText : null,
  138842. ariaRole: 'slider',
  138843. // private override
  138844. initValue: function() {
  138845. var me = this,
  138846. extValue = Ext.value,
  138847. // Fallback for initial values: values config -> value config -> minValue config -> 0
  138848. values = extValue(me.values, [extValue(me.value, extValue(me.minValue, 0))]),
  138849. i = 0,
  138850. len = values.length;
  138851. // Store for use in dirty check
  138852. me.originalValue = values;
  138853. // Add a thumb for each value
  138854. for (; i < len; i++) {
  138855. me.addThumb(values[i]);
  138856. }
  138857. },
  138858. // private override
  138859. initComponent : function() {
  138860. var me = this,
  138861. tipPlug,
  138862. hasTip,
  138863. p, pLen, plugins;
  138864. /**
  138865. * @property {Array} thumbs
  138866. * Array containing references to each thumb
  138867. */
  138868. me.thumbs = [];
  138869. me.keyIncrement = Math.max(me.increment, me.keyIncrement);
  138870. me.addEvents(
  138871. /**
  138872. * @event beforechange
  138873. * Fires before the slider value is changed. By returning false from an event handler, you can cancel the
  138874. * event and prevent the slider from changing.
  138875. * @param {Ext.slider.Multi} slider The slider
  138876. * @param {Number} newValue The new value which the slider is being changed to.
  138877. * @param {Number} oldValue The old value which the slider was previously.
  138878. */
  138879. 'beforechange',
  138880. /**
  138881. * @event change
  138882. * Fires when the slider value is changed.
  138883. * @param {Ext.slider.Multi} slider The slider
  138884. * @param {Number} newValue The new value which the slider has been changed to.
  138885. * @param {Ext.slider.Thumb} thumb The thumb that was changed
  138886. */
  138887. 'change',
  138888. /**
  138889. * @event changecomplete
  138890. * Fires when the slider value is changed by the user and any drag operations have completed.
  138891. * @param {Ext.slider.Multi} slider The slider
  138892. * @param {Number} newValue The new value which the slider has been changed to.
  138893. * @param {Ext.slider.Thumb} thumb The thumb that was changed
  138894. */
  138895. 'changecomplete',
  138896. /**
  138897. * @event dragstart
  138898. * Fires after a drag operation has started.
  138899. * @param {Ext.slider.Multi} slider The slider
  138900. * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
  138901. */
  138902. 'dragstart',
  138903. /**
  138904. * @event drag
  138905. * Fires continuously during the drag operation while the mouse is moving.
  138906. * @param {Ext.slider.Multi} slider The slider
  138907. * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
  138908. */
  138909. 'drag',
  138910. /**
  138911. * @event dragend
  138912. * Fires after the drag operation has completed.
  138913. * @param {Ext.slider.Multi} slider The slider
  138914. * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
  138915. */
  138916. 'dragend'
  138917. );
  138918. // Ensure that the maxValue is a snap point, and that the initial value is snapped.
  138919. if (me.increment) {
  138920. me.maxValue = Ext.Number.snapInRange(me.maxValue, me.increment, me.minValue);
  138921. me.value = me.normalizeValue(me.value);
  138922. }
  138923. me.callParent();
  138924. // only can use it if it exists.
  138925. if (me.useTips) {
  138926. if (Ext.isObject(me.useTips)) {
  138927. tipPlug = Ext.apply({}, me.useTips);
  138928. } else {
  138929. tipPlug = me.tipText ? {getText: me.tipText} : {};
  138930. }
  138931. plugins = me.plugins = me.plugins || [];
  138932. pLen = plugins.length;
  138933. for (p = 0; p < pLen; p++) {
  138934. if (plugins[p].isSliderTip) {
  138935. hasTip = true;
  138936. break;
  138937. }
  138938. }
  138939. if (!hasTip) {
  138940. me.plugins.push(new Ext.slider.Tip(tipPlug));
  138941. }
  138942. }
  138943. },
  138944. /**
  138945. * Creates a new thumb and adds it to the slider
  138946. * @param {Number} [value=0] The initial value to set on the thumb.
  138947. * @return {Ext.slider.Thumb} The thumb
  138948. */
  138949. addThumb: function(value) {
  138950. var me = this,
  138951. thumb = new Ext.slider.Thumb({
  138952. ownerCt : me,
  138953. ownerLayout : me.getComponentLayout(),
  138954. value : value,
  138955. slider : me,
  138956. index : me.thumbs.length,
  138957. constrain : me.constrainThumbs,
  138958. disabled : !!me.readOnly
  138959. });
  138960. me.thumbs.push(thumb);
  138961. //render the thumb now if needed
  138962. if (me.rendered) {
  138963. thumb.render();
  138964. }
  138965. return thumb;
  138966. },
  138967. /**
  138968. * @private
  138969. * Moves the given thumb above all other by increasing its z-index. This is called when as drag
  138970. * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
  138971. * required when the thumbs are stacked on top of each other at one of the ends of the slider's
  138972. * range, which can result in the user not being able to move any of them.
  138973. * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
  138974. */
  138975. promoteThumb: function(topThumb) {
  138976. var thumbs = this.thumbs,
  138977. ln = thumbs.length,
  138978. zIndex, thumb, i;
  138979. for (i = 0; i < ln; i++) {
  138980. thumb = thumbs[i];
  138981. if (thumb == topThumb) {
  138982. thumb.bringToFront();
  138983. } else {
  138984. thumb.sendToBack();
  138985. }
  138986. }
  138987. },
  138988. // private override
  138989. getSubTplData : function() {
  138990. var me = this;
  138991. return Ext.apply(me.callParent(), {
  138992. $comp: me,
  138993. vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',
  138994. minValue: me.minValue,
  138995. maxValue: me.maxValue,
  138996. value: me.value
  138997. });
  138998. },
  138999. onRender : function() {
  139000. var me = this,
  139001. thumbs = me.thumbs,
  139002. len = thumbs.length,
  139003. i = 0,
  139004. thumb;
  139005. me.callParent(arguments);
  139006. for (i = 0; i < len; i++) {
  139007. thumb = thumbs[i];
  139008. thumb.el = me.el.getById(me.id + '-thumb-' + i);
  139009. thumb.onRender();
  139010. }
  139011. },
  139012. /**
  139013. * @private
  139014. * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
  139015. */
  139016. initEvents : function() {
  139017. var me = this;
  139018. me.mon(me.el, {
  139019. scope : me,
  139020. mousedown: me.onMouseDown,
  139021. keydown : me.onKeyDown
  139022. });
  139023. },
  139024. /**
  139025. * @private
  139026. * Given an `[x, y]` position within the slider's track (Points outside the slider's track are coerced to either the minimum or maximum value),
  139027. * calculate how many pixels **from the slider origin** (left for horizontal Sliders and bottom for vertical Sliders) that point is.
  139028. *
  139029. * If the point is outside the range of the Slider's track, the return value is `undefined`
  139030. * @param {Number[]} xy The point to calculate the track point for
  139031. */
  139032. getTrackpoint : function(xy) {
  139033. var me = this,
  139034. result,
  139035. positionProperty,
  139036. sliderTrack = me.innerEl,
  139037. trackLength;
  139038. if (me.vertical) {
  139039. positionProperty = 'top';
  139040. trackLength = sliderTrack.getHeight();
  139041. } else {
  139042. positionProperty = 'left';
  139043. trackLength = sliderTrack.getWidth();
  139044. }
  139045. result = Ext.Number.constrain(sliderTrack.translatePoints(xy)[positionProperty], 0, trackLength);
  139046. return me.vertical ? trackLength - result : result;
  139047. },
  139048. /**
  139049. * @private
  139050. * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
  139051. * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
  139052. * @param {Ext.EventObject} e The click event
  139053. */
  139054. onMouseDown : function(e) {
  139055. var me = this,
  139056. thumbClicked = false,
  139057. i = 0,
  139058. thumbs = me.thumbs,
  139059. len = thumbs.length,
  139060. trackPoint;
  139061. if (me.disabled) {
  139062. return;
  139063. }
  139064. //see if the click was on any of the thumbs
  139065. for (; i < len; i++) {
  139066. thumbClicked = thumbClicked || e.target == thumbs[i].el.dom;
  139067. }
  139068. if (me.clickToChange && !thumbClicked) {
  139069. trackPoint = me.getTrackpoint(e.getXY());
  139070. if (trackPoint !== undefined) {
  139071. me.onClickChange(trackPoint);
  139072. }
  139073. }
  139074. me.focus();
  139075. },
  139076. /**
  139077. * @private
  139078. * Moves the thumb to the indicated position.
  139079. * Only changes the value if the click was within this.clickRange.
  139080. * @param {Number} trackPoint local pixel offset **from the origin** (left for horizontal and bottom for vertical) along the Slider's axis at which the click event occured.
  139081. */
  139082. onClickChange : function(trackPoint) {
  139083. var me = this,
  139084. thumb, index;
  139085. // How far along the track *from the origin* was the click.
  139086. // If vertical, the origin is the bottom of the slider track.
  139087. //find the nearest thumb to the click event
  139088. thumb = me.getNearest(trackPoint);
  139089. if (!thumb.disabled) {
  139090. index = thumb.index;
  139091. me.setValue(index, Ext.util.Format.round(me.reversePixelValue(trackPoint), me.decimalPrecision), undefined, true);
  139092. }
  139093. },
  139094. /**
  139095. * @private
  139096. * Returns the nearest thumb to a click event, along with its distance
  139097. * @param {Number} trackPoint local pixel position along the Slider's axis to find the Thumb for
  139098. * @return {Object} The closest thumb object and its distance from the click event
  139099. */
  139100. getNearest: function(trackPoint) {
  139101. var me = this,
  139102. clickValue = me.reversePixelValue(trackPoint),
  139103. nearestDistance = (me.maxValue - me.minValue) + 5, //add a small fudge for the end of the slider
  139104. nearest = null,
  139105. thumbs = me.thumbs,
  139106. i = 0,
  139107. len = thumbs.length,
  139108. thumb,
  139109. value,
  139110. dist;
  139111. for (; i < len; i++) {
  139112. thumb = me.thumbs[i];
  139113. value = thumb.value;
  139114. dist = Math.abs(value - clickValue);
  139115. if (Math.abs(dist <= nearestDistance)) {
  139116. nearest = thumb;
  139117. nearestDistance = dist;
  139118. }
  139119. }
  139120. return nearest;
  139121. },
  139122. /**
  139123. * @private
  139124. * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
  139125. * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
  139126. * @param {Ext.EventObject} e The Event object
  139127. */
  139128. onKeyDown : function(e) {
  139129. /*
  139130. * The behaviour for keyboard handling with multiple thumbs is currently undefined.
  139131. * There's no real sane default for it, so leave it like this until we come up
  139132. * with a better way of doing it.
  139133. */
  139134. var me = this,
  139135. k,
  139136. val;
  139137. if(me.disabled || me.thumbs.length !== 1) {
  139138. e.preventDefault();
  139139. return;
  139140. }
  139141. k = e.getKey();
  139142. switch(k) {
  139143. case e.UP:
  139144. case e.RIGHT:
  139145. e.stopEvent();
  139146. val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;
  139147. me.setValue(0, val, undefined, true);
  139148. break;
  139149. case e.DOWN:
  139150. case e.LEFT:
  139151. e.stopEvent();
  139152. val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;
  139153. me.setValue(0, val, undefined, true);
  139154. break;
  139155. default:
  139156. e.preventDefault();
  139157. }
  139158. },
  139159. /**
  139160. * @private
  139161. * Returns a snapped, constrained value when given a desired value
  139162. * @param {Number} value Raw number value
  139163. * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
  139164. */
  139165. normalizeValue : function(v) {
  139166. var me = this,
  139167. Num = Ext.Number,
  139168. snapFn = Num[me.zeroBasedSnapping ? 'snap' : 'snapInRange'];
  139169. v = snapFn.call(Num, v, me.increment, me.minValue, me.maxValue);
  139170. v = Ext.util.Format.round(v, me.decimalPrecision);
  139171. v = Ext.Number.constrain(v, me.minValue, me.maxValue);
  139172. return v;
  139173. },
  139174. /**
  139175. * Sets the minimum value for the slider instance. If the current value is less than the minimum value, the current
  139176. * value will be changed.
  139177. * @param {Number} val The new minimum value
  139178. */
  139179. setMinValue : function(val) {
  139180. var me = this,
  139181. i = 0,
  139182. thumbs = me.thumbs,
  139183. len = thumbs.length,
  139184. t;
  139185. me.minValue = val;
  139186. if (me.rendered) {
  139187. me.inputEl.dom.setAttribute('aria-valuemin', val);
  139188. }
  139189. for (; i < len; ++i) {
  139190. t = thumbs[i];
  139191. t.value = t.value < val ? val : t.value;
  139192. }
  139193. me.syncThumbs();
  139194. },
  139195. /**
  139196. * Sets the maximum value for the slider instance. If the current value is more than the maximum value, the current
  139197. * value will be changed.
  139198. * @param {Number} val The new maximum value
  139199. */
  139200. setMaxValue : function(val) {
  139201. var me = this,
  139202. i = 0,
  139203. thumbs = me.thumbs,
  139204. len = thumbs.length,
  139205. t;
  139206. me.maxValue = val;
  139207. if (me.rendered) {
  139208. me.inputEl.dom.setAttribute('aria-valuemax', val);
  139209. }
  139210. for (; i < len; ++i) {
  139211. t = thumbs[i];
  139212. t.value = t.value > val ? val : t.value;
  139213. }
  139214. me.syncThumbs();
  139215. },
  139216. /**
  139217. * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
  139218. * maxValue.
  139219. * @param {Number} index Index of the thumb to move
  139220. * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
  139221. * @param {Boolean} [animate=true] Turn on or off animation
  139222. */
  139223. setValue : function(index, value, animate, changeComplete) {
  139224. var me = this,
  139225. thumb = me.thumbs[index];
  139226. // ensures value is contstrained and snapped
  139227. value = me.normalizeValue(value);
  139228. if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {
  139229. thumb.value = value;
  139230. if (me.rendered) {
  139231. // TODO this only handles a single value; need a solution for exposing multiple values to aria.
  139232. // Perhaps this should go on each thumb element rather than the outer element.
  139233. me.inputEl.set({
  139234. 'aria-valuenow': value,
  139235. 'aria-valuetext': value
  139236. });
  139237. thumb.move(me.calculateThumbPosition(value), Ext.isDefined(animate) ? animate !== false : me.animate);
  139238. me.fireEvent('change', me, value, thumb);
  139239. me.checkDirty();
  139240. if (changeComplete) {
  139241. me.fireEvent('changecomplete', me, value, thumb);
  139242. }
  139243. }
  139244. }
  139245. },
  139246. /**
  139247. * @private
  139248. * Given a value within this Slider's range, calculates a Thumb's percentage CSS position to map that value.
  139249. */
  139250. calculateThumbPosition : function(v) {
  139251. return (v - this.minValue) / (this.maxValue - this.minValue) * 100;
  139252. },
  139253. /**
  139254. * @private
  139255. * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
  139256. * the ratio is 2
  139257. * @return {Number} The ratio of pixels to mapped values
  139258. */
  139259. getRatio : function() {
  139260. var me = this,
  139261. trackLength = this.vertical ? this.innerEl.getHeight() : this.innerEl.getWidth(),
  139262. valueRange = this.maxValue - this.minValue;
  139263. return valueRange === 0 ? trackLength : (trackLength / valueRange);
  139264. },
  139265. /**
  139266. * @private
  139267. * Given a pixel location along the slider, returns the mapped slider value for that pixel.
  139268. * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reversePixelValue(50)
  139269. * returns 200
  139270. * @param {Number} pos The position along the slider to return a mapped value for
  139271. * @return {Number} The mapped value for the given position
  139272. */
  139273. reversePixelValue : function(pos) {
  139274. return this.minValue + (pos / this.getRatio());
  139275. },
  139276. /**
  139277. * @private
  139278. * Given a Thumb's percentage position along the slider, returns the mapped slider value for that pixel.
  139279. * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reversePercentageValue(25)
  139280. * returns 200
  139281. * @param {Number} pos The percentage along the slider track to return a mapped value for
  139282. * @return {Number} The mapped value for the given position
  139283. */
  139284. reversePercentageValue : function(pos) {
  139285. return this.minValue + (this.maxValue - this.minValue) * (pos / 100);
  139286. },
  139287. //private
  139288. onDisable: function() {
  139289. var me = this,
  139290. i = 0,
  139291. thumbs = me.thumbs,
  139292. len = thumbs.length,
  139293. thumb,
  139294. el,
  139295. xy;
  139296. me.callParent();
  139297. for (; i < len; i++) {
  139298. thumb = thumbs[i];
  139299. el = thumb.el;
  139300. thumb.disable();
  139301. if(Ext.isIE) {
  139302. //IE breaks when using overflow visible and opacity other than 1.
  139303. //Create a place holder for the thumb and display it.
  139304. xy = el.getXY();
  139305. el.hide();
  139306. me.innerEl.addCls(me.disabledCls).dom.disabled = true;
  139307. if (!me.thumbHolder) {
  139308. me.thumbHolder = me.endEl.createChild({cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls});
  139309. }
  139310. me.thumbHolder.show().setXY(xy);
  139311. }
  139312. }
  139313. },
  139314. //private
  139315. onEnable: function() {
  139316. var me = this,
  139317. i = 0,
  139318. thumbs = me.thumbs,
  139319. len = thumbs.length,
  139320. thumb,
  139321. el;
  139322. this.callParent();
  139323. for (; i < len; i++) {
  139324. thumb = thumbs[i];
  139325. el = thumb.el;
  139326. thumb.enable();
  139327. if (Ext.isIE) {
  139328. me.innerEl.removeCls(me.disabledCls).dom.disabled = false;
  139329. if (me.thumbHolder) {
  139330. me.thumbHolder.hide();
  139331. }
  139332. el.show();
  139333. me.syncThumbs();
  139334. }
  139335. }
  139336. },
  139337. /**
  139338. * Synchronizes thumbs position to the proper proportion of the total component width based on the current slider
  139339. * {@link #value}. This will be called automatically when the Slider is resized by a layout, but if it is rendered
  139340. * auto width, this method can be called from another resize handler to sync the Slider if necessary.
  139341. */
  139342. syncThumbs : function() {
  139343. if (this.rendered) {
  139344. var thumbs = this.thumbs,
  139345. length = thumbs.length,
  139346. i = 0;
  139347. for (; i < length; i++) {
  139348. thumbs[i].move(this.calculateThumbPosition(thumbs[i].value));
  139349. }
  139350. }
  139351. },
  139352. /**
  139353. * Returns the current value of the slider
  139354. * @param {Number} index The index of the thumb to return a value for
  139355. * @return {Number/Number[]} The current value of the slider at the given index, or an array of all thumb values if
  139356. * no index is given.
  139357. */
  139358. getValue : function(index) {
  139359. return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();
  139360. },
  139361. /**
  139362. * Returns an array of values - one for the location of each thumb
  139363. * @return {Number[]} The set of thumb values
  139364. */
  139365. getValues: function() {
  139366. var values = [],
  139367. i = 0,
  139368. thumbs = this.thumbs,
  139369. len = thumbs.length;
  139370. for (; i < len; i++) {
  139371. values.push(thumbs[i].value);
  139372. }
  139373. return values;
  139374. },
  139375. getSubmitValue: function() {
  139376. var me = this;
  139377. return (me.disabled || !me.submitValue) ? null : me.getValue();
  139378. },
  139379. reset: function() {
  139380. var me = this,
  139381. arr = [].concat(me.originalValue),
  139382. a = 0,
  139383. aLen = arr.length,
  139384. val;
  139385. for (; a < aLen; a++) {
  139386. val = arr[a];
  139387. me.setValue(a, val);
  139388. }
  139389. me.clearInvalid();
  139390. // delete here so we reset back to the original state
  139391. delete me.wasValid;
  139392. },
  139393. setReadOnly: function(readOnly){
  139394. var me = this,
  139395. thumbs = me.thumbs,
  139396. len = thumbs.length,
  139397. i = 0;
  139398. me.callParent(arguments);
  139399. readOnly = me.readOnly;
  139400. for (; i < len; ++i) {
  139401. if (readOnly) {
  139402. thumbs[i].disable();
  139403. } else {
  139404. thumbs[i].enable();
  139405. }
  139406. }
  139407. },
  139408. // private
  139409. beforeDestroy : function() {
  139410. var me = this,
  139411. thumbs = me.thumbs,
  139412. t = 0,
  139413. tLen = thumbs.length,
  139414. thumb;
  139415. Ext.destroy(me.innerEl, me.endEl, me.focusEl);
  139416. for (; t < tLen; t++) {
  139417. thumb = thumbs[t];
  139418. Ext.destroy(thumb);
  139419. }
  139420. me.callParent();
  139421. }
  139422. });
  139423. /**
  139424. * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
  139425. * and animation. Can be added as an item to any container.
  139426. *
  139427. * @example
  139428. * Ext.create('Ext.slider.Single', {
  139429. * width: 200,
  139430. * value: 50,
  139431. * increment: 10,
  139432. * minValue: 0,
  139433. * maxValue: 100,
  139434. * renderTo: Ext.getBody()
  139435. * });
  139436. *
  139437. * The class Ext.slider.Single is aliased to Ext.Slider for backwards compatibility.
  139438. */
  139439. Ext.define('Ext.slider.Single', {
  139440. extend: 'Ext.slider.Multi',
  139441. alias: ['widget.slider', 'widget.sliderfield'],
  139442. alternateClassName: ['Ext.Slider', 'Ext.form.SliderField', 'Ext.slider.SingleSlider', 'Ext.slider.Slider'],
  139443. /**
  139444. * Returns the current value of the slider
  139445. * @return {Number} The current value of the slider
  139446. */
  139447. getValue: function() {
  139448. // just returns the value of the first thumb, which should be the only one in a single slider
  139449. return this.callParent([0]);
  139450. },
  139451. /**
  139452. * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
  139453. * maxValue.
  139454. * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
  139455. * @param {Boolean} [animate] Turn on or off animation
  139456. */
  139457. setValue: function(value, animate) {
  139458. var args = arguments,
  139459. len = args.length;
  139460. // this is to maintain backwards compatiblity for sliders with only one thunb. Usually you must pass the thumb
  139461. // index to setValue, but if we only have one thumb we inject the index here first if given the multi-slider
  139462. // signature without the required index. The index will always be 0 for a single slider
  139463. if (len == 1 || (len <= 3 && typeof args[1] != 'number')) {
  139464. args = Ext.toArray(args);
  139465. args.unshift(0);
  139466. }
  139467. return this.callParent(args);
  139468. },
  139469. // private
  139470. getNearest : function(){
  139471. // Since there's only 1 thumb, it's always the nearest
  139472. return this.thumbs[0];
  139473. }
  139474. });
  139475. /**
  139476. * A Provider implementation which saves and retrieves state via cookies. The CookieProvider supports the usual cookie
  139477. * options, such as:
  139478. *
  139479. * - {@link #path}
  139480. * - {@link #expires}
  139481. * - {@link #domain}
  139482. * - {@link #secure}
  139483. *
  139484. * Example:
  139485. *
  139486. * var cp = Ext.create('Ext.state.CookieProvider', {
  139487. * path: "/cgi-bin/",
  139488. * expires: new Date(new Date().getTime()+(1000*60*60*24*30)), //30 days
  139489. * domain: "sencha.com"
  139490. * });
  139491. *
  139492. * Ext.state.Manager.setProvider(cp);
  139493. *
  139494. */
  139495. Ext.define('Ext.state.CookieProvider', {
  139496. extend: 'Ext.state.Provider',
  139497. /**
  139498. * @cfg {String} path
  139499. * The path for which the cookie is active. Defaults to root '/' which makes it active for all pages in the site.
  139500. */
  139501. /**
  139502. * @cfg {Date} expires
  139503. * The cookie expiration date. Defaults to 7 days from now.
  139504. */
  139505. /**
  139506. * @cfg {String} domain
  139507. * The domain to save the cookie for. Note that you cannot specify a different domain than your page is on, but you can
  139508. * specify a sub-domain, or simply the domain itself like 'sencha.com' to include all sub-domains if you need to access
  139509. * cookies across different sub-domains. Defaults to null which uses the same domain the page is running on including
  139510. * the 'www' like 'www.sencha.com'.
  139511. */
  139512. /**
  139513. * @cfg {Boolean} [secure=false]
  139514. * True if the site is using SSL
  139515. */
  139516. /**
  139517. * Creates a new CookieProvider.
  139518. * @param {Object} [config] Config object.
  139519. */
  139520. constructor : function(config){
  139521. var me = this;
  139522. me.path = "/";
  139523. me.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
  139524. me.domain = null;
  139525. me.secure = false;
  139526. me.callParent(arguments);
  139527. me.state = me.readCookies();
  139528. },
  139529. // private
  139530. set : function(name, value){
  139531. var me = this;
  139532. if(typeof value == "undefined" || value === null){
  139533. me.clear(name);
  139534. return;
  139535. }
  139536. me.setCookie(name, value);
  139537. me.callParent(arguments);
  139538. },
  139539. // private
  139540. clear : function(name){
  139541. this.clearCookie(name);
  139542. this.callParent(arguments);
  139543. },
  139544. // private
  139545. readCookies : function(){
  139546. var cookies = {},
  139547. c = document.cookie + ";",
  139548. re = /\s?(.*?)=(.*?);/g,
  139549. prefix = this.prefix,
  139550. len = prefix.length,
  139551. matches,
  139552. name,
  139553. value;
  139554. while((matches = re.exec(c)) != null){
  139555. name = matches[1];
  139556. value = matches[2];
  139557. if (name && name.substring(0, len) == prefix){
  139558. cookies[name.substr(len)] = this.decodeValue(value);
  139559. }
  139560. }
  139561. return cookies;
  139562. },
  139563. // private
  139564. setCookie : function(name, value){
  139565. var me = this;
  139566. document.cookie = me.prefix + name + "=" + me.encodeValue(value) +
  139567. ((me.expires == null) ? "" : ("; expires=" + me.expires.toGMTString())) +
  139568. ((me.path == null) ? "" : ("; path=" + me.path)) +
  139569. ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
  139570. ((me.secure == true) ? "; secure" : "");
  139571. },
  139572. // private
  139573. clearCookie : function(name){
  139574. var me = this;
  139575. document.cookie = me.prefix + name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
  139576. ((me.path == null) ? "" : ("; path=" + me.path)) +
  139577. ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
  139578. ((me.secure == true) ? "; secure" : "");
  139579. }
  139580. });
  139581. /**
  139582. * @class Ext.state.LocalStorageProvider
  139583. * A Provider implementation which saves and retrieves state via the HTML5 localStorage object.
  139584. * If the browser does not support local storage, an exception will be thrown upon instantiating
  139585. * this class.
  139586. */
  139587. Ext.define('Ext.state.LocalStorageProvider', {
  139588. /* Begin Definitions */
  139589. extend: 'Ext.state.Provider',
  139590. alias: 'state.localstorage',
  139591. /* End Definitions */
  139592. constructor: function(){
  139593. var me = this;
  139594. me.callParent(arguments);
  139595. me.store = me.getStorageObject();
  139596. me.state = me.readLocalStorage();
  139597. },
  139598. readLocalStorage: function(){
  139599. var store = this.store,
  139600. i = 0,
  139601. len = store.length,
  139602. prefix = this.prefix,
  139603. prefixLen = prefix.length,
  139604. data = {},
  139605. key;
  139606. for (; i < len; ++i) {
  139607. key = store.key(i);
  139608. if (key.substring(0, prefixLen) == prefix) {
  139609. data[key.substr(prefixLen)] = this.decodeValue(store.getItem(key));
  139610. }
  139611. }
  139612. return data;
  139613. },
  139614. set : function(name, value){
  139615. var me = this;
  139616. me.clear(name);
  139617. if (typeof value == "undefined" || value === null) {
  139618. return;
  139619. }
  139620. me.store.setItem(me.prefix + name, me.encodeValue(value));
  139621. me.callParent(arguments);
  139622. },
  139623. // private
  139624. clear : function(name){
  139625. this.store.removeItem(this.prefix + name);
  139626. this.callParent(arguments);
  139627. },
  139628. getStorageObject: function(){
  139629. try {
  139630. var supports = 'localStorage' in window && window['localStorage'] !== null;
  139631. if (supports) {
  139632. return window.localStorage;
  139633. }
  139634. } catch (e) {
  139635. return false;
  139636. }
  139637. Ext.Error.raise('LocalStorage is not supported by the current browser');
  139638. }
  139639. });
  139640. /**
  139641. * @author Ed Spencer
  139642. *
  139643. * Represents a single Tab in a {@link Ext.tab.Panel TabPanel}. A Tab is simply a slightly customized {@link Ext.button.Button Button},
  139644. * styled to look like a tab. Tabs are optionally closable, and can also be disabled. 99% of the time you will not
  139645. * need to create Tabs manually as the framework does so automatically when you use a {@link Ext.tab.Panel TabPanel}
  139646. */
  139647. Ext.define('Ext.tab.Tab', {
  139648. extend: 'Ext.button.Button',
  139649. alias: 'widget.tab',
  139650. requires: [
  139651. 'Ext.layout.component.Tab',
  139652. 'Ext.util.KeyNav'
  139653. ],
  139654. componentLayout: 'tab',
  139655. /**
  139656. * @property {Boolean} isTab
  139657. * `true` in this class to identify an object as an instantiated Tab, or subclass thereof.
  139658. */
  139659. isTab: true,
  139660. baseCls: Ext.baseCSSPrefix + 'tab',
  139661. /**
  139662. * @cfg {String} activeCls
  139663. * The CSS class to be applied to a Tab when it is active.
  139664. * Providing your own CSS for this class enables you to customize the active state.
  139665. */
  139666. activeCls: 'active',
  139667. /**
  139668. * @cfg {String} [disabledCls='x-tab-disabled']
  139669. * The CSS class to be applied to a Tab when it is disabled.
  139670. */
  139671. /**
  139672. * @cfg {String} closableCls
  139673. * The CSS class which is added to the tab when it is closable
  139674. */
  139675. closableCls: 'closable',
  139676. /**
  139677. * @cfg {Boolean} closable
  139678. * True to make the Tab start closable (the close icon will be visible).
  139679. */
  139680. closable: true,
  139681. //<locale>
  139682. /**
  139683. * @cfg {String} closeText
  139684. * The accessible text label for the close button link; only used when {@link #cfg-closable} = true.
  139685. */
  139686. closeText: 'Close Tab',
  139687. //</locale>
  139688. /**
  139689. * @property {Boolean} active
  139690. * Indicates that this tab is currently active. This is NOT a public configuration.
  139691. * @readonly
  139692. */
  139693. active: false,
  139694. /**
  139695. * @property {Boolean} closable
  139696. * True if the tab is currently closable
  139697. */
  139698. childEls: [
  139699. 'closeEl'
  139700. ],
  139701. scale: false,
  139702. position: 'top',
  139703. initComponent: function() {
  139704. var me = this;
  139705. me.addEvents(
  139706. /**
  139707. * @event activate
  139708. * Fired when the tab is activated.
  139709. * @param {Ext.tab.Tab} this
  139710. */
  139711. 'activate',
  139712. /**
  139713. * @event deactivate
  139714. * Fired when the tab is deactivated.
  139715. * @param {Ext.tab.Tab} this
  139716. */
  139717. 'deactivate',
  139718. /**
  139719. * @event beforeclose
  139720. * Fires if the user clicks on the Tab's close button, but before the {@link #close} event is fired. Return
  139721. * false from any listener to stop the close event being fired
  139722. * @param {Ext.tab.Tab} tab The Tab object
  139723. */
  139724. 'beforeclose',
  139725. /**
  139726. * @event close
  139727. * Fires to indicate that the tab is to be closed, usually because the user has clicked the close button.
  139728. * @param {Ext.tab.Tab} tab The Tab object
  139729. */
  139730. 'close'
  139731. );
  139732. me.callParent(arguments);
  139733. if (me.card) {
  139734. me.setCard(me.card);
  139735. }
  139736. },
  139737. getTemplateArgs: function() {
  139738. var me = this,
  139739. result = me.callParent();
  139740. result.closable = me.closable;
  139741. result.closeText = me.closeText;
  139742. return result;
  139743. },
  139744. beforeRender: function() {
  139745. var me = this,
  139746. tabBar = me.up('tabbar'),
  139747. tabPanel = me.up('tabpanel');
  139748. me.callParent();
  139749. me.addClsWithUI(me.position);
  139750. // Set all the state classNames, as they need to include the UI
  139751. // me.disabledCls = me.getClsWithUIs('disabled');
  139752. me.syncClosableUI();
  139753. // Propagate minTabWidth and maxTabWidth settings from the owning TabBar then TabPanel
  139754. if (!me.minWidth) {
  139755. me.minWidth = (tabBar) ? tabBar.minTabWidth : me.minWidth;
  139756. if (!me.minWidth && tabPanel) {
  139757. me.minWidth = tabPanel.minTabWidth;
  139758. }
  139759. if (me.minWidth && me.iconCls) {
  139760. me.minWidth += 25;
  139761. }
  139762. }
  139763. if (!me.maxWidth) {
  139764. me.maxWidth = (tabBar) ? tabBar.maxTabWidth : me.maxWidth;
  139765. if (!me.maxWidth && tabPanel) {
  139766. me.maxWidth = tabPanel.maxTabWidth;
  139767. }
  139768. }
  139769. },
  139770. onRender: function() {
  139771. var me = this;
  139772. me.callParent(arguments);
  139773. me.keyNav = new Ext.util.KeyNav(me.el, {
  139774. enter: me.onEnterKey,
  139775. del: me.onDeleteKey,
  139776. scope: me
  139777. });
  139778. },
  139779. // inherit docs
  139780. enable : function(silent) {
  139781. var me = this;
  139782. me.callParent(arguments);
  139783. me.removeClsWithUI(me.position + '-disabled');
  139784. return me;
  139785. },
  139786. // inherit docs
  139787. disable : function(silent) {
  139788. var me = this;
  139789. me.callParent(arguments);
  139790. me.addClsWithUI(me.position + '-disabled');
  139791. return me;
  139792. },
  139793. onDestroy: function() {
  139794. var me = this;
  139795. Ext.destroy(me.keyNav);
  139796. delete me.keyNav;
  139797. me.callParent(arguments);
  139798. },
  139799. /**
  139800. * Sets the tab as either closable or not.
  139801. * @param {Boolean} closable Pass false to make the tab not closable. Otherwise the tab will be made closable (eg a
  139802. * close button will appear on the tab)
  139803. */
  139804. setClosable: function(closable) {
  139805. var me = this;
  139806. // Closable must be true if no args
  139807. closable = (!arguments.length || !!closable);
  139808. if (me.closable != closable) {
  139809. me.closable = closable;
  139810. // set property on the user-facing item ('card'):
  139811. if (me.card) {
  139812. me.card.closable = closable;
  139813. }
  139814. me.syncClosableUI();
  139815. if (me.rendered) {
  139816. me.syncClosableElements();
  139817. // Tab will change width to accommodate close icon
  139818. me.updateLayout();
  139819. }
  139820. }
  139821. },
  139822. /**
  139823. * This method ensures that the closeBtn element exists or not based on 'closable'.
  139824. * @private
  139825. */
  139826. syncClosableElements: function () {
  139827. var me = this,
  139828. closeEl = me.closeEl;
  139829. if (me.closable) {
  139830. if (!closeEl) {
  139831. me.closeEl = me.btnWrap.insertSibling({
  139832. tag: 'a',
  139833. cls: me.baseCls + '-close-btn',
  139834. href: '#',
  139835. title: me.closeText
  139836. }, 'after');
  139837. }
  139838. } else if (closeEl) {
  139839. closeEl.remove();
  139840. delete me.closeEl;
  139841. }
  139842. },
  139843. /**
  139844. * This method ensures that the UI classes are added or removed based on 'closable'.
  139845. * @private
  139846. */
  139847. syncClosableUI: function () {
  139848. var me = this,
  139849. classes = [me.closableCls, me.closableCls + '-' + me.position];
  139850. if (me.closable) {
  139851. me.addClsWithUI(classes);
  139852. } else {
  139853. me.removeClsWithUI(classes);
  139854. }
  139855. },
  139856. /**
  139857. * Sets this tab's attached card. Usually this is handled automatically by the {@link Ext.tab.Panel} that this Tab
  139858. * belongs to and would not need to be done by the developer
  139859. * @param {Ext.Component} card The card to set
  139860. */
  139861. setCard: function(card) {
  139862. var me = this;
  139863. me.card = card;
  139864. me.setText(me.title || card.title);
  139865. me.setIconCls(me.iconCls || card.iconCls);
  139866. me.setIcon(me.icon || card.icon);
  139867. },
  139868. /**
  139869. * @private
  139870. * Listener attached to click events on the Tab's close button
  139871. */
  139872. onCloseClick: function() {
  139873. var me = this;
  139874. if (me.fireEvent('beforeclose', me) !== false) {
  139875. if (me.tabBar) {
  139876. if (me.tabBar.closeTab(me) === false) {
  139877. // beforeclose on the panel vetoed the event, stop here
  139878. return;
  139879. }
  139880. } else {
  139881. // if there's no tabbar, fire the close event
  139882. me.fireClose();
  139883. }
  139884. }
  139885. },
  139886. /**
  139887. * Fires the close event on the tab.
  139888. * @private
  139889. */
  139890. fireClose: function(){
  139891. this.fireEvent('close', this);
  139892. },
  139893. /**
  139894. * @private
  139895. */
  139896. onEnterKey: function(e) {
  139897. var me = this;
  139898. if (me.tabBar) {
  139899. me.tabBar.onClick(e, me.el);
  139900. }
  139901. },
  139902. /**
  139903. * @private
  139904. */
  139905. onDeleteKey: function(e) {
  139906. if (this.closable) {
  139907. this.onCloseClick();
  139908. }
  139909. },
  139910. // @private
  139911. activate : function(supressEvent) {
  139912. var me = this;
  139913. me.active = true;
  139914. me.addClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
  139915. if (supressEvent !== true) {
  139916. me.fireEvent('activate', me);
  139917. }
  139918. },
  139919. // @private
  139920. deactivate : function(supressEvent) {
  139921. var me = this;
  139922. me.active = false;
  139923. me.removeClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
  139924. if (supressEvent !== true) {
  139925. me.fireEvent('deactivate', me);
  139926. }
  139927. }
  139928. });
  139929. /**
  139930. * @author Ed Spencer
  139931. * TabBar is used internally by a {@link Ext.tab.Panel TabPanel} and typically should not need to be created manually.
  139932. * The tab bar automatically removes the default title provided by {@link Ext.panel.Header}
  139933. */
  139934. Ext.define('Ext.tab.Bar', {
  139935. extend: 'Ext.panel.Header',
  139936. alias: 'widget.tabbar',
  139937. baseCls: Ext.baseCSSPrefix + 'tab-bar',
  139938. requires: ['Ext.tab.Tab'],
  139939. /**
  139940. * @property {Boolean} isTabBar
  139941. * `true` in this class to identify an objact as an instantiated Tab Bar, or subclass thereof.
  139942. */
  139943. isTabBar: true,
  139944. /**
  139945. * @cfg {String} title @hide
  139946. */
  139947. /**
  139948. * @cfg {String} iconCls @hide
  139949. */
  139950. // @private
  139951. defaultType: 'tab',
  139952. /**
  139953. * @cfg {Boolean} plain
  139954. * True to not show the full background on the tabbar
  139955. */
  139956. plain: false,
  139957. childEls: [
  139958. 'body', 'strip'
  139959. ],
  139960. // @private
  139961. renderTpl: [
  139962. '<div id="{id}-body" class="{baseCls}-body {bodyCls}<tpl if="ui"> {baseCls}-body-{ui}<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl></tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>>',
  139963. '{%this.renderContainer(out,values)%}',
  139964. '</div>',
  139965. '<div id="{id}-strip" class="{baseCls}-strip<tpl if="ui"> {baseCls}-strip-{ui}<tpl for="uiCls"> {parent.baseCls}-strip-{parent.ui}-{.}</tpl></tpl>"></div>'
  139966. ],
  139967. /**
  139968. * @cfg {Number} minTabWidth
  139969. * The minimum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#minTabWidth minTabWidth} value.
  139970. * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#minTabWidth minTabWidth} config on the TabPanel.
  139971. */
  139972. /**
  139973. * @cfg {Number} maxTabWidth
  139974. * The maximum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#maxTabWidth maxTabWidth} value.
  139975. * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#maxTabWidth maxTabWidth} config on the TabPanel.
  139976. */
  139977. // @private
  139978. initComponent: function() {
  139979. var me = this;
  139980. if (me.plain) {
  139981. me.setUI(me.ui + '-plain');
  139982. }
  139983. me.addClsWithUI(me.dock);
  139984. me.addEvents(
  139985. /**
  139986. * @event change
  139987. * Fired when the currently-active tab has changed
  139988. * @param {Ext.tab.Bar} tabBar The TabBar
  139989. * @param {Ext.tab.Tab} tab The new Tab
  139990. * @param {Ext.Component} card The card that was just shown in the TabPanel
  139991. */
  139992. 'change'
  139993. );
  139994. // Element onClick listener added by Header base class
  139995. me.callParent(arguments);
  139996. // TabBar must override the Header's align setting.
  139997. me.layout.align = (me.orientation == 'vertical') ? 'left' : 'top';
  139998. me.layout.overflowHandler = new Ext.layout.container.boxOverflow.Scroller(me.layout);
  139999. me.remove(me.titleCmp);
  140000. delete me.titleCmp;
  140001. Ext.apply(me.renderData, {
  140002. bodyCls: me.bodyCls
  140003. });
  140004. },
  140005. getLayout: function() {
  140006. var me = this;
  140007. me.layout.type = (me.dock === 'top' || me.dock === 'bottom') ? 'hbox' : 'vbox';
  140008. return me.callParent(arguments);
  140009. },
  140010. // @private
  140011. onAdd: function(tab) {
  140012. tab.position = this.dock;
  140013. this.callParent(arguments);
  140014. },
  140015. onRemove: function(tab) {
  140016. var me = this;
  140017. if (tab === me.previousTab) {
  140018. me.previousTab = null;
  140019. }
  140020. me.callParent(arguments);
  140021. },
  140022. afterComponentLayout : function(width) {
  140023. this.callParent(arguments);
  140024. this.strip.setWidth(width);
  140025. },
  140026. // @private
  140027. onClick: function(e, target) {
  140028. // The target might not be a valid tab el.
  140029. var me = this,
  140030. tabEl = e.getTarget('.' + Ext.tab.Tab.prototype.baseCls),
  140031. tab = tabEl && Ext.getCmp(tabEl.id),
  140032. tabPanel = me.tabPanel,
  140033. isCloseClick = tab && tab.closeEl && (target === tab.closeEl.dom);
  140034. if (isCloseClick) {
  140035. e.preventDefault();
  140036. }
  140037. if (tab && tab.isDisabled && !tab.isDisabled()) {
  140038. if (tab.closable && isCloseClick) {
  140039. tab.onCloseClick();
  140040. } else {
  140041. if (tabPanel) {
  140042. // TabPanel will card setActiveTab of the TabBar
  140043. tabPanel.setActiveTab(tab.card);
  140044. } else {
  140045. me.setActiveTab(tab);
  140046. }
  140047. tab.focus();
  140048. }
  140049. }
  140050. },
  140051. /**
  140052. * @private
  140053. * Closes the given tab by removing it from the TabBar and removing the corresponding card from the TabPanel
  140054. * @param {Ext.tab.Tab} toClose The tab to close
  140055. */
  140056. closeTab: function(toClose) {
  140057. var me = this,
  140058. card = toClose.card,
  140059. tabPanel = me.tabPanel,
  140060. toActivate;
  140061. if (card && card.fireEvent('beforeclose', card) === false) {
  140062. return false;
  140063. }
  140064. // If we are closing the active tab, revert to the previously active tab (or the previous or next enabled sibling if
  140065. // there *is* no previously active tab, or the previously active tab is the one that's being closed or the previously
  140066. // active tab has since been disabled)
  140067. toActivate = me.findNextActivatable(toClose);
  140068. // We are going to remove the associated card, and then, if that was sucessful, remove the Tab,
  140069. // And then potentially activate another Tab. We should not layout for each of these operations.
  140070. Ext.suspendLayouts();
  140071. if (tabPanel && card) {
  140072. // Remove the ownerCt so the tab doesn't get destroyed if the remove is successful
  140073. // We need this so we can have the tab fire it's own close event.
  140074. delete toClose.ownerCt;
  140075. // we must fire 'close' before removing the card from panel, otherwise
  140076. // the event will no loger have any listener
  140077. card.fireEvent('close', card);
  140078. tabPanel.remove(card);
  140079. // Remove succeeded
  140080. if (!tabPanel.getComponent(card)) {
  140081. /*
  140082. * Force the close event to fire. By the time this function returns,
  140083. * the tab is already destroyed and all listeners have been purged
  140084. * so the tab can't fire itself.
  140085. */
  140086. toClose.fireClose();
  140087. me.remove(toClose);
  140088. } else {
  140089. // Restore the ownerCt from above
  140090. toClose.ownerCt = me;
  140091. Ext.resumeLayouts(true);
  140092. return false;
  140093. }
  140094. }
  140095. // If we are closing the active tab, revert to the previously active tab (or the previous sibling or the nnext sibling)
  140096. if (toActivate) {
  140097. // Our owning TabPanel calls our setActiveTab method, so only call that if this Bar is being used
  140098. // in some other context (unlikely)
  140099. if (tabPanel) {
  140100. tabPanel.setActiveTab(toActivate.card);
  140101. } else {
  140102. me.setActiveTab(toActivate);
  140103. }
  140104. toActivate.focus();
  140105. }
  140106. Ext.resumeLayouts(true);
  140107. },
  140108. // private - used by TabPanel too.
  140109. // Works out the next tab to activate when one tab is closed.
  140110. findNextActivatable: function(toClose) {
  140111. var me = this;
  140112. if (toClose.active && me.items.getCount() > 1) {
  140113. return (me.previousTab && me.previousTab !== toClose && !me.previousTab.disabled) ? me.previousTab : (toClose.next('tab[disabled=false]') || toClose.prev('tab[disabled=false]'));
  140114. }
  140115. },
  140116. /**
  140117. * @private
  140118. * Marks the given tab as active
  140119. * @param {Ext.tab.Tab} tab The tab to mark active
  140120. */
  140121. setActiveTab: function(tab) {
  140122. var me = this;
  140123. if (!tab.disabled && tab !== me.activeTab) {
  140124. if (me.activeTab) {
  140125. if (me.activeTab.isDestroyed) {
  140126. me.previousTab = null;
  140127. } else {
  140128. me.previousTab = me.activeTab;
  140129. me.activeTab.deactivate();
  140130. }
  140131. }
  140132. tab.activate();
  140133. me.activeTab = tab;
  140134. me.fireEvent('change', me, tab, tab.card);
  140135. // Ensure that after the currently in progress layout, the active tab is scrolled into view
  140136. me.on({
  140137. afterlayout: me.afterTabActivate,
  140138. scope: me,
  140139. single: true
  140140. });
  140141. me.updateLayout();
  140142. }
  140143. },
  140144. afterTabActivate: function() {
  140145. this.layout.overflowHandler.scrollToItem(this.activeTab);
  140146. }
  140147. });
  140148. /**
  140149. * @author Ed Spencer, Tommy Maintz, Brian Moeskau
  140150. *
  140151. * A basic tab container. TabPanels can be used exactly like a standard {@link Ext.panel.Panel} for
  140152. * layout purposes, but also have special support for containing child Components
  140153. * (`{@link Ext.container.Container#cfg-items items}`) that are managed using a
  140154. * {@link Ext.layout.container.Card CardLayout layout manager}, and displayed as separate tabs.
  140155. *
  140156. * **Note:** By default, a tab's close tool _destroys_ the child tab Component and all its descendants.
  140157. * This makes the child tab Component, and all its descendants **unusable**. To enable re-use of a tab,
  140158. * configure the TabPanel with `{@link #autoDestroy autoDestroy: false}`.
  140159. *
  140160. * ## TabPanel's layout
  140161. *
  140162. * TabPanels use a Dock layout to position the {@link Ext.tab.Bar TabBar} at the top of the widget.
  140163. * Panels added to the TabPanel will have their header hidden by default because the Tab will
  140164. * automatically take the Panel's configured title and icon.
  140165. *
  140166. * TabPanels use their {@link Ext.panel.Header header} or {@link Ext.panel.Panel#fbar footer}
  140167. * element (depending on the {@link #tabPosition} configuration) to accommodate the tab selector buttons.
  140168. * This means that a TabPanel will not display any configured title, and will not display any configured
  140169. * header {@link Ext.panel.Panel#tools tools}.
  140170. *
  140171. * To display a header, embed the TabPanel in a {@link Ext.panel.Panel Panel} which uses
  140172. * `{@link Ext.container.Container#layout layout: 'fit'}`.
  140173. *
  140174. * ## Controlling tabs
  140175. *
  140176. * Configuration options for the {@link Ext.tab.Tab} that represents the component can be passed in
  140177. * by specifying the tabConfig option:
  140178. *
  140179. * @example
  140180. * Ext.create('Ext.tab.Panel', {
  140181. * width: 400,
  140182. * height: 400,
  140183. * renderTo: document.body,
  140184. * items: [{
  140185. * title: 'Foo'
  140186. * }, {
  140187. * title: 'Bar',
  140188. * tabConfig: {
  140189. * title: 'Custom Title',
  140190. * tooltip: 'A button tooltip'
  140191. * }
  140192. * }]
  140193. * });
  140194. *
  140195. * # Examples
  140196. *
  140197. * Here is a basic TabPanel rendered to the body. This also shows the useful configuration {@link #activeTab},
  140198. * which allows you to set the active tab on render. If you do not set an {@link #activeTab}, no tabs will be
  140199. * active by default.
  140200. *
  140201. * @example
  140202. * Ext.create('Ext.tab.Panel', {
  140203. * width: 300,
  140204. * height: 200,
  140205. * activeTab: 0,
  140206. * items: [
  140207. * {
  140208. * title: 'Tab 1',
  140209. * bodyPadding: 10,
  140210. * html : 'A simple tab'
  140211. * },
  140212. * {
  140213. * title: 'Tab 2',
  140214. * html : 'Another one'
  140215. * }
  140216. * ],
  140217. * renderTo : Ext.getBody()
  140218. * });
  140219. *
  140220. * It is easy to control the visibility of items in the tab bar. Specify hidden: true to have the
  140221. * tab button hidden initially. Items can be subsequently hidden and show by accessing the
  140222. * tab property on the child item.
  140223. *
  140224. * @example
  140225. * var tabs = Ext.create('Ext.tab.Panel', {
  140226. * width: 400,
  140227. * height: 400,
  140228. * renderTo: document.body,
  140229. * items: [{
  140230. * title: 'Home',
  140231. * html: 'Home',
  140232. * itemId: 'home'
  140233. * }, {
  140234. * title: 'Users',
  140235. * html: 'Users',
  140236. * itemId: 'users',
  140237. * hidden: true
  140238. * }, {
  140239. * title: 'Tickets',
  140240. * html: 'Tickets',
  140241. * itemId: 'tickets'
  140242. * }]
  140243. * });
  140244. *
  140245. * setTimeout(function(){
  140246. * tabs.child('#home').tab.hide();
  140247. * var users = tabs.child('#users');
  140248. * users.tab.show();
  140249. * tabs.setActiveTab(users);
  140250. * }, 1000);
  140251. *
  140252. * You can remove the background of the TabBar by setting the {@link #plain} property to `true`.
  140253. *
  140254. * @example
  140255. * Ext.create('Ext.tab.Panel', {
  140256. * width: 300,
  140257. * height: 200,
  140258. * activeTab: 0,
  140259. * plain: true,
  140260. * items: [
  140261. * {
  140262. * title: 'Tab 1',
  140263. * bodyPadding: 10,
  140264. * html : 'A simple tab'
  140265. * },
  140266. * {
  140267. * title: 'Tab 2',
  140268. * html : 'Another one'
  140269. * }
  140270. * ],
  140271. * renderTo : Ext.getBody()
  140272. * });
  140273. *
  140274. * Another useful configuration of TabPanel is {@link #tabPosition}. This allows you to change the
  140275. * position where the tabs are displayed. The available options for this are `'top'` (default) and
  140276. * `'bottom'`.
  140277. *
  140278. * @example
  140279. * Ext.create('Ext.tab.Panel', {
  140280. * width: 300,
  140281. * height: 200,
  140282. * activeTab: 0,
  140283. * bodyPadding: 10,
  140284. * tabPosition: 'bottom',
  140285. * items: [
  140286. * {
  140287. * title: 'Tab 1',
  140288. * html : 'A simple tab'
  140289. * },
  140290. * {
  140291. * title: 'Tab 2',
  140292. * html : 'Another one'
  140293. * }
  140294. * ],
  140295. * renderTo : Ext.getBody()
  140296. * });
  140297. *
  140298. * The {@link #setActiveTab} is a very useful method in TabPanel which will allow you to change the
  140299. * current active tab. You can either give it an index or an instance of a tab. For example:
  140300. *
  140301. * @example
  140302. * var tabs = Ext.create('Ext.tab.Panel', {
  140303. * items: [
  140304. * {
  140305. * id : 'my-tab',
  140306. * title: 'Tab 1',
  140307. * html : 'A simple tab'
  140308. * },
  140309. * {
  140310. * title: 'Tab 2',
  140311. * html : 'Another one'
  140312. * }
  140313. * ],
  140314. * renderTo : Ext.getBody()
  140315. * });
  140316. *
  140317. * var tab = Ext.getCmp('my-tab');
  140318. *
  140319. * Ext.create('Ext.button.Button', {
  140320. * renderTo: Ext.getBody(),
  140321. * text : 'Select the first tab',
  140322. * scope : this,
  140323. * handler : function() {
  140324. * tabs.setActiveTab(tab);
  140325. * }
  140326. * });
  140327. *
  140328. * Ext.create('Ext.button.Button', {
  140329. * text : 'Select the second tab',
  140330. * scope : this,
  140331. * handler : function() {
  140332. * tabs.setActiveTab(1);
  140333. * },
  140334. * renderTo : Ext.getBody()
  140335. * });
  140336. *
  140337. * The {@link #getActiveTab} is a another useful method in TabPanel which will return the current active tab.
  140338. *
  140339. * @example
  140340. * var tabs = Ext.create('Ext.tab.Panel', {
  140341. * items: [
  140342. * {
  140343. * title: 'Tab 1',
  140344. * html : 'A simple tab'
  140345. * },
  140346. * {
  140347. * title: 'Tab 2',
  140348. * html : 'Another one'
  140349. * }
  140350. * ],
  140351. * renderTo : Ext.getBody()
  140352. * });
  140353. *
  140354. * Ext.create('Ext.button.Button', {
  140355. * text : 'Get active tab',
  140356. * scope : this,
  140357. * handler : function() {
  140358. * var tab = tabs.getActiveTab();
  140359. * alert('Current tab: ' + tab.title);
  140360. * },
  140361. * renderTo : Ext.getBody()
  140362. * });
  140363. *
  140364. * Adding a new tab is very simple with a TabPanel. You simple call the {@link #method-add} method with an config
  140365. * object for a panel.
  140366. *
  140367. * @example
  140368. * var tabs = Ext.create('Ext.tab.Panel', {
  140369. * items: [
  140370. * {
  140371. * title: 'Tab 1',
  140372. * html : 'A simple tab'
  140373. * },
  140374. * {
  140375. * title: 'Tab 2',
  140376. * html : 'Another one'
  140377. * }
  140378. * ],
  140379. * renderTo : Ext.getBody()
  140380. * });
  140381. *
  140382. * Ext.create('Ext.button.Button', {
  140383. * text : 'New tab',
  140384. * scope : this,
  140385. * handler : function() {
  140386. * var tab = tabs.add({
  140387. * // we use the tabs.items property to get the length of current items/tabs
  140388. * title: 'Tab ' + (tabs.items.length + 1),
  140389. * html : 'Another one'
  140390. * });
  140391. *
  140392. * tabs.setActiveTab(tab);
  140393. * },
  140394. * renderTo : Ext.getBody()
  140395. * });
  140396. *
  140397. * Additionally, removing a tab is very also simple with a TabPanel. You simple call the {@link #method-remove} method
  140398. * with an config object for a panel.
  140399. *
  140400. * @example
  140401. * var tabs = Ext.create('Ext.tab.Panel', {
  140402. * items: [
  140403. * {
  140404. * title: 'Tab 1',
  140405. * html : 'A simple tab'
  140406. * },
  140407. * {
  140408. * id : 'remove-this-tab',
  140409. * title: 'Tab 2',
  140410. * html : 'Another one'
  140411. * }
  140412. * ],
  140413. * renderTo : Ext.getBody()
  140414. * });
  140415. *
  140416. * Ext.create('Ext.button.Button', {
  140417. * text : 'Remove tab',
  140418. * scope : this,
  140419. * handler : function() {
  140420. * var tab = Ext.getCmp('remove-this-tab');
  140421. * tabs.remove(tab);
  140422. * },
  140423. * renderTo : Ext.getBody()
  140424. * });
  140425. */
  140426. Ext.define('Ext.tab.Panel', {
  140427. extend: 'Ext.panel.Panel',
  140428. alias: 'widget.tabpanel',
  140429. alternateClassName: ['Ext.TabPanel'],
  140430. requires: ['Ext.layout.container.Card', 'Ext.tab.Bar'],
  140431. /**
  140432. * @cfg {String} tabPosition
  140433. * The position where the tab strip should be rendered. Can be `top` or `bottom`.
  140434. */
  140435. tabPosition : 'top',
  140436. /**
  140437. * @cfg {String/Number} activeItem
  140438. * Doesn't apply for {@link Ext.tab.Panel TabPanel}, use {@link #activeTab} instead.
  140439. */
  140440. /**
  140441. * @cfg {String/Number/Ext.Component} activeTab
  140442. * The tab to activate initially. Either an ID, index or the tab component itself.
  140443. */
  140444. /**
  140445. * @cfg {Object} tabBar
  140446. * Optional configuration object for the internal {@link Ext.tab.Bar}.
  140447. * If present, this is passed straight through to the TabBar's constructor
  140448. */
  140449. /**
  140450. * @cfg {Object} layout
  140451. * Optional configuration object for the internal {@link Ext.layout.container.Card card layout}.
  140452. * If present, this is passed straight through to the layout's constructor
  140453. */
  140454. /**
  140455. * @cfg {Boolean} removePanelHeader
  140456. * True to instruct each Panel added to the TabContainer to not render its header element.
  140457. * This is to ensure that the title of the panel does not appear twice.
  140458. */
  140459. removePanelHeader: true,
  140460. /**
  140461. * @cfg {Boolean} plain
  140462. * True to not show the full background on the TabBar.
  140463. */
  140464. plain: false,
  140465. /**
  140466. * @cfg {String} [itemCls='x-tabpanel-child']
  140467. * The class added to each child item of this TabPanel.
  140468. */
  140469. itemCls: Ext.baseCSSPrefix + 'tabpanel-child',
  140470. /**
  140471. * @cfg {Number} minTabWidth
  140472. * The minimum width for a tab in the {@link #cfg-tabBar}.
  140473. */
  140474. minTabWidth: undefined,
  140475. /**
  140476. * @cfg {Number} maxTabWidth The maximum width for each tab.
  140477. */
  140478. maxTabWidth: undefined,
  140479. /**
  140480. * @cfg {Boolean} deferredRender
  140481. *
  140482. * True by default to defer the rendering of child {@link Ext.container.Container#cfg-items items} to the browsers DOM
  140483. * until a tab is activated. False will render all contained {@link Ext.container.Container#cfg-items items} as soon as
  140484. * the {@link Ext.layout.container.Card layout} is rendered. If there is a significant amount of content or a lot of
  140485. * heavy controls being rendered into panels that are not displayed by default, setting this to true might improve
  140486. * performance.
  140487. *
  140488. * The deferredRender property is internally passed to the layout manager for TabPanels ({@link
  140489. * Ext.layout.container.Card}) as its {@link Ext.layout.container.Card#deferredRender} configuration value.
  140490. *
  140491. * **Note**: leaving deferredRender as true means that the content within an unactivated tab will not be available
  140492. */
  140493. deferredRender : true,
  140494. //inherit docs
  140495. initComponent: function() {
  140496. var me = this,
  140497. dockedItems = [].concat(me.dockedItems || []),
  140498. activeTab = me.activeTab || (me.activeTab = 0);
  140499. // Configure the layout with our deferredRender, and with our activeTeb
  140500. me.layout = new Ext.layout.container.Card(Ext.apply({
  140501. owner: me,
  140502. deferredRender: me.deferredRender,
  140503. itemCls: me.itemCls,
  140504. activeItem: me.activeTab
  140505. }, me.layout));
  140506. /**
  140507. * @property {Ext.tab.Bar} tabBar Internal reference to the docked TabBar
  140508. */
  140509. me.tabBar = new Ext.tab.Bar(Ext.apply({
  140510. dock: me.tabPosition,
  140511. plain: me.plain,
  140512. border: me.border,
  140513. cardLayout: me.layout,
  140514. tabPanel: me
  140515. }, me.tabBar));
  140516. dockedItems.push(me.tabBar);
  140517. me.dockedItems = dockedItems;
  140518. me.addEvents(
  140519. /**
  140520. * @event
  140521. * Fires before a tab change (activated by {@link #setActiveTab}). Return false in any listener to cancel
  140522. * the tabchange
  140523. * @param {Ext.tab.Panel} tabPanel The TabPanel
  140524. * @param {Ext.Component} newCard The card that is about to be activated
  140525. * @param {Ext.Component} oldCard The card that is currently active
  140526. */
  140527. 'beforetabchange',
  140528. /**
  140529. * @event
  140530. * Fires when a new tab has been activated (activated by {@link #setActiveTab}).
  140531. * @param {Ext.tab.Panel} tabPanel The TabPanel
  140532. * @param {Ext.Component} newCard The newly activated item
  140533. * @param {Ext.Component} oldCard The previously active item
  140534. */
  140535. 'tabchange'
  140536. );
  140537. me.callParent(arguments);
  140538. // We have to convert the numeric index/string ID config into its component reference
  140539. me.activeTab = me.getComponent(activeTab);
  140540. // Ensure that the active child's tab is rendered in the active UI state
  140541. if (me.activeTab) {
  140542. me.activeTab.tab.activate(true);
  140543. // So that it knows what to deactivate in subsequent tab changes
  140544. me.tabBar.activeTab = me.activeTab.tab;
  140545. }
  140546. },
  140547. /**
  140548. * Makes the given card active. Makes it the visible card in the TabPanel's CardLayout and highlights the Tab.
  140549. * @param {String/Number/Ext.Component} card The card to make active. Either an ID, index or the component itself.
  140550. * @return {Ext.Component} The resulting active child Component. The call may have been vetoed, or otherwise
  140551. * modified by an event listener.
  140552. */
  140553. setActiveTab: function(card) {
  140554. var me = this,
  140555. previous;
  140556. card = me.getComponent(card);
  140557. if (card) {
  140558. previous = me.getActiveTab();
  140559. if (previous !== card && me.fireEvent('beforetabchange', me, card, previous) === false) {
  140560. return false;
  140561. }
  140562. // We may be passed a config object, so add it.
  140563. // Without doing a layout!
  140564. if (!card.isComponent) {
  140565. Ext.suspendLayouts();
  140566. card = me.add(card);
  140567. Ext.resumeLayouts();
  140568. }
  140569. // MUST set the activeTab first so that the machinery which listens for show doesn't
  140570. // think that the show is "driving" the activation and attempt to recurse into here.
  140571. me.activeTab = card;
  140572. // Attempt to switch to the requested card. Suspend layouts because if that was successful
  140573. // we have to also update the active tab in the tab bar which is another layout operation
  140574. // and we must coalesce them.
  140575. Ext.suspendLayouts();
  140576. me.layout.setActiveItem(card);
  140577. // Read the result of the card layout. Events dear boy, events!
  140578. card = me.activeTab = me.layout.getActiveItem();
  140579. // Card switch was not vetoed by an event listener
  140580. if (card && card !== previous) {
  140581. // Update the active tab in the tab bar and resume layouts.
  140582. me.tabBar.setActiveTab(card.tab);
  140583. Ext.resumeLayouts(true);
  140584. // previous will be undefined or this.activeTab at instantiation
  140585. if (previous !== card) {
  140586. me.fireEvent('tabchange', me, card, previous);
  140587. }
  140588. }
  140589. // Card switch was vetoed by an event listener. Resume layouts (Nothing should have changed on a veto).
  140590. else {
  140591. Ext.resumeLayouts(true);
  140592. }
  140593. return card;
  140594. }
  140595. },
  140596. /**
  140597. * Returns the item that is currently active inside this TabPanel.
  140598. * @return {Ext.Component} The currently active item.
  140599. */
  140600. getActiveTab: function() {
  140601. var me = this,
  140602. // Ensure the calculated result references a Component
  140603. result = me.getComponent(me.activeTab);
  140604. // Sanitize the result in case the active tab is no longer there.
  140605. if (result && me.items.indexOf(result) != -1) {
  140606. me.activeTab = result;
  140607. } else {
  140608. me.activeTab = null;
  140609. }
  140610. return me.activeTab;
  140611. },
  140612. /**
  140613. * Returns the {@link Ext.tab.Bar} currently used in this TabPanel
  140614. * @return {Ext.tab.Bar} The TabBar
  140615. */
  140616. getTabBar: function() {
  140617. return this.tabBar;
  140618. },
  140619. /**
  140620. * @protected
  140621. * Makes sure we have a Tab for each item added to the TabPanel
  140622. */
  140623. onAdd: function(item, index) {
  140624. var me = this,
  140625. cfg = item.tabConfig || {},
  140626. defaultConfig = {
  140627. xtype: 'tab',
  140628. card: item,
  140629. disabled: item.disabled,
  140630. closable: item.closable,
  140631. hidden: item.hidden && !item.hiddenByLayout, // only hide if it wasn't hidden by the layout itself
  140632. tooltip: item.tooltip,
  140633. tabBar: me.tabBar,
  140634. closeText: item.closeText
  140635. };
  140636. cfg = Ext.applyIf(cfg, defaultConfig);
  140637. // Create the correspondiong tab in the tab bar
  140638. item.tab = me.tabBar.insert(index, cfg);
  140639. item.on({
  140640. scope : me,
  140641. enable: me.onItemEnable,
  140642. disable: me.onItemDisable,
  140643. beforeshow: me.onItemBeforeShow,
  140644. iconchange: me.onItemIconChange,
  140645. iconclschange: me.onItemIconClsChange,
  140646. titlechange: me.onItemTitleChange
  140647. });
  140648. if (item.isPanel) {
  140649. if (me.removePanelHeader) {
  140650. if (item.rendered) {
  140651. if (item.header) {
  140652. item.header.hide();
  140653. }
  140654. } else {
  140655. item.header = false;
  140656. }
  140657. }
  140658. if (item.isPanel && me.border) {
  140659. item.setBorder(false);
  140660. }
  140661. }
  140662. },
  140663. /**
  140664. * @private
  140665. * Enable corresponding tab when item is enabled.
  140666. */
  140667. onItemEnable: function(item){
  140668. item.tab.enable();
  140669. },
  140670. /**
  140671. * @private
  140672. * Disable corresponding tab when item is enabled.
  140673. */
  140674. onItemDisable: function(item){
  140675. item.tab.disable();
  140676. },
  140677. /**
  140678. * @private
  140679. * Sets activeTab before item is shown.
  140680. */
  140681. onItemBeforeShow: function(item) {
  140682. if (item !== this.activeTab) {
  140683. this.setActiveTab(item);
  140684. return false;
  140685. }
  140686. },
  140687. /**
  140688. * @private
  140689. * Update the tab icon when panel icon has been set or changed.
  140690. */
  140691. onItemIconChange: function(item, newIcon) {
  140692. item.tab.setIcon(newIcon);
  140693. },
  140694. /**
  140695. * @private
  140696. * Update the tab iconCls when panel iconCls has been set or changed.
  140697. */
  140698. onItemIconClsChange: function(item, newIconCls) {
  140699. item.tab.setIconCls(newIconCls);
  140700. },
  140701. /**
  140702. * @private
  140703. * Update the tab title when panel title has been set or changed.
  140704. */
  140705. onItemTitleChange: function(item, newTitle) {
  140706. item.tab.setText(newTitle);
  140707. },
  140708. /**
  140709. * @private
  140710. * Unlink the removed child item from its (@link Ext.tab.Tab Tab}.
  140711. *
  140712. * If we're removing the currently active tab, activate the nearest one. The item is removed when we call super,
  140713. * so we can do preprocessing before then to find the card's index
  140714. */
  140715. doRemove: function(item, autoDestroy) {
  140716. var me = this,
  140717. toActivate;
  140718. // Destroying, or removing the last item, nothing to activate
  140719. if (me.destroying || me.items.getCount() == 1) {
  140720. me.activeTab = null;
  140721. }
  140722. // Ask the TabBar which tab to activate next.
  140723. // Set the active child panel using the index of that tab
  140724. else if ((toActivate = me.tabBar.items.indexOf(me.tabBar.findNextActivatable(item.tab))) !== -1) {
  140725. me.setActiveTab(toActivate);
  140726. }
  140727. this.callParent(arguments);
  140728. // Remove the two references
  140729. delete item.tab.card;
  140730. delete item.tab;
  140731. },
  140732. /**
  140733. * @private
  140734. * Makes sure we remove the corresponding Tab when an item is removed
  140735. */
  140736. onRemove: function(item, destroying) {
  140737. var me = this;
  140738. item.un({
  140739. scope : me,
  140740. enable: me.onItemEnable,
  140741. disable: me.onItemDisable,
  140742. beforeshow: me.onItemBeforeShow
  140743. });
  140744. if (!me.destroying && item.tab.ownerCt === me.tabBar) {
  140745. me.tabBar.remove(item.tab);
  140746. }
  140747. }
  140748. });
  140749. /**
  140750. * A simple element that adds extra horizontal space between items in a toolbar.
  140751. * By default a 2px wide space is added via CSS specification:
  140752. *
  140753. * .x-toolbar .x-toolbar-spacer {
  140754. * width: 2px;
  140755. * }
  140756. *
  140757. * Example:
  140758. *
  140759. * @example
  140760. * Ext.create('Ext.panel.Panel', {
  140761. * title: 'Toolbar Spacer Example',
  140762. * width: 300,
  140763. * height: 200,
  140764. * tbar : [
  140765. * 'Item 1',
  140766. * { xtype: 'tbspacer' }, // or ' '
  140767. * 'Item 2',
  140768. * // space width is also configurable via javascript
  140769. * { xtype: 'tbspacer', width: 50 }, // add a 50px space
  140770. * 'Item 3'
  140771. * ],
  140772. * renderTo: Ext.getBody()
  140773. * });
  140774. */
  140775. Ext.define('Ext.toolbar.Spacer', {
  140776. extend: 'Ext.Component',
  140777. alias: 'widget.tbspacer',
  140778. alternateClassName: 'Ext.Toolbar.Spacer',
  140779. baseCls: Ext.baseCSSPrefix + 'toolbar-spacer',
  140780. focusable: false
  140781. });
  140782. /**
  140783. * Provides indentation and folder structure markup for a Tree taking into account
  140784. * depth and position within the tree hierarchy.
  140785. *
  140786. * @private
  140787. */
  140788. Ext.define('Ext.tree.Column', {
  140789. extend: 'Ext.grid.column.Column',
  140790. alias: 'widget.treecolumn',
  140791. tdCls: Ext.baseCSSPrefix + 'grid-cell-treecolumn',
  140792. treePrefix: Ext.baseCSSPrefix + 'tree-',
  140793. elbowPrefix: Ext.baseCSSPrefix + 'tree-elbow-',
  140794. expanderCls: Ext.baseCSSPrefix + 'tree-expander',
  140795. imgText: '<img src="{1}" class="{0}" />',
  140796. checkboxText: '<input type="button" role="checkbox" class="{0}" {1} />',
  140797. initComponent: function() {
  140798. var me = this;
  140799. me.origRenderer = me.renderer || me.defaultRenderer;
  140800. me.origScope = me.scope || window;
  140801. me.renderer = me.treeRenderer;
  140802. me.scope = me;
  140803. me.callParent();
  140804. },
  140805. treeRenderer: function(value, metaData, record, rowIdx, colIdx, store, view){
  140806. var me = this,
  140807. buf = [],
  140808. format = Ext.String.format,
  140809. depth = record.getDepth(),
  140810. treePrefix = me.treePrefix,
  140811. elbowPrefix = me.elbowPrefix,
  140812. expanderCls = me.expanderCls,
  140813. imgText = me.imgText,
  140814. checkboxText= me.checkboxText,
  140815. formattedValue = me.origRenderer.apply(me.origScope, arguments),
  140816. blank = Ext.BLANK_IMAGE_URL,
  140817. href = record.get('href'),
  140818. target = record.get('hrefTarget'),
  140819. cls = record.get('cls');
  140820. while (record) {
  140821. if (!record.isRoot() || (record.isRoot() && view.rootVisible)) {
  140822. if (record.getDepth() === depth) {
  140823. buf.unshift(format(imgText,
  140824. treePrefix + 'icon ' +
  140825. treePrefix + 'icon' + (record.get('icon') ? '-inline ' : (record.isLeaf() ? '-leaf ' : '-parent ')) +
  140826. (record.get('iconCls') || ''),
  140827. record.get('icon') || blank
  140828. ));
  140829. if (record.get('checked') !== null) {
  140830. buf.unshift(format(
  140831. checkboxText,
  140832. (treePrefix + 'checkbox') + (record.get('checked') ? ' ' + treePrefix + 'checkbox-checked' : ''),
  140833. record.get('checked') ? 'aria-checked="true"' : ''
  140834. ));
  140835. if (record.get('checked')) {
  140836. metaData.tdCls += (' ' + treePrefix + 'checked');
  140837. }
  140838. }
  140839. if (record.isLast()) {
  140840. if (record.isExpandable()) {
  140841. buf.unshift(format(imgText, (elbowPrefix + 'end-plus ' + expanderCls), blank));
  140842. } else {
  140843. buf.unshift(format(imgText, (elbowPrefix + 'end'), blank));
  140844. }
  140845. } else {
  140846. if (record.isExpandable()) {
  140847. buf.unshift(format(imgText, (elbowPrefix + 'plus ' + expanderCls), blank));
  140848. } else {
  140849. buf.unshift(format(imgText, (treePrefix + 'elbow'), blank));
  140850. }
  140851. }
  140852. } else {
  140853. if (record.isLast() || record.getDepth() === 0) {
  140854. buf.unshift(format(imgText, (elbowPrefix + 'empty'), blank));
  140855. } else if (record.getDepth() !== 0) {
  140856. buf.unshift(format(imgText, (elbowPrefix + 'line'), blank));
  140857. }
  140858. }
  140859. }
  140860. record = record.parentNode;
  140861. }
  140862. if (href) {
  140863. buf.push('<a href="', href, '" target="', target, '">', formattedValue, '</a>');
  140864. } else {
  140865. buf.push(formattedValue);
  140866. }
  140867. if (cls) {
  140868. metaData.tdCls += ' ' + cls;
  140869. }
  140870. return buf.join('');
  140871. },
  140872. defaultRenderer: function(value) {
  140873. return value;
  140874. }
  140875. });
  140876. /**
  140877. * Used as a view by {@link Ext.tree.Panel TreePanel}.
  140878. */
  140879. Ext.define('Ext.tree.View', {
  140880. extend: 'Ext.view.Table',
  140881. alias: 'widget.treeview',
  140882. requires: [
  140883. 'Ext.data.NodeStore'
  140884. ],
  140885. loadingCls: Ext.baseCSSPrefix + 'grid-tree-loading',
  140886. expandedCls: Ext.baseCSSPrefix + 'grid-tree-node-expanded',
  140887. leafCls: Ext.baseCSSPrefix + 'grid-tree-node-leaf',
  140888. expanderSelector: '.' + Ext.baseCSSPrefix + 'tree-expander',
  140889. checkboxSelector: '.' + Ext.baseCSSPrefix + 'tree-checkbox',
  140890. expanderIconOverCls: Ext.baseCSSPrefix + 'tree-expander-over',
  140891. // Class to add to the node wrap element used to hold nodes when a parent is being
  140892. // collapsed or expanded. During the animation, UI interaction is forbidden by testing
  140893. // for an ancestor node with this class.
  140894. nodeAnimWrapCls: Ext.baseCSSPrefix + 'tree-animator-wrap',
  140895. blockRefresh: true,
  140896. /**
  140897. * @cfg
  140898. * @inheritdoc
  140899. */
  140900. loadMask: false,
  140901. /**
  140902. * @cfg {Boolean} rootVisible
  140903. * False to hide the root node.
  140904. */
  140905. rootVisible: true,
  140906. /**
  140907. * @cfg {Boolean} deferInitialRefresh
  140908. * Must be false for Tree Views because the root node must be rendered in order to be updated with its child nodes.
  140909. */
  140910. deferInitialRefresh: false,
  140911. /**
  140912. * @cfg {Boolean} animate
  140913. * True to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
  140914. */
  140915. expandDuration: 250,
  140916. collapseDuration: 250,
  140917. toggleOnDblClick: true,
  140918. stripeRows: false,
  140919. // fields that will trigger a change in the ui that aren't likely to be bound to a column
  140920. uiFields: ['expanded', 'loaded', 'checked', 'expandable', 'leaf', 'icon', 'iconCls', 'loading', 'qtip', 'qtitle'],
  140921. initComponent: function() {
  140922. var me = this,
  140923. treeStore = me.panel.getStore();
  140924. if (me.initialConfig.animate === undefined) {
  140925. me.animate = Ext.enableFx;
  140926. }
  140927. me.store = new Ext.data.NodeStore({
  140928. treeStore: treeStore,
  140929. recursive: true,
  140930. rootVisible: me.rootVisible,
  140931. listeners: {
  140932. beforeexpand: me.onBeforeExpand,
  140933. expand: me.onExpand,
  140934. beforecollapse: me.onBeforeCollapse,
  140935. collapse: me.onCollapse,
  140936. write: me.onStoreWrite,
  140937. datachanged: me.onStoreDataChanged,
  140938. scope: me
  140939. }
  140940. });
  140941. if (me.node) {
  140942. me.setRootNode(me.node);
  140943. }
  140944. me.animQueue = {};
  140945. me.animWraps = {};
  140946. me.addEvents(
  140947. /**
  140948. * @event afteritemexpand
  140949. * Fires after an item has been visually expanded and is visible in the tree.
  140950. * @param {Ext.data.NodeInterface} node The node that was expanded
  140951. * @param {Number} index The index of the node
  140952. * @param {HTMLElement} item The HTML element for the node that was expanded
  140953. */
  140954. 'afteritemexpand',
  140955. /**
  140956. * @event afteritemcollapse
  140957. * Fires after an item has been visually collapsed and is no longer visible in the tree.
  140958. * @param {Ext.data.NodeInterface} node The node that was collapsed
  140959. * @param {Number} index The index of the node
  140960. * @param {HTMLElement} item The HTML element for the node that was collapsed
  140961. */
  140962. 'afteritemcollapse'
  140963. );
  140964. me.callParent(arguments);
  140965. me.on({
  140966. element: 'el',
  140967. scope: me,
  140968. delegate: me.expanderSelector,
  140969. mouseover: me.onExpanderMouseOver,
  140970. mouseout: me.onExpanderMouseOut
  140971. });
  140972. me.on({
  140973. element: 'el',
  140974. scope: me,
  140975. delegate: me.checkboxSelector,
  140976. click: me.onCheckboxChange
  140977. });
  140978. },
  140979. getMaskStore: function(){
  140980. return this.panel.getStore();
  140981. },
  140982. afterComponentLayout: function(){
  140983. this.callParent(arguments);
  140984. var stretcher = this.stretcher;
  140985. if (stretcher) {
  140986. stretcher.setWidth((this.getWidth() - Ext.getScrollbarSize().width));
  140987. }
  140988. },
  140989. processUIEvent: function(e) {
  140990. // If the clicked node is part of an animation, ignore the click.
  140991. // This is because during a collapse animation, the associated Records
  140992. // will already have been removed from the Store, and the event is not processable.
  140993. if (e.getTarget('.' + this.nodeAnimWrapCls, this.el)) {
  140994. return false;
  140995. }
  140996. return this.callParent(arguments);
  140997. },
  140998. onClear: function(){
  140999. this.store.removeAll();
  141000. },
  141001. setRootNode: function(node) {
  141002. var me = this;
  141003. me.store.setNode(node);
  141004. me.node = node;
  141005. },
  141006. onCheckboxChange: function(e, t) {
  141007. var me = this,
  141008. item = e.getTarget(me.getItemSelector(), me.getTargetEl());
  141009. if (item) {
  141010. me.onCheckChange(me.getRecord(item));
  141011. }
  141012. },
  141013. onCheckChange: function(record){
  141014. var checked = record.get('checked');
  141015. if (Ext.isBoolean(checked)) {
  141016. checked = !checked;
  141017. record.set('checked', checked);
  141018. this.fireEvent('checkchange', record, checked);
  141019. }
  141020. },
  141021. getChecked: function() {
  141022. var checked = [];
  141023. this.node.cascadeBy(function(rec){
  141024. if (rec.get('checked')) {
  141025. checked.push(rec);
  141026. }
  141027. });
  141028. return checked;
  141029. },
  141030. isItemChecked: function(rec){
  141031. return rec.get('checked');
  141032. },
  141033. /**
  141034. * @private
  141035. */
  141036. createAnimWrap: function(record, index) {
  141037. var thHtml = '',
  141038. headerCt = this.panel.headerCt,
  141039. headers = headerCt.getGridColumns(),
  141040. i = 0, len = headers.length, item,
  141041. node = this.getNode(record),
  141042. tmpEl, nodeEl;
  141043. for (; i < len; i++) {
  141044. item = headers[i];
  141045. thHtml += '<th style="width: ' + (item.hidden ? 0 : item.getDesiredWidth()) + 'px; height: 0px;"></th>';
  141046. }
  141047. nodeEl = Ext.get(node);
  141048. tmpEl = nodeEl.insertSibling({
  141049. tag: 'tr',
  141050. html: [
  141051. '<td colspan="' + headerCt.getColumnCount() + '">',
  141052. '<div class="' + this.nodeAnimWrapCls + '">',
  141053. '<table class="' + Ext.baseCSSPrefix + 'grid-table" style="width: ' + headerCt.getFullWidth() + 'px;"><tbody>',
  141054. thHtml,
  141055. '</tbody></table>',
  141056. '</div>',
  141057. '</td>'
  141058. ].join('')
  141059. }, 'after');
  141060. return {
  141061. record: record,
  141062. node: node,
  141063. el: tmpEl,
  141064. expanding: false,
  141065. collapsing: false,
  141066. animating: false,
  141067. animateEl: tmpEl.down('div'),
  141068. targetEl: tmpEl.down('tbody')
  141069. };
  141070. },
  141071. /**
  141072. * @private
  141073. * Returns the animation wrapper element for the specified parent node, used to wrap the child nodes as
  141074. * they slide up or down during expand/collapse.
  141075. *
  141076. * @param parent The parent node to be expanded or collapsed
  141077. *
  141078. * @param [bubble=true] If the passed parent node does not already have a wrap element created, by default
  141079. * this function will bubble up to each parent node looking for a valid wrap element to reuse, returning
  141080. * the first one it finds. This is the appropriate behavior, e.g., for the collapse direction, so that the
  141081. * entire expanded set of branch nodes can collapse as a single unit.
  141082. *
  141083. * However for expanding each parent node should instead always create its own animation wrap if one
  141084. * doesn't exist, so that its children can expand independently of any other nodes -- this is crucial
  141085. * when executing the "expand all" behavior. If multiple nodes attempt to reuse the same ancestor wrap
  141086. * element concurrently during expansion it will lead to problems as the first animation to complete will
  141087. * delete the wrap el out from under other running animations. For that reason, when expanding you should
  141088. * always pass `bubble: false` to be on the safe side.
  141089. *
  141090. * If the passed parent has no wrap (or there is no valid ancestor wrap after bubbling), this function
  141091. * will return null and the calling code should then call {@link #createAnimWrap} if needed.
  141092. *
  141093. * @return {Ext.Element} The wrapping element as created in {@link #createAnimWrap}, or null
  141094. */
  141095. getAnimWrap: function(parent, bubble) {
  141096. if (!this.animate) {
  141097. return null;
  141098. }
  141099. var wraps = this.animWraps,
  141100. wrap = wraps[parent.internalId];
  141101. if (bubble !== false) {
  141102. while (!wrap && parent) {
  141103. parent = parent.parentNode;
  141104. if (parent) {
  141105. wrap = wraps[parent.internalId];
  141106. }
  141107. }
  141108. }
  141109. return wrap;
  141110. },
  141111. doAdd: function(nodes, records, index) {
  141112. // If we are adding records which have a parent that is currently expanding
  141113. // lets add them to the animation wrap
  141114. var me = this,
  141115. record = records[0],
  141116. parent = record.parentNode,
  141117. a = me.all.elements,
  141118. relativeIndex = 0,
  141119. animWrap = me.getAnimWrap(parent),
  141120. targetEl, children, len;
  141121. if (!animWrap || !animWrap.expanding) {
  141122. return me.callParent(arguments);
  141123. }
  141124. // We need the parent that has the animWrap, not the nodes parent
  141125. parent = animWrap.record;
  141126. // If there is an anim wrap we do our special magic logic
  141127. targetEl = animWrap.targetEl;
  141128. children = targetEl.dom.childNodes;
  141129. // We subtract 1 from the childrens length because we have a tr in there with the th'es
  141130. len = children.length - 1;
  141131. // The relative index is the index in the full flat collection minus the index of the wraps parent
  141132. relativeIndex = index - me.indexOf(parent) - 1;
  141133. // If we are adding records to the wrap that have a higher relative index then there are currently children
  141134. // it means we have to append the nodes to the wrap
  141135. if (!len || relativeIndex >= len) {
  141136. targetEl.appendChild(nodes);
  141137. }
  141138. // If there are already more children then the relative index it means we are adding child nodes of
  141139. // some expanded node in the anim wrap. In this case we have to insert the nodes in the right location
  141140. else {
  141141. // +1 because of the tr with th'es that is already there
  141142. Ext.fly(children[relativeIndex + 1]).insertSibling(nodes, 'before', true);
  141143. }
  141144. // We also have to update the CompositeElementLite collection of the DataView
  141145. Ext.Array.insert(a, index, nodes);
  141146. // If we were in an animation we need to now change the animation
  141147. // because the targetEl just got higher.
  141148. if (animWrap.isAnimating) {
  141149. me.onExpand(parent);
  141150. }
  141151. },
  141152. beginBulkUpdate: function(){
  141153. this.bulkUpdate = true;
  141154. },
  141155. endBulkUpdate: function(){
  141156. this.bulkUpdate = false;
  141157. },
  141158. onRemove : function(ds, record, index) {
  141159. var me = this,
  141160. bulk = me.bulkUpdate;
  141161. if (me.viewReady) {
  141162. me.doRemove(record, index);
  141163. if (!bulk) {
  141164. me.updateIndexes(index);
  141165. }
  141166. if (me.store.getCount() === 0){
  141167. me.refresh();
  141168. }
  141169. if (!bulk) {
  141170. me.fireEvent('itemremove', record, index);
  141171. }
  141172. }
  141173. },
  141174. doRemove: function(record, index) {
  141175. // If we are adding records which have a parent that is currently expanding
  141176. // lets add them to the animation wrap
  141177. var me = this,
  141178. all = me.all,
  141179. animWrap = me.getAnimWrap(record),
  141180. item = all.item(index),
  141181. node = item ? item.dom : null;
  141182. if (!node || !animWrap || !animWrap.collapsing) {
  141183. return me.callParent(arguments);
  141184. }
  141185. animWrap.targetEl.appendChild(node);
  141186. all.removeElement(index);
  141187. },
  141188. onBeforeExpand: function(parent, records, index) {
  141189. var me = this,
  141190. animWrap;
  141191. if (!me.rendered || !me.animate) {
  141192. return;
  141193. }
  141194. if (me.getNode(parent)) {
  141195. animWrap = me.getAnimWrap(parent, false);
  141196. if (!animWrap) {
  141197. animWrap = me.animWraps[parent.internalId] = me.createAnimWrap(parent);
  141198. animWrap.animateEl.setHeight(0);
  141199. }
  141200. else if (animWrap.collapsing) {
  141201. // If we expand this node while it is still expanding then we
  141202. // have to remove the nodes from the animWrap.
  141203. animWrap.targetEl.select(me.itemSelector).remove();
  141204. }
  141205. animWrap.expanding = true;
  141206. animWrap.collapsing = false;
  141207. }
  141208. },
  141209. onExpand: function(parent) {
  141210. var me = this,
  141211. queue = me.animQueue,
  141212. id = parent.getId(),
  141213. node = me.getNode(parent),
  141214. index = node ? me.indexOf(node) : -1,
  141215. animWrap,
  141216. animateEl,
  141217. targetEl;
  141218. if (me.singleExpand) {
  141219. me.ensureSingleExpand(parent);
  141220. }
  141221. // The item is not visible yet
  141222. if (index === -1) {
  141223. return;
  141224. }
  141225. animWrap = me.getAnimWrap(parent, false);
  141226. if (!animWrap) {
  141227. me.isExpandingOrCollapsing = false;
  141228. me.fireEvent('afteritemexpand', parent, index, node);
  141229. return;
  141230. }
  141231. animateEl = animWrap.animateEl;
  141232. targetEl = animWrap.targetEl;
  141233. animateEl.stopAnimation();
  141234. // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
  141235. queue[id] = true;
  141236. animateEl.slideIn('t', {
  141237. duration: me.expandDuration,
  141238. listeners: {
  141239. scope: me,
  141240. lastframe: function() {
  141241. // Move all the nodes out of the anim wrap to their proper location
  141242. animWrap.el.insertSibling(targetEl.query(me.itemSelector), 'before');
  141243. animWrap.el.remove();
  141244. me.refreshSize();
  141245. delete me.animWraps[animWrap.record.internalId];
  141246. delete queue[id];
  141247. }
  141248. },
  141249. callback: function() {
  141250. me.isExpandingOrCollapsing = false;
  141251. me.fireEvent('afteritemexpand', parent, index, node);
  141252. }
  141253. });
  141254. animWrap.isAnimating = true;
  141255. },
  141256. onBeforeCollapse: function(parent, records, index) {
  141257. var me = this,
  141258. animWrap;
  141259. if (!me.rendered || !me.animate) {
  141260. return;
  141261. }
  141262. if (me.getNode(parent)) {
  141263. animWrap = me.getAnimWrap(parent);
  141264. if (!animWrap) {
  141265. animWrap = me.animWraps[parent.internalId] = me.createAnimWrap(parent, index);
  141266. }
  141267. else if (animWrap.expanding) {
  141268. // If we collapse this node while it is still expanding then we
  141269. // have to remove the nodes from the animWrap.
  141270. animWrap.targetEl.select(this.itemSelector).remove();
  141271. }
  141272. animWrap.expanding = false;
  141273. animWrap.collapsing = true;
  141274. }
  141275. },
  141276. onCollapse: function(parent) {
  141277. var me = this,
  141278. queue = me.animQueue,
  141279. id = parent.getId(),
  141280. node = me.getNode(parent),
  141281. index = node ? me.indexOf(node) : -1,
  141282. animWrap = me.getAnimWrap(parent),
  141283. animateEl, targetEl;
  141284. // The item has already been removed by a parent node
  141285. if (index === -1) {
  141286. return;
  141287. }
  141288. if (!animWrap) {
  141289. me.isExpandingOrCollapsing = false;
  141290. me.fireEvent('afteritemcollapse', parent, index, node);
  141291. return;
  141292. }
  141293. animateEl = animWrap.animateEl;
  141294. targetEl = animWrap.targetEl;
  141295. queue[id] = true;
  141296. // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
  141297. animateEl.stopAnimation();
  141298. animateEl.slideOut('t', {
  141299. duration: me.collapseDuration,
  141300. listeners: {
  141301. scope: me,
  141302. lastframe: function() {
  141303. animWrap.el.remove();
  141304. me.refreshSize();
  141305. delete me.animWraps[animWrap.record.internalId];
  141306. delete queue[id];
  141307. }
  141308. },
  141309. callback: function() {
  141310. me.isExpandingOrCollapsing = false;
  141311. me.fireEvent('afteritemcollapse', parent, index, node);
  141312. }
  141313. });
  141314. animWrap.isAnimating = true;
  141315. },
  141316. /**
  141317. * Checks if a node is currently undergoing animation
  141318. * @private
  141319. * @param {Ext.data.Model} node The node
  141320. * @return {Boolean} True if the node is animating
  141321. */
  141322. isAnimating: function(node) {
  141323. return !!this.animQueue[node.getId()];
  141324. },
  141325. collectData: function(records) {
  141326. var data = this.callParent(arguments),
  141327. rows = data.rows,
  141328. len = rows.length,
  141329. i = 0,
  141330. row, record;
  141331. for (; i < len; i++) {
  141332. row = rows[i];
  141333. record = records[i];
  141334. if (record.get('qtip')) {
  141335. row.rowAttr = 'data-qtip="' + record.get('qtip') + '"';
  141336. if (record.get('qtitle')) {
  141337. row.rowAttr += ' ' + 'data-qtitle="' + record.get('qtitle') + '"';
  141338. }
  141339. }
  141340. if (record.isExpanded()) {
  141341. row.rowCls = (row.rowCls || '') + ' ' + this.expandedCls;
  141342. }
  141343. if (record.isLeaf()) {
  141344. row.rowCls = (row.rowCls || '') + ' ' + this.leafCls;
  141345. }
  141346. if (record.isLoading()) {
  141347. row.rowCls = (row.rowCls || '') + ' ' + this.loadingCls;
  141348. }
  141349. }
  141350. return data;
  141351. },
  141352. /**
  141353. * Expands a record that is loaded in the view.
  141354. * @param {Ext.data.Model} record The record to expand
  141355. * @param {Boolean} [deep] True to expand nodes all the way down the tree hierarchy.
  141356. * @param {Function} [callback] The function to run after the expand is completed
  141357. * @param {Object} [scope] The scope of the callback function.
  141358. */
  141359. expand: function(record, deep, callback, scope) {
  141360. return record.expand(deep, callback, scope);
  141361. },
  141362. /**
  141363. * Collapses a record that is loaded in the view.
  141364. * @param {Ext.data.Model} record The record to collapse
  141365. * @param {Boolean} [deep] True to collapse nodes all the way up the tree hierarchy.
  141366. * @param {Function} [callback] The function to run after the collapse is completed
  141367. * @param {Object} [scope] The scope of the callback function.
  141368. */
  141369. collapse: function(record, deep, callback, scope) {
  141370. return record.collapse(deep, callback, scope);
  141371. },
  141372. /**
  141373. * Toggles a record between expanded and collapsed.
  141374. * @param {Ext.data.Model} record
  141375. * @param {Boolean} [deep] True to collapse nodes all the way up the tree hierarchy.
  141376. * @param {Function} [callback] The function to run after the expand/collapse is completed
  141377. * @param {Object} [scope] The scope of the callback function.
  141378. */
  141379. toggle: function(record, deep, callback, scope) {
  141380. var me = this,
  141381. doAnimate = !!this.animate;
  141382. // Block toggling if we are already animating an expand or collapse operation.
  141383. if (!doAnimate || !this.isExpandingOrCollapsing) {
  141384. if (!record.isLeaf()) {
  141385. this.isExpandingOrCollapsing = doAnimate;
  141386. }
  141387. if (record.isExpanded()) {
  141388. me.collapse(record, deep, callback, scope);
  141389. } else {
  141390. me.expand(record, deep, callback, scope);
  141391. }
  141392. }
  141393. },
  141394. onItemDblClick: function(record, item, index) {
  141395. var me = this,
  141396. editingPlugin = me.editingPlugin;
  141397. me.callParent(arguments);
  141398. if (me.toggleOnDblClick && record.isExpandable() && !(editingPlugin && editingPlugin.clicksToEdit === 2)) {
  141399. me.toggle(record);
  141400. }
  141401. },
  141402. onBeforeItemMouseDown: function(record, item, index, e) {
  141403. if (e.getTarget(this.expanderSelector, item)) {
  141404. return false;
  141405. }
  141406. return this.callParent(arguments);
  141407. },
  141408. onItemClick: function(record, item, index, e) {
  141409. if (e.getTarget(this.expanderSelector, item) && record.isExpandable()) {
  141410. this.toggle(record, e.ctrlKey);
  141411. return false;
  141412. }
  141413. return this.callParent(arguments);
  141414. },
  141415. onExpanderMouseOver: function(e, t) {
  141416. e.getTarget(this.cellSelector, 10, true).addCls(this.expanderIconOverCls);
  141417. },
  141418. onExpanderMouseOut: function(e, t) {
  141419. e.getTarget(this.cellSelector, 10, true).removeCls(this.expanderIconOverCls);
  141420. },
  141421. /**
  141422. * Gets the base TreeStore from the bound TreePanel.
  141423. */
  141424. getTreeStore: function() {
  141425. return this.panel.store;
  141426. },
  141427. ensureSingleExpand: function(node) {
  141428. var parent = node.parentNode;
  141429. if (parent) {
  141430. parent.eachChild(function(child) {
  141431. if (child !== node && child.isExpanded()) {
  141432. child.collapse();
  141433. }
  141434. });
  141435. }
  141436. },
  141437. shouldUpdateCell: function(column, changedFieldNames){
  141438. if (changedFieldNames) {
  141439. var i = 0,
  141440. len = changedFieldNames.length;
  141441. for (; i < len; ++i) {
  141442. if (Ext.Array.contains(this.uiFields, changedFieldNames[i])) {
  141443. return true;
  141444. }
  141445. }
  141446. }
  141447. return this.callParent(arguments);
  141448. },
  141449. /**
  141450. * Re-fires the NodeStore's "write" event as a TreeStore event
  141451. * @private
  141452. * @param {Ext.data.NodeStore} store
  141453. * @param {Ext.data.Operation} operation
  141454. */
  141455. onStoreWrite: function(store, operation) {
  141456. var treeStore = this.panel.store;
  141457. treeStore.fireEvent('write', treeStore, operation);
  141458. },
  141459. /**
  141460. * Re-fires the NodeStore's "datachanged" event as a TreeStore event
  141461. * @private
  141462. * @param {Ext.data.NodeStore} store
  141463. * @param {Ext.data.Operation} operation
  141464. */
  141465. onStoreDataChanged: function(store, operation) {
  141466. var treeStore = this.panel.store;
  141467. treeStore.fireEvent('datachanged', treeStore);
  141468. }
  141469. });
  141470. /**
  141471. * The TreePanel provides tree-structured UI representation of tree-structured data.
  141472. * A TreePanel must be bound to a {@link Ext.data.TreeStore}. TreePanel's support
  141473. * multiple columns through the {@link #columns} configuration.
  141474. *
  141475. * Simple TreePanel using inline data:
  141476. *
  141477. * @example
  141478. * var store = Ext.create('Ext.data.TreeStore', {
  141479. * root: {
  141480. * expanded: true,
  141481. * children: [
  141482. * { text: "detention", leaf: true },
  141483. * { text: "homework", expanded: true, children: [
  141484. * { text: "book report", leaf: true },
  141485. * { text: "alegrbra", leaf: true}
  141486. * ] },
  141487. * { text: "buy lottery tickets", leaf: true }
  141488. * ]
  141489. * }
  141490. * });
  141491. *
  141492. * Ext.create('Ext.tree.Panel', {
  141493. * title: 'Simple Tree',
  141494. * width: 200,
  141495. * height: 150,
  141496. * store: store,
  141497. * rootVisible: false,
  141498. * renderTo: Ext.getBody()
  141499. * });
  141500. *
  141501. * For the tree node config options (like `text`, `leaf`, `expanded`), see the documentation of
  141502. * {@link Ext.data.NodeInterface NodeInterface} config options.
  141503. */
  141504. Ext.define('Ext.tree.Panel', {
  141505. extend: 'Ext.panel.Table',
  141506. alias: 'widget.treepanel',
  141507. alternateClassName: ['Ext.tree.TreePanel', 'Ext.TreePanel'],
  141508. requires: ['Ext.tree.View', 'Ext.selection.TreeModel', 'Ext.tree.Column', 'Ext.data.TreeStore'],
  141509. viewType: 'treeview',
  141510. selType: 'treemodel',
  141511. treeCls: Ext.baseCSSPrefix + 'tree-panel',
  141512. deferRowRender: false,
  141513. /**
  141514. * @cfg {Boolean} rowLines
  141515. * False so that rows are not separated by lines.
  141516. */
  141517. rowLines: false,
  141518. /**
  141519. * @cfg {Boolean} lines
  141520. * False to disable tree lines.
  141521. */
  141522. lines: true,
  141523. /**
  141524. * @cfg {Boolean} useArrows
  141525. * True to use Vista-style arrows in the tree.
  141526. */
  141527. useArrows: false,
  141528. /**
  141529. * @cfg {Boolean} singleExpand
  141530. * True if only 1 node per branch may be expanded.
  141531. */
  141532. singleExpand: false,
  141533. ddConfig: {
  141534. enableDrag: true,
  141535. enableDrop: true
  141536. },
  141537. /**
  141538. * @cfg {Boolean} animate
  141539. * True to enable animated expand/collapse. Defaults to the value of {@link Ext#enableFx}.
  141540. */
  141541. /**
  141542. * @cfg {Boolean} rootVisible
  141543. * False to hide the root node.
  141544. */
  141545. rootVisible: true,
  141546. /**
  141547. * @cfg {String} displayField
  141548. * The field inside the model that will be used as the node's text.
  141549. */
  141550. displayField: 'text',
  141551. /**
  141552. * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
  141553. * Allows you to not specify a store on this TreePanel. This is useful for creating a simple tree with preloaded
  141554. * data without having to specify a TreeStore and Model. A store and model will be created and root will be passed
  141555. * to that store. For example:
  141556. *
  141557. * Ext.create('Ext.tree.Panel', {
  141558. * title: 'Simple Tree',
  141559. * root: {
  141560. * text: "Root node",
  141561. * expanded: true,
  141562. * children: [
  141563. * { text: "Child 1", leaf: true },
  141564. * { text: "Child 2", leaf: true }
  141565. * ]
  141566. * },
  141567. * renderTo: Ext.getBody()
  141568. * });
  141569. */
  141570. root: null,
  141571. // Required for the Lockable Mixin. These are the configurations which will be copied to the
  141572. // normal and locked sub tablepanels
  141573. normalCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible', 'scroll'],
  141574. lockedCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible'],
  141575. isTree: true,
  141576. /**
  141577. * @cfg {Boolean} hideHeaders
  141578. * True to hide the headers.
  141579. */
  141580. /**
  141581. * @cfg {Boolean} folderSort
  141582. * True to automatically prepend a leaf sorter to the store.
  141583. */
  141584. /**
  141585. * @cfg {Ext.data.TreeStore} store (required)
  141586. * The {@link Ext.data.TreeStore Store} the tree should use as its data source.
  141587. */
  141588. constructor: function(config) {
  141589. config = config || {};
  141590. if (config.animate === undefined) {
  141591. config.animate = Ext.isDefined(this.animate) ? this.animate : Ext.enableFx;
  141592. }
  141593. this.enableAnimations = config.animate;
  141594. delete config.animate;
  141595. this.callParent([config]);
  141596. },
  141597. initComponent: function() {
  141598. var me = this,
  141599. cls = [me.treeCls],
  141600. view;
  141601. if (me.useArrows) {
  141602. cls.push(Ext.baseCSSPrefix + 'tree-arrows');
  141603. me.lines = false;
  141604. }
  141605. if (me.lines) {
  141606. cls.push(Ext.baseCSSPrefix + 'tree-lines');
  141607. } else if (!me.useArrows) {
  141608. cls.push(Ext.baseCSSPrefix + 'tree-no-lines');
  141609. }
  141610. if (Ext.isString(me.store)) {
  141611. me.store = Ext.StoreMgr.lookup(me.store);
  141612. } else if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
  141613. me.store = new Ext.data.TreeStore(Ext.apply({}, me.store || {}, {
  141614. root: me.root,
  141615. fields: me.fields,
  141616. model: me.model,
  141617. folderSort: me.folderSort
  141618. }));
  141619. } else if (me.root) {
  141620. me.store = Ext.data.StoreManager.lookup(me.store);
  141621. me.store.setRootNode(me.root);
  141622. if (me.folderSort !== undefined) {
  141623. me.store.folderSort = me.folderSort;
  141624. me.store.sort();
  141625. }
  141626. }
  141627. // I'm not sure if we want to this. It might be confusing
  141628. // if (me.initialConfig.rootVisible === undefined && !me.getRootNode()) {
  141629. // me.rootVisible = false;
  141630. // }
  141631. me.viewConfig = Ext.apply({}, me.viewConfig);
  141632. me.viewConfig = Ext.applyIf(me.viewConfig, {
  141633. rootVisible: me.rootVisible,
  141634. animate: me.enableAnimations,
  141635. singleExpand: me.singleExpand,
  141636. node: me.store.getRootNode(),
  141637. hideHeaders: me.hideHeaders
  141638. });
  141639. me.mon(me.store, {
  141640. scope: me,
  141641. rootchange: me.onRootChange,
  141642. clear: me.onClear
  141643. });
  141644. me.relayEvents(me.store, [
  141645. /**
  141646. * @event beforeload
  141647. * @inheritdoc Ext.data.TreeStore#beforeload
  141648. */
  141649. 'beforeload',
  141650. /**
  141651. * @event load
  141652. * @inheritdoc Ext.data.TreeStore#load
  141653. */
  141654. 'load'
  141655. ]);
  141656. me.mon(me.store, {
  141657. /**
  141658. * @event itemappend
  141659. * @inheritdoc Ext.data.TreeStore#append
  141660. */
  141661. append: me.createRelayer('itemappend'),
  141662. /**
  141663. * @event itemremove
  141664. * @inheritdoc Ext.data.TreeStore#remove
  141665. */
  141666. remove: me.createRelayer('itemremove'),
  141667. /**
  141668. * @event itemmove
  141669. * @inheritdoc Ext.data.TreeStore#move
  141670. */
  141671. move: me.createRelayer('itemmove', [0, 4]),
  141672. /**
  141673. * @event iteminsert
  141674. * @inheritdoc Ext.data.TreeStore#insert
  141675. */
  141676. insert: me.createRelayer('iteminsert'),
  141677. /**
  141678. * @event beforeitemappend
  141679. * @inheritdoc Ext.data.TreeStore#beforeappend
  141680. */
  141681. beforeappend: me.createRelayer('beforeitemappend'),
  141682. /**
  141683. * @event beforeitemremove
  141684. * @inheritdoc Ext.data.TreeStore#beforeremove
  141685. */
  141686. beforeremove: me.createRelayer('beforeitemremove'),
  141687. /**
  141688. * @event beforeitemmove
  141689. * @inheritdoc Ext.data.TreeStore#beforemove
  141690. */
  141691. beforemove: me.createRelayer('beforeitemmove'),
  141692. /**
  141693. * @event beforeiteminsert
  141694. * @inheritdoc Ext.data.TreeStore#beforeinsert
  141695. */
  141696. beforeinsert: me.createRelayer('beforeiteminsert'),
  141697. /**
  141698. * @event itemexpand
  141699. * @inheritdoc Ext.data.TreeStore#expand
  141700. */
  141701. expand: me.createRelayer('itemexpand', [0, 1]),
  141702. /**
  141703. * @event itemcollapse
  141704. * @inheritdoc Ext.data.TreeStore#collapse
  141705. */
  141706. collapse: me.createRelayer('itemcollapse', [0, 1]),
  141707. /**
  141708. * @event beforeitemexpand
  141709. * @inheritdoc Ext.data.TreeStore#beforeexpand
  141710. */
  141711. beforeexpand: me.createRelayer('beforeitemexpand', [0, 1]),
  141712. /**
  141713. * @event beforeitemcollapse
  141714. * @inheritdoc Ext.data.TreeStore#beforecollapse
  141715. */
  141716. beforecollapse: me.createRelayer('beforeitemcollapse', [0, 1])
  141717. });
  141718. // If the user specifies the headers collection manually then dont inject our own
  141719. if (!me.columns) {
  141720. if (me.initialConfig.hideHeaders === undefined) {
  141721. me.hideHeaders = true;
  141722. }
  141723. me.addCls(Ext.baseCSSPrefix + 'autowidth-table');
  141724. me.columns = [{
  141725. xtype : 'treecolumn',
  141726. text : 'Name',
  141727. width : Ext.isIE6 ? null : 10000,
  141728. dataIndex: me.displayField
  141729. }];
  141730. }
  141731. if (me.cls) {
  141732. cls.push(me.cls);
  141733. }
  141734. me.cls = cls.join(' ');
  141735. me.callParent();
  141736. view = me.getView();
  141737. me.relayEvents(view, [
  141738. /**
  141739. * @event checkchange
  141740. * Fires when a node with a checkbox's checked property changes
  141741. * @param {Ext.data.NodeInterface} node The node who's checked property was changed
  141742. * @param {Boolean} checked The node's new checked state
  141743. */
  141744. 'checkchange',
  141745. /**
  141746. * @event afteritemexpand
  141747. * @inheritdoc Ext.tree.View#afteritemexpand
  141748. */
  141749. 'afteritemexpand',
  141750. /**
  141751. * @event afteritemcollapse
  141752. * @inheritdoc Ext.tree.View#afteritemcollapse
  141753. */
  141754. 'afteritemcollapse'
  141755. ]);
  141756. // If the root is not visible and there is no rootnode defined, then just lets load the store
  141757. if (!view.rootVisible && !me.getRootNode()) {
  141758. me.setRootNode({
  141759. expanded: true
  141760. });
  141761. }
  141762. },
  141763. onClear: function(){
  141764. this.view.onClear();
  141765. },
  141766. /**
  141767. * Sets root node of this tree.
  141768. * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
  141769. * @return {Ext.data.NodeInterface} The new root
  141770. */
  141771. setRootNode: function() {
  141772. return this.store.setRootNode.apply(this.store, arguments);
  141773. },
  141774. /**
  141775. * Returns the root node for this tree.
  141776. * @return {Ext.data.NodeInterface}
  141777. */
  141778. getRootNode: function() {
  141779. return this.store.getRootNode();
  141780. },
  141781. onRootChange: function(root) {
  141782. this.view.setRootNode(root);
  141783. },
  141784. /**
  141785. * Retrieve an array of checked records.
  141786. * @return {Ext.data.NodeInterface[]} An array containing the checked records
  141787. */
  141788. getChecked: function() {
  141789. return this.getView().getChecked();
  141790. },
  141791. isItemChecked: function(rec) {
  141792. return rec.get('checked');
  141793. },
  141794. /**
  141795. * Expands a record that is loaded in the tree.
  141796. * @param {Ext.data.Model} record The record to expand
  141797. * @param {Boolean} [deep] True to expand nodes all the way down the tree hierarchy.
  141798. * @param {Function} [callback] The function to run after the expand is completed
  141799. * @param {Object} [scope] The scope of the callback function.
  141800. */
  141801. expandNode: function(record, deep, callback, scope) {
  141802. return this.getView().expand(record, deep, callback, scope || this);
  141803. },
  141804. /**
  141805. * Collapses a record that is loaded in the tree.
  141806. * @param {Ext.data.Model} record The record to collapse
  141807. * @param {Boolean} [deep] True to collapse nodes all the way up the tree hierarchy.
  141808. * @param {Function} [callback] The function to run after the collapse is completed
  141809. * @param {Object} [scope] The scope of the callback function.
  141810. */
  141811. collapseNode: function(record, deep, callback, scope) {
  141812. return this.getView().collapse(record, deep, callback, scope || this);
  141813. },
  141814. /**
  141815. * Expand all nodes
  141816. * @param {Function} [callback] A function to execute when the expand finishes.
  141817. * @param {Object} [scope] The scope of the callback function
  141818. */
  141819. expandAll : function(callback, scope) {
  141820. var me = this,
  141821. root = me.getRootNode(),
  141822. animate = me.enableAnimations,
  141823. view = me.getView();
  141824. if (root) {
  141825. if (!animate) {
  141826. view.beginBulkUpdate();
  141827. }
  141828. root.expand(true, callback, scope || me);
  141829. if (!animate) {
  141830. view.endBulkUpdate();
  141831. }
  141832. }
  141833. },
  141834. /**
  141835. * Collapse all nodes
  141836. * @param {Function} [callback] A function to execute when the collapse finishes.
  141837. * @param {Object} [scope] The scope of the callback function
  141838. */
  141839. collapseAll : function(callback, scope) {
  141840. var me = this,
  141841. root = me.getRootNode(),
  141842. animate = me.enableAnimations,
  141843. view = me.getView();
  141844. if (root) {
  141845. if (!animate) {
  141846. view.beginBulkUpdate();
  141847. }
  141848. scope = scope || me;
  141849. if (view.rootVisible) {
  141850. root.collapse(true, callback, scope);
  141851. } else {
  141852. root.collapseChildren(true, callback, scope);
  141853. }
  141854. if (!animate) {
  141855. view.endBulkUpdate();
  141856. }
  141857. }
  141858. },
  141859. /**
  141860. * Expand the tree to the path of a particular node.
  141861. * @param {String} path The path to expand. The path should include a leading separator.
  141862. * @param {String} [field] The field to get the data from. Defaults to the model idProperty.
  141863. * @param {String} [separator='/'] A separator to use.
  141864. * @param {Function} [callback] A function to execute when the expand finishes. The callback will be called with
  141865. * (success, lastNode) where success is if the expand was successful and lastNode is the last node that was expanded.
  141866. * @param {Object} [scope] The scope of the callback function
  141867. */
  141868. expandPath: function(path, field, separator, callback, scope) {
  141869. var me = this,
  141870. current = me.getRootNode(),
  141871. index = 1,
  141872. view = me.getView(),
  141873. keys,
  141874. expander;
  141875. field = field || me.getRootNode().idProperty;
  141876. separator = separator || '/';
  141877. if (Ext.isEmpty(path)) {
  141878. Ext.callback(callback, scope || me, [false, null]);
  141879. return;
  141880. }
  141881. keys = path.split(separator);
  141882. if (current.get(field) != keys[1]) {
  141883. // invalid root
  141884. Ext.callback(callback, scope || me, [false, current]);
  141885. return;
  141886. }
  141887. expander = function(){
  141888. if (++index === keys.length) {
  141889. Ext.callback(callback, scope || me, [true, current]);
  141890. return;
  141891. }
  141892. var node = current.findChild(field, keys[index]);
  141893. if (!node) {
  141894. Ext.callback(callback, scope || me, [false, current]);
  141895. return;
  141896. }
  141897. current = node;
  141898. current.expand(false, expander);
  141899. };
  141900. current.expand(false, expander);
  141901. },
  141902. /**
  141903. * Expand the tree to the path of a particular node, then select it.
  141904. * @param {String} path The path to select. The path should include a leading separator.
  141905. * @param {String} [field] The field to get the data from. Defaults to the model idProperty.
  141906. * @param {String} [separator='/'] A separator to use.
  141907. * @param {Function} [callback] A function to execute when the select finishes. The callback will be called with
  141908. * (bSuccess, oLastNode) where bSuccess is if the select was successful and oLastNode is the last node that was expanded.
  141909. * @param {Object} [scope] The scope of the callback function
  141910. */
  141911. selectPath: function(path, field, separator, callback, scope) {
  141912. var me = this,
  141913. root,
  141914. keys,
  141915. last;
  141916. field = field || me.getRootNode().idProperty;
  141917. separator = separator || '/';
  141918. keys = path.split(separator);
  141919. last = keys.pop();
  141920. if (keys.length > 1) {
  141921. me.expandPath(keys.join(separator), field, separator, function(success, node){
  141922. var lastNode = node;
  141923. if (success && node) {
  141924. node = node.findChild(field, last);
  141925. if (node) {
  141926. me.getSelectionModel().select(node);
  141927. Ext.callback(callback, scope || me, [true, node]);
  141928. return;
  141929. }
  141930. }
  141931. Ext.callback(callback, scope || me, [false, lastNode]);
  141932. }, me);
  141933. } else {
  141934. root = me.getRootNode();
  141935. if (root.getId() === last) {
  141936. me.getSelectionModel().select(root);
  141937. Ext.callback(callback, scope || me, [true, root]);
  141938. } else {
  141939. Ext.callback(callback, scope || me, [false, null]);
  141940. }
  141941. }
  141942. }
  141943. });
  141944. /**
  141945. * @private
  141946. */
  141947. Ext.define('Ext.view.DragZone', {
  141948. extend: 'Ext.dd.DragZone',
  141949. containerScroll: false,
  141950. constructor: function(config) {
  141951. var me = this,
  141952. view,
  141953. ownerCt,
  141954. el;
  141955. Ext.apply(me, config);
  141956. // Create a ddGroup unless one has been configured.
  141957. // User configuration of ddGroups allows users to specify which
  141958. // DD instances can interact with each other. Using one
  141959. // based on the id of the View would isolate it and mean it can only
  141960. // interact with a DropZone on the same View also using a generated ID.
  141961. if (!me.ddGroup) {
  141962. me.ddGroup = 'view-dd-zone-' + me.view.id;
  141963. }
  141964. // Ext.dd.DragDrop instances are keyed by the ID of their encapsulating element.
  141965. // So a View's DragZone cannot use the View's main element because the DropZone must use that
  141966. // because the DropZone may need to scroll on hover at a scrolling boundary, and it is the View's
  141967. // main element which handles scrolling.
  141968. // We use the View's parent element to drag from. Ideally, we would use the internal structure, but that
  141969. // is transient; DataView's recreate the internal structure dynamically as data changes.
  141970. // TODO: Ext 5.0 DragDrop must allow multiple DD objects to share the same element.
  141971. view = me.view;
  141972. ownerCt = view.ownerCt;
  141973. // We don't just grab the parent el, since the parent el may be
  141974. // some el injected by the layout
  141975. if (ownerCt) {
  141976. el = ownerCt.getTargetEl().dom;
  141977. } else {
  141978. el = view.el.dom.parentNode;
  141979. }
  141980. me.callParent([el]);
  141981. me.ddel = Ext.get(document.createElement('div'));
  141982. me.ddel.addCls(Ext.baseCSSPrefix + 'grid-dd-wrap');
  141983. },
  141984. init: function(id, sGroup, config) {
  141985. this.initTarget(id, sGroup, config);
  141986. this.view.mon(this.view, {
  141987. itemmousedown: this.onItemMouseDown,
  141988. scope: this
  141989. });
  141990. },
  141991. onValidDrop: function(target, e, id) {
  141992. this.callParent();
  141993. // focus the view that the node was dropped onto so that keynav will be enabled.
  141994. target.el.focus();
  141995. },
  141996. onItemMouseDown: function(view, record, item, index, e) {
  141997. if (!this.isPreventDrag(e, record, item, index)) {
  141998. // Since handleMouseDown prevents the default behavior of the event, which
  141999. // is to focus the view, we focus the view now. This ensures that the view
  142000. // remains focused if the drag is cancelled, or if no drag occurs.
  142001. this.view.focus();
  142002. this.handleMouseDown(e);
  142003. // If we want to allow dragging of multi-selections, then veto the following handlers (which, in the absence of ctrlKey, would deselect)
  142004. // if the mousedowned record is selected
  142005. if (view.getSelectionModel().selectionMode == 'MULTI' && !e.ctrlKey && view.getSelectionModel().isSelected(record)) {
  142006. return false;
  142007. }
  142008. }
  142009. },
  142010. // private template method
  142011. isPreventDrag: function(e) {
  142012. return false;
  142013. },
  142014. getDragData: function(e) {
  142015. var view = this.view,
  142016. item = e.getTarget(view.getItemSelector());
  142017. if (item) {
  142018. return {
  142019. copy: view.copy || (view.allowCopy && e.ctrlKey),
  142020. event: new Ext.EventObjectImpl(e),
  142021. view: view,
  142022. ddel: this.ddel,
  142023. item: item,
  142024. records: view.getSelectionModel().getSelection(),
  142025. fromPosition: Ext.fly(item).getXY()
  142026. };
  142027. }
  142028. },
  142029. onInitDrag: function(x, y) {
  142030. var me = this,
  142031. data = me.dragData,
  142032. view = data.view,
  142033. selectionModel = view.getSelectionModel(),
  142034. record = view.getRecord(data.item),
  142035. e = data.event;
  142036. // Update the selection to match what would have been selected if the user had
  142037. // done a full click on the target node rather than starting a drag from it
  142038. if (!selectionModel.isSelected(record)) {
  142039. selectionModel.select(record, true);
  142040. }
  142041. data.records = selectionModel.getSelection();
  142042. me.ddel.update(me.getDragText());
  142043. me.proxy.update(me.ddel.dom);
  142044. me.onStartDrag(x, y);
  142045. return true;
  142046. },
  142047. getDragText: function() {
  142048. var count = this.dragData.records.length;
  142049. return Ext.String.format(this.dragText, count, count == 1 ? '' : 's');
  142050. },
  142051. getRepairXY : function(e, data){
  142052. return data ? data.fromPosition : false;
  142053. }
  142054. });
  142055. /**
  142056. * @private
  142057. */
  142058. Ext.define('Ext.tree.ViewDragZone', {
  142059. extend: 'Ext.view.DragZone',
  142060. isPreventDrag: function(e, record) {
  142061. return (record.get('allowDrag') === false) || !!e.getTarget(this.view.expanderSelector);
  142062. },
  142063. afterRepair: function() {
  142064. var me = this,
  142065. view = me.view,
  142066. selectedRowCls = view.selectedItemCls,
  142067. records = me.dragData.records,
  142068. r,
  142069. rLen = records.length,
  142070. fly = Ext.fly,
  142071. item;
  142072. if (Ext.enableFx && me.repairHighlight) {
  142073. // Roll through all records and highlight all the ones we attempted to drag.
  142074. for (r = 0; r < rLen; r++) {
  142075. // anonymous fns below, don't hoist up unless below is wrapped in
  142076. // a self-executing function passing in item.
  142077. item = view.getNode(records[r]);
  142078. // We must remove the selected row class before animating, because
  142079. // the selected row class declares !important on its background-color.
  142080. fly(item.firstChild).highlight(me.repairHighlightColor, {
  142081. listeners: {
  142082. beforeanimate: function() {
  142083. if (view.isSelected(item)) {
  142084. fly(item).removeCls(selectedRowCls);
  142085. }
  142086. },
  142087. afteranimate: function() {
  142088. if (view.isSelected(item)) {
  142089. fly(item).addCls(selectedRowCls);
  142090. }
  142091. }
  142092. }
  142093. });
  142094. }
  142095. }
  142096. me.dragging = false;
  142097. }
  142098. });
  142099. /**
  142100. * @private
  142101. */
  142102. Ext.define('Ext.tree.ViewDropZone', {
  142103. extend: 'Ext.view.DropZone',
  142104. /**
  142105. * @cfg {Boolean} allowParentInsert
  142106. * Allow inserting a dragged node between an expanded parent node and its first child that will become a
  142107. * sibling of the parent when dropped.
  142108. */
  142109. allowParentInserts: false,
  142110. /**
  142111. * @cfg {String} allowContainerDrop
  142112. * True if drops on the tree container (outside of a specific tree node) are allowed.
  142113. */
  142114. allowContainerDrops: false,
  142115. /**
  142116. * @cfg {String} appendOnly
  142117. * True if the tree should only allow append drops (use for trees which are sorted).
  142118. */
  142119. appendOnly: false,
  142120. /**
  142121. * @cfg {String} expandDelay
  142122. * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
  142123. * over the target.
  142124. */
  142125. expandDelay : 500,
  142126. indicatorCls: Ext.baseCSSPrefix + 'tree-ddindicator',
  142127. // private
  142128. expandNode : function(node) {
  142129. var view = this.view;
  142130. if (!node.isLeaf() && !node.isExpanded()) {
  142131. view.expand(node);
  142132. this.expandProcId = false;
  142133. }
  142134. },
  142135. // private
  142136. queueExpand : function(node) {
  142137. this.expandProcId = Ext.Function.defer(this.expandNode, this.expandDelay, this, [node]);
  142138. },
  142139. // private
  142140. cancelExpand : function() {
  142141. if (this.expandProcId) {
  142142. clearTimeout(this.expandProcId);
  142143. this.expandProcId = false;
  142144. }
  142145. },
  142146. getPosition: function(e, node) {
  142147. var view = this.view,
  142148. record = view.getRecord(node),
  142149. y = e.getPageY(),
  142150. noAppend = record.isLeaf(),
  142151. noBelow = false,
  142152. region = Ext.fly(node).getRegion(),
  142153. fragment;
  142154. // If we are dragging on top of the root node of the tree, we always want to append.
  142155. if (record.isRoot()) {
  142156. return 'append';
  142157. }
  142158. // Return 'append' if the node we are dragging on top of is not a leaf else return false.
  142159. if (this.appendOnly) {
  142160. return noAppend ? false : 'append';
  142161. }
  142162. if (!this.allowParentInsert) {
  142163. noBelow = record.hasChildNodes() && record.isExpanded();
  142164. }
  142165. fragment = (region.bottom - region.top) / (noAppend ? 2 : 3);
  142166. if (y >= region.top && y < (region.top + fragment)) {
  142167. return 'before';
  142168. }
  142169. else if (!noBelow && (noAppend || (y >= (region.bottom - fragment) && y <= region.bottom))) {
  142170. return 'after';
  142171. }
  142172. else {
  142173. return 'append';
  142174. }
  142175. },
  142176. isValidDropPoint : function(node, position, dragZone, e, data) {
  142177. if (!node || !data.item) {
  142178. return false;
  142179. }
  142180. var view = this.view,
  142181. targetNode = view.getRecord(node),
  142182. draggedRecords = data.records,
  142183. dataLength = draggedRecords.length,
  142184. ln = draggedRecords.length,
  142185. i, record;
  142186. // No drop position, or dragged records: invalid drop point
  142187. if (!(targetNode && position && dataLength)) {
  142188. return false;
  142189. }
  142190. // If the targetNode is within the folder we are dragging
  142191. for (i = 0; i < ln; i++) {
  142192. record = draggedRecords[i];
  142193. if (record.isNode && record.contains(targetNode)) {
  142194. return false;
  142195. }
  142196. }
  142197. // Respect the allowDrop field on Tree nodes
  142198. if (position === 'append' && targetNode.get('allowDrop') === false) {
  142199. return false;
  142200. }
  142201. else if (position != 'append' && targetNode.parentNode.get('allowDrop') === false) {
  142202. return false;
  142203. }
  142204. // If the target record is in the dragged dataset, then invalid drop
  142205. if (Ext.Array.contains(draggedRecords, targetNode)) {
  142206. return false;
  142207. }
  142208. // @TODO: fire some event to notify that there is a valid drop possible for the node you're dragging
  142209. // Yes: this.fireViewEvent(blah....) fires an event through the owning View.
  142210. return true;
  142211. },
  142212. onNodeOver : function(node, dragZone, e, data) {
  142213. var position = this.getPosition(e, node),
  142214. returnCls = this.dropNotAllowed,
  142215. view = this.view,
  142216. targetNode = view.getRecord(node),
  142217. indicator = this.getIndicator(),
  142218. indicatorX = 0,
  142219. indicatorY = 0;
  142220. // auto node expand check
  142221. this.cancelExpand();
  142222. if (position == 'append' && !this.expandProcId && !Ext.Array.contains(data.records, targetNode) && !targetNode.isLeaf() && !targetNode.isExpanded()) {
  142223. this.queueExpand(targetNode);
  142224. }
  142225. if (this.isValidDropPoint(node, position, dragZone, e, data)) {
  142226. this.valid = true;
  142227. this.currentPosition = position;
  142228. this.overRecord = targetNode;
  142229. indicator.setWidth(Ext.fly(node).getWidth());
  142230. indicatorY = Ext.fly(node).getY() - Ext.fly(view.el).getY() - 1;
  142231. /*
  142232. * In the code below we show the proxy again. The reason for doing this is showing the indicator will
  142233. * call toFront, causing it to get a new z-index which can sometimes push the proxy behind it. We always
  142234. * want the proxy to be above, so calling show on the proxy will call toFront and bring it forward.
  142235. */
  142236. if (position == 'before') {
  142237. returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
  142238. indicator.showAt(0, indicatorY);
  142239. dragZone.proxy.show();
  142240. } else if (position == 'after') {
  142241. returnCls = targetNode.isLast() ? Ext.baseCSSPrefix + 'tree-drop-ok-below' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
  142242. indicatorY += Ext.fly(node).getHeight();
  142243. indicator.showAt(0, indicatorY);
  142244. dragZone.proxy.show();
  142245. } else {
  142246. returnCls = Ext.baseCSSPrefix + 'tree-drop-ok-append';
  142247. // @TODO: set a class on the parent folder node to be able to style it
  142248. indicator.hide();
  142249. }
  142250. } else {
  142251. this.valid = false;
  142252. }
  142253. this.currentCls = returnCls;
  142254. return returnCls;
  142255. },
  142256. onContainerOver : function(dd, e, data) {
  142257. return e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed;
  142258. },
  142259. notifyOut: function() {
  142260. this.callParent(arguments);
  142261. this.cancelExpand();
  142262. },
  142263. handleNodeDrop : function(data, targetNode, position) {
  142264. var me = this,
  142265. view = me.view,
  142266. parentNode = targetNode.parentNode,
  142267. store = view.getStore(),
  142268. recordDomNodes = [],
  142269. records, i, len,
  142270. insertionMethod, argList,
  142271. needTargetExpand,
  142272. transferData,
  142273. processDrop;
  142274. // If the copy flag is set, create a copy of the Models with the same IDs
  142275. if (data.copy) {
  142276. records = data.records;
  142277. data.records = [];
  142278. for (i = 0, len = records.length; i < len; i++) {
  142279. data.records.push(Ext.apply({}, records[i].data));
  142280. }
  142281. }
  142282. // Cancel any pending expand operation
  142283. me.cancelExpand();
  142284. // Grab a reference to the correct node insertion method.
  142285. // Create an arg list array intended for the apply method of the
  142286. // chosen node insertion method.
  142287. // Ensure the target object for the method is referenced by 'targetNode'
  142288. if (position == 'before') {
  142289. insertionMethod = parentNode.insertBefore;
  142290. argList = [null, targetNode];
  142291. targetNode = parentNode;
  142292. }
  142293. else if (position == 'after') {
  142294. if (targetNode.nextSibling) {
  142295. insertionMethod = parentNode.insertBefore;
  142296. argList = [null, targetNode.nextSibling];
  142297. }
  142298. else {
  142299. insertionMethod = parentNode.appendChild;
  142300. argList = [null];
  142301. }
  142302. targetNode = parentNode;
  142303. }
  142304. else {
  142305. if (!targetNode.isExpanded()) {
  142306. needTargetExpand = true;
  142307. }
  142308. insertionMethod = targetNode.appendChild;
  142309. argList = [null];
  142310. }
  142311. // A function to transfer the data into the destination tree
  142312. transferData = function() {
  142313. var node,
  142314. r, rLen, color, n;
  142315. for (i = 0, len = data.records.length; i < len; i++) {
  142316. argList[0] = data.records[i];
  142317. node = insertionMethod.apply(targetNode, argList);
  142318. if (Ext.enableFx && me.dropHighlight) {
  142319. recordDomNodes.push(view.getNode(node));
  142320. }
  142321. }
  142322. // Kick off highlights after everything's been inserted, so they are
  142323. // more in sync without insertion/render overhead.
  142324. if (Ext.enableFx && me.dropHighlight) {
  142325. //FIXME: the check for n.firstChild is not a great solution here. Ideally the line should simply read
  142326. //Ext.fly(n.firstChild) but this yields errors in IE6 and 7. See ticket EXTJSIV-1705 for more details
  142327. rLen = recordDomNodes.length;
  142328. color = me.dropHighlightColor;
  142329. for (r = 0; r < rLen; r++) {
  142330. n = recordDomNodes[r];
  142331. if (n) {
  142332. Ext.fly(n.firstChild ? n.firstChild : n).highlight(color);
  142333. }
  142334. }
  142335. }
  142336. };
  142337. // If dropping right on an unexpanded node, transfer the data after it is expanded.
  142338. if (needTargetExpand) {
  142339. targetNode.expand(false, transferData);
  142340. }
  142341. // Otherwise, call the data transfer function immediately
  142342. else {
  142343. transferData();
  142344. }
  142345. }
  142346. });
  142347. /**
  142348. * This plugin provides drag and/or drop functionality for a TreeView.
  142349. *
  142350. * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a
  142351. * {@link Ext.tree.View TreeView} and loads the data object which is passed to a cooperating
  142352. * {@link Ext.dd.DragZone DragZone}'s methods with the following properties:
  142353. *
  142354. * - copy : Boolean
  142355. *
  142356. * The value of the TreeView's `copy` property, or `true` if the TreeView was configured with `allowCopy: true` *and*
  142357. * the control key was pressed when the drag operation was begun.
  142358. *
  142359. * - view : TreeView
  142360. *
  142361. * The source TreeView from which the drag originated.
  142362. *
  142363. * - ddel : HtmlElement
  142364. *
  142365. * The drag proxy element which moves with the mouse
  142366. *
  142367. * - item : HtmlElement
  142368. *
  142369. * The TreeView node upon which the mousedown event was registered.
  142370. *
  142371. * - records : Array
  142372. *
  142373. * An Array of {@link Ext.data.Model Models} representing the selected data being dragged from the source TreeView.
  142374. *
  142375. * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
  142376. * members of the same ddGroup which processes such data objects.
  142377. *
  142378. * Adding this plugin to a view means that two new events may be fired from the client TreeView, {@link #beforedrop} and
  142379. * {@link #drop}.
  142380. *
  142381. * Note that the plugin must be added to the tree view, not to the tree panel. For example using viewConfig:
  142382. *
  142383. * viewConfig: {
  142384. * plugins: { ptype: 'treeviewdragdrop' }
  142385. * }
  142386. */
  142387. Ext.define('Ext.tree.plugin.TreeViewDragDrop', {
  142388. extend: 'Ext.AbstractPlugin',
  142389. alias: 'plugin.treeviewdragdrop',
  142390. uses: [
  142391. 'Ext.tree.ViewDragZone',
  142392. 'Ext.tree.ViewDropZone'
  142393. ],
  142394. /**
  142395. * @event beforedrop
  142396. *
  142397. * **This event is fired through the TreeView. Add listeners to the TreeView object**
  142398. *
  142399. * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the TreeView.
  142400. *
  142401. * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
  142402. *
  142403. * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
  142404. * back to the point from which the drag began.
  142405. *
  142406. * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
  142407. * was valid, and that the repair operation should not take place.
  142408. *
  142409. * Any other return value continues with the data transfer operation.
  142410. *
  142411. * @param {Object} data The data object gathered at mousedown time by the cooperating
  142412. * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
  142413. * properties:
  142414. * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
  142415. * `allowCopy: true` and the control key was pressed when the drag operation was begun
  142416. * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
  142417. * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
  142418. * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
  142419. * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
  142420. * dragged from the source TreeView.
  142421. *
  142422. * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
  142423. *
  142424. * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
  142425. * the midline of the node, or the node is a branch node which accepts new child nodes.
  142426. *
  142427. * @param {Object} dropHandler An object containing methods to complete/cancel the data transfer operation and either
  142428. * move or copy Model instances from the source View's Store to the destination View's Store.
  142429. *
  142430. * This is useful when you want to perform some kind of asynchronous processing before confirming/cancelling
  142431. * the drop, such as an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
  142432. *
  142433. * Set dropHandler.wait = true in this event handler to delay processing. When you want to complete the event, call
  142434. * dropHandler.processDrop(). To cancel the drop, call dropHandler.cancelDrop.
  142435. */
  142436. /**
  142437. * @event drop
  142438. *
  142439. * **This event is fired through the TreeView. Add listeners to the TreeView object** Fired when a drop operation
  142440. * has been completed and the data has been moved or copied.
  142441. *
  142442. * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
  142443. *
  142444. * @param {Object} data The data object gathered at mousedown time by the cooperating
  142445. * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
  142446. * properties:
  142447. * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
  142448. * `allowCopy: true` and the control key was pressed when the drag operation was begun
  142449. * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
  142450. * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
  142451. * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
  142452. * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
  142453. * dragged from the source TreeView.
  142454. *
  142455. * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
  142456. *
  142457. * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
  142458. * the midline of the node, or the node is a branch node which accepts new child nodes.
  142459. */
  142460. //<locale>
  142461. /**
  142462. * @cfg
  142463. * The text to show while dragging.
  142464. *
  142465. * Two placeholders can be used in the text:
  142466. *
  142467. * - `{0}` The number of selected items.
  142468. * - `{1}` 's' when more than 1 items (only useful for English).
  142469. */
  142470. dragText : '{0} selected node{1}',
  142471. //</locale>
  142472. /**
  142473. * @cfg {Boolean} allowParentInserts
  142474. * Allow inserting a dragged node between an expanded parent node and its first child that will become a sibling of
  142475. * the parent when dropped.
  142476. */
  142477. allowParentInserts: false,
  142478. /**
  142479. * @cfg {Boolean} allowContainerDrops
  142480. * True if drops on the tree container (outside of a specific tree node) are allowed.
  142481. */
  142482. allowContainerDrops: false,
  142483. /**
  142484. * @cfg {Boolean} appendOnly
  142485. * True if the tree should only allow append drops (use for trees which are sorted).
  142486. */
  142487. appendOnly: false,
  142488. /**
  142489. * @cfg {String} ddGroup
  142490. * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
  142491. * DropZone used by this plugin will only interact with other drag drop objects in the same group.
  142492. */
  142493. ddGroup : "TreeDD",
  142494. /**
  142495. * @cfg {String} dragGroup
  142496. * The ddGroup to which the DragZone will belong.
  142497. *
  142498. * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
  142499. * Drag/DropZones which are members of the same ddGroup.
  142500. */
  142501. /**
  142502. * @cfg {String} dropGroup
  142503. * The ddGroup to which the DropZone will belong.
  142504. *
  142505. * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
  142506. * Drag/DropZones which are members of the same ddGroup.
  142507. */
  142508. /**
  142509. * @cfg {String} expandDelay
  142510. * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node over the
  142511. * target.
  142512. */
  142513. expandDelay : 1000,
  142514. /**
  142515. * @cfg {Boolean} enableDrop
  142516. * Set to `false` to disallow the View from accepting drop gestures.
  142517. */
  142518. enableDrop: true,
  142519. /**
  142520. * @cfg {Boolean} enableDrag
  142521. * Set to `false` to disallow dragging items from the View.
  142522. */
  142523. enableDrag: true,
  142524. /**
  142525. * @cfg {String} nodeHighlightColor
  142526. * The color to use when visually highlighting the dragged or dropped node (default value is light blue).
  142527. * The color must be a 6 digit hex value, without a preceding '#'. See also {@link #nodeHighlightOnDrop} and
  142528. * {@link #nodeHighlightOnRepair}.
  142529. */
  142530. nodeHighlightColor: 'c3daf9',
  142531. /**
  142532. * @cfg {Boolean} nodeHighlightOnDrop
  142533. * Whether or not to highlight any nodes after they are
  142534. * successfully dropped on their target. Defaults to the value of `Ext.enableFx`.
  142535. * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnRepair}.
  142536. */
  142537. nodeHighlightOnDrop: Ext.enableFx,
  142538. /**
  142539. * @cfg {Boolean} nodeHighlightOnRepair
  142540. * Whether or not to highlight any nodes after they are
  142541. * repaired from an unsuccessful drag/drop. Defaults to the value of `Ext.enableFx`.
  142542. * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnDrop}.
  142543. */
  142544. nodeHighlightOnRepair: Ext.enableFx,
  142545. init : function(view) {
  142546. view.on('render', this.onViewRender, this, {single: true});
  142547. },
  142548. /**
  142549. * @private
  142550. * AbstractComponent calls destroy on all its plugins at destroy time.
  142551. */
  142552. destroy: function() {
  142553. Ext.destroy(this.dragZone, this.dropZone);
  142554. },
  142555. onViewRender : function(view) {
  142556. var me = this;
  142557. if (me.enableDrag) {
  142558. me.dragZone = new Ext.tree.ViewDragZone({
  142559. view: view,
  142560. ddGroup: me.dragGroup || me.ddGroup,
  142561. dragText: me.dragText,
  142562. repairHighlightColor: me.nodeHighlightColor,
  142563. repairHighlight: me.nodeHighlightOnRepair
  142564. });
  142565. }
  142566. if (me.enableDrop) {
  142567. me.dropZone = new Ext.tree.ViewDropZone({
  142568. view: view,
  142569. ddGroup: me.dropGroup || me.ddGroup,
  142570. allowContainerDrops: me.allowContainerDrops,
  142571. appendOnly: me.appendOnly,
  142572. allowParentInserts: me.allowParentInserts,
  142573. expandDelay: me.expandDelay,
  142574. dropHighlightColor: me.nodeHighlightColor,
  142575. dropHighlight: me.nodeHighlightOnDrop
  142576. });
  142577. }
  142578. }
  142579. });
  142580. /**
  142581. * Utility class for manipulating CSS rules
  142582. * @singleton
  142583. */
  142584. Ext.define('Ext.util.CSS', (function() {
  142585. var rules = null,
  142586. doc = document,
  142587. camelRe = /(-[a-z])/gi,
  142588. camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
  142589. return {
  142590. singleton: true,
  142591. constructor: function() {
  142592. this.rules = {};
  142593. this.initialized = false;
  142594. },
  142595. /**
  142596. * Creates a stylesheet from a text blob of rules.
  142597. * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
  142598. * @param {String} cssText The text containing the css rules
  142599. * @param {String} id An id to add to the stylesheet for later removal
  142600. * @return {CSSStyleSheet}
  142601. */
  142602. createStyleSheet : function(cssText, id) {
  142603. var ss,
  142604. head = doc.getElementsByTagName("head")[0],
  142605. styleEl = doc.createElement("style");
  142606. styleEl.setAttribute("type", "text/css");
  142607. if (id) {
  142608. styleEl.setAttribute("id", id);
  142609. }
  142610. if (Ext.isIE) {
  142611. head.appendChild(styleEl);
  142612. ss = styleEl.styleSheet;
  142613. ss.cssText = cssText;
  142614. } else {
  142615. try{
  142616. styleEl.appendChild(doc.createTextNode(cssText));
  142617. } catch(e) {
  142618. styleEl.cssText = cssText;
  142619. }
  142620. head.appendChild(styleEl);
  142621. ss = styleEl.styleSheet ? styleEl.styleSheet : (styleEl.sheet || doc.styleSheets[doc.styleSheets.length-1]);
  142622. }
  142623. this.cacheStyleSheet(ss);
  142624. return ss;
  142625. },
  142626. /**
  142627. * Removes a style or link tag by id
  142628. * @param {String} id The id of the tag
  142629. */
  142630. removeStyleSheet : function(id) {
  142631. var existing = document.getElementById(id);
  142632. if (existing) {
  142633. existing.parentNode.removeChild(existing);
  142634. }
  142635. },
  142636. /**
  142637. * Dynamically swaps an existing stylesheet reference for a new one
  142638. * @param {String} id The id of an existing link tag to remove
  142639. * @param {String} url The href of the new stylesheet to include
  142640. */
  142641. swapStyleSheet : function(id, url) {
  142642. var doc = document,
  142643. ss;
  142644. this.removeStyleSheet(id);
  142645. ss = doc.createElement("link");
  142646. ss.setAttribute("rel", "stylesheet");
  142647. ss.setAttribute("type", "text/css");
  142648. ss.setAttribute("id", id);
  142649. ss.setAttribute("href", url);
  142650. doc.getElementsByTagName("head")[0].appendChild(ss);
  142651. },
  142652. /**
  142653. * Refresh the rule cache if you have dynamically added stylesheets
  142654. * @return {Object} An object (hash) of rules indexed by selector
  142655. */
  142656. refreshCache : function() {
  142657. return this.getRules(true);
  142658. },
  142659. // private
  142660. cacheStyleSheet : function(ss) {
  142661. if(!rules){
  142662. rules = {};
  142663. }
  142664. try {// try catch for cross domain access issue
  142665. var ssRules = ss.cssRules || ss.rules,
  142666. selectorText,
  142667. i = ssRules.length - 1,
  142668. j,
  142669. selectors;
  142670. for (; i >= 0; --i) {
  142671. selectorText = ssRules[i].selectorText;
  142672. if (selectorText) {
  142673. // Split in case there are multiple, comma-delimited selectors
  142674. selectorText = selectorText.split(',');
  142675. selectors = selectorText.length;
  142676. for (j = 0; j < selectors; j++) {
  142677. rules[Ext.String.trim(selectorText[j]).toLowerCase()] = ssRules[i];
  142678. }
  142679. }
  142680. }
  142681. } catch(e) {}
  142682. },
  142683. /**
  142684. * Gets all css rules for the document
  142685. * @param {Boolean} refreshCache true to refresh the internal cache
  142686. * @return {Object} An object (hash) of rules indexed by selector
  142687. */
  142688. getRules : function(refreshCache) {
  142689. if (rules === null || refreshCache) {
  142690. rules = {};
  142691. var ds = doc.styleSheets,
  142692. i = 0,
  142693. len = ds.length;
  142694. for (; i < len; i++) {
  142695. try {
  142696. if (!ds[i].disabled) {
  142697. this.cacheStyleSheet(ds[i]);
  142698. }
  142699. } catch(e) {}
  142700. }
  142701. }
  142702. return rules;
  142703. },
  142704. /**
  142705. * Gets an an individual CSS rule by selector(s)
  142706. * @param {String/String[]} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
  142707. * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
  142708. * @return {CSSStyleRule} The CSS rule or null if one is not found
  142709. */
  142710. getRule: function(selector, refreshCache) {
  142711. var rs = this.getRules(refreshCache),
  142712. i;
  142713. if (!Ext.isArray(selector)) {
  142714. return rs[selector.toLowerCase()];
  142715. }
  142716. for (i = 0; i < selector.length; i++) {
  142717. if (rs[selector[i]]) {
  142718. return rs[selector[i].toLowerCase()];
  142719. }
  142720. }
  142721. return null;
  142722. },
  142723. /**
  142724. * Updates a rule property
  142725. * @param {String/String[]} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
  142726. * @param {String} property The css property
  142727. * @param {String} value The new value for the property
  142728. * @return {Boolean} true If a rule was found and updated
  142729. */
  142730. updateRule : function(selector, property, value){
  142731. var rule, i;
  142732. if (!Ext.isArray(selector)) {
  142733. rule = this.getRule(selector);
  142734. if (rule) {
  142735. rule.style[property.replace(camelRe, camelFn)] = value;
  142736. return true;
  142737. }
  142738. } else {
  142739. for (i = 0; i < selector.length; i++) {
  142740. if (this.updateRule(selector[i], property, value)) {
  142741. return true;
  142742. }
  142743. }
  142744. }
  142745. return false;
  142746. }
  142747. };
  142748. }()));
  142749. /**
  142750. * Utility class for setting/reading values from browser cookies.
  142751. * Values can be written using the {@link #set} method.
  142752. * Values can be read using the {@link #get} method.
  142753. * A cookie can be invalidated on the client machine using the {@link #clear} method.
  142754. */
  142755. Ext.define('Ext.util.Cookies', {
  142756. singleton: true,
  142757. /**
  142758. * Creates a cookie with the specified name and value. Additional settings for the cookie may be optionally specified
  142759. * (for example: expiration, access restriction, SSL).
  142760. * @param {String} name The name of the cookie to set.
  142761. * @param {Object} value The value to set for the cookie.
  142762. * @param {Object} [expires] Specify an expiration date the cookie is to persist until. Note that the specified Date
  142763. * object will be converted to Greenwich Mean Time (GMT).
  142764. * @param {String} [path] Setting a path on the cookie restricts access to pages that match that path. Defaults to all
  142765. * pages ('/').
  142766. * @param {String} [domain] Setting a domain restricts access to pages on a given domain (typically used to allow
  142767. * cookie access across subdomains). For example, "sencha.com" will create a cookie that can be accessed from any
  142768. * subdomain of sencha.com, including www.sencha.com, support.sencha.com, etc.
  142769. * @param {Boolean} [secure] Specify true to indicate that the cookie should only be accessible via SSL on a page
  142770. * using the HTTPS protocol. Defaults to false. Note that this will only work if the page calling this code uses the
  142771. * HTTPS protocol, otherwise the cookie will be created with default options.
  142772. */
  142773. set : function(name, value){
  142774. var argv = arguments,
  142775. argc = arguments.length,
  142776. expires = (argc > 2) ? argv[2] : null,
  142777. path = (argc > 3) ? argv[3] : '/',
  142778. domain = (argc > 4) ? argv[4] : null,
  142779. secure = (argc > 5) ? argv[5] : false;
  142780. document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
  142781. },
  142782. /**
  142783. * Retrieves cookies that are accessible by the current page. If a cookie does not exist, `get()` returns null. The
  142784. * following example retrieves the cookie called "valid" and stores the String value in the variable validStatus.
  142785. *
  142786. * var validStatus = Ext.util.Cookies.get("valid");
  142787. *
  142788. * @param {String} name The name of the cookie to get
  142789. * @return {Object} Returns the cookie value for the specified name;
  142790. * null if the cookie name does not exist.
  142791. */
  142792. get : function(name){
  142793. var arg = name + "=",
  142794. alen = arg.length,
  142795. clen = document.cookie.length,
  142796. i = 0,
  142797. j = 0;
  142798. while(i < clen){
  142799. j = i + alen;
  142800. if(document.cookie.substring(i, j) == arg){
  142801. return this.getCookieVal(j);
  142802. }
  142803. i = document.cookie.indexOf(" ", i) + 1;
  142804. if(i === 0){
  142805. break;
  142806. }
  142807. }
  142808. return null;
  142809. },
  142810. /**
  142811. * Removes a cookie with the provided name from the browser
  142812. * if found by setting its expiration date to sometime in the past.
  142813. * @param {String} name The name of the cookie to remove
  142814. * @param {String} [path] The path for the cookie.
  142815. * This must be included if you included a path while setting the cookie.
  142816. */
  142817. clear : function(name, path){
  142818. if(this.get(name)){
  142819. path = path || '/';
  142820. document.cookie = name + '=' + '; expires=Thu, 01-Jan-70 00:00:01 GMT; path=' + path;
  142821. }
  142822. },
  142823. /**
  142824. * @private
  142825. */
  142826. getCookieVal : function(offset){
  142827. var endstr = document.cookie.indexOf(";", offset);
  142828. if(endstr == -1){
  142829. endstr = document.cookie.length;
  142830. }
  142831. return unescape(document.cookie.substring(offset, endstr));
  142832. }
  142833. });
  142834. /**
  142835. * @class Ext.util.Grouper
  142836. Represents a single grouper that can be applied to a Store. The grouper works
  142837. in the same fashion as the {@link Ext.util.Sorter}.
  142838. * @markdown
  142839. */
  142840. Ext.define('Ext.util.Grouper', {
  142841. /* Begin Definitions */
  142842. extend: 'Ext.util.Sorter',
  142843. /* End Definitions */
  142844. isGrouper: true,
  142845. /**
  142846. * Returns the value for grouping to be used.
  142847. * @param {Ext.data.Model} instance The Model instance
  142848. * @return {String} The group string for this model
  142849. */
  142850. getGroupString: function(instance) {
  142851. return instance.get(this.property);
  142852. }
  142853. });
  142854. /**
  142855. * History management component that allows you to register arbitrary tokens that signify application
  142856. * history state on navigation actions. You can then handle the history {@link #change} event in order
  142857. * to reset your application UI to the appropriate state when the user navigates forward or backward through
  142858. * the browser history stack.
  142859. *
  142860. * ## Initializing
  142861. *
  142862. * The {@link #init} method of the History object must be called before using History. This sets up the internal
  142863. * state and must be the first thing called before using History.
  142864. */
  142865. Ext.define('Ext.util.History', {
  142866. singleton: true,
  142867. alternateClassName: 'Ext.History',
  142868. mixins: {
  142869. observable: 'Ext.util.Observable'
  142870. },
  142871. /**
  142872. * @property
  142873. * True to use `window.top.location.hash` or false to use `window.location.hash`.
  142874. */
  142875. useTopWindow: true,
  142876. /**
  142877. * @property
  142878. * The id of the hidden field required for storing the current history token.
  142879. */
  142880. fieldId: Ext.baseCSSPrefix + 'history-field',
  142881. /**
  142882. * @property
  142883. * The id of the iframe required by IE to manage the history stack.
  142884. */
  142885. iframeId: Ext.baseCSSPrefix + 'history-frame',
  142886. constructor: function() {
  142887. var me = this;
  142888. me.oldIEMode = Ext.isIE6 || Ext.isIE7 || !Ext.isStrict && Ext.isIE8;
  142889. me.iframe = null;
  142890. me.hiddenField = null;
  142891. me.ready = false;
  142892. me.currentToken = null;
  142893. me.mixins.observable.constructor.call(me);
  142894. },
  142895. getHash: function() {
  142896. var href = window.location.href,
  142897. i = href.indexOf("#");
  142898. return i >= 0 ? href.substr(i + 1) : null;
  142899. },
  142900. setHash: function (hash) {
  142901. var me = this,
  142902. win = me.useTopWindow ? window.top : window;
  142903. try {
  142904. win.location.hash = hash;
  142905. } catch (e) {
  142906. // IE can give Access Denied (esp. in popup windows)
  142907. }
  142908. },
  142909. doSave: function() {
  142910. this.hiddenField.value = this.currentToken;
  142911. },
  142912. handleStateChange: function(token) {
  142913. this.currentToken = token;
  142914. this.fireEvent('change', token);
  142915. },
  142916. updateIFrame: function(token) {
  142917. var html = '<html><body><div id="state">' +
  142918. Ext.util.Format.htmlEncode(token) +
  142919. '</div></body></html>',
  142920. doc;
  142921. try {
  142922. doc = this.iframe.contentWindow.document;
  142923. doc.open();
  142924. doc.write(html);
  142925. doc.close();
  142926. return true;
  142927. } catch (e) {
  142928. return false;
  142929. }
  142930. },
  142931. checkIFrame: function () {
  142932. var me = this,
  142933. contentWindow = me.iframe.contentWindow,
  142934. doc, elem, oldToken, oldHash;
  142935. if (!contentWindow || !contentWindow.document) {
  142936. Ext.Function.defer(this.checkIFrame, 10, this);
  142937. return;
  142938. }
  142939. doc = contentWindow.document;
  142940. elem = doc.getElementById("state");
  142941. oldToken = elem ? elem.innerText : null;
  142942. oldHash = me.getHash();
  142943. Ext.TaskManager.start({
  142944. run: function () {
  142945. var doc = contentWindow.document,
  142946. elem = doc.getElementById("state"),
  142947. newToken = elem ? elem.innerText : null,
  142948. newHash = me.getHash();
  142949. if (newToken !== oldToken) {
  142950. oldToken = newToken;
  142951. me.handleStateChange(newToken);
  142952. me.setHash(newToken);
  142953. oldHash = newToken;
  142954. me.doSave();
  142955. } else if (newHash !== oldHash) {
  142956. oldHash = newHash;
  142957. me.updateIFrame(newHash);
  142958. }
  142959. },
  142960. interval: 50,
  142961. scope: me
  142962. });
  142963. me.ready = true;
  142964. me.fireEvent('ready', me);
  142965. },
  142966. startUp: function () {
  142967. var me = this,
  142968. hash;
  142969. me.currentToken = me.hiddenField.value || this.getHash();
  142970. if (me.oldIEMode) {
  142971. me.checkIFrame();
  142972. } else {
  142973. hash = me.getHash();
  142974. Ext.TaskManager.start({
  142975. run: function () {
  142976. var newHash = me.getHash();
  142977. if (newHash !== hash) {
  142978. hash = newHash;
  142979. me.handleStateChange(hash);
  142980. me.doSave();
  142981. }
  142982. },
  142983. interval: 50,
  142984. scope: me
  142985. });
  142986. me.ready = true;
  142987. me.fireEvent('ready', me);
  142988. }
  142989. },
  142990. /**
  142991. * Initializes the global History instance.
  142992. * @param {Function} [onReady] A callback function that will be called once the history
  142993. * component is fully initialized.
  142994. * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
  142995. * Defaults to the browser window.
  142996. */
  142997. init: function (onReady, scope) {
  142998. var me = this,
  142999. DomHelper = Ext.DomHelper;
  143000. if (me.ready) {
  143001. Ext.callback(onReady, scope, [me]);
  143002. return;
  143003. }
  143004. if (!Ext.isReady) {
  143005. Ext.onReady(function() {
  143006. me.init(onReady, scope);
  143007. });
  143008. return;
  143009. }
  143010. /*
  143011. <form id="history-form" class="x-hide-display">
  143012. <input type="hidden" id="x-history-field" />
  143013. <iframe id="x-history-frame"></iframe>
  143014. </form>
  143015. */
  143016. me.hiddenField = Ext.getDom(me.fieldId);
  143017. if (!me.hiddenField) {
  143018. me.hiddenField = Ext.getBody().createChild({
  143019. id: Ext.id(),
  143020. tag: 'form',
  143021. cls: Ext.baseCSSPrefix + 'hide-display',
  143022. children: [{
  143023. tag: 'input',
  143024. type: 'hidden',
  143025. id: me.fieldId
  143026. }]
  143027. }, false, true).firstChild;
  143028. }
  143029. if (me.oldIEMode) {
  143030. me.iframe = Ext.getDom(me.iframeId);
  143031. if (!me.iframe) {
  143032. me.iframe = DomHelper.append(me.hiddenField.parentNode, {
  143033. tag: 'iframe',
  143034. id: me.iframeId,
  143035. src: Ext.SSL_SECURE_URL
  143036. });
  143037. }
  143038. }
  143039. me.addEvents(
  143040. /**
  143041. * @event ready
  143042. * Fires when the Ext.util.History singleton has been initialized and is ready for use.
  143043. * @param {Ext.util.History} The Ext.util.History singleton.
  143044. */
  143045. 'ready',
  143046. /**
  143047. * @event change
  143048. * Fires when navigation back or forwards within the local page's history occurs.
  143049. * @param {String} token An identifier associated with the page state at that point in its history.
  143050. */
  143051. 'change'
  143052. );
  143053. if (onReady) {
  143054. me.on('ready', onReady, scope, {single: true});
  143055. }
  143056. me.startUp();
  143057. },
  143058. /**
  143059. * Add a new token to the history stack. This can be any arbitrary value, although it would
  143060. * commonly be the concatenation of a component id and another id marking the specific history
  143061. * state of that component. Example usage:
  143062. *
  143063. * // Handle tab changes on a TabPanel
  143064. * tabPanel.on('tabchange', function(tabPanel, tab){
  143065. * Ext.History.add(tabPanel.id + ':' + tab.id);
  143066. * });
  143067. *
  143068. * @param {String} token The value that defines a particular application-specific history state
  143069. * @param {Boolean} [preventDuplicates=true] When true, if the passed token matches the current token
  143070. * it will not save a new history step. Set to false if the same state can be saved more than once
  143071. * at the same history stack location.
  143072. */
  143073. add: function (token, preventDup) {
  143074. var me = this;
  143075. if (preventDup !== false) {
  143076. if (me.getToken() === token) {
  143077. return true;
  143078. }
  143079. }
  143080. if (me.oldIEMode) {
  143081. return me.updateIFrame(token);
  143082. } else {
  143083. me.setHash(token);
  143084. return true;
  143085. }
  143086. },
  143087. /**
  143088. * Programmatically steps back one step in browser history (equivalent to the user pressing the Back button).
  143089. */
  143090. back: function() {
  143091. window.history.go(-1);
  143092. },
  143093. /**
  143094. * Programmatically steps forward one step in browser history (equivalent to the user pressing the Forward button).
  143095. */
  143096. forward: function(){
  143097. window.history.go(1);
  143098. },
  143099. /**
  143100. * Retrieves the currently-active history token.
  143101. * @return {String} The token
  143102. */
  143103. getToken: function() {
  143104. return this.ready ? this.currentToken : this.getHash();
  143105. }
  143106. });
  143107. /**
  143108. * Represents a 2D point with x and y properties, useful for comparison and instantiation
  143109. * from an event:
  143110. *
  143111. * var point = Ext.util.Point.fromEvent(e);
  143112. *
  143113. */
  143114. Ext.define('Ext.util.Point', {
  143115. /* Begin Definitions */
  143116. extend: 'Ext.util.Region',
  143117. statics: {
  143118. /**
  143119. * Returns a new instance of Ext.util.Point base on the pageX / pageY values of the given event
  143120. * @static
  143121. * @param {Event} e The event
  143122. * @return {Ext.util.Point}
  143123. */
  143124. fromEvent: function(e) {
  143125. e = (e.changedTouches && e.changedTouches.length > 0) ? e.changedTouches[0] : e;
  143126. return new this(e.pageX, e.pageY);
  143127. }
  143128. },
  143129. /* End Definitions */
  143130. /**
  143131. * Creates a point from two coordinates.
  143132. * @param {Number} x X coordinate.
  143133. * @param {Number} y Y coordinate.
  143134. */
  143135. constructor: function(x, y) {
  143136. this.callParent([y, x, y, x]);
  143137. },
  143138. /**
  143139. * Returns a human-eye-friendly string that represents this point,
  143140. * useful for debugging
  143141. * @return {String}
  143142. */
  143143. toString: function() {
  143144. return "Point[" + this.x + "," + this.y + "]";
  143145. },
  143146. /**
  143147. * Compare this point and another point
  143148. * @param {Ext.util.Point/Object} The point to compare with, either an instance
  143149. * of Ext.util.Point or an object with left and top properties
  143150. * @return {Boolean} Returns whether they are equivalent
  143151. */
  143152. equals: function(p) {
  143153. return (this.x == p.x && this.y == p.y);
  143154. },
  143155. /**
  143156. * Whether the given point is not away from this point within the given threshold amount.
  143157. * @param {Ext.util.Point/Object} p The point to check with, either an instance
  143158. * of Ext.util.Point or an object with left and top properties
  143159. * @param {Object/Number} threshold Can be either an object with x and y properties or a number
  143160. * @return {Boolean}
  143161. */
  143162. isWithin: function(p, threshold) {
  143163. if (!Ext.isObject(threshold)) {
  143164. threshold = {
  143165. x: threshold,
  143166. y: threshold
  143167. };
  143168. }
  143169. return (this.x <= p.x + threshold.x && this.x >= p.x - threshold.x &&
  143170. this.y <= p.y + threshold.y && this.y >= p.y - threshold.y);
  143171. },
  143172. /**
  143173. * Compare this point with another point when the x and y values of both points are rounded. E.g:
  143174. * [100.3,199.8] will equals to [100, 200]
  143175. * @param {Ext.util.Point/Object} p The point to compare with, either an instance
  143176. * of Ext.util.Point or an object with x and y properties
  143177. * @return {Boolean}
  143178. */
  143179. roundedEquals: function(p) {
  143180. return (Math.round(this.x) == Math.round(p.x) && Math.round(this.y) == Math.round(p.y));
  143181. }
  143182. }, function() {
  143183. /**
  143184. * @method
  143185. * Alias for {@link #translateBy}
  143186. * @inheritdoc Ext.util.Region#translateBy
  143187. */
  143188. this.prototype.translate = Ext.util.Region.prototype.translateBy;
  143189. });
  143190. /**
  143191. * Produces optimized XTemplates for chunks of tables to be
  143192. * used in grids, trees and other table based widgets.
  143193. */
  143194. Ext.define('Ext.view.TableChunker', {
  143195. singleton: true,
  143196. requires: ['Ext.XTemplate'],
  143197. metaTableTpl: [
  143198. '{%if (this.openTableWrap)out.push(this.openTableWrap())%}',
  143199. '<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" border="0" cellspacing="0" cellpadding="0" {[this.embedFullWidth(values)]}>',
  143200. '<tbody>',
  143201. '<tr class="' + Ext.baseCSSPrefix + 'grid-header-row">',
  143202. '<tpl for="columns">',
  143203. '<th class="' + Ext.baseCSSPrefix + 'grid-col-resizer-{id}" style="width: {width}px; height: 0px;"></th>',
  143204. '</tpl>',
  143205. '</tr>',
  143206. '{[this.openRows()]}',
  143207. '{row}',
  143208. '<tpl for="features">',
  143209. '{[this.embedFeature(values, parent, xindex, xcount)]}',
  143210. '</tpl>',
  143211. '{[this.closeRows()]}',
  143212. '</tbody>',
  143213. '</table>',
  143214. '{%if (this.closeTableWrap)out.push(this.closeTableWrap())%}'
  143215. ],
  143216. constructor: function() {
  143217. Ext.XTemplate.prototype.recurse = function(values, reference) {
  143218. return this.apply(reference ? values[reference] : values);
  143219. };
  143220. },
  143221. embedFeature: function(values, parent, x, xcount) {
  143222. var tpl = '';
  143223. if (!values.disabled) {
  143224. tpl = values.getFeatureTpl(values, parent, x, xcount);
  143225. }
  143226. return tpl;
  143227. },
  143228. embedFullWidth: function(values) {
  143229. var result = 'style="width:{fullWidth}px;';
  143230. // If there are no records, we need to give the table a height so that it
  143231. // is displayed and causes q scrollbar if the width exceeds the View's width.
  143232. if (!values.rowCount) {
  143233. result += 'height:1px;';
  143234. }
  143235. return result + '"';
  143236. },
  143237. openRows: function() {
  143238. return '<tpl for="rows">';
  143239. },
  143240. closeRows: function() {
  143241. return '</tpl>';
  143242. },
  143243. metaRowTpl: [
  143244. '<tr class="' + Ext.baseCSSPrefix + 'grid-row {[this.embedRowCls()]}" {[this.embedRowAttr()]}>',
  143245. '<tpl for="columns">',
  143246. '<td class="{cls} ' + Ext.baseCSSPrefix + 'grid-cell ' + Ext.baseCSSPrefix + 'grid-cell-{columnId} {{id}-modified} {{id}-tdCls} {[this.firstOrLastCls(xindex, xcount)]}" {{id}-tdAttr}>',
  143247. '<div {unselectableAttr} class="' + Ext.baseCSSPrefix + 'grid-cell-inner {unselectableCls}" style="text-align: {align}; {{id}-style};">{{id}}</div>',
  143248. '</td>',
  143249. '</tpl>',
  143250. '</tr>'
  143251. ],
  143252. firstOrLastCls: function(xindex, xcount) {
  143253. if (xindex === 1) {
  143254. return Ext.view.Table.prototype.firstCls;
  143255. } else if (xindex === xcount) {
  143256. return Ext.view.Table.prototype.lastCls;
  143257. }
  143258. },
  143259. embedRowCls: function() {
  143260. return '{rowCls}';
  143261. },
  143262. embedRowAttr: function() {
  143263. return '{rowAttr}';
  143264. },
  143265. openTableWrap: undefined,
  143266. closeTableWrap: undefined,
  143267. getTableTpl: function(cfg, textOnly) {
  143268. var tpl,
  143269. tableTplMemberFns = {
  143270. openRows: this.openRows,
  143271. closeRows: this.closeRows,
  143272. embedFeature: this.embedFeature,
  143273. embedFullWidth: this.embedFullWidth,
  143274. openTableWrap: this.openTableWrap,
  143275. closeTableWrap: this.closeTableWrap
  143276. },
  143277. tplMemberFns = {},
  143278. features = cfg.features || [],
  143279. ln = features.length,
  143280. i = 0,
  143281. memberFns = {
  143282. embedRowCls: this.embedRowCls,
  143283. embedRowAttr: this.embedRowAttr,
  143284. firstOrLastCls: this.firstOrLastCls,
  143285. unselectableAttr: cfg.enableTextSelection ? '' : 'unselectable="on"',
  143286. unselectableCls: cfg.enableTextSelection ? '' : Ext.baseCSSPrefix + 'unselectable'
  143287. },
  143288. // copy the default
  143289. metaRowTpl = Array.prototype.slice.call(this.metaRowTpl, 0),
  143290. metaTableTpl;
  143291. for (; i < ln; i++) {
  143292. if (!features[i].disabled) {
  143293. features[i].mutateMetaRowTpl(metaRowTpl);
  143294. Ext.apply(memberFns, features[i].getMetaRowTplFragments());
  143295. Ext.apply(tplMemberFns, features[i].getFragmentTpl());
  143296. Ext.apply(tableTplMemberFns, features[i].getTableFragments());
  143297. }
  143298. }
  143299. metaRowTpl = new Ext.XTemplate(metaRowTpl.join(''), memberFns);
  143300. cfg.row = metaRowTpl.applyTemplate(cfg);
  143301. metaTableTpl = new Ext.XTemplate(this.metaTableTpl.join(''), tableTplMemberFns);
  143302. tpl = metaTableTpl.applyTemplate(cfg);
  143303. // TODO: Investigate eliminating.
  143304. if (!textOnly) {
  143305. tpl = new Ext.XTemplate(tpl, tplMemberFns);
  143306. }
  143307. return tpl;
  143308. }
  143309. });
  143310. //@tail
  143311. /*
  143312. * This file represents the very last stage of the Ext definition process and is ensured
  143313. * to be included at the end of the build via the 'tail' package of extjs.jsb3.
  143314. *
  143315. */
  143316. Ext._endTime = new Date().getTime();
  143317. if (Ext._beforereadyhandler){
  143318. Ext._beforereadyhandler();
  143319. }