123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314 |
- Ext.define(null, {
- override: 'Ext.ux.gauge.needle.Abstract',
- compatibility: Ext.isIE10p,
- setTransform: function(centerX, centerY, rotation) {
- var needleGroup = this.getNeedleGroup();
- this.callParent([
- centerX,
- centerY,
- rotation
- ]);
- needleGroup.set({
- transform: getComputedStyle(needleGroup.dom).getPropertyValue('transform')
- });
- },
- updateStyle: function(style) {
- var pathElement;
- this.callParent([
- style
- ]);
- if (Ext.isObject(style) && 'transform' in style) {
- pathElement = this.getNeedlePath();
- pathElement.set({
- transform: getComputedStyle(pathElement.dom).getPropertyValue('transform')
- });
- }
- }
- });
- /**
- * This is a base class for more advanced "simlets" (simulated servers). A simlet is asked
- * to provide a response given a {@link Ext.ux.ajax.SimXhr} instance.
- */
- Ext.define('Ext.ux.ajax.Simlet', function() {
- var urlRegex = /([^?#]*)(#.*)?$/,
- dateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/,
- intRegex = /^[+-]?\d+$/,
- floatRegex = /^[+-]?\d+\.\d+$/;
- function parseParamValue(value) {
- var m;
- if (Ext.isDefined(value)) {
- value = decodeURIComponent(value);
- if (intRegex.test(value)) {
- value = parseInt(value, 10);
- } else if (floatRegex.test(value)) {
- value = parseFloat(value);
- } else if (!!(m = dateRegex.exec(value))) {
- value = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6]));
- }
- }
- return value;
- }
- return {
- alias: 'simlet.basic',
- isSimlet: true,
- responseProps: [
- 'responseText',
- 'responseXML',
- 'status',
- 'statusText',
- 'responseHeaders'
- ],
- /**
- * @cfg {String/Function} responseText
- */
- /**
- * @cfg {String/Function} responseXML
- */
- /**
- * @cfg {Object/Function} responseHeaders
- */
- /**
- * @cfg {Number/Function} status
- */
- status: 200,
- /**
- * @cfg {String/Function} statusText
- */
- statusText: 'OK',
- constructor: function(config) {
- Ext.apply(this, config);
- },
- doGet: function(ctx) {
- return this.handleRequest(ctx);
- },
- doPost: function(ctx) {
- return this.handleRequest(ctx);
- },
- doRedirect: function(ctx) {
- return false;
- },
- doDelete: function(ctx) {
- var me = this,
- xhr = ctx.xhr,
- records = xhr.options.records;
- me.removeFromData(ctx, records);
- },
- /**
- * Performs the action requested by the given XHR and returns an object to be applied
- * on to the XHR (containing `status`, `responseText`, etc.). For the most part,
- * this is delegated to `doMethod` methods on this class, such as `doGet`.
- *
- * @param {Ext.ux.ajax.SimXhr} xhr The simulated XMLHttpRequest instance.
- * @return {Object} The response properties to add to the XMLHttpRequest.
- */
- exec: function(xhr) {
- var me = this,
- ret = {},
- method = 'do' + Ext.String.capitalize(xhr.method.toLowerCase()),
- // doGet
- fn = me[method];
- if (fn) {
- ret = fn.call(me, me.getCtx(xhr.method, xhr.url, xhr));
- } else {
- ret = {
- status: 405,
- statusText: 'Method Not Allowed'
- };
- }
- return ret;
- },
- getCtx: function(method, url, xhr) {
- return {
- method: method,
- params: this.parseQueryString(url),
- url: url,
- xhr: xhr
- };
- },
- handleRequest: function(ctx) {
- var me = this,
- ret = {},
- val;
- Ext.Array.forEach(me.responseProps, function(prop) {
- if (prop in me) {
- val = me[prop];
- if (Ext.isFunction(val)) {
- val = val.call(me, ctx);
- }
- ret[prop] = val;
- }
- });
- return ret;
- },
- openRequest: function(method, url, options, async) {
- var ctx = this.getCtx(method, url),
- redirect = this.doRedirect(ctx),
- xhr;
- if (options.action === 'destroy') {
- method = 'delete';
- }
- if (redirect) {
- xhr = redirect;
- } else {
- xhr = new Ext.ux.ajax.SimXhr({
- mgr: this.manager,
- simlet: this,
- options: options
- });
- xhr.open(method, url, async);
- }
- return xhr;
- },
- parseQueryString: function(str) {
- var m = urlRegex.exec(str),
- ret = {},
- key, value, i, n;
- if (m && m[1]) {
- var pair,
- parts = m[1].split('&');
- for (i = 0 , n = parts.length; i < n; ++i) {
- if ((pair = parts[i].split('='))[0]) {
- key = decodeURIComponent(pair.shift());
- value = parseParamValue((pair.length > 1) ? pair.join('=') : pair[0]);
- if (!(key in ret)) {
- ret[key] = value;
- } else if (Ext.isArray(ret[key])) {
- ret[key].push(value);
- } else {
- ret[key] = [
- ret[key],
- value
- ];
- }
- }
- }
- }
- return ret;
- },
- redirect: function(method, url, params) {
- switch (arguments.length) {
- case 2:
- if (typeof url == 'string') {
- break;
- };
- params = url;
- // fall...
- case 1:
- url = method;
- method = 'GET';
- break;
- }
- if (params) {
- url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
- }
- return this.manager.openRequest(method, url);
- },
- removeFromData: function(ctx, records) {
- var me = this,
- data = me.getData(ctx),
- model = (ctx.xhr.options.proxy && ctx.xhr.options.proxy.getModel()) || {},
- idProperty = model.idProperty || 'id';
- Ext.each(records, function(record) {
- var id = record.get(idProperty);
- for (var i = data.length; i-- > 0; ) {
- if (data[i][idProperty] === id) {
- me.deleteRecord(i);
- break;
- }
- }
- });
- }
- };
- }());
- /**
- * This base class is used to handle data preparation (e.g., sorting, filtering and
- * group summary).
- */
- Ext.define('Ext.ux.ajax.DataSimlet', function() {
- function makeSortFn(def, cmp) {
- var order = def.direction,
- sign = (order && order.toUpperCase() === 'DESC') ? -1 : 1;
- return function(leftRec, rightRec) {
- var lhs = leftRec[def.property],
- rhs = rightRec[def.property],
- c = (lhs < rhs) ? -1 : ((rhs < lhs) ? 1 : 0);
- if (c || !cmp) {
- return c * sign;
- }
- return cmp(leftRec, rightRec);
- };
- }
- function makeSortFns(defs, cmp) {
- for (var sortFn = cmp,
- i = defs && defs.length; i; ) {
- sortFn = makeSortFn(defs[--i], sortFn);
- }
- return sortFn;
- }
- return {
- extend: 'Ext.ux.ajax.Simlet',
- buildNodes: function(node, path) {
- var me = this,
- nodeData = {
- data: []
- },
- len = node.length,
- children, i, child, name;
- me.nodes[path] = nodeData;
- for (i = 0; i < len; ++i) {
- nodeData.data.push(child = node[i]);
- name = child.text || child.title;
- child.id = path ? path + '/' + name : name;
- children = child.children;
- if (!(child.leaf = !children)) {
- delete child.children;
- me.buildNodes(children, child.id);
- }
- }
- },
- deleteRecord: function(pos) {
- if (this.data && typeof this.data !== 'function') {
- Ext.Array.removeAt(this.data, pos);
- }
- },
- fixTree: function(ctx, tree) {
- var me = this,
- node = ctx.params.node,
- nodes;
- if (!(nodes = me.nodes)) {
- me.nodes = nodes = {};
- me.buildNodes(tree, '');
- }
- node = nodes[node];
- if (node) {
- if (me.node) {
- me.node.sortedData = me.sortedData;
- me.node.currentOrder = me.currentOrder;
- }
- me.node = node;
- me.data = node.data;
- me.sortedData = node.sortedData;
- me.currentOrder = node.currentOrder;
- } else {
- me.data = null;
- }
- },
- getData: function(ctx) {
- var me = this,
- params = ctx.params,
- order = (params.filter || '') + (params.group || '') + '-' + (params.sort || '') + '-' + (params.dir || ''),
- tree = me.tree,
- dynamicData, data, fields, sortFn;
- if (tree) {
- me.fixTree(ctx, tree);
- }
- data = me.data;
- if (typeof data === 'function') {
- dynamicData = true;
- data = data.call(this, ctx);
- }
- // If order is '--' then it means we had no order passed, due to the string concat above
- if (!data || order === '--') {
- return data || [];
- }
- if (!dynamicData && order == me.currentOrder) {
- return me.sortedData;
- }
- ctx.filterSpec = params.filter && Ext.decode(params.filter);
- ctx.groupSpec = params.group && Ext.decode(params.group);
- fields = params.sort;
- if (params.dir) {
- fields = [
- {
- direction: params.dir,
- property: fields
- }
- ];
- } else if (params.sort) {
- fields = Ext.decode(params.sort);
- } else {
- fields = null;
- }
- if (ctx.filterSpec) {
- var filters = new Ext.util.FilterCollection();
- filters.add(this.processFilters(ctx.filterSpec));
- data = Ext.Array.filter(data, filters.getFilterFn());
- }
- sortFn = makeSortFns((ctx.sortSpec = fields));
- if (ctx.groupSpec) {
- sortFn = makeSortFns([
- ctx.groupSpec
- ], sortFn);
- }
- // If a straight Ajax request, data may not be an array.
- // If an Array, preserve 'physical' order of raw data...
- data = Ext.isArray(data) ? data.slice(0) : data;
- if (sortFn) {
- Ext.Array.sort(data, sortFn);
- }
- me.sortedData = data;
- me.currentOrder = order;
- return data;
- },
- processFilters: Ext.identityFn,
- getPage: function(ctx, data) {
- var ret = data,
- length = data.length,
- start = ctx.params.start || 0,
- end = ctx.params.limit ? Math.min(length, start + ctx.params.limit) : length;
- if (start || end < length) {
- ret = ret.slice(start, end);
- }
- return ret;
- },
- getGroupSummary: function(groupField, rows, ctx) {
- return rows[0];
- },
- getSummary: function(ctx, data, page) {
- var me = this,
- groupField = ctx.groupSpec.property,
- accum,
- todo = {},
- summary = [],
- fieldValue, lastFieldValue;
- Ext.each(page, function(rec) {
- fieldValue = rec[groupField];
- todo[fieldValue] = true;
- });
- function flush() {
- if (accum) {
- summary.push(me.getGroupSummary(groupField, accum, ctx));
- accum = null;
- }
- }
- // data is ordered primarily by the groupField, so one pass can pick up all
- // the summaries one at a time.
- Ext.each(data, function(rec) {
- fieldValue = rec[groupField];
- if (lastFieldValue !== fieldValue) {
- flush();
- lastFieldValue = fieldValue;
- }
- if (!todo[fieldValue]) {
- // if we have even 1 summary, we have summarized all that we need
- // (again because data and page are ordered by groupField)
- return !summary.length;
- }
- if (accum) {
- accum.push(rec);
- } else {
- accum = [
- rec
- ];
- }
- return true;
- });
- flush();
- // make sure that last pesky summary goes...
- return summary;
- }
- };
- }());
- /**
- * JSON Simlet.
- */
- Ext.define('Ext.ux.ajax.JsonSimlet', {
- extend: 'Ext.ux.ajax.DataSimlet',
- alias: 'simlet.json',
- doGet: function(ctx) {
- var me = this,
- data = me.getData(ctx),
- page = me.getPage(ctx, data),
- reader = ctx.xhr.options.proxy && ctx.xhr.options.proxy.getReader(),
- root = reader && reader.getRootProperty(),
- ret = me.callParent(arguments),
- // pick up status/statusText
- response = {};
- if (root && Ext.isArray(page)) {
- response[root] = page;
- response[reader.getTotalProperty()] = data.length;
- } else {
- response = page;
- }
- if (ctx.groupSpec) {
- response.summaryData = me.getSummary(ctx, data, page);
- }
- ret.responseText = Ext.encode(response);
- return ret;
- },
- doPost: function(ctx) {
- return this.doGet(ctx);
- }
- });
- /**
- * Pivot Simlet does remote pivot calculations.
- * Filtering the pivot results doesn't work.
- */
- Ext.define('Ext.ux.ajax.PivotSimlet', {
- extend: 'Ext.ux.ajax.JsonSimlet',
- alias: 'simlet.pivot',
- lastPost: null,
- // last Ajax params sent to this simlet
- lastResponse: null,
- // last JSON response produced by this simlet
- keysSeparator: '',
- grandTotalKey: '',
- doPost: function(ctx) {
- var me = this,
- ret = me.callParent(arguments);
- // pick up status/statusText
- me.lastResponse = me.processData(me.getData(ctx), Ext.decode(ctx.xhr.body));
- ret.responseText = Ext.encode(me.lastResponse);
- return ret;
- },
- processData: function(data, params) {
- var me = this,
- len = data.length,
- response = {
- success: true,
- leftAxis: [],
- topAxis: [],
- results: []
- },
- leftAxis = new Ext.util.MixedCollection(),
- topAxis = new Ext.util.MixedCollection(),
- results = new Ext.util.MixedCollection(),
- i, j, k, leftKeys, topKeys, item, agg;
- me.lastPost = params;
- me.keysSeparator = params.keysSeparator;
- me.grandTotalKey = params.grandTotalKey;
- for (i = 0; i < len; i++) {
- leftKeys = me.extractValues(data[i], params.leftAxis, leftAxis);
- topKeys = me.extractValues(data[i], params.topAxis, topAxis);
- // add record to grand totals
- me.addResult(data[i], me.grandTotalKey, me.grandTotalKey, results);
- for (j = 0; j < leftKeys.length; j++) {
- // add record to col grand totals
- me.addResult(data[i], leftKeys[j], me.grandTotalKey, results);
- // add record to left/top keys pair
- for (k = 0; k < topKeys.length; k++) {
- me.addResult(data[i], leftKeys[j], topKeys[k], results);
- }
- }
- // add record to row grand totals
- for (j = 0; j < topKeys.length; j++) {
- me.addResult(data[i], me.grandTotalKey, topKeys[j], results);
- }
- }
- // extract items from their left/top collections and build the json response
- response.leftAxis = leftAxis.getRange();
- response.topAxis = topAxis.getRange();
- len = results.getCount();
- for (i = 0; i < len; i++) {
- item = results.getAt(i);
- item.values = {};
- for (j = 0; j < params.aggregate.length; j++) {
- agg = params.aggregate[j];
- item.values[agg.id] = me[agg.aggregator](item.records, agg.dataIndex, item.leftKey, item.topKey);
- }
- delete (item.records);
- response.results.push(item);
- }
- leftAxis.clear();
- topAxis.clear();
- results.clear();
- return response;
- },
- getKey: function(value) {
- var me = this;
- me.keysMap = me.keysMap || {};
- if (!Ext.isDefined(me.keysMap[value])) {
- me.keysMap[value] = Ext.id();
- }
- return me.keysMap[value];
- },
- extractValues: function(record, dimensions, col) {
- var len = dimensions.length,
- keys = [],
- i, j, key, item, dim;
- key = '';
- for (j = 0; j < len; j++) {
- dim = dimensions[j];
- key += (j > 0 ? this.keysSeparator : '') + this.getKey(record[dim.dataIndex]);
- item = col.getByKey(key);
- if (!item) {
- item = col.add(key, {
- key: key,
- value: record[dim.dataIndex],
- dimensionId: dim.id
- });
- }
- keys.push(key);
- }
- return keys;
- },
- addResult: function(record, leftKey, topKey, results) {
- var item = results.getByKey(leftKey + '/' + topKey);
- if (!item) {
- item = results.add(leftKey + '/' + topKey, {
- leftKey: leftKey,
- topKey: topKey,
- records: []
- });
- }
- item.records.push(record);
- },
- sum: function(records, measure, rowGroupKey, colGroupKey) {
- var length = records.length,
- total = 0,
- i;
- for (i = 0; i < length; i++) {
- total += Ext.Number.from(records[i][measure], 0);
- }
- return total;
- },
- avg: function(records, measure, rowGroupKey, colGroupKey) {
- var length = records.length,
- total = 0,
- i;
- for (i = 0; i < length; i++) {
- total += Ext.Number.from(records[i][measure], 0);
- }
- return length > 0 ? (total / length) : 0;
- },
- min: function(records, measure, rowGroupKey, colGroupKey) {
- var data = [],
- length = records.length,
- i, v;
- for (i = 0; i < length; i++) {
- data.push(records[i][measure]);
- }
- v = Ext.Array.min(data);
- return v;
- },
- max: function(records, measure, rowGroupKey, colGroupKey) {
- var data = [],
- length = records.length,
- i;
- for (i = 0; i < length; i++) {
- data.push(records[i][measure]);
- }
- v = Ext.Array.max(data);
- return v;
- },
- count: function(records, measure, rowGroupKey, colGroupKey) {
- return records.length;
- },
- variance: function(records, measure, rowGroupKey, colGroupKey) {
- var me = Ext.pivot.Aggregators,
- length = records.length,
- avg = me.avg.apply(me, arguments),
- total = 0,
- i;
- if (avg > 0) {
- for (i = 0; i < length; i++) {
- total += Math.pow(Ext.Number.from(records[i][measure], 0) - avg, 2);
- }
- }
- return (total > 0 && length > 1) ? (total / (length - 1)) : 0;
- },
- varianceP: function(records, measure, rowGroupKey, colGroupKey) {
- var me = Ext.pivot.Aggregators,
- length = records.length,
- avg = me.avg.apply(me, arguments),
- total = 0,
- i;
- if (avg > 0) {
- for (i = 0; i < length; i++) {
- total += Math.pow(Ext.Number.from(records[i][measure], 0) - avg, 2);
- }
- }
- return (total > 0 && length > 0) ? (total / length) : 0;
- },
- stdDev: function(records, measure, rowGroupKey, colGroupKey) {
- var me = Ext.pivot.Aggregators,
- v = me.variance.apply(me, arguments);
- return v > 0 ? Math.sqrt(v) : 0;
- },
- stdDevP: function(records, measure, rowGroupKey, colGroupKey) {
- var me = Ext.pivot.Aggregators,
- v = me.varianceP.apply(me, arguments);
- return v > 0 ? Math.sqrt(v) : 0;
- }
- });
- /**
- * Simulates an XMLHttpRequest object's methods and properties but is backed by a
- * {@link Ext.ux.ajax.Simlet} instance that provides the data.
- */
- Ext.define('Ext.ux.ajax.SimXhr', {
- readyState: 0,
- mgr: null,
- simlet: null,
- constructor: function(config) {
- var me = this;
- Ext.apply(me, config);
- me.requestHeaders = {};
- },
- abort: function() {
- var me = this;
- if (me.timer) {
- Ext.undefer(me.timer);
- me.timer = null;
- }
- me.aborted = true;
- },
- getAllResponseHeaders: function() {
- var headers = [];
- if (Ext.isObject(this.responseHeaders)) {
- Ext.Object.each(this.responseHeaders, function(name, value) {
- headers.push(name + ': ' + value);
- });
- }
- return headers.join('\r\n');
- },
- getResponseHeader: function(header) {
- var headers = this.responseHeaders;
- return (headers && headers[header]) || null;
- },
- open: function(method, url, async, user, password) {
- var me = this;
- me.method = method;
- me.url = url;
- me.async = async !== false;
- me.user = user;
- me.password = password;
- me.setReadyState(1);
- },
- overrideMimeType: function(mimeType) {
- this.mimeType = mimeType;
- },
- schedule: function() {
- var me = this,
- delay = me.simlet.delay || me.mgr.delay;
- if (delay) {
- me.timer = Ext.defer(function() {
- me.onTick();
- }, delay);
- } else {
- me.onTick();
- }
- },
- send: function(body) {
- var me = this;
- me.body = body;
- if (me.async) {
- me.schedule();
- } else {
- me.onComplete();
- }
- },
- setReadyState: function(state) {
- var me = this;
- if (me.readyState != state) {
- me.readyState = state;
- me.onreadystatechange();
- }
- },
- setRequestHeader: function(header, value) {
- this.requestHeaders[header] = value;
- },
- // handlers
- onreadystatechange: Ext.emptyFn,
- onComplete: function() {
- var me = this,
- callback;
- me.readyState = 4;
- Ext.apply(me, me.simlet.exec(me));
- callback = me.jsonpCallback;
- if (callback) {
- var text = callback + '(' + me.responseText + ')';
- eval(text);
- }
- },
- onTick: function() {
- var me = this;
- me.timer = null;
- me.onComplete();
- me.onreadystatechange && me.onreadystatechange();
- }
- });
- /**
- * This singleton manages simulated Ajax responses. This allows application logic to be
- * written unaware that its Ajax calls are being handled by simulations ("simlets"). This
- * is currently done by hooking {@link Ext.data.Connection} methods, so all users of that
- * class (and {@link Ext.Ajax} since it is a derived class) qualify for simulation.
- *
- * The requires hooks are inserted when either the {@link #init} method is called or the
- * first {@link Ext.ux.ajax.Simlet} is registered. For example:
- *
- * Ext.onReady(function () {
- * initAjaxSim();
- *
- * // normal stuff
- * });
- *
- * function initAjaxSim () {
- * Ext.ux.ajax.SimManager.init({
- * delay: 300
- * }).register({
- * '/app/data/url': {
- * type: 'json', // use JsonSimlet (type is like xtype for components)
- * data: [
- * { foo: 42, bar: 'abc' },
- * ...
- * ]
- * }
- * });
- * }
- *
- * As many URL's as desired can be registered and associated with a {@link Ext.ux.ajax.Simlet}. To make
- * non-simulated Ajax requests once this singleton is initialized, add a `nosim:true` option
- * to the Ajax options:
- *
- * Ext.Ajax.request({
- * url: 'page.php',
- * nosim: true, // ignored by normal Ajax request
- * params: {
- * id: 1
- * },
- * success: function(response){
- * var text = response.responseText;
- * // process server response here
- * }
- * });
- */
- Ext.define('Ext.ux.ajax.SimManager', {
- singleton: true,
- requires: [
- 'Ext.data.Connection',
- 'Ext.ux.ajax.SimXhr',
- 'Ext.ux.ajax.Simlet',
- 'Ext.ux.ajax.JsonSimlet'
- ],
- /**
- * @cfg {Ext.ux.ajax.Simlet} defaultSimlet
- * The {@link Ext.ux.ajax.Simlet} instance to use for non-matching URL's. By default, this will
- * return 404. Set this to null to use real Ajax calls for non-matching URL's.
- */
- /**
- * @cfg {String} defaultType
- * The default `type` to apply to generic {@link Ext.ux.ajax.Simlet} configuration objects. The
- * default is 'basic'.
- */
- defaultType: 'basic',
- /**
- * @cfg {Number} delay
- * The number of milliseconds to delay before delivering a response to an async request.
- */
- delay: 150,
- /**
- * @property {Boolean} ready
- * True once this singleton has initialized and applied its Ajax hooks.
- * @private
- */
- ready: false,
- constructor: function() {
- this.simlets = [];
- },
- getSimlet: function(url) {
- // Strip down to base URL (no query parameters or hash):
- var me = this,
- index = url.indexOf('?'),
- simlets = me.simlets,
- len = simlets.length,
- i, simlet, simUrl, match;
- if (index < 0) {
- index = url.indexOf('#');
- }
- if (index > 0) {
- url = url.substring(0, index);
- }
- for (i = 0; i < len; ++i) {
- simlet = simlets[i];
- simUrl = simlet.url;
- if (simUrl instanceof RegExp) {
- match = simUrl.test(url);
- } else {
- match = simUrl === url;
- }
- if (match) {
- return simlet;
- }
- }
- return me.defaultSimlet;
- },
- getXhr: function(method, url, options, async) {
- var simlet = this.getSimlet(url);
- if (simlet) {
- return simlet.openRequest(method, url, options, async);
- }
- return null;
- },
- /**
- * Initializes this singleton and applies configuration options.
- * @param {Object} config An optional object with configuration properties to apply.
- * @return {Ext.ux.ajax.SimManager} this
- */
- init: function(config) {
- var me = this;
- Ext.apply(me, config);
- if (!me.ready) {
- me.ready = true;
- if (!('defaultSimlet' in me)) {
- me.defaultSimlet = new Ext.ux.ajax.Simlet({
- status: 404,
- statusText: 'Not Found'
- });
- }
- me._openRequest = Ext.data.Connection.prototype.openRequest;
- Ext.data.request.Ajax.override({
- openRequest: function(options, requestOptions, async) {
- var xhr = !options.nosim && me.getXhr(requestOptions.method, requestOptions.url, options, async);
- if (!xhr) {
- xhr = this.callParent(arguments);
- }
- return xhr;
- }
- });
- if (Ext.data.JsonP) {
- Ext.data.JsonP.self.override({
- createScript: function(url, params, options) {
- var fullUrl = Ext.urlAppend(url, Ext.Object.toQueryString(params)),
- script = !options.nosim && me.getXhr('GET', fullUrl, options, true);
- if (!script) {
- script = this.callParent(arguments);
- }
- return script;
- },
- loadScript: function(request) {
- var script = request.script;
- if (script.simlet) {
- script.jsonpCallback = request.params[request.callbackKey];
- script.send(null);
- // Ext.data.JsonP will attempt dom removal of a script tag, so emulate its presence
- request.script = document.createElement('script');
- } else {
- this.callParent(arguments);
- }
- }
- });
- }
- }
- return me;
- },
- openRequest: function(method, url, async) {
- var opt = {
- method: method,
- url: url
- };
- return this._openRequest.call(Ext.data.Connection.prototype, {}, opt, async);
- },
- /**
- * Registeres one or more {@link Ext.ux.ajax.Simlet} instances.
- * @param {Array/Object} simlet Either a {@link Ext.ux.ajax.Simlet} instance or config, an Array
- * of such elements or an Object keyed by URL with values that are {@link Ext.ux.ajax.Simlet}
- * instances or configs.
- */
- register: function(simlet) {
- var me = this;
- me.init();
- function reg(one) {
- var simlet = one;
- if (!simlet.isSimlet) {
- simlet = Ext.create('simlet.' + (simlet.type || simlet.stype || me.defaultType), one);
- }
- me.simlets.push(simlet);
- simlet.manager = me;
- }
- if (Ext.isArray(simlet)) {
- Ext.each(simlet, reg);
- } else if (simlet.isSimlet || simlet.url) {
- reg(simlet);
- } else {
- Ext.Object.each(simlet, function(url, s) {
- s.url = url;
- reg(s);
- });
- }
- return me;
- }
- });
- /**
- * This class simulates XML-based requests.
- */
- Ext.define('Ext.ux.ajax.XmlSimlet', {
- extend: 'Ext.ux.ajax.DataSimlet',
- alias: 'simlet.xml',
- /**
- * This template is used to populate the XML response. The configuration of the Reader
- * is available so that its `root` and `record` properties can be used as well as the
- * `fields` of the associated `model`. But beyond that, the way these pieces are put
- * together in the document requires the flexibility of a template.
- */
- xmlTpl: [
- '<{root}>\n',
- '<tpl for="data">',
- ' <{parent.record}>\n',
- '<tpl for="parent.fields">',
- ' <{name}>{[parent[values.name]]}</{name}>\n',
- '</tpl>',
- ' </{parent.record}>\n',
- '</tpl>',
- '</{root}>'
- ],
- doGet: function(ctx) {
- var me = this,
- data = me.getData(ctx),
- page = me.getPage(ctx, data),
- proxy = ctx.xhr.options.operation.getProxy(),
- reader = proxy && proxy.getReader(),
- model = reader && reader.getModel(),
- ret = me.callParent(arguments),
- // pick up status/statusText
- response = {
- data: page,
- reader: reader,
- fields: model && model.fields,
- root: reader && reader.getRootProperty(),
- record: reader && reader.record
- },
- tpl, xml, doc;
- if (ctx.groupSpec) {
- response.summaryData = me.getSummary(ctx, data, page);
- }
- // If a straight Ajax request there won't be an xmlTpl.
- if (me.xmlTpl) {
- tpl = Ext.XTemplate.getTpl(me, 'xmlTpl');
- xml = tpl.apply(response);
- } else {
- xml = data;
- }
- if (typeof DOMParser != 'undefined') {
- doc = (new DOMParser()).parseFromString(xml, "text/xml");
- } else {
- // IE doesn't have DOMParser, but fortunately, there is an ActiveX for XML
- doc = new ActiveXObject("Microsoft.XMLDOM");
- doc.async = false;
- doc.loadXML(xml);
- }
- ret.responseText = xml;
- ret.responseXML = doc;
- return ret;
- },
- fixTree: function() {
- this.callParent(arguments);
- var buffer = [];
- this.buildTreeXml(this.data, buffer);
- this.data = buffer.join('');
- },
- buildTreeXml: function(nodes, buffer) {
- var rootProperty = this.rootProperty,
- recordProperty = this.recordProperty;
- buffer.push('<', rootProperty, '>');
- Ext.Array.forEach(nodes, function(node) {
- buffer.push('<', recordProperty, '>');
- for (var key in node) {
- if (key == 'children') {
- this.buildTreeXml(node.children, buffer);
- } else {
- buffer.push('<', key, '>', node[key], '</', key, '>');
- }
- }
- buffer.push('</', recordProperty, '>');
- });
- buffer.push('</', rootProperty, '>');
- }
- });
- /**
- * This is the base class for {@link Ext.ux.event.Recorder} and {@link Ext.ux.event.Player}.
- */
- Ext.define('Ext.ux.event.Driver', {
- extend: 'Ext.util.Observable',
- active: null,
- specialKeysByName: {
- PGUP: 33,
- PGDN: 34,
- END: 35,
- HOME: 36,
- LEFT: 37,
- UP: 38,
- RIGHT: 39,
- DOWN: 40
- },
- specialKeysByCode: {},
- /**
- * @event start
- * Fires when this object is started.
- * @param {Ext.ux.event.Driver} this
- */
- /**
- * @event stop
- * Fires when this object is stopped.
- * @param {Ext.ux.event.Driver} this
- */
- getTextSelection: function(el) {
- // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js
- var doc = el.ownerDocument,
- range, range2, start, end;
- if (typeof el.selectionStart === "number") {
- start = el.selectionStart;
- end = el.selectionEnd;
- } else if (doc.selection) {
- range = doc.selection.createRange();
- range2 = el.createTextRange();
- range2.setEndPoint('EndToStart', range);
- start = range2.text.length;
- end = start + range.text.length;
- }
- return [
- start,
- end
- ];
- },
- getTime: function() {
- return new Date().getTime();
- },
- /**
- * Returns the number of milliseconds since start was called.
- */
- getTimestamp: function() {
- var d = this.getTime();
- return d - this.startTime;
- },
- onStart: function() {},
- onStop: function() {},
- /**
- * Starts this object. If this object is already started, nothing happens.
- */
- start: function() {
- var me = this;
- if (!me.active) {
- me.active = new Date();
- me.startTime = me.getTime();
- me.onStart();
- me.fireEvent('start', me);
- }
- },
- /**
- * Stops this object. If this object is not started, nothing happens.
- */
- stop: function() {
- var me = this;
- if (me.active) {
- me.active = null;
- me.onStop();
- me.fireEvent('stop', me);
- }
- }
- }, function() {
- var proto = this.prototype;
- Ext.Object.each(proto.specialKeysByName, function(name, value) {
- proto.specialKeysByCode[value] = name;
- });
- });
- /**
- * Event maker.
- */
- Ext.define('Ext.ux.event.Maker', {
- eventQueue: [],
- startAfter: 500,
- timerIncrement: 500,
- currentTiming: 0,
- constructor: function(config) {
- var me = this;
- me.currentTiming = me.startAfter;
- if (!Ext.isArray(config)) {
- config = [
- config
- ];
- }
- Ext.Array.each(config, function(item) {
- item.el = item.el || 'el';
- Ext.Array.each(Ext.ComponentQuery.query(item.cmpQuery), function(cmp) {
- var event = {},
- x, y, el;
- if (!item.domQuery) {
- el = cmp[item.el];
- } else {
- el = cmp.el.down(item.domQuery);
- }
- event.target = '#' + el.dom.id;
- event.type = item.type;
- event.button = config.button || 0;
- x = el.getX() + (el.getWidth() / 2);
- y = el.getY() + (el.getHeight() / 2);
- event.xy = [
- x,
- y
- ];
- event.ts = me.currentTiming;
- me.currentTiming += me.timerIncrement;
- me.eventQueue.push(event);
- });
- if (item.screenshot) {
- me.eventQueue[me.eventQueue.length - 1].screenshot = true;
- }
- });
- return me.eventQueue;
- }
- });
- /**
- * @extends Ext.ux.event.Driver
- * This class manages the playback of an array of "event descriptors". For details on the
- * contents of an "event descriptor", see {@link Ext.ux.event.Recorder}. The events recorded by the
- * {@link Ext.ux.event.Recorder} class are designed to serve as input for this class.
- *
- * The simplest use of this class is to instantiate it with an {@link #eventQueue} and call
- * {@link #method-start}. Like so:
- *
- * var player = Ext.create('Ext.ux.event.Player', {
- * eventQueue: [ ... ],
- * speed: 2, // play at 2x speed
- * listeners: {
- * stop: function () {
- * player = null; // all done
- * }
- * }
- * });
- *
- * player.start();
- *
- * A more complex use would be to incorporate keyframe generation after playing certain
- * events.
- *
- * var player = Ext.create('Ext.ux.event.Player', {
- * eventQueue: [ ... ],
- * keyFrameEvents: {
- * click: true
- * },
- * listeners: {
- * stop: function () {
- * // play has completed... probably time for another keyframe...
- * player = null;
- * },
- * keyframe: onKeyFrame
- * }
- * });
- *
- * player.start();
- *
- * If a keyframe can be handled immediately (synchronously), the listener would be:
- *
- * function onKeyFrame () {
- * handleKeyFrame();
- * }
- *
- * If the keyframe event is always handled asynchronously, then the event listener is only
- * a bit more:
- *
- * function onKeyFrame (p, eventDescriptor) {
- * eventDescriptor.defer(); // pause event playback...
- *
- * handleKeyFrame(function () {
- * eventDescriptor.finish(); // ...resume event playback
- * });
- * }
- *
- * Finally, if the keyframe could be either handled synchronously or asynchronously (perhaps
- * differently by browser), a slightly more complex listener is required.
- *
- * function onKeyFrame (p, eventDescriptor) {
- * var async;
- *
- * handleKeyFrame(function () {
- * // either this callback is being called immediately by handleKeyFrame (in
- * // which case async is undefined) or it is being called later (in which case
- * // async will be true).
- *
- * if (async) {
- * eventDescriptor.finish();
- * } else {
- * async = false;
- * }
- * });
- *
- * // either the callback was called (and async is now false) or it was not
- * // called (and async remains undefined).
- *
- * if (async !== false) {
- * eventDescriptor.defer();
- * async = true; // let the callback know that we have gone async
- * }
- * }
- */
- Ext.define('Ext.ux.event.Player', function(Player) {
- var defaults = {},
- mouseEvents = {},
- keyEvents = {},
- doc,
- //HTML events supported
- uiEvents = {},
- //events that bubble by default
- bubbleEvents = {
- //scroll: 1,
- resize: 1,
- reset: 1,
- submit: 1,
- change: 1,
- select: 1,
- error: 1,
- abort: 1
- };
- Ext.each([
- 'click',
- 'dblclick',
- 'mouseover',
- 'mouseout',
- 'mousedown',
- 'mouseup',
- 'mousemove'
- ], function(type) {
- bubbleEvents[type] = defaults[type] = mouseEvents[type] = {
- bubbles: true,
- cancelable: (type != "mousemove"),
- // mousemove cannot be cancelled
- detail: 1,
- screenX: 0,
- screenY: 0,
- clientX: 0,
- clientY: 0,
- ctrlKey: false,
- altKey: false,
- shiftKey: false,
- metaKey: false,
- button: 0
- };
- });
- Ext.each([
- 'keydown',
- 'keyup',
- 'keypress'
- ], function(type) {
- bubbleEvents[type] = defaults[type] = keyEvents[type] = {
- bubbles: true,
- cancelable: true,
- ctrlKey: false,
- altKey: false,
- shiftKey: false,
- metaKey: false,
- keyCode: 0,
- charCode: 0
- };
- });
- Ext.each([
- 'blur',
- 'change',
- 'focus',
- 'resize',
- 'scroll',
- 'select'
- ], function(type) {
- defaults[type] = uiEvents[type] = {
- bubbles: (type in bubbleEvents),
- cancelable: false,
- detail: 1
- };
- });
- var inputSpecialKeys = {
- 8: function(target, start, end) {
- // backspace: 8,
- if (start < end) {
- target.value = target.value.substring(0, start) + target.value.substring(end);
- } else if (start > 0) {
- target.value = target.value.substring(0, --start) + target.value.substring(end);
- }
- this.setTextSelection(target, start, start);
- },
- 46: function(target, start, end) {
- // delete: 46
- if (start < end) {
- target.value = target.value.substring(0, start) + target.value.substring(end);
- } else if (start < target.value.length - 1) {
- target.value = target.value.substring(0, start) + target.value.substring(start + 1);
- }
- this.setTextSelection(target, start, start);
- }
- };
- return {
- extend: 'Ext.ux.event.Driver',
- /**
- * @cfg {Array} eventQueue The event queue to playback. This must be provided before
- * the {@link #method-start} method is called.
- */
- /**
- * @cfg {Object} keyFrameEvents An object that describes the events that should generate
- * keyframe events. For example, `{ click: true }` would generate keyframe events after
- * each `click` event.
- */
- keyFrameEvents: {
- click: true
- },
- /**
- * @cfg {Boolean} pauseForAnimations True to pause event playback during animations, false
- * to ignore animations. Default is true.
- */
- pauseForAnimations: true,
- /**
- * @cfg {Number} speed The playback speed multiplier. Default is 1.0 (to playback at the
- * recorded speed). A value of 2 would playback at 2x speed.
- */
- speed: 1,
- stallTime: 0,
- _inputSpecialKeys: {
- INPUT: inputSpecialKeys,
- TEXTAREA: Ext.apply({}, //13: function (target, start, end) { // enter: 8,
- //TODO ?
- //}
- inputSpecialKeys)
- },
- tagPathRegEx: /(\w+)(?:\[(\d+)\])?/,
- /**
- * @event beforeplay
- * Fires before an event is played.
- * @param {Ext.ux.event.Player} this
- * @param {Object} eventDescriptor The event descriptor about to be played.
- */
- /**
- * @event keyframe
- * Fires when this player reaches a keyframe. Typically, this is after events
- * like `click` are injected and any resulting animations have been completed.
- * @param {Ext.ux.event.Player} this
- * @param {Object} eventDescriptor The keyframe event descriptor.
- */
- constructor: function(config) {
- var me = this;
- me.callParent(arguments);
- me.timerFn = function() {
- me.onTick();
- };
- me.attachTo = me.attachTo || window;
- doc = me.attachTo.document;
- },
- /**
- * Returns the element given is XPath-like description.
- * @param {String} xpath The XPath-like description of the element.
- * @return {HTMLElement}
- */
- getElementFromXPath: function(xpath) {
- var me = this,
- parts = xpath.split('/'),
- regex = me.tagPathRegEx,
- i, n, m, count, tag, child,
- el = me.attachTo.document;
- el = (parts[0] == '~') ? el.body : el.getElementById(parts[0].substring(1));
- // remove '#'
- for (i = 1 , n = parts.length; el && i < n; ++i) {
- m = regex.exec(parts[i]);
- count = m[2] ? parseInt(m[2], 10) : 1;
- tag = m[1].toUpperCase();
- for (child = el.firstChild; child; child = child.nextSibling) {
- if (child.tagName == tag) {
- if (count == 1) {
- break;
- }
- --count;
- }
- }
- el = child;
- }
- return el;
- },
- // Moving across a line break only counts as moving one character in a TextRange, whereas a line break in
- // the textarea value is two characters. This function corrects for that by converting a text offset into a
- // range character offset by subtracting one character for every line break in the textarea prior to the
- // offset
- offsetToRangeCharacterMove: function(el, offset) {
- return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
- },
- setTextSelection: function(el, startOffset, endOffset) {
- // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js
- if (startOffset < 0) {
- startOffset += el.value.length;
- }
- if (endOffset == null) {
- endOffset = startOffset;
- }
- if (endOffset < 0) {
- endOffset += el.value.length;
- }
- if (typeof el.selectionStart === "number") {
- el.selectionStart = startOffset;
- el.selectionEnd = endOffset;
- } else {
- var range = el.createTextRange();
- var startCharMove = this.offsetToRangeCharacterMove(el, startOffset);
- range.collapse(true);
- if (startOffset == endOffset) {
- range.move("character", startCharMove);
- } else {
- range.moveEnd("character", this.offsetToRangeCharacterMove(el, endOffset));
- range.moveStart("character", startCharMove);
- }
- range.select();
- }
- },
- getTimeIndex: function() {
- var t = this.getTimestamp() - this.stallTime;
- return t * this.speed;
- },
- makeToken: function(eventDescriptor, signal) {
- var me = this,
- t0;
- eventDescriptor[signal] = true;
- eventDescriptor.defer = function() {
- eventDescriptor[signal] = false;
- t0 = me.getTime();
- };
- eventDescriptor.finish = function() {
- eventDescriptor[signal] = true;
- me.stallTime += me.getTime() - t0;
- me.schedule();
- };
- },
- /**
- * This method is called after an event has been played to prepare for the next event.
- * @param {Object} eventDescriptor The descriptor of the event just played.
- */
- nextEvent: function(eventDescriptor) {
- var me = this,
- index = ++me.queueIndex;
- // keyframe events are inserted after a keyFrameEvent is played.
- if (me.keyFrameEvents[eventDescriptor.type]) {
- Ext.Array.insert(me.eventQueue, index, [
- {
- keyframe: true,
- ts: eventDescriptor.ts
- }
- ]);
- }
- },
- /**
- * This method returns the event descriptor at the front of the queue. This does not
- * dequeue the event. Repeated calls return the same object (until {@link #nextEvent}
- * is called).
- */
- peekEvent: function() {
- return this.eventQueue[this.queueIndex] || null;
- },
- /**
- * Replaces an event in the queue with an array of events. This is often used to roll
- * up a multi-step pseudo-event and expand it just-in-time to be played. The process
- * for doing this in a derived class would be this:
- *
- * Ext.define('My.Player', {
- * extend: 'Ext.ux.event.Player',
- *
- * peekEvent: function () {
- * var event = this.callParent();
- *
- * if (event.multiStepSpecial) {
- * this.replaceEvent(null, [
- * ... expand to actual events
- * ]);
- *
- * event = this.callParent(); // get the new next event
- * }
- *
- * return event;
- * }
- * });
- *
- * This method ensures that the `beforeplay` hook (if any) from the replaced event is
- * placed on the first new event and the `afterplay` hook (if any) is placed on the
- * last new event.
- *
- * @param {Number} index The queue index to replace. Pass `null` to replace the event
- * at the current `queueIndex`.
- * @param {Event[]} events The array of events with which to replace the specified
- * event.
- */
- replaceEvent: function(index, events) {
- for (var t,
- i = 0,
- n = events.length; i < n; ++i) {
- if (i) {
- t = events[i - 1];
- delete t.afterplay;
- delete t.screenshot;
- delete events[i].beforeplay;
- }
- }
- Ext.Array.replace(this.eventQueue, (index == null) ? this.queueIndex : index, 1, events);
- },
- /**
- * This method dequeues and injects events until it has arrived at the time index. If
- * no events are ready (based on the time index), this method does nothing.
- * @return {Boolean} True if there is more to do; false if not (at least for now).
- */
- processEvents: function() {
- var me = this,
- animations = me.pauseForAnimations && me.attachTo.Ext.fx.Manager.items,
- eventDescriptor;
- while ((eventDescriptor = me.peekEvent()) !== null) {
- if (animations && animations.getCount()) {
- return true;
- }
- if (eventDescriptor.keyframe) {
- if (!me.processKeyFrame(eventDescriptor)) {
- return false;
- }
- me.nextEvent(eventDescriptor);
- } else if (eventDescriptor.ts <= me.getTimeIndex() && me.fireEvent('beforeplay', me, eventDescriptor) !== false && me.playEvent(eventDescriptor)) {
- me.nextEvent(eventDescriptor);
- } else {
- return true;
- }
- }
- me.stop();
- return false;
- },
- /**
- * This method is called when a keyframe is reached. This will fire the keyframe event.
- * If the keyframe has been handled, true is returned. Otherwise, false is returned.
- * @param {Object} eventDescriptor The event descriptor of the keyframe.
- * @return {Boolean} True if the keyframe was handled, false if not.
- */
- processKeyFrame: function(eventDescriptor) {
- var me = this;
- // only fire keyframe event (and setup the eventDescriptor) once...
- if (!eventDescriptor.defer) {
- me.makeToken(eventDescriptor, 'done');
- me.fireEvent('keyframe', me, eventDescriptor);
- }
- return eventDescriptor.done;
- },
- /**
- * Called to inject the given event on the specified target.
- * @param {HTMLElement} target The target of the event.
- * @param {Object} event The event to inject. The properties of this object should be
- * those of standard DOM events but vary based on the `type` property. For details on
- * event types and their properties, see the class documentation.
- */
- injectEvent: function(target, event) {
- var me = this,
- type = event.type,
- options = Ext.apply({}, event, defaults[type]),
- handler;
- if (type === 'type') {
- handler = me._inputSpecialKeys[target.tagName];
- if (handler) {
- return me.injectTypeInputEvent(target, event, handler);
- }
- return me.injectTypeEvent(target, event);
- }
- if (type === 'focus' && target.focus) {
- target.focus();
- return true;
- }
- if (type === 'blur' && target.blur) {
- target.blur();
- return true;
- }
- if (type === 'scroll') {
- target.scrollLeft = event.pos[0];
- target.scrollTop = event.pos[1];
- return true;
- }
- if (type === 'mduclick') {
- return me.injectEvent(target, Ext.applyIf({
- type: 'mousedown'
- }, event)) && me.injectEvent(target, Ext.applyIf({
- type: 'mouseup'
- }, event)) && me.injectEvent(target, Ext.applyIf({
- type: 'click'
- }, event));
- }
- if (mouseEvents[type]) {
- return Player.injectMouseEvent(target, options, me.attachTo);
- }
- if (keyEvents[type]) {
- return Player.injectKeyEvent(target, options, me.attachTo);
- }
- if (uiEvents[type]) {
- return Player.injectUIEvent(target, type, options.bubbles, options.cancelable, options.view || me.attachTo, options.detail);
- }
- return false;
- },
- injectTypeEvent: function(target, event) {
- var me = this,
- text = event.text,
- xlat = [],
- ch, chUp, i, n, sel, upper, isInput;
- if (text) {
- delete event.text;
- upper = text.toUpperCase();
- for (i = 0 , n = text.length; i < n; ++i) {
- ch = text.charCodeAt(i);
- chUp = upper.charCodeAt(i);
- xlat.push(Ext.applyIf({
- type: 'keydown',
- charCode: chUp,
- keyCode: chUp
- }, event), Ext.applyIf({
- type: 'keypress',
- charCode: ch,
- keyCode: ch
- }, event), Ext.applyIf({
- type: 'keyup',
- charCode: chUp,
- keyCode: chUp
- }, event));
- }
- } else {
- xlat.push(Ext.applyIf({
- type: 'keydown',
- charCode: event.keyCode
- }, event), Ext.applyIf({
- type: 'keyup',
- charCode: event.keyCode
- }, event));
- }
- for (i = 0 , n = xlat.length; i < n; ++i) {
- me.injectEvent(target, xlat[i]);
- }
- return true;
- },
- injectTypeInputEvent: function(target, event, handler) {
- var me = this,
- text = event.text,
- sel, n;
- if (handler) {
- sel = me.getTextSelection(target);
- if (text) {
- n = sel[0];
- target.value = target.value.substring(0, n) + text + target.value.substring(sel[1]);
- n += text.length;
- me.setTextSelection(target, n, n);
- } else {
- if (!(handler = handler[event.keyCode])) {
- // no handler for the special key for this element
- if ('caret' in event) {
- me.setTextSelection(target, event.caret, event.caret);
- } else if (event.selection) {
- me.setTextSelection(target, event.selection[0], event.selection[1]);
- }
- return me.injectTypeEvent(target, event);
- }
- handler.call(this, target, sel[0], sel[1]);
- return true;
- }
- }
- return true;
- },
- playEvent: function(eventDescriptor) {
- var me = this,
- target = me.getElementFromXPath(eventDescriptor.target),
- event;
- if (!target) {
- // not present (yet)... wait for element present...
- // TODO - need a timeout here
- return false;
- }
- if (!me.playEventHook(eventDescriptor, 'beforeplay')) {
- return false;
- }
- if (!eventDescriptor.injected) {
- eventDescriptor.injected = true;
- event = me.translateEvent(eventDescriptor, target);
- me.injectEvent(target, event);
- }
- return me.playEventHook(eventDescriptor, 'afterplay');
- },
- playEventHook: function(eventDescriptor, hookName) {
- var me = this,
- doneName = hookName + '.done',
- firedName = hookName + '.fired',
- hook = eventDescriptor[hookName];
- if (hook && !eventDescriptor[doneName]) {
- if (!eventDescriptor[firedName]) {
- eventDescriptor[firedName] = true;
- me.makeToken(eventDescriptor, doneName);
- if (me.eventScope && Ext.isString(hook)) {
- hook = me.eventScope[hook];
- }
- if (hook) {
- hook.call(me.eventScope || me, eventDescriptor);
- }
- }
- return false;
- }
- return true;
- },
- schedule: function() {
- var me = this;
- if (!me.timer) {
- me.timer = Ext.defer(me.timerFn, 10);
- }
- },
- _translateAcross: [
- 'type',
- 'button',
- 'charCode',
- 'keyCode',
- 'caret',
- 'pos',
- 'text',
- 'selection'
- ],
- translateEvent: function(eventDescriptor, target) {
- var me = this,
- event = {},
- modKeys = eventDescriptor.modKeys || '',
- names = me._translateAcross,
- i = names.length,
- name, xy;
- while (i--) {
- name = names[i];
- if (name in eventDescriptor) {
- event[name] = eventDescriptor[name];
- }
- }
- event.altKey = modKeys.indexOf('A') > 0;
- event.ctrlKey = modKeys.indexOf('C') > 0;
- event.metaKey = modKeys.indexOf('M') > 0;
- event.shiftKey = modKeys.indexOf('S') > 0;
- if (target && 'x' in eventDescriptor) {
- xy = Ext.fly(target).getXY();
- xy[0] += eventDescriptor.x;
- xy[1] += eventDescriptor.y;
- } else if ('x' in eventDescriptor) {
- xy = [
- eventDescriptor.x,
- eventDescriptor.y
- ];
- } else if ('px' in eventDescriptor) {
- xy = [
- eventDescriptor.px,
- eventDescriptor.py
- ];
- }
- if (xy) {
- event.clientX = event.screenX = xy[0];
- event.clientY = event.screenY = xy[1];
- }
- if (eventDescriptor.key) {
- event.keyCode = me.specialKeysByName[eventDescriptor.key];
- }
- if (eventDescriptor.type === 'wheel') {
- if ('onwheel' in me.attachTo.document) {
- event.wheelX = eventDescriptor.dx;
- event.wheelY = eventDescriptor.dy;
- } else {
- event.type = 'mousewheel';
- event.wheelDeltaX = -40 * eventDescriptor.dx;
- event.wheelDeltaY = event.wheelDelta = -40 * eventDescriptor.dy;
- }
- }
- return event;
- },
- //---------------------------------
- // Driver overrides
- onStart: function() {
- var me = this;
- me.queueIndex = 0;
- me.schedule();
- },
- onStop: function() {
- var me = this;
- if (me.timer) {
- Ext.undefer(me.timer);
- me.timer = null;
- }
- },
- //---------------------------------
- onTick: function() {
- var me = this;
- me.timer = null;
- if (me.processEvents()) {
- me.schedule();
- }
- },
- statics: {
- ieButtonCodeMap: {
- 0: 1,
- 1: 4,
- 2: 2
- },
- /**
- * Injects a key event using the given event information to populate the event
- * object.
- *
- * **Note:** `keydown` causes Safari 2.x to crash.
- *
- * @param {HTMLElement} target The target of the given event.
- * @param {Object} options Object object containing all of the event injection
- * options.
- * @param {String} options.type The type of event to fire. This can be any one of
- * the following: `keyup`, `keydown` and `keypress`.
- * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
- * DOM Level 3 specifies that all key events bubble by default.
- * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
- * using `preventDefault`. DOM Level 3 specifies that all key events can be
- * cancelled.
- * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is
- * pressed while the event is firing.
- * @param {Number} [options.keyCode=0] The code for the key that is in use.
- * @param {Number} [options.charCode=0] The Unicode code for the character
- * associated with the key being used.
- * @param {Window} [view=window] The view containing the target. This is typically
- * the window object.
- * @private
- */
- injectKeyEvent: function(target, options, view) {
- var type = options.type,
- customEvent = null;
- if (type === 'textevent') {
- type = 'keypress';
- }
- view = view || window;
- //check for DOM-compliant browsers first
- if (doc.createEvent) {
- try {
- customEvent = doc.createEvent("KeyEvents");
- // Interesting problem: Firefox implemented a non-standard
- // version of initKeyEvent() based on DOM Level 2 specs.
- // Key event was removed from DOM Level 2 and re-introduced
- // in DOM Level 3 with a different interface. Firefox is the
- // only browser with any implementation of Key Events, so for
- // now, assume it's Firefox if the above line doesn't error.
- // @TODO: Decipher between Firefox's implementation and a correct one.
- customEvent.initKeyEvent(type, options.bubbles, options.cancelable, view, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode);
- } catch (ex) {
- // If it got here, that means key events aren't officially supported.
- // Safari/WebKit is a real problem now. WebKit 522 won't let you
- // set keyCode, charCode, or other properties if you use a
- // UIEvent, so we first must try to create a generic event. The
- // fun part is that this will throw an error on Safari 2.x. The
- // end result is that we need another try...catch statement just to
- // deal with this mess.
- try {
- //try to create generic event - will fail in Safari 2.x
- customEvent = doc.createEvent("Events");
- } catch (uierror) {
- //the above failed, so create a UIEvent for Safari 2.x
- customEvent = doc.createEvent("UIEvents");
- } finally {
- customEvent.initEvent(type, options.bubbles, options.cancelable);
- customEvent.view = view;
- customEvent.altKey = options.altKey;
- customEvent.ctrlKey = options.ctrlKey;
- customEvent.shiftKey = options.shiftKey;
- customEvent.metaKey = options.metaKey;
- customEvent.keyCode = options.keyCode;
- customEvent.charCode = options.charCode;
- }
- }
- target.dispatchEvent(customEvent);
- } else if (doc.createEventObject) {
- //IE
- customEvent = doc.createEventObject();
- customEvent.bubbles = options.bubbles;
- customEvent.cancelable = options.cancelable;
- customEvent.view = view;
- customEvent.ctrlKey = options.ctrlKey;
- customEvent.altKey = options.altKey;
- customEvent.shiftKey = options.shiftKey;
- customEvent.metaKey = options.metaKey;
- // IE doesn't support charCode explicitly. CharCode should
- // take precedence over any keyCode value for accurate
- // representation.
- customEvent.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode;
- target.fireEvent("on" + type, customEvent);
- } else {
- return false;
- }
- return true;
- },
- /**
- * Injects a mouse event using the given event information to populate the event
- * object.
- *
- * @param {HTMLElement} target The target of the given event.
- * @param {Object} options Object object containing all of the event injection
- * options.
- * @param {String} options.type The type of event to fire. This can be any one of
- * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,
- * `mouseover` and `mousemove`.
- * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
- * DOM Level 2 specifies that all mouse events bubble by default.
- * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
- * using `preventDefault`. DOM Level 2 specifies that all mouse events except
- * `mousemove` can be cancelled. This defaults to `false` for `mousemove`.
- * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is
- * pressed while the event is firing.
- * @param {Number} [options.detail=1] The number of times the mouse button has
- * been used.
- * @param {Number} [options.screenX=0] The x-coordinate on the screen at which point
- * the event occurred.
- * @param {Number} [options.screenY=0] The y-coordinate on the screen at which point
- * the event occurred.
- * @param {Number} [options.clientX=0] The x-coordinate on the client at which point
- * the event occurred.
- * @param {Number} [options.clientY=0] The y-coordinate on the client at which point
- * the event occurred.
- * @param {Number} [options.button=0] The button being pressed while the event is
- * executing. The value should be 0 for the primary mouse button (typically the
- * left button), 1 for the tertiary mouse button (typically the middle button),
- * and 2 for the secondary mouse button (typically the right button).
- * @param {HTMLElement} [options.relatedTarget=null] For `mouseout` events, this
- * is the element that the mouse has moved to. For `mouseover` events, this is
- * the element that the mouse has moved from. This argument is ignored for all
- * other events.
- * @param {Window} [view=window] The view containing the target. This is typically
- * the window object.
- * @private
- */
- injectMouseEvent: function(target, options, view) {
- var type = options.type,
- customEvent = null;
- view = view || window;
- //check for DOM-compliant browsers first
- if (doc.createEvent) {
- customEvent = doc.createEvent("MouseEvents");
- //Safari 2.x (WebKit 418) still doesn't implement initMouseEvent()
- if (customEvent.initMouseEvent) {
- customEvent.initMouseEvent(type, options.bubbles, options.cancelable, view, options.detail, options.screenX, options.screenY, options.clientX, options.clientY, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, options.relatedTarget);
- } else {
- //Safari
- //the closest thing available in Safari 2.x is UIEvents
- customEvent = doc.createEvent("UIEvents");
- customEvent.initEvent(type, options.bubbles, options.cancelable);
- customEvent.view = view;
- customEvent.detail = options.detail;
- customEvent.screenX = options.screenX;
- customEvent.screenY = options.screenY;
- customEvent.clientX = options.clientX;
- customEvent.clientY = options.clientY;
- customEvent.ctrlKey = options.ctrlKey;
- customEvent.altKey = options.altKey;
- customEvent.metaKey = options.metaKey;
- customEvent.shiftKey = options.shiftKey;
- customEvent.button = options.button;
- customEvent.relatedTarget = options.relatedTarget;
- }
- /*
- * Check to see if relatedTarget has been assigned. Firefox
- * versions less than 2.0 don't allow it to be assigned via
- * initMouseEvent() and the property is readonly after event
- * creation, so in order to keep YAHOO.util.getRelatedTarget()
- * working, assign to the IE proprietary toElement property
- * for mouseout event and fromElement property for mouseover
- * event.
- */
- if (options.relatedTarget && !customEvent.relatedTarget) {
- if (type == "mouseout") {
- customEvent.toElement = options.relatedTarget;
- } else if (type == "mouseover") {
- customEvent.fromElement = options.relatedTarget;
- }
- }
- target.dispatchEvent(customEvent);
- } else if (doc.createEventObject) {
- //IE
- customEvent = doc.createEventObject();
- customEvent.bubbles = options.bubbles;
- customEvent.cancelable = options.cancelable;
- customEvent.view = view;
- customEvent.detail = options.detail;
- customEvent.screenX = options.screenX;
- customEvent.screenY = options.screenY;
- customEvent.clientX = options.clientX;
- customEvent.clientY = options.clientY;
- customEvent.ctrlKey = options.ctrlKey;
- customEvent.altKey = options.altKey;
- customEvent.metaKey = options.metaKey;
- customEvent.shiftKey = options.shiftKey;
- customEvent.button = Player.ieButtonCodeMap[options.button] || 0;
- /*
- * Have to use relatedTarget because IE won't allow assignment
- * to toElement or fromElement on generic events. This keeps
- * YAHOO.util.customEvent.getRelatedTarget() functional.
- */
- customEvent.relatedTarget = options.relatedTarget;
- target.fireEvent('on' + type, customEvent);
- } else {
- return false;
- }
- return true;
- },
- /**
- * Injects a UI event using the given event information to populate the event
- * object.
- *
- * @param {HTMLElement} target The target of the given event.
- * @param {Object} options
- * @param {String} options.type The type of event to fire. This can be any one of
- * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,
- * `mouseover` and `mousemove`.
- * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
- * DOM Level 2 specifies that all mouse events bubble by default.
- * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
- * using `preventDefault`. DOM Level 2 specifies that all mouse events except
- * `mousemove` can be canceled. This defaults to `false` for `mousemove`.
- * @param {Number} [options.detail=1] The number of times the mouse button has been
- * used.
- * @param {Window} [view=window] The view containing the target. This is typically
- * the window object.
- * @private
- */
- injectUIEvent: function(target, options, view) {
- var customEvent = null;
- view = view || window;
- //check for DOM-compliant browsers first
- if (doc.createEvent) {
- //just a generic UI Event object is needed
- customEvent = doc.createEvent("UIEvents");
- customEvent.initUIEvent(options.type, options.bubbles, options.cancelable, view, options.detail);
- target.dispatchEvent(customEvent);
- } else if (doc.createEventObject) {
- //IE
- customEvent = doc.createEventObject();
- customEvent.bubbles = options.bubbles;
- customEvent.cancelable = options.cancelable;
- customEvent.view = view;
- customEvent.detail = options.detail;
- target.fireEvent("on" + options.type, customEvent);
- } else {
- return false;
- }
- return true;
- }
- }
- };
- });
- // statics
- /**
- * @extends Ext.ux.event.Driver
- * Event recorder.
- */
- Ext.define('Ext.ux.event.Recorder', function(Recorder) {
- function apply() {
- var a = arguments,
- n = a.length,
- obj = {
- kind: 'other'
- },
- i;
- for (i = 0; i < n; ++i) {
- Ext.apply(obj, arguments[i]);
- }
- if (obj.alt && !obj.event) {
- obj.event = obj.alt;
- }
- return obj;
- }
- function key(extra) {
- return apply({
- kind: 'keyboard',
- modKeys: true,
- key: true
- }, extra);
- }
- function mouse(extra) {
- return apply({
- kind: 'mouse',
- button: true,
- modKeys: true,
- xy: true
- }, extra);
- }
- var eventsToRecord = {
- keydown: key(),
- keypress: key(),
- keyup: key(),
- dragmove: mouse({
- alt: 'mousemove',
- pageCoords: true,
- whileDrag: true
- }),
- mousemove: mouse({
- pageCoords: true
- }),
- mouseover: mouse(),
- mouseout: mouse(),
- click: mouse(),
- wheel: mouse({
- wheel: true
- }),
- mousedown: mouse({
- press: true
- }),
- mouseup: mouse({
- release: true
- }),
- scroll: apply({
- listen: false
- }),
- focus: apply(),
- blur: apply()
- };
- for (var key in eventsToRecord) {
- if (!eventsToRecord[key].event) {
- eventsToRecord[key].event = key;
- }
- }
- eventsToRecord.wheel.event = null;
- // must detect later
- return {
- extend: 'Ext.ux.event.Driver',
- /**
- * @event add
- * Fires when an event is added to the recording.
- * @param {Ext.ux.event.Recorder} this
- * @param {Object} eventDescriptor The event descriptor.
- */
- /**
- * @event coalesce
- * Fires when an event is coalesced. This edits the tail of the recorded
- * event list.
- * @param {Ext.ux.event.Recorder} this
- * @param {Object} eventDescriptor The event descriptor that was coalesced.
- */
- eventsToRecord: eventsToRecord,
- ignoreIdRegEx: /ext-gen(?:\d+)/,
- inputRe: /^(input|textarea)$/i,
- constructor: function(config) {
- var me = this,
- events = config && config.eventsToRecord;
- if (events) {
- me.eventsToRecord = Ext.apply(Ext.apply({}, me.eventsToRecord), // duplicate
- events);
- // and merge
- delete config.eventsToRecord;
- }
- // don't smash
- me.callParent(arguments);
- me.clear();
- me.modKeys = [];
- me.attachTo = me.attachTo || window;
- },
- clear: function() {
- this.eventsRecorded = [];
- },
- listenToEvent: function(event) {
- var me = this,
- el = me.attachTo.document.body,
- fn = function() {
- return me.onEvent.apply(me, arguments);
- },
- cleaner = {};
- if (el.attachEvent && el.ownerDocument.documentMode < 10) {
- event = 'on' + event;
- el.attachEvent(event, fn);
- cleaner.destroy = function() {
- if (fn) {
- el.detachEvent(event, fn);
- fn = null;
- }
- };
- } else {
- el.addEventListener(event, fn, true);
- cleaner.destroy = function() {
- if (fn) {
- el.removeEventListener(event, fn, true);
- fn = null;
- }
- };
- }
- return cleaner;
- },
- coalesce: function(rec, ev) {
- var me = this,
- events = me.eventsRecorded,
- length = events.length,
- tail = length && events[length - 1],
- tail2 = (length > 1) && events[length - 2],
- tail3 = (length > 2) && events[length - 3];
- if (!tail) {
- return false;
- }
- if (rec.type === 'mousemove') {
- if (tail.type === 'mousemove' && rec.ts - tail.ts < 200) {
- rec.ts = tail.ts;
- events[length - 1] = rec;
- return true;
- }
- } else if (rec.type === 'click') {
- if (tail2 && tail.type === 'mouseup' && tail2.type === 'mousedown') {
- if (rec.button == tail.button && rec.button == tail2.button && rec.target == tail.target && rec.target == tail2.target && me.samePt(rec, tail) && me.samePt(rec, tail2)) {
- events.pop();
- // remove mouseup
- tail2.type = 'mduclick';
- return true;
- }
- }
- } else if (rec.type === 'keyup') {
- // tail3 = { type: "type", text: "..." },
- // tail2 = { type: "keydown", charCode: 65, keyCode: 65 },
- // tail = { type: "keypress", charCode: 97, keyCode: 97 },
- // rec = { type: "keyup", charCode: 65, keyCode: 65 },
- if (tail2 && tail.type === 'keypress' && tail2.type === 'keydown') {
- if (rec.target === tail.target && rec.target === tail2.target) {
- events.pop();
- // remove keypress
- tail2.type = 'type';
- tail2.text = String.fromCharCode(tail.charCode);
- delete tail2.charCode;
- delete tail2.keyCode;
- if (tail3 && tail3.type === 'type') {
- if (tail3.text && tail3.target === tail2.target) {
- tail3.text += tail2.text;
- events.pop();
- }
- }
- return true;
- }
- }
- // tail = { type: "keydown", charCode: 40, keyCode: 40 },
- // rec = { type: "keyup", charCode: 40, keyCode: 40 },
- else if (me.completeKeyStroke(tail, rec)) {
- tail.type = 'type';
- me.completeSpecialKeyStroke(ev.target, tail, rec);
- return true;
- }
- // tail2 = { type: "keydown", charCode: 40, keyCode: 40 },
- // tail = { type: "scroll", ... },
- // rec = { type: "keyup", charCode: 40, keyCode: 40 },
- else if (tail.type === 'scroll' && me.completeKeyStroke(tail2, rec)) {
- tail2.type = 'type';
- me.completeSpecialKeyStroke(ev.target, tail2, rec);
- // swap the order of type and scroll events
- events.pop();
- events.pop();
- events.push(tail, tail2);
- return true;
- }
- }
- return false;
- },
- completeKeyStroke: function(down, up) {
- if (down && down.type === 'keydown' && down.keyCode === up.keyCode) {
- delete down.charCode;
- return true;
- }
- return false;
- },
- completeSpecialKeyStroke: function(target, down, up) {
- var key = this.specialKeysByCode[up.keyCode];
- if (key && this.inputRe.test(target.tagName)) {
- // home,end,arrow keys + shift get crazy, so encode selection/caret
- delete down.keyCode;
- down.key = key;
- down.selection = this.getTextSelection(target);
- if (down.selection[0] === down.selection[1]) {
- down.caret = down.selection[0];
- delete down.selection;
- }
- return true;
- }
- return false;
- },
- getElementXPath: function(el) {
- var me = this,
- good = false,
- xpath = [],
- count, sibling, t, tag;
- for (t = el; t; t = t.parentNode) {
- if (t == me.attachTo.document.body) {
- xpath.unshift('~');
- good = true;
- break;
- }
- if (t.id && !me.ignoreIdRegEx.test(t.id)) {
- xpath.unshift('#' + t.id);
- good = true;
- break;
- }
- for (count = 1 , sibling = t; !!(sibling = sibling.previousSibling); ) {
- if (sibling.tagName == t.tagName) {
- ++count;
- }
- }
- tag = t.tagName.toLowerCase();
- if (count < 2) {
- xpath.unshift(tag);
- } else {
- xpath.unshift(tag + '[' + count + ']');
- }
- }
- return good ? xpath.join('/') : null;
- },
- getRecordedEvents: function() {
- return this.eventsRecorded;
- },
- onEvent: function(ev) {
- var me = this,
- e = new Ext.event.Event(ev),
- info = me.eventsToRecord[e.type],
- root, modKeys, elXY,
- rec = {
- type: e.type,
- ts: me.getTimestamp(),
- target: me.getElementXPath(e.target)
- },
- xy;
- if (!info || !rec.target) {
- return;
- }
- root = e.target.ownerDocument;
- root = root.defaultView || root.parentWindow;
- // Standards || IE
- if (root !== me.attachTo) {
- return;
- }
- if (me.eventsToRecord.scroll) {
- me.syncScroll(e.target);
- }
- if (info.xy) {
- xy = e.getXY();
- if (info.pageCoords || !rec.target) {
- rec.px = xy[0];
- rec.py = xy[1];
- } else {
- elXY = Ext.fly(e.getTarget()).getXY();
- xy[0] -= elXY[0];
- xy[1] -= elXY[1];
- rec.x = xy[0];
- rec.y = xy[1];
- }
- }
- if (info.button) {
- if ('buttons' in ev) {
- rec.button = ev.buttons;
- } else // LEFT=1, RIGHT=2, MIDDLE=4, etc.
- {
- rec.button = ev.button;
- }
- if (!rec.button && info.whileDrag) {
- return;
- }
- }
- if (info.wheel) {
- rec.type = 'wheel';
- if (info.event === 'wheel') {
- // Current FireFox (technically IE9+ if we use addEventListener but
- // checking document.onwheel does not detect this)
- rec.dx = ev.deltaX;
- rec.dy = ev.deltaY;
- } else if (typeof ev.wheelDeltaX === 'number') {
- // new WebKit has both X & Y
- rec.dx = -1 / 40 * ev.wheelDeltaX;
- rec.dy = -1 / 40 * ev.wheelDeltaY;
- } else if (ev.wheelDelta) {
- // old WebKit and IE
- rec.dy = -1 / 40 * ev.wheelDelta;
- } else if (ev.detail) {
- // Old Gecko
- rec.dy = ev.detail;
- }
- }
- if (info.modKeys) {
- me.modKeys[0] = e.altKey ? 'A' : '';
- me.modKeys[1] = e.ctrlKey ? 'C' : '';
- me.modKeys[2] = e.metaKey ? 'M' : '';
- me.modKeys[3] = e.shiftKey ? 'S' : '';
- modKeys = me.modKeys.join('');
- if (modKeys) {
- rec.modKeys = modKeys;
- }
- }
- if (info.key) {
- rec.charCode = e.getCharCode();
- rec.keyCode = e.getKey();
- }
- if (me.coalesce(rec, e)) {
- me.fireEvent('coalesce', me, rec);
- } else {
- me.eventsRecorded.push(rec);
- me.fireEvent('add', me, rec);
- }
- },
- onStart: function() {
- var me = this,
- ddm = me.attachTo.Ext.dd.DragDropManager,
- evproto = me.attachTo.Ext.EventObjectImpl.prototype,
- special = [];
- // FireFox does not support the 'mousewheel' event but does support the
- // 'wheel' event instead.
- Recorder.prototype.eventsToRecord.wheel.event = ('onwheel' in me.attachTo.document) ? 'wheel' : 'mousewheel';
- me.listeners = [];
- Ext.Object.each(me.eventsToRecord, function(name, value) {
- if (value && value.listen !== false) {
- if (!value.event) {
- value.event = name;
- }
- if (value.alt && value.alt !== name) {
- // The 'drag' event is just mousemove while buttons are pressed,
- // so if there is a mousemove entry as well, ignore the drag
- if (!me.eventsToRecord[value.alt]) {
- special.push(value);
- }
- } else {
- me.listeners.push(me.listenToEvent(value.event));
- }
- }
- });
- Ext.each(special, function(info) {
- me.eventsToRecord[info.alt] = info;
- me.listeners.push(me.listenToEvent(info.alt));
- });
- me.ddmStopEvent = ddm.stopEvent;
- ddm.stopEvent = Ext.Function.createSequence(ddm.stopEvent, function(e) {
- me.onEvent(e);
- });
- me.evStopEvent = evproto.stopEvent;
- evproto.stopEvent = Ext.Function.createSequence(evproto.stopEvent, function() {
- me.onEvent(this);
- });
- },
- onStop: function() {
- var me = this;
- Ext.destroy(me.listeners);
- me.listeners = null;
- me.attachTo.Ext.dd.DragDropManager.stopEvent = me.ddmStopEvent;
- me.attachTo.Ext.EventObjectImpl.prototype.stopEvent = me.evStopEvent;
- },
- samePt: function(pt1, pt2) {
- return pt1.x == pt2.x && pt1.y == pt2.y;
- },
- syncScroll: function(el) {
- var me = this,
- ts = me.getTimestamp(),
- oldX, oldY, x, y, scrolled, rec;
- for (var p = el; p; p = p.parentNode) {
- oldX = p.$lastScrollLeft;
- oldY = p.$lastScrollTop;
- x = p.scrollLeft;
- y = p.scrollTop;
- scrolled = false;
- if (oldX !== x) {
- if (x) {
- scrolled = true;
- }
- p.$lastScrollLeft = x;
- }
- if (oldY !== y) {
- if (y) {
- scrolled = true;
- }
- p.$lastScrollTop = y;
- }
- if (scrolled) {
- //console.log('scroll x:' + x + ' y:' + y, p);
- me.eventsRecorded.push(rec = {
- type: 'scroll',
- target: me.getElementXPath(p),
- ts: ts,
- pos: [
- x,
- y
- ]
- });
- me.fireEvent('add', me, rec);
- }
- if (p.tagName === 'BODY') {
- break;
- }
- }
- }
- };
- });
- /**
- * Describes a gauge needle as a shape defined in SVG path syntax.
- *
- * Note: this class and its subclasses are not supposed to be instantiated directly
- * - an object should be passed the gauge's {@link Ext.ux.gauge.Gauge#needle}
- * config instead. Needle instances are also not supposed to be moved
- * between gauges.
- */
- Ext.define('Ext.ux.gauge.needle.Abstract', {
- mixins: [
- 'Ext.mixin.Factoryable'
- ],
- alias: 'gauge.needle.abstract',
- isNeedle: true,
- config: {
- /**
- * The generator function for the needle's shape.
- * Because the gauge component is resizable, and it is generally
- * desirable to resize the needle along with the gauge, the needle's
- * shape should have an ability to grow, typically non-uniformly,
- * which necessitates a generator function that will update the needle's
- * path, so that its proportions are appropriate for the current gauge size.
- *
- * The generator function is given two parameters: the inner and outer
- * radius of the needle. For example, for a straight arrow, the path
- * definition is expected to have the base of the needle at the origin
- * - (0, 0) coordinates - and point downwards. The needle will be automatically
- * translated to the center of the gauge and rotated to represent the current
- * gauge {@link Ext.ux.gauge.Gauge#value value}.
- *
- * @param {Function} path The path generator function.
- * @param {Number} path.innerRadius The function's first parameter.
- * @param {Number} path.outerRadius The function's second parameter.
- * @return {String} path.return The shape of the needle in the SVG path syntax returned by
- * the generator function.
- */
- path: null,
- /**
- * The inner radius of the needle. This works just like the `innerRadius`
- * config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
- * The default value is `25` to make sure the needle doesn't overlap with
- * the value of the gauge shown at its center by default.
- *
- * @param {Number/String} [innerRadius=25]
- */
- innerRadius: 25,
- /**
- * The outer radius of the needle. This works just like the `outerRadius`
- * config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
- *
- * @param {Number/String} [outerRadius='100% - 20']
- */
- outerRadius: '100% - 20',
- /**
- * The shape generated by the {@link #path} function is used as the value
- * for the `d` attribute of the SVG `<path>` element. This element
- * has the default class name of `.x-gauge-needle`, so that CSS can be used
- * to give all gauge needles some common styling. To style a particular needle,
- * one can use this config to add styles to the needle's `<path>` element directly,
- * or use a custom {@link Ext.ux.gauge.Gauge#cls class} for the needle's gauge
- * and style the needle from there.
- *
- * This config is not supposed to be updated manually, the styles should
- * always be updated by the means of the `setStyle` call. For example,
- * this is not allowed:
- *
- * gauge.getStyle().fill = 'red'; // WRONG!
- * gauge.setStyle({ 'fill': 'red' }); // correct
- *
- * Subsequent calls to the `setStyle` will add to the styles set previously
- * or overwrite their values, but won't remove them. If you'd like to style
- * from a clean slate, setting the style to `null` first will remove the styles
- * previously set:
- *
- * gauge.getNeedle().setStyle(null);
- *
- * If an SVG shape was produced by a designer rather than programmatically,
- * in other words, the {@link #path} function returns the same shape regardless
- * of the parameters it was given, the uniform scaling of said shape is the only
- * option, if one wants to use gauges of different sizes. In this case,
- * it's possible to specify the desired scale by using the `transform` style,
- * for example:
- *
- * transform: 'scale(0.35)'
- *
- * @param {Object} style
- */
- style: null,
- /**
- * @private
- * @param {Number} radius
- */
- radius: 0,
- /**
- * @private
- * Expected in the initial config, required during construction.
- * @param {Ext.ux.gauge.Gauge} gauge
- */
- gauge: null
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- applyInnerRadius: function(innerRadius) {
- return this.getGauge().getRadiusFn(innerRadius);
- },
- applyOuterRadius: function(outerRadius) {
- return this.getGauge().getRadiusFn(outerRadius);
- },
- updateRadius: function() {
- this.regeneratePath();
- },
- setTransform: function(centerX, centerY, rotation) {
- var needleGroup = this.getNeedleGroup();
- needleGroup.setStyle('transform', 'translate(' + centerX + 'px,' + centerY + 'px) ' + 'rotate(' + rotation + 'deg)');
- },
- applyPath: function(path) {
- return Ext.isFunction(path) ? path : null;
- },
- updatePath: function(path) {
- this.regeneratePath(path);
- },
- regeneratePath: function(path) {
- path = path || this.getPath();
- var me = this,
- radius = me.getRadius(),
- inner = me.getInnerRadius()(radius),
- outer = me.getOuterRadius()(radius),
- d = outer > inner ? path(inner, outer) : '';
- me.getNeedlePath().dom.setAttribute('d', d);
- },
- getNeedleGroup: function() {
- var gauge = this.getGauge(),
- group = this.needleGroup;
- // The gauge positions the needle by calling its `setTransform` method,
- // which applies a transformation to the needle's group, that contains
- // the actual path element. This is done because we need the ability to
- // transform the path independently from it's position in the gauge.
- // For example, if the needle has to be made bigger, is shouldn't be
- // part of the transform that centers it in the gauge and rotates it
- // to point at the current value.
- if (!group) {
- group = this.needleGroup = Ext.get(document.createElementNS(gauge.svgNS, 'g'));
- gauge.getSvg().appendChild(group);
- }
- return group;
- },
- getNeedlePath: function() {
- var me = this,
- pathElement = me.pathElement;
- if (!pathElement) {
- pathElement = me.pathElement = Ext.get(document.createElementNS(me.getGauge().svgNS, 'path'));
- pathElement.dom.setAttribute('class', Ext.baseCSSPrefix + 'gauge-needle');
- me.getNeedleGroup().appendChild(pathElement);
- }
- return pathElement;
- },
- updateStyle: function(style) {
- var pathElement = this.getNeedlePath();
- // Note that we are setting the `style` attribute, e.g `style="fill: red"`,
- // instead of path attributes individually, e.g. `fill="red"` because
- // the attribute styles defined in CSS classes will override the values
- // of attributes set on the elements individually.
- if (Ext.isObject(style)) {
- pathElement.setStyle(style);
- } else {
- pathElement.dom.removeAttribute('style');
- }
- },
- destroy: function() {
- var me = this;
- me.pathElement = Ext.destroy(me.pathElement);
- me.needleGroup = Ext.destroy(me.needleGroup);
- me.setGauge(null);
- }
- });
- /**
- * Displays a value within the given interval as a gauge. For example:
- *
- * @example
- * Ext.create({
- * xtype: 'panel',
- * renderTo: document.body,
- * width: 200,
- * height: 200,
- * layout: 'fit',
- * items: {
- * xtype: 'gauge',
- * padding: 20,
- * value: 55,
- * minValue: 40,
- * maxValue: 80
- * }
- * });
- *
- * It's also possible to use gauges to create loading indicators:
- *
- * @example
- * Ext.create({
- * xtype: 'panel',
- * renderTo: document.body,
- * width: 200,
- * height: 200,
- * layout: 'fit',
- * items: {
- * xtype: 'gauge',
- * padding: 20,
- * trackStart: 0,
- * trackLength: 360,
- * value: 20,
- * valueStyle: {
- * round: true
- * },
- * textTpl: 'Loading...',
- * animation: {
- * easing: 'linear',
- * duration: 100000
- * }
- * }
- * }).items.first().setAngleOffset(360 * 100);
- *
- * Gauges can contain needles as well.
- *
- * @example
- * Ext.create({
- * xtype: 'panel',
- * renderTo: document.body,
- * width: 200,
- * height: 200,
- * layout: 'fit',
- * items: {
- * xtype: 'gauge',
- * padding: 20,
- * value: 55,
- * minValue: 40,
- * maxValue: 80,
- * needle: 'wedge'
- * }
- * });
- *
- */
- Ext.define('Ext.ux.gauge.Gauge', {
- alternateClassName: 'Ext.ux.Gauge',
- extend: 'Ext.Gadget',
- xtype: 'gauge',
- requires: [
- 'Ext.ux.gauge.needle.Abstract',
- 'Ext.util.Region'
- ],
- config: {
- /**
- * @cfg {Number/String} padding
- * Gauge sector padding in pixels or percent of width/height, whichever is smaller.
- */
- padding: 10,
- /**
- * @cfg {Number} trackStart
- * The angle in the [0, 360) interval at which the gauge's track sector starts.
- * E.g. 0 for 3 o-clock, 90 for 6 o-clock, 180 for 9 o-clock, 270 for noon.
- */
- trackStart: 135,
- /**
- * @cfg {Number} trackLength
- * The angle in the (0, 360] interval to add to the {@link #trackStart} angle
- * to determine the angle at which the track ends.
- */
- trackLength: 270,
- /**
- * @cfg {Number} angleOffset
- * The angle at which the {@link #minValue} starts in case of a circular gauge.
- */
- angleOffset: 0,
- /**
- * @cfg {Number} minValue
- * The minimum value that the gauge can represent.
- */
- minValue: 0,
- /**
- * @cfg {Number} maxValue
- * The maximum value that the gauge can represent.
- */
- maxValue: 100,
- /**
- * @cfg {Number} value
- * The current value of the gauge.
- */
- value: 50,
- /**
- * @cfg {Ext.ux.gauge.needle.Abstract} needle
- * A config object for the needle to be used by the gauge.
- * The needle will track the current {@link #value}.
- * The default needle type is 'diamond', so if a config like
- *
- * needle: {
- * outerRadius: '100%'
- * }
- *
- * is used, the app/view still has to require
- * the `Ext.ux.gauge.needle.Diamond` class.
- * If a type is specified explicitly
- *
- * needle: {
- * type: 'arrow'
- * }
- *
- * it's straightforward which class should be required.
- */
- needle: null,
- needleDefaults: {
- cached: true,
- $value: {
- type: 'diamond'
- }
- },
- /**
- * @cfg {Boolean} [clockwise=true]
- * `true` - {@link #cfg!value} increments in a clockwise fashion
- * `false` - {@link #cfg!value} increments in an anticlockwise fashion
- */
- clockwise: true,
- /**
- * @cfg {Ext.XTemplate} textTpl
- * The template for the text in the center of the gauge.
- * The available data values are:
- * - `value` - The {@link #cfg!value} of the gauge.
- * - `percent` - The value as a percentage between 0 and 100.
- * - `minValue` - The value of the {@link #cfg!minValue} config.
- * - `maxValue` - The value of the {@link #cfg!maxValue} config.
- * - `delta` - The delta between the {@link #cfg!minValue} and {@link #cfg!maxValue}.
- */
- textTpl: [
- '<tpl>{value:number("0.00")}%</tpl>'
- ],
- /**
- * @cfg {String} [textAlign='c-c']
- * If the gauge has a donut hole, the text will be centered inside it.
- * Otherwise, the text will be centered in the middle of the gauge's
- * bounding box. This config allows to alter the position of the text
- * in the latter case. See the docs for the `align` option to the
- * {@link Ext.util.Region#alignTo} method for possible ways of alignment
- * of the text to the guage's bounding box.
- */
- textAlign: 'c-c',
- /**
- * @cfg {Object} textOffset
- * This config can be used to displace the {@link #textTpl text} from its default
- * position in the center of the gauge by providing values for horizontal and
- * vertical displacement.
- * @cfg {Number} textOffset.dx Horizontal displacement.
- * @cfg {Number} textOffset.dy Vertical displacement.
- */
- textOffset: {
- dx: 0,
- dy: 0
- },
- /**
- * @cfg {Object} trackStyle
- * Track sector styles.
- * @cfg {String/Object[]} trackStyle.fill Track sector fill color. Defaults to CSS value.
- * It's also possible to have a linear gradient fill that starts at the top-left corner
- * of the gauge and ends at its bottom-right corner, by providing an array of color stop
- * objects. For example:
- *
- * trackStyle: {
- * fill: [{
- * offset: 0,
- * color: 'green',
- * opacity: 0.8
- * }, {
- * offset: 1,
- * color: 'gold'
- * }]
- * }
- *
- * @cfg {Number} trackStyle.fillOpacity Track sector fill opacity. Defaults to CSS value.
- * @cfg {String} trackStyle.stroke Track sector stroke color. Defaults to CSS value.
- * @cfg {Number} trackStyle.strokeOpacity Track sector stroke opacity. Defaults to CSS value.
- * @cfg {Number} trackStyle.strokeWidth Track sector stroke width. Defaults to CSS value.
- * @cfg {Number/String} [trackStyle.outerRadius='100%'] The outer radius of the track sector.
- * For example:
- *
- * outerRadius: '90%', // 90% of the maximum radius
- * outerRadius: 100, // radius of 100 pixels
- * outerRadius: '70% + 5', // 70% of the maximum radius plus 5 pixels
- * outerRadius: '80% - 10', // 80% of the maximum radius minus 10 pixels
- *
- * @cfg {Number/String} [trackStyle.innerRadius='50%'] The inner radius of the track sector.
- * See the `trackStyle.outerRadius` config documentation for more information.
- * @cfg {Boolean} [trackStyle.round=false] Whether to round the track sector edges or not.
- */
- trackStyle: {
- outerRadius: '100%',
- innerRadius: '100% - 20',
- round: false
- },
- /**
- * @cfg {Object} valueStyle
- * Value sector styles.
- * @cfg {String/Object[]} valueStyle.fill Value sector fill color. Defaults to CSS value.
- * See the `trackStyle.fill` config documentation for more information.
- * @cfg {Number} valueStyle.fillOpacity Value sector fill opacity. Defaults to CSS value.
- * @cfg {String} valueStyle.stroke Value sector stroke color. Defaults to CSS value.
- * @cfg {Number} valueStyle.strokeOpacity Value sector stroke opacity. Defaults to CSS value.
- * @cfg {Number} valueStyle.strokeWidth Value sector stroke width. Defaults to CSS value.
- * @cfg {Number/String} [valueStyle.outerRadius='100% - 4'] The outer radius of the value sector.
- * See the `trackStyle.outerRadius` config documentation for more information.
- * @cfg {Number/String} [valueStyle.innerRadius='50% + 4'] The inner radius of the value sector.
- * See the `trackStyle.outerRadius` config documentation for more information.
- * @cfg {Boolean} [valueStyle.round=false] Whether to round the value sector edges or not.
- */
- valueStyle: {
- outerRadius: '100% - 2',
- innerRadius: '100% - 18',
- round: false
- },
- /**
- * @cfg {Object/Boolean} [animation=true]
- * The animation applied to the gauge on changes to the {@link #value}
- * and the {@link #angleOffset} configs. Defaults to 1 second animation
- * with the 'out' easing.
- * @cfg {Number} animation.duration The duraction of the animation.
- * @cfg {String} animation.easing The easing function to use for the animation.
- * Possible values are:
- * - `linear` - no easing, no acceleration
- * - `in` - accelerating from zero velocity
- * - `out` - (default) decelerating to zero velocity
- * - `inOut` - acceleration until halfway, then deceleration
- */
- animation: true
- },
- baseCls: Ext.baseCSSPrefix + 'gauge',
- template: [
- {
- reference: 'bodyElement',
- children: [
- {
- reference: 'textElement',
- cls: Ext.baseCSSPrefix + 'gauge-text'
- }
- ]
- }
- ],
- defaultBindProperty: 'value',
- pathAttributes: {
- // The properties in the `trackStyle` and `valueStyle` configs
- // that are path attributes.
- fill: true,
- fillOpacity: true,
- stroke: true,
- strokeOpacity: true,
- strokeWidth: true
- },
- easings: {
- linear: Ext.identityFn,
- // cubic easings
- 'in': function(t) {
- return t * t * t;
- },
- out: function(t) {
- return (--t) * t * t + 1;
- },
- inOut: function(t) {
- return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
- }
- },
- resizeDelay: 0,
- // in milliseconds
- resizeTimerId: 0,
- size: null,
- // cached size
- svgNS: 'http://www.w3.org/2000/svg',
- svg: null,
- // SVG document
- defs: null,
- // the `defs` section of the SVG document
- trackArc: null,
- valueArc: null,
- trackGradient: null,
- valueGradient: null,
- fx: null,
- // either the `value` or the `angleOffset` animation
- fxValue: 0,
- // the actual value rendered/animated
- fxAngleOffset: 0,
- constructor: function(config) {
- var me = this;
- me.fitSectorInRectCache = {
- startAngle: null,
- lengthAngle: null,
- minX: null,
- maxX: null,
- minY: null,
- maxY: null
- };
- me.interpolator = me.createInterpolator();
- me.callParent([
- config
- ]);
- me.el.on('resize', 'onElementResize', me);
- },
- doDestroy: function() {
- var me = this;
- Ext.undefer(me.resizeTimerId);
- me.el.un('resize', 'onElementResize', me);
- me.stopAnimation();
- me.setNeedle(null);
- me.trackGradient = Ext.destroy(me.trackGradient);
- me.valueGradient = Ext.destroy(me.valueGradient);
- me.defs = Ext.destroy(me.defs);
- me.svg = Ext.destroy(me.svg);
- me.callParent();
- },
- onElementResize: function(element, size) {
- this.handleResize(size);
- },
- handleResize: function(size, instantly) {
- var me = this,
- el = me.element;
- if (!(el && (size = size || el.getSize()) && size.width && size.height)) {
- return;
- }
- me.resizeTimerId = Ext.undefer(me.resizeTimerId);
- if (!instantly && me.resizeDelay) {
- me.resizeTimerId = Ext.defer(me.handleResize, me.resizeDelay, me, [
- size,
- true
- ]);
- return;
- }
- me.size = size;
- me.resizeHandler(size);
- },
- updateMinValue: function(minValue) {
- var me = this;
- me.interpolator.setDomain(minValue, me.getMaxValue());
- if (!me.isConfiguring) {
- me.render();
- }
- },
- updateMaxValue: function(maxValue) {
- var me = this;
- me.interpolator.setDomain(me.getMinValue(), maxValue);
- if (!me.isConfiguring) {
- me.render();
- }
- },
- updateAngleOffset: function(angleOffset, oldAngleOffset) {
- var me = this,
- animation = me.getAnimation();
- me.fxAngleOffset = angleOffset;
- if (me.isConfiguring) {
- return;
- }
- if (animation.duration) {
- me.animate(oldAngleOffset, angleOffset, animation.duration, me.easings[animation.easing], function(angleOffset) {
- me.fxAngleOffset = angleOffset;
- me.render();
- });
- } else {
- me.render();
- }
- },
- //<debug>
- applyTrackStart: function(trackStart) {
- if (trackStart < 0 || trackStart >= 360) {
- Ext.raise("'trackStart' should be within [0, 360).");
- }
- return trackStart;
- },
- applyTrackLength: function(trackLength) {
- if (trackLength <= 0 || trackLength > 360) {
- Ext.raise("'trackLength' should be within (0, 360].");
- }
- return trackLength;
- },
- //</debug>
- updateTrackStart: function(trackStart) {
- var me = this;
- if (!me.isConfiguring) {
- me.render();
- }
- },
- updateTrackLength: function(trackLength) {
- var me = this;
- me.interpolator.setRange(0, trackLength);
- if (!me.isConfiguring) {
- me.render();
- }
- },
- applyPadding: function(padding) {
- if (typeof padding === 'string') {
- var ratio = parseFloat(padding) / 100;
- return function(x) {
- return x * ratio;
- };
- }
- return function() {
- return padding;
- };
- },
- updatePadding: function() {
- if (!this.isConfiguring) {
- this.render();
- }
- },
- applyValue: function(value) {
- var minValue = this.getMinValue(),
- maxValue = this.getMaxValue();
- return Math.min(Math.max(value, minValue), maxValue);
- },
- updateValue: function(value, oldValue) {
- var me = this,
- animation = me.getAnimation();
- me.fxValue = value;
- if (me.isConfiguring) {
- return;
- }
- me.writeText();
- if (animation.duration) {
- me.animate(oldValue, value, animation.duration, me.easings[animation.easing], function(value) {
- me.fxValue = value;
- me.render();
- });
- } else {
- me.render();
- }
- },
- applyTextTpl: function(textTpl) {
- if (textTpl && !textTpl.isTemplate) {
- textTpl = new Ext.XTemplate(textTpl);
- }
- return textTpl;
- },
- applyTextOffset: function(offset) {
- offset = offset || {};
- offset.dx = offset.dx || 0;
- offset.dy = offset.dy || 0;
- return offset;
- },
- updateTextTpl: function() {
- this.writeText();
- if (!this.isConfiguring) {
- this.centerText();
- }
- },
- // text will be centered on first size
- writeText: function(options) {
- var me = this,
- value = me.getValue(),
- minValue = me.getMinValue(),
- maxValue = me.getMaxValue(),
- delta = maxValue - minValue,
- textTpl = me.getTextTpl();
- textTpl.overwrite(me.textElement, {
- value: value,
- percent: (value - minValue) / delta * 100,
- minValue: minValue,
- maxValue: maxValue,
- delta: delta
- });
- },
- centerText: function(cx, cy, sectorRegion, innerRadius, outerRadius) {
- var textElement = this.textElement,
- textAlign = this.getTextAlign(),
- alignedRegion, textBox;
- if (Ext.Number.isEqual(innerRadius, 0, 0.1) || sectorRegion.isOutOfBound({
- x: cx,
- y: cy
- })) {
- alignedRegion = textElement.getRegion().alignTo({
- align: textAlign,
- // align text region's center to sector region's center
- target: sectorRegion
- });
- textElement.setLeft(alignedRegion.left);
- textElement.setTop(alignedRegion.top);
- } else {
- textBox = textElement.getBox();
- textElement.setLeft(cx - textBox.width / 2);
- textElement.setTop(cy - textBox.height / 2);
- }
- },
- camelCaseRe: /([a-z])([A-Z])/g,
- /**
- * @private
- */
- camelToHyphen: function(name) {
- return name.replace(this.camelCaseRe, '$1-$2').toLowerCase();
- },
- applyTrackStyle: function(trackStyle) {
- var me = this,
- trackGradient;
- trackStyle.innerRadius = me.getRadiusFn(trackStyle.innerRadius);
- trackStyle.outerRadius = me.getRadiusFn(trackStyle.outerRadius);
- if (Ext.isArray(trackStyle.fill)) {
- trackGradient = me.getTrackGradient();
- me.setGradientStops(trackGradient, trackStyle.fill);
- trackStyle.fill = 'url(#' + trackGradient.dom.getAttribute('id') + ')';
- }
- return trackStyle;
- },
- updateTrackStyle: function(trackStyle) {
- var me = this,
- trackArc = Ext.fly(me.getTrackArc()),
- name;
- for (name in trackStyle) {
- if (name in me.pathAttributes) {
- trackArc.setStyle(me.camelToHyphen(name), trackStyle[name]);
- } else {
- trackArc.setStyle(name, trackStyle[name]);
- }
- }
- },
- applyValueStyle: function(valueStyle) {
- var me = this,
- valueGradient;
- valueStyle.innerRadius = me.getRadiusFn(valueStyle.innerRadius);
- valueStyle.outerRadius = me.getRadiusFn(valueStyle.outerRadius);
- if (Ext.isArray(valueStyle.fill)) {
- valueGradient = me.getValueGradient();
- me.setGradientStops(valueGradient, valueStyle.fill);
- valueStyle.fill = 'url(#' + valueGradient.dom.getAttribute('id') + ')';
- }
- return valueStyle;
- },
- updateValueStyle: function(valueStyle) {
- var me = this,
- valueArc = Ext.fly(me.getValueArc()),
- name;
- for (name in valueStyle) {
- if (name in me.pathAttributes) {
- valueArc.setStyle(me.camelToHyphen(name), valueStyle[name]);
- } else {
- valueArc.setStyle(name, valueStyle[name]);
- }
- }
- },
- /**
- * @private
- */
- getRadiusFn: function(radius) {
- var result, pos, ratio,
- increment = 0;
- if (Ext.isNumber(radius)) {
- result = function() {
- return radius;
- };
- } else if (Ext.isString(radius)) {
- radius = radius.replace(/ /g, '');
- ratio = parseFloat(radius) / 100;
- pos = radius.search('%');
- // E.g. '100% - 4'
- if (pos < radius.length - 1) {
- increment = parseFloat(radius.substr(pos + 1));
- }
- result = function(radius) {
- return radius * ratio + increment;
- };
- result.ratio = ratio;
- }
- return result;
- },
- getSvg: function() {
- var me = this,
- svg = me.svg;
- if (!svg) {
- svg = me.svg = Ext.get(document.createElementNS(me.svgNS, 'svg'));
- me.bodyElement.append(svg);
- }
- return svg;
- },
- getTrackArc: function() {
- var me = this,
- trackArc = me.trackArc;
- if (!trackArc) {
- trackArc = me.trackArc = document.createElementNS(me.svgNS, 'path');
- me.getSvg().append(trackArc, true);
- // Note: Ext.dom.Element.addCls doesn't work on SVG elements,
- // as it simply assigns a class string to el.dom.className,
- // which in case of SVG is no simple string:
- // SVGAnimatedString {baseVal: "x-gauge-track", animVal: "x-gauge-track"}
- trackArc.setAttribute('class', Ext.baseCSSPrefix + 'gauge-track');
- }
- return trackArc;
- },
- getValueArc: function() {
- var me = this,
- valueArc = me.valueArc;
- me.getTrackArc();
- // make sure the track arc is created first for proper draw order
- if (!valueArc) {
- valueArc = me.valueArc = document.createElementNS(me.svgNS, 'path');
- me.getSvg().append(valueArc, true);
- valueArc.setAttribute('class', Ext.baseCSSPrefix + 'gauge-value');
- }
- return valueArc;
- },
- applyNeedle: function(needle, oldNeedle) {
- // Make sure the track and value elements have been already created,
- // so that the needle element renders on top.
- this.getValueArc();
- return Ext.Factory.gaugeNeedle.update(oldNeedle, needle, this, 'createNeedle', 'needleDefaults');
- },
- createNeedle: function(config) {
- return Ext.apply({
- gauge: this
- }, config);
- },
- getDefs: function() {
- var me = this,
- defs = me.defs;
- if (!defs) {
- defs = me.defs = Ext.get(document.createElementNS(me.svgNS, 'defs'));
- me.getSvg().appendChild(defs);
- }
- return defs;
- },
- /**
- * @private
- */
- setGradientSize: function(gradient, x1, y1, x2, y2) {
- gradient.setAttribute('x1', x1);
- gradient.setAttribute('y1', y1);
- gradient.setAttribute('x2', x2);
- gradient.setAttribute('y2', y2);
- },
- /**
- * @private
- */
- resizeGradients: function(size) {
- var me = this,
- trackGradient = me.getTrackGradient(),
- valueGradient = me.getValueGradient(),
- x1 = 0,
- y1 = size.height / 2,
- x2 = size.width,
- y2 = size.height / 2;
- me.setGradientSize(trackGradient.dom, x1, y1, x2, y2);
- me.setGradientSize(valueGradient.dom, x1, y1, x2, y2);
- },
- /**
- * @private
- */
- setGradientStops: function(gradient, stops) {
- var ln = stops.length,
- i, stopCfg, stopEl;
- while (gradient.firstChild) {
- gradient.removeChild(gradient.firstChild);
- }
- for (i = 0; i < ln; i++) {
- stopCfg = stops[i];
- stopEl = document.createElementNS(this.svgNS, 'stop');
- gradient.appendChild(stopEl);
- stopEl.setAttribute('offset', stopCfg.offset);
- stopEl.setAttribute('stop-color', stopCfg.color);
- ('opacity' in stopCfg) && stopEl.setAttribute('stop-opacity', stopCfg.opacity);
- }
- },
- getTrackGradient: function() {
- var me = this,
- trackGradient = me.trackGradient;
- if (!trackGradient) {
- trackGradient = me.trackGradient = Ext.get(document.createElementNS(me.svgNS, 'linearGradient'));
- // Using absolute values for x1, y1, x2, y2 attributes.
- trackGradient.dom.setAttribute('gradientUnits', 'userSpaceOnUse');
- me.getDefs().appendChild(trackGradient);
- Ext.get(trackGradient);
- }
- // assign unique ID
- return trackGradient;
- },
- getValueGradient: function() {
- var me = this,
- valueGradient = me.valueGradient;
- if (!valueGradient) {
- valueGradient = me.valueGradient = Ext.get(document.createElementNS(me.svgNS, 'linearGradient'));
- // Using absolute values for x1, y1, x2, y2 attributes.
- valueGradient.dom.setAttribute('gradientUnits', 'userSpaceOnUse');
- me.getDefs().appendChild(valueGradient);
- Ext.get(valueGradient);
- }
- // assign unique ID
- return valueGradient;
- },
- getArcPoint: function(centerX, centerY, radius, degrees) {
- var radians = degrees / 180 * Math.PI;
- return [
- centerX + radius * Math.cos(radians),
- centerY + radius * Math.sin(radians)
- ];
- },
- isCircle: function(startAngle, endAngle) {
- return Ext.Number.isEqual(Math.abs(endAngle - startAngle), 360, 0.001);
- },
- getArcPath: function(centerX, centerY, innerRadius, outerRadius, startAngle, endAngle, round) {
- var me = this,
- isCircle = me.isCircle(startAngle, endAngle),
- // It's not possible to draw a circle using arcs.
- endAngle = endAngle - 0.01,
- innerStartPoint = me.getArcPoint(centerX, centerY, innerRadius, startAngle),
- innerEndPoint = me.getArcPoint(centerX, centerY, innerRadius, endAngle),
- outerStartPoint = me.getArcPoint(centerX, centerY, outerRadius, startAngle),
- outerEndPoint = me.getArcPoint(centerX, centerY, outerRadius, endAngle),
- large = endAngle - startAngle <= 180 ? 0 : 1,
- path = [
- 'M',
- innerStartPoint[0],
- innerStartPoint[1],
- 'A',
- innerRadius,
- innerRadius,
- 0,
- large,
- 1,
- innerEndPoint[0],
- innerEndPoint[1]
- ],
- capRadius = (outerRadius - innerRadius) / 2;
- if (isCircle) {
- path.push('M', outerEndPoint[0], outerEndPoint[1]);
- } else {
- if (round) {
- path.push('A', capRadius, capRadius, 0, 0, 0, outerEndPoint[0], outerEndPoint[1]);
- } else {
- path.push('L', outerEndPoint[0], outerEndPoint[1]);
- }
- }
- path.push('A', outerRadius, outerRadius, 0, large, 0, outerStartPoint[0], outerStartPoint[1]);
- if (round && !isCircle) {
- path.push('A', capRadius, capRadius, 0, 0, 0, innerStartPoint[0], innerStartPoint[1]);
- }
- path.push('Z');
- return path.join(' ');
- },
- resizeHandler: function(size) {
- var me = this,
- svg = me.getSvg();
- svg.setSize(size);
- me.resizeGradients(size);
- me.render();
- },
- /**
- * @private
- * Creates a linear interpolator function that itself has a few methods:
- * - `setDomain(from, to)`
- * - `setRange(from, to)`
- * - `getDomain` - returns the domain as a [from, to] array
- * - `getRange` - returns the range as a [from, to] array
- * @param {Boolean} [rangeCheck=false]
- * Whether to allow out of bounds values for domain and range.
- * @return {Function} The interpolator function:
- * `interpolator(domainValue, isInvert)`.
- * If the `isInvert` parameter is `true`, the start of domain will correspond
- * to the end of range. This is useful, for example, when you want to render
- * increasing domain values counter-clockwise instead of clockwise.
- */
- createInterpolator: function(rangeCheck) {
- var domainStart = 0,
- domainDelta = 1,
- rangeStart = 0,
- rangeEnd = 1;
- var interpolator = function(x, invert) {
- var t = 0;
- if (domainDelta) {
- t = (x - domainStart) / domainDelta;
- if (rangeCheck) {
- t = Math.max(0, t);
- t = Math.min(1, t);
- }
- if (invert) {
- t = 1 - t;
- }
- }
- return (1 - t) * rangeStart + t * rangeEnd;
- };
- interpolator.setDomain = function(a, b) {
- domainStart = a;
- domainDelta = b - a;
- return this;
- };
- interpolator.setRange = function(a, b) {
- rangeStart = a;
- rangeEnd = b;
- return this;
- };
- interpolator.getDomain = function() {
- return [
- domainStart,
- domainStart + domainDelta
- ];
- };
- interpolator.getRange = function() {
- return [
- rangeStart,
- rangeEnd
- ];
- };
- return interpolator;
- },
- applyAnimation: function(animation) {
- if (true === animation) {
- animation = {};
- } else if (false === animation) {
- animation = {
- duration: 0
- };
- }
- if (!('duration' in animation)) {
- animation.duration = 1000;
- }
- if (!(animation.easing in this.easings)) {
- animation.easing = 'out';
- }
- return animation;
- },
- updateAnimation: function() {
- this.stopAnimation();
- },
- /**
- * @private
- * @param {Number} from
- * @param {Number} to
- * @param {Number} duration
- * @param {Function} easing
- * @param {Function} fn Function to execute on every frame of animation.
- * The function takes a single parameter - the value in the [from, to]
- * range, interpolated based on current time and easing function.
- * With certain easings, the value may overshoot the range slighly.
- * @param {Object} scope
- */
- animate: function(from, to, duration, easing, fn, scope) {
- var me = this,
- start = Ext.now(),
- interpolator = me.createInterpolator().setRange(from, to);
- function frame() {
- var now = Ext.AnimationQueue.frameStartTime,
- t = Math.min(now - start, duration) / duration,
- value = interpolator(easing(t));
- if (scope) {
- if (typeof fn === 'string') {
- scope[fn].call(scope, value);
- } else {
- fn.call(scope, value);
- }
- } else {
- fn(value);
- }
- if (t >= 1) {
- Ext.AnimationQueue.stop(frame, scope);
- me.fx = null;
- }
- }
- me.stopAnimation();
- Ext.AnimationQueue.start(frame, scope);
- me.fx = {
- frame: frame,
- scope: scope
- };
- },
- /**
- * Stops the current {@link #value} or {@link #angleOffset} animation.
- */
- stopAnimation: function() {
- var me = this;
- if (me.fx) {
- Ext.AnimationQueue.stop(me.fx.frame, me.fx.scope);
- me.fx = null;
- }
- },
- unitCircleExtrema: {
- 0: [
- 1,
- 0
- ],
- 90: [
- 0,
- 1
- ],
- 180: [
- -1,
- 0
- ],
- 270: [
- 0,
- -1
- ],
- 360: [
- 1,
- 0
- ],
- 450: [
- 0,
- 1
- ],
- 540: [
- -1,
- 0
- ],
- 630: [
- 0,
- -1
- ]
- },
- /**
- * @private
- */
- getUnitSectorExtrema: function(startAngle, lengthAngle) {
- var extrema = this.unitCircleExtrema,
- points = [],
- angle;
- for (angle in extrema) {
- if (angle > startAngle && angle < startAngle + lengthAngle) {
- points.push(extrema[angle]);
- }
- }
- return points;
- },
- /**
- * @private
- * Given a rect with a known width and height, find the maximum radius of the donut
- * sector that can fit into it, as well as the center point of such a sector.
- * The end and start angles of the sector are also known, as well as the relationship
- * between the inner and outer radii.
- */
- fitSectorInRect: function(width, height, startAngle, lengthAngle, ratio) {
- if (Ext.Number.isEqual(lengthAngle, 360, 0.001)) {
- return {
- cx: width / 2,
- cy: height / 2,
- radius: Math.min(width, height) / 2,
- region: new Ext.util.Region(0, width, height, 0)
- };
- }
- var me = this,
- points, xx, yy, minX, maxX, minY, maxY,
- cache = me.fitSectorInRectCache,
- sameAngles = cache.startAngle === startAngle && cache.lengthAngle === lengthAngle;
- if (sameAngles) {
- minX = cache.minX;
- maxX = cache.maxX;
- minY = cache.minY;
- maxY = cache.maxY;
- } else {
- points = me.getUnitSectorExtrema(startAngle, lengthAngle).concat([
- me.getArcPoint(0, 0, 1, startAngle),
- // start angle outer radius point
- me.getArcPoint(0, 0, ratio, startAngle),
- // start angle inner radius point
- me.getArcPoint(0, 0, 1, startAngle + lengthAngle),
- // end angle outer radius point
- me.getArcPoint(0, 0, ratio, startAngle + lengthAngle)
- ]);
- // end angle inner radius point
- xx = points.map(function(point) {
- return point[0];
- });
- yy = points.map(function(point) {
- return point[1];
- });
- // The bounding box of a unit sector with the given properties.
- minX = Math.min.apply(null, xx);
- maxX = Math.max.apply(null, xx);
- minY = Math.min.apply(null, yy);
- maxY = Math.max.apply(null, yy);
- cache.startAngle = startAngle;
- cache.lengthAngle = lengthAngle;
- cache.minX = minX;
- cache.maxX = maxX;
- cache.minY = minY;
- cache.maxY = maxY;
- }
- var sectorWidth = maxX - minX,
- sectorHeight = maxY - minY,
- scaleX = width / sectorWidth,
- scaleY = height / sectorHeight,
- scale = Math.min(scaleX, scaleY),
- // Region constructor takes: top, right, bottom, left.
- sectorRegion = new Ext.util.Region(minY * scale, maxX * scale, maxY * scale, minX * scale),
- rectRegion = new Ext.util.Region(0, width, height, 0),
- alignedRegion = sectorRegion.alignTo({
- align: 'c-c',
- // align sector region's center to rect region's center
- target: rectRegion
- }),
- dx = alignedRegion.left - minX * scale,
- dy = alignedRegion.top - minY * scale;
- return {
- cx: dx,
- cy: dy,
- radius: scale,
- region: alignedRegion
- };
- },
- /**
- * @private
- */
- fitSectorInPaddedRect: function(width, height, padding, startAngle, lengthAngle, ratio) {
- var result = this.fitSectorInRect(width - padding * 2, height - padding * 2, startAngle, lengthAngle, ratio);
- result.cx += padding;
- result.cy += padding;
- result.region.translateBy(padding, padding);
- return result;
- },
- /**
- * @private
- */
- normalizeAngle: function(angle) {
- return (angle % 360 + 360) % 360;
- },
- render: function() {
- if (!this.size) {
- return;
- }
- var me = this,
- textOffset = me.getTextOffset(),
- trackArc = me.getTrackArc(),
- valueArc = me.getValueArc(),
- needle = me.getNeedle(),
- clockwise = me.getClockwise(),
- value = me.fxValue,
- angleOffset = me.fxAngleOffset,
- trackLength = me.getTrackLength(),
- width = me.size.width,
- height = me.size.height,
- paddingFn = me.getPadding(),
- padding = paddingFn(Math.min(width, height)),
- trackStart = me.normalizeAngle(me.getTrackStart() + angleOffset),
- // in the range of [0, 360)
- trackEnd = trackStart + trackLength,
- // in the range of (0, 720)
- valueLength = me.interpolator(value),
- trackStyle = me.getTrackStyle(),
- valueStyle = me.getValueStyle(),
- sector = me.fitSectorInPaddedRect(width, height, padding, trackStart, trackLength, trackStyle.innerRadius.ratio),
- cx = sector.cx,
- cy = sector.cy,
- radius = sector.radius,
- trackInnerRadius = Math.max(0, trackStyle.innerRadius(radius)),
- trackOuterRadius = Math.max(0, trackStyle.outerRadius(radius)),
- valueInnerRadius = Math.max(0, valueStyle.innerRadius(radius)),
- valueOuterRadius = Math.max(0, valueStyle.outerRadius(radius)),
- trackPath = me.getArcPath(cx, cy, trackInnerRadius, trackOuterRadius, trackStart, trackEnd, trackStyle.round),
- valuePath = me.getArcPath(cx, cy, valueInnerRadius, valueOuterRadius, clockwise ? trackStart : trackEnd - valueLength, clockwise ? trackStart + valueLength : trackEnd, valueStyle.round);
- me.centerText(cx + textOffset.dx, cy + textOffset.dy, sector.region, trackInnerRadius, trackOuterRadius);
- trackArc.setAttribute('d', trackPath);
- valueArc.setAttribute('d', valuePath);
- if (needle) {
- needle.setRadius(radius);
- needle.setTransform(cx, cy, -90 + trackStart + valueLength);
- }
- me.fireEvent('render', me);
- }
- });
- Ext.define('Ext.ux.gauge.needle.Arrow', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.arrow',
- config: {
- path: function(ir, or) {
- return or - ir > 30 ? "M0," + (ir + 5) + " L-4," + ir + " L-4," + (ir + 10) + " L-1," + (ir + 15) + " L-1," + (or - 7) + " L-5," + (or - 10) + " L0," + or + " L5," + (or - 10) + " L1," + (or - 7) + " L1," + (ir + 15) + " L4," + (ir + 10) + " L4," + ir + " Z" : '';
- }
- }
- });
- Ext.define('Ext.ux.gauge.needle.Diamond', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.diamond',
- config: {
- path: function(ir, or) {
- return or - ir > 10 ? 'M0,' + ir + ' L-4,' + (ir + 5) + ' L0,' + or + ' L4,' + (ir + 5) + ' Z' : '';
- }
- }
- });
- Ext.define('Ext.ux.gauge.needle.Rectangle', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.rectangle',
- config: {
- path: function(ir, or) {
- return or - ir > 10 ? "M-2," + ir + " L2," + ir + " L2," + or + " L-2," + or + " Z" : '';
- }
- }
- });
- Ext.define('Ext.ux.gauge.needle.Spike', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.spike',
- config: {
- path: function(ir, or) {
- return or - ir > 10 ? "M0," + (ir + 5) + " L-4," + ir + " L0," + or + " L4," + ir + " Z" : '';
- }
- }
- });
- Ext.define('Ext.ux.gauge.needle.Wedge', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.wedge',
- config: {
- path: function(ir, or) {
- return or - ir > 10 ? "M-4," + ir + " L0," + or + " L4," + ir + " Z" : '';
- }
- }
- });
- /**
- * A ratings picker based on `Ext.Gadget`.
- *
- * @example
- * Ext.create({
- * xtype: 'rating',
- * renderTo: Ext.getBody(),
- * listeners: {
- * change: function (picker, value) {
- * console.log('Rating ' + value);
- * }
- * }
- * });
- */
- Ext.define('Ext.ux.rating.Picker', {
- extend: 'Ext.Gadget',
- xtype: 'rating',
- focusable: true,
- /*
- * The "cachedConfig" block is basically the same as "config" except that these
- * values are applied specially to the first instance of the class. After processing
- * these configs, the resulting values are stored on the class `prototype` and the
- * template DOM element also reflects these default values.
- */
- cachedConfig: {
- /**
- * @cfg {String} [family]
- * The CSS `font-family` to use for displaying the `{@link #glyphs}`.
- */
- family: 'monospace',
- /**
- * @cfg {String/String[]/Number[]} [glyphs]
- * Either a string containing the two glyph characters, or an array of two strings
- * containing the individual glyph characters or an array of two numbers with the
- * character codes for the individual glyphs.
- *
- * For example:
- *
- * @example
- * Ext.create({
- * xtype: 'rating',
- * renderTo: Ext.getBody(),
- * glyphs: [ 9671, 9670 ], // '◇◆',
- * listeners: {
- * change: function (picker, value) {
- * console.log('Rating ' + value);
- * }
- * }
- * });
- */
- glyphs: '☆★',
- /**
- * @cfg {Number} [minimum=1]
- * The minimum allowed `{@link #value}` (rating).
- */
- minimum: 1,
- /**
- * @cfg {Number} [limit]
- * The maximum allowed `{@link #value}` (rating).
- */
- limit: 5,
- /**
- * @cfg {String/Object} [overStyle]
- * Optional styles to apply to the rating glyphs when `{@link #trackOver}` is
- * enabled.
- */
- overStyle: null,
- /**
- * @cfg {Number} [rounding=1]
- * The rounding to apply to values. Common choices are 0.5 (for half-steps) or
- * 0.25 (for quarter steps).
- */
- rounding: 1,
- /**
- * @cfg {String} [scale="125%"]
- * The CSS `font-size` to apply to the glyphs. This value defaults to 125% because
- * glyphs in the stock font tend to be too small. When using specially designed
- * "icon fonts" you may want to set this to 100%.
- */
- scale: '125%',
- /**
- * @cfg {String/Object} [selectedStyle]
- * Optional styles to apply to the rating value glyphs.
- */
- selectedStyle: null,
- /**
- * @cfg {Object/String/String[]/Ext.XTemplate/Function} tip
- * A template or a function that produces the tooltip text. The `Object`, `String`
- * and `String[]` forms are converted to an `Ext.XTemplate`. If a function is given,
- * it will be called with an object parameter and should return the tooltip text.
- * The object contains these properties:
- *
- * - component: The rating component requesting the tooltip.
- * - tracking: The current value under the mouse cursor.
- * - trackOver: The value of the `{@link #trackOver}` config.
- * - value: The current value.
- *
- * Templates can use these properties to generate the proper text.
- */
- tip: null,
- /**
- * @cfg {Boolean} [trackOver=true]
- * Determines if mouse movements should temporarily update the displayed value.
- * The actual `value` is only updated on `click` but this rather acts as the
- * "preview" of the value prior to click.
- */
- trackOver: true,
- /**
- * @cfg {Number} value
- * The rating value. This value is bounded by `minimum` and `limit` and is also
- * adjusted by the `rounding`.
- */
- value: null,
- //---------------------------------------------------------------------
- // Private configs
- /**
- * @cfg {String} tooltipText
- * The current tooltip text. This value is set into the DOM by the updater (hence
- * only when it changes). This is intended for use by the tip manager
- * (`{@link Ext.tip.QuickTipManager}`). Developers should never need to set this
- * config since it is handled by virtue of setting other configs (such as the
- * {@link #tooltip} or the {@link #value}.).
- * @private
- */
- tooltipText: null,
- /**
- * @cfg {Number} trackingValue
- * This config is used to when `trackOver` is `true` and represents the tracked
- * value. This config is maintained by our `mousemove` handler. This should not
- * need to be set directly by user code.
- * @private
- */
- trackingValue: null
- },
- config: {
- /**
- * @cfg {Boolean/Object} [animate=false]
- * Specifies an animation to use when changing the `{@link #value}`. When setting
- * this config, it is probably best to set `{@link #trackOver}` to `false`.
- */
- animate: null
- },
- // This object describes our element tree from the root.
- element: {
- cls: 'u' + Ext.baseCSSPrefix + 'rating-picker',
- // Since we are replacing the entire "element" tree, we have to assign this
- // "reference" as would our base class.
- reference: 'element',
- children: [
- {
- reference: 'innerEl',
- cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-inner',
- listeners: {
- click: 'onClick',
- mousemove: 'onMouseMove',
- mouseenter: 'onMouseEnter',
- mouseleave: 'onMouseLeave'
- },
- children: [
- {
- reference: 'valueEl',
- cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-value'
- },
- {
- reference: 'trackerEl',
- cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-tracker'
- }
- ]
- }
- ]
- },
- // Tell the Binding system to default to our "value" config.
- defaultBindProperty: 'value',
- // Enable two-way data binding for the "value" config.
- twoWayBindable: 'value',
- overCls: 'u' + Ext.baseCSSPrefix + 'rating-picker-over',
- trackOverCls: 'u' + Ext.baseCSSPrefix + 'rating-picker-track-over',
- //-------------------------------------------------------------------------
- // Config Appliers
- applyGlyphs: function(value) {
- if (typeof value === 'string') {
- //<debug>
- if (value.length !== 2) {
- Ext.raise('Expected 2 characters for "glyphs" not "' + value + '".');
- }
- //</debug>
- value = [
- value.charAt(0),
- value.charAt(1)
- ];
- } else if (typeof value[0] === 'number') {
- value = [
- String.fromCharCode(value[0]),
- String.fromCharCode(value[1])
- ];
- }
- return value;
- },
- applyOverStyle: function(style) {
- this.trackerEl.applyStyles(style);
- },
- applySelectedStyle: function(style) {
- this.valueEl.applyStyles(style);
- },
- applyTip: function(tip) {
- if (tip && typeof tip !== 'function') {
- if (!tip.isTemplate) {
- tip = new Ext.XTemplate(tip);
- }
- tip = tip.apply.bind(tip);
- }
- return tip;
- },
- applyTrackingValue: function(value) {
- return this.applyValue(value);
- },
- // same rounding as normal value
- applyValue: function(v) {
- if (v !== null) {
- var rounding = this.getRounding(),
- limit = this.getLimit(),
- min = this.getMinimum();
- v = Math.round(Math.round(v / rounding) * rounding * 1000) / 1000;
- v = (v < min) ? min : (v > limit ? limit : v);
- }
- return v;
- },
- //-------------------------------------------------------------------------
- // Event Handlers
- onClick: function(event) {
- var value = this.valueFromEvent(event);
- this.setValue(value);
- },
- onMouseEnter: function() {
- this.element.addCls(this.overCls);
- },
- onMouseLeave: function() {
- this.element.removeCls(this.overCls);
- },
- onMouseMove: function(event) {
- var value = this.valueFromEvent(event);
- this.setTrackingValue(value);
- },
- //-------------------------------------------------------------------------
- // Config Updaters
- updateFamily: function(family) {
- this.element.setStyle('fontFamily', "'" + family + "'");
- },
- updateGlyphs: function() {
- this.refreshGlyphs();
- },
- updateLimit: function() {
- this.refreshGlyphs();
- },
- updateScale: function(size) {
- this.element.setStyle('fontSize', size);
- },
- updateTip: function() {
- this.refreshTip();
- },
- updateTooltipText: function(text) {
- this.setTooltip(text);
- },
- // modern only (replaced by classic override)
- updateTrackingValue: function(value) {
- var me = this,
- trackerEl = me.trackerEl,
- newWidth = me.valueToPercent(value);
- trackerEl.setStyle('width', newWidth);
- me.refreshTip();
- },
- updateTrackOver: function(trackOver) {
- this.element.toggleCls(this.trackOverCls, trackOver);
- },
- updateValue: function(value, oldValue) {
- var me = this,
- animate = me.getAnimate(),
- valueEl = me.valueEl,
- newWidth = me.valueToPercent(value),
- column, record;
- if (me.isConfiguring || !animate) {
- valueEl.setStyle('width', newWidth);
- } else {
- valueEl.stopAnimation();
- valueEl.animate(Ext.merge({
- from: {
- width: me.valueToPercent(oldValue)
- },
- to: {
- width: newWidth
- }
- }, animate));
- }
- me.refreshTip();
- if (!me.isConfiguring) {
- // Since we are (re)configured many times as we are used in a grid cell, we
- // avoid firing the change event unless there are listeners.
- if (me.hasListeners.change) {
- me.fireEvent('change', me, value, oldValue);
- }
- column = me.getWidgetColumn && me.getWidgetColumn();
- record = column && me.getWidgetRecord && me.getWidgetRecord();
- if (record && column.dataIndex) {
- // When used in a widgetcolumn, we should update the backing field. The
- // linkages will be cleared as we are being recycled, so this will only
- // reach this line when we are properly attached to a record and the
- // change is coming from the user (or a call to setValue).
- record.set(column.dataIndex, value);
- }
- }
- },
- //-------------------------------------------------------------------------
- // Config System Optimizations
- //
- // These are to deal with configs that combine to determine what should be
- // rendered in the DOM. For example, "glyphs" and "limit" must both be known
- // to render the proper text nodes. The "tip" and "value" likewise are
- // used to update the tooltipText.
- //
- // To avoid multiple updates to the DOM (one for each config), we simply mark
- // the rendering as invalid and post-process these flags on the tail of any
- // bulk updates.
- afterCachedConfig: function() {
- // Now that we are done setting up the initial values we need to refresh the
- // DOM before we allow Ext.Widget's implementation to cloneNode on it.
- this.refresh();
- return this.callParent(arguments);
- },
- initConfig: function(instanceConfig) {
- this.isConfiguring = true;
- this.callParent([
- instanceConfig
- ]);
- // The firstInstance will already have refreshed the DOM (in afterCacheConfig)
- // but all instances beyond the first need to refresh if they have custom values
- // for one or more configs that affect the DOM (such as "glyphs" and "limit").
- this.refresh();
- },
- setConfig: function() {
- var me = this;
- // Since we could be updating multiple configs, save any updates that need
- // multiple values for afterwards.
- me.isReconfiguring = true;
- me.callParent(arguments);
- me.isReconfiguring = false;
- // Now that all new values are set, we can refresh the DOM.
- me.refresh();
- return me;
- },
- //-------------------------------------------------------------------------
- privates: {
- /**
- * This method returns the DOM text node into which glyphs are placed.
- * @param {HTMLElement} dom The DOM node parent of the text node.
- * @return {HTMLElement} The text node.
- * @private
- */
- getGlyphTextNode: function(dom) {
- var node = dom.lastChild;
- // We want all our text nodes to be at the end of the child list, most
- // especially the text node on the innerEl. That text node affects the
- // default left/right position of our absolutely positioned child divs
- // (trackerEl and valueEl).
- if (!node || node.nodeType !== 3) {
- node = dom.ownerDocument.createTextNode('');
- dom.appendChild(node);
- }
- return node;
- },
- getTooltipData: function() {
- var me = this;
- return {
- component: me,
- tracking: me.getTrackingValue(),
- trackOver: me.getTrackOver(),
- value: me.getValue()
- };
- },
- /**
- * Forcibly refreshes both glyph and tooltip rendering.
- * @private
- */
- refresh: function() {
- var me = this;
- if (me.invalidGlyphs) {
- me.refreshGlyphs(true);
- }
- if (me.invalidTip) {
- me.refreshTip(true);
- }
- },
- /**
- * Refreshes the glyph text rendering unless we are currently performing a
- * bulk config change (initConfig or setConfig).
- * @param {Boolean} now Pass `true` to force the refresh to happen now.
- * @private
- */
- refreshGlyphs: function(now) {
- var me = this,
- later = !now && (me.isConfiguring || me.isReconfiguring),
- el, glyphs, limit, on, off, trackerEl, valueEl;
- if (!later) {
- el = me.getGlyphTextNode(me.innerEl.dom);
- valueEl = me.getGlyphTextNode(me.valueEl.dom);
- trackerEl = me.getGlyphTextNode(me.trackerEl.dom);
- glyphs = me.getGlyphs();
- limit = me.getLimit();
- for (on = off = ''; limit--; ) {
- off += glyphs[0];
- on += glyphs[1];
- }
- el.nodeValue = off;
- valueEl.nodeValue = on;
- trackerEl.nodeValue = on;
- }
- me.invalidGlyphs = later;
- },
- /**
- * Refreshes the tooltip text rendering unless we are currently performing a
- * bulk config change (initConfig or setConfig).
- * @param {Boolean} now Pass `true` to force the refresh to happen now.
- * @private
- */
- refreshTip: function(now) {
- var me = this,
- later = !now && (me.isConfiguring || me.isReconfiguring),
- data, text, tooltip;
- if (!later) {
- tooltip = me.getTip();
- if (tooltip) {
- data = me.getTooltipData();
- text = tooltip(data);
- me.setTooltipText(text);
- }
- }
- me.invalidTip = later;
- },
- /**
- * Convert the coordinates of the given `Event` into a rating value.
- * @param {Ext.event.Event} event The event.
- * @return {Number} The rating based on the given event coordinates.
- * @private
- */
- valueFromEvent: function(event) {
- var me = this,
- el = me.innerEl,
- ex = event.getX(),
- rounding = me.getRounding(),
- cx = el.getX(),
- x = ex - cx,
- w = el.getWidth(),
- limit = me.getLimit(),
- v;
- if (me.getInherited().rtl) {
- x = w - x;
- }
- v = x / w * limit;
- // We have to round up here so that the area we are over is considered
- // the value.
- v = Math.ceil(v / rounding) * rounding;
- return v;
- },
- /**
- * Convert the given rating into a width percentage.
- * @param {Number} value The rating value to convert.
- * @return {String} The width percentage to represent the given value.
- * @private
- */
- valueToPercent: function(value) {
- value = (value / this.getLimit()) * 100;
- return value + '%';
- }
- }
- });
|