aria.js 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764
  1. Ext.require([
  2. 'Ext.form.*',
  3. 'Ext.layout.container.Column',
  4. 'Ext.tab.Panel'
  5. ]);
  6. /**
  7. * @class Ext.aria.AriaController
  8. * <p>This class supplies support methods for integrating ARIA support into Components.</p>
  9. *
  10. */
  11. Ext.define('Ext.aria.AriaController', {
  12. singleton: true,
  13. requires: ['Ext.AbstractComponent'],
  14. handlerRe: /^on(\w+)/,
  15. // This function is called as an interceptor of the target class's fireEvent method
  16. // It invokes the event's ariaHandler if present
  17. ariaFireEvent: function(evt){
  18. var me = this,
  19. evtName = evt.toLowerCase(),
  20. args = Array.prototype.slice.call(arguments, 1),
  21. classFn = Ext.aria.AriaController.getClassAriaHandler(this.self.prototype, evtName),
  22. ariaFn = Ext.aria.AriaController[evtName],
  23. result;
  24. // Call user event handlers first.
  25. result = Ext.util.Observable.prototype.fireEvent.apply(me, arguments);
  26. if (result !== false) {
  27. // Then AriaController's event handler
  28. if (typeof ariaFn === 'function') {
  29. result = ariaFn.apply(me, args);
  30. }
  31. // Then class's configured ARIA event handler
  32. if (typeof classFn === 'function') {
  33. result = classFn.apply(me, args);
  34. }
  35. }
  36. return result;
  37. },
  38. // Examine the passed class prototype for an aria_event_handler, and if not found, continue up the prototype chain.
  39. getClassAriaHandler: function(clsProto, evtName) {
  40. var curProto = clsProto,
  41. generatedHandlerName = 'generated_aria_' + evtName + '_handler',
  42. configuredHandlerName = 'aria_' + evtName + '_handler',
  43. fn,
  44. result = clsProto.hasOwnProperty(generatedHandlerName) ? clsProto[generatedHandlerName] : null;
  45. // If there is no generated ariaEventHandler for the class/event, we must generate one
  46. // which calls the cascade of event handlers for the inheritance chain
  47. if (!result) {
  48. var handlerStack = [];
  49. // Collect the stack of event handlers from each level in the inheritance tree
  50. while (curProto) {
  51. if (curProto.hasOwnProperty(configuredHandlerName)) {
  52. fn = curProto[configuredHandlerName];
  53. if (fn) {
  54. handlerStack.unshift(fn);
  55. }
  56. }
  57. curProto = curProto.superclass;
  58. }
  59. // Generate a closure which captures the handler stack, and loops through it
  60. // to invoke them all.
  61. result = clsProto[generatedHandlerName] = (function(handlerStack) {
  62. var result = handlerStack.length ? function() {
  63. var i = 0, len = handlerStack.length;
  64. for (; i < len; i++) {
  65. handlerStack[i].apply(this, arguments);
  66. }
  67. } : Ext.emptyFn;
  68. return result;
  69. })(handlerStack);
  70. }
  71. return result;
  72. },
  73. processClass: function(cls, props) {
  74. var me = this,
  75. clsProto = cls.prototype,
  76. propName,
  77. value,
  78. k;
  79. for (propName in props) {
  80. if (props.hasOwnProperty(propName)) {
  81. value = props[propName];
  82. k = me.handlerRe.exec(propName);
  83. // Handle properties like onHide: function(){...}
  84. // Copy it into ariaHandlers.hide where it can be called
  85. // by the fireEvent interceptor
  86. if (k && (typeof value === 'function')) {
  87. propName = propName.toLowerCase();
  88. // Hang the ariaHandlers on the class's prototype.
  89. clsProto['aria_' + k[1].toLowerCase() + '_handler'] = value;
  90. } else if (propName === 'role') {
  91. clsProto.ariaRole = value;
  92. }
  93. }
  94. }
  95. // Replace the class's fireEvent method with our code which intercepts relevant events,
  96. // performs known processing (such as setting the role on render), and calls any defined
  97. // ariaHandlers
  98. clsProto.fireEvent = me.ariaFireEvent;
  99. },
  100. // ARIA event handler for the render event.
  101. render: function() {
  102. var me = this,
  103. p = me.pendingProps,
  104. len = p ? p.length : 0,
  105. i = 0;
  106. // Apply any pending ARIA updates from when applied before render
  107. for (; i < len; i++) {
  108. me.updateAria.apply(me, p[i]);
  109. }
  110. }
  111. }, function() {
  112. // Inject an updateAria method into AbstractComponent's prorotype
  113. Ext.override(Ext.AbstractComponent, {
  114. getElConfig: function() {
  115. var result = this.callOverridden();
  116. result.role = this.ariaRole;
  117. return result;
  118. },
  119. updateAria: function(el, props) {
  120. var me = this;
  121. // Queue the attributes up if not rendered.
  122. // They will be applied in the global render handler.
  123. if (!me.rendered) {
  124. if (!me.pendingProps) {
  125. me.pendingProps = [];
  126. }
  127. me.pendingProps.push(Array.prototype.slice.call(arguments));
  128. return;
  129. }
  130. // The one argument form updates the actionEl
  131. if (arguments.length == 1) {
  132. props = el;
  133. el = this.getActionEl();
  134. }
  135. // Ensure events are added
  136. if (!me.events.beforeariaupdate) {
  137. me.addEvents('beforeariaupdate', 'ariaupdate');
  138. }
  139. if (me.fireEvent('beforeariaupdate', el, props) !== false) {
  140. Ext.fly(el).set(props);
  141. me.fireEvent('ariaupdate', el, props);
  142. }
  143. }
  144. });
  145. Ext.core.Element.prototype.set = function(o, useSet) {
  146. var el = this.dom,
  147. attr,
  148. val;
  149. useSet = (useSet !== false) && !!el.setAttribute;
  150. for (attr in o) {
  151. if (o.hasOwnProperty(attr)) {
  152. val = o[attr];
  153. if (attr == 'style') {
  154. DH.applyStyles(el, val);
  155. } else if (attr == 'cls') {
  156. el.className = val;
  157. } else if (useSet) {
  158. if (val === undefined) {
  159. el.removeAttribute(attr);
  160. } else {
  161. el.setAttribute(attr, val);
  162. }
  163. } else {
  164. el[attr] = val;
  165. }
  166. }
  167. }
  168. return this;
  169. };
  170. });
  171. Ext.onReady(function() {
  172. Ext.aria.AriaController.processClass(Ext.AbstractComponent, {
  173. onDisable: function() {
  174. this.updateAria(this.getActionEl(), {
  175. 'aria-disabled': true
  176. })
  177. },
  178. onEnable: function() {
  179. this.updateAria(this.getActionEl(), {
  180. 'aria-disabled': false
  181. })
  182. },
  183. onHide: function() {
  184. this.updateAria(this.getActionEl(), {
  185. 'aria-hidden': true
  186. })
  187. },
  188. onShow: function() {
  189. this.updateAria(this.getActionEl(), {
  190. 'aria-hidden': false
  191. })
  192. }
  193. });
  194. Ext.aria.AriaController.processClass(Ext.form.FieldSet, {
  195. onRender: function() {
  196. this.updateAria({
  197. 'aria-expanded': !this.collapsed
  198. });
  199. },
  200. onExpand: function() {
  201. this.updateAria({
  202. 'aria-expanded': true
  203. });
  204. },
  205. onCollapse: function() {
  206. this.updateAria({
  207. 'aria-expanded': false
  208. });
  209. }
  210. });
  211. Ext.aria.AriaController.processClass(Ext.form.field.Base, {
  212. onRender: function() {
  213. if (this.labelEl) {
  214. this.updateAria(this.inputEl, {
  215. 'aria-labelledby': this.labelEl.id
  216. });
  217. }
  218. },
  219. onValidityChange: function(f, isValid) {
  220. this.updateAria(this.inputEl, {
  221. 'aria-invalid': !isValid,
  222. 'aria-describedby': isValid ? undefined : f.errorEl.id
  223. });
  224. }
  225. });
  226. Ext.aria.AriaController.processClass(Ext.form.field.Text, {
  227. role: 'textbox'
  228. });
  229. Ext.aria.AriaController.processClass(Ext.form.field.Number, {
  230. role: 'spinbutton',
  231. onRender: function() {
  232. var me = this,
  233. props = {};
  234. if (isFinite(me.minValue)) {
  235. props['aria-valuemin'] = me.minValue;
  236. }
  237. if (isFinite(me.maxValue)) {
  238. props['aria-valuemax'] = me.maxValue;
  239. }
  240. me.updateAria(me.inputEl, props);
  241. },
  242. onChange: function(f) {
  243. var v = this.getValue();
  244. this.updateAria(this.inputEl, {
  245. 'aria-valuenow': (this.isValid() && v !== null) ? v: undefined
  246. });
  247. }
  248. });
  249. // Picker fields are combobox type functionality
  250. Ext.aria.AriaController.processClass(Ext.form.field.Picker, function() {
  251. var listFns = {
  252. onListHighlight: function(node) {
  253. this.updateAria({
  254. 'aria-activedescendant': node.id
  255. });
  256. },
  257. onListUnhighlight: function(node) {
  258. var n = this.getPicker().getSelectedNodes();
  259. this.updateAria({
  260. 'aria-activedescendant': n.length ? n[0].id : undefined
  261. });
  262. },
  263. onListSelectionChange: function(sm, selected) {
  264. if (selected.length) {
  265. var n = this.getPicker().getSelectedNodes();
  266. this.updateAria({
  267. 'aria-activedescendant': n[0].id
  268. });
  269. } else {
  270. this.updateAria({
  271. 'aria-activedescendant': undefined
  272. });
  273. }
  274. }
  275. };
  276. return {
  277. role: 'combobox',
  278. onRender: function() {
  279. this.updateAria(this.inputEl, {
  280. 'aria-autocomplete': 'inline'
  281. });
  282. },
  283. onExpand: function() {
  284. var me = this,
  285. picker = me.getPicker();
  286. me.updateAria(me.inputEl, {
  287. 'aria-owns': picker.el.id,
  288. 'aria-expanded': true
  289. });
  290. // On first expand, add listeners to the "picker" list to maintain our ARIA state
  291. if (!me.pickerListenersAdded) {
  292. picker.mon(picker, {
  293. highlight: listFns.onListHighlight,
  294. unhighlight: listFns.onListUnhighlight,
  295. selectionchange: listFns.onListSelectionChange,
  296. scope: me
  297. });
  298. me.pickerListenersAdded = true;
  299. }
  300. },
  301. onCollapse: function() {
  302. var p = this.getPicker(),
  303. n = p.getSelectedNodes ? p.getSelectedNodes() : [null];
  304. this.updateAria({
  305. 'aria-expanded': false,
  306. 'aria-activedescendant': n.length ? n[0].id: undefined
  307. });
  308. }
  309. };
  310. }());
  311. Ext.aria.AriaController.processClass(Ext.view.BoundList, {
  312. role: 'listbox'
  313. });
  314. Ext.QuickTips.init();
  315. var bd = Ext.getBody();
  316. /*
  317. * ================ Simple form =======================
  318. */
  319. bd.createChild({tag: 'h2', html: 'Form 1 - Very Simple'});
  320. var simple = Ext.create('Ext.form.Panel', {
  321. url:'save-form.php',
  322. frame:true,
  323. title: 'Simple Form',
  324. bodyStyle:'padding:5px 5px 0',
  325. width: 350,
  326. fieldDefaults: {
  327. msgTarget: 'side',
  328. labelWidth: 75
  329. },
  330. defaultType: 'textfield',
  331. defaults: {
  332. anchor: '100%'
  333. },
  334. items: [{
  335. fieldLabel: 'First Name',
  336. name: 'first',
  337. allowBlank:false
  338. },{
  339. fieldLabel: 'Last Name',
  340. name: 'last'
  341. },{
  342. fieldLabel: 'Company',
  343. name: 'company'
  344. }, {
  345. fieldLabel: 'Email',
  346. name: 'email',
  347. vtype:'email'
  348. }, {
  349. fieldLabel: 'DOB',
  350. name: 'dob',
  351. xtype: 'datefield'
  352. }, {
  353. fieldLabel: 'Age',
  354. name: 'age',
  355. xtype: 'numberfield',
  356. minValue: 0,
  357. maxValue: 100
  358. }, {
  359. xtype: 'timefield',
  360. fieldLabel: 'Time',
  361. name: 'time',
  362. minValue: '8:00am',
  363. maxValue: '6:00pm'
  364. }],
  365. buttons: [{
  366. text: 'Save'
  367. },{
  368. text: 'Cancel'
  369. }]
  370. });
  371. simple.render(document.body);
  372. /*
  373. * ================ Form 2 =======================
  374. */
  375. bd.createChild({tag: 'h2', html: 'Form 2 - Adding fieldsets'});
  376. var fsf = Ext.create('Ext.form.Panel', {
  377. url:'save-form.php',
  378. frame:true,
  379. title: 'Simple Form with FieldSets',
  380. bodyStyle:'padding:5px 5px 0',
  381. width: 350,
  382. fieldDefaults: {
  383. msgTarget: 'side',
  384. labelWidth: 75
  385. },
  386. defaults: {
  387. anchor: '100%'
  388. },
  389. items: [{
  390. xtype:'fieldset',
  391. checkboxToggle:true,
  392. title: 'User Information',
  393. defaultType: 'textfield',
  394. collapsed: true,
  395. layout: 'anchor',
  396. defaults: {
  397. anchor: '100%'
  398. },
  399. items :[{
  400. fieldLabel: 'First Name',
  401. name: 'first',
  402. allowBlank:false
  403. },{
  404. fieldLabel: 'Last Name',
  405. name: 'last'
  406. },{
  407. fieldLabel: 'Company',
  408. name: 'company'
  409. }, {
  410. fieldLabel: 'Email',
  411. name: 'email',
  412. vtype:'email'
  413. }]
  414. },{
  415. xtype:'fieldset',
  416. title: 'Phone Number',
  417. collapsible: true,
  418. defaultType: 'textfield',
  419. layout: 'anchor',
  420. defaults: {
  421. anchor: '100%'
  422. },
  423. items :[{
  424. fieldLabel: 'Home',
  425. name: 'home',
  426. value: '(888) 555-1212'
  427. },{
  428. fieldLabel: 'Business',
  429. name: 'business'
  430. },{
  431. fieldLabel: 'Mobile',
  432. name: 'mobile'
  433. },{
  434. fieldLabel: 'Fax',
  435. name: 'fax'
  436. }]
  437. }],
  438. buttons: [{
  439. text: 'Save'
  440. },{
  441. text: 'Cancel'
  442. }]
  443. });
  444. fsf.render(document.body);
  445. /*
  446. * ================ Form 3 =======================
  447. */
  448. bd.createChild({tag: 'h2', html: 'Form 3 - A little more complex'});
  449. var top = Ext.create('Ext.form.Panel', {
  450. frame:true,
  451. title: 'Multi Column, Nested Layouts and Anchoring',
  452. bodyStyle:'padding:5px 5px 0',
  453. width: 600,
  454. fieldDefaults: {
  455. labelAlign: 'top',
  456. msgTarget: 'side'
  457. },
  458. items: [{
  459. xtype: 'container',
  460. anchor: '100%',
  461. layout:'column',
  462. items:[{
  463. xtype: 'container',
  464. columnWidth:.5,
  465. layout: 'anchor',
  466. items: [{
  467. xtype:'textfield',
  468. fieldLabel: 'First Name',
  469. name: 'first',
  470. anchor:'96%'
  471. }, {
  472. xtype:'textfield',
  473. fieldLabel: 'Company',
  474. name: 'company',
  475. anchor:'96%'
  476. }]
  477. },{
  478. xtype: 'container',
  479. columnWidth:.5,
  480. layout: 'anchor',
  481. items: [{
  482. xtype:'textfield',
  483. fieldLabel: 'Last Name',
  484. name: 'last',
  485. anchor:'100%'
  486. },{
  487. xtype:'textfield',
  488. fieldLabel: 'Email',
  489. name: 'email',
  490. vtype:'email',
  491. anchor:'100%'
  492. }]
  493. }]
  494. }, {
  495. xtype: 'htmleditor',
  496. name: 'bio',
  497. fieldLabel: 'Biography',
  498. height: 200,
  499. anchor: '100%'
  500. }],
  501. buttons: [{
  502. text: 'Save'
  503. },{
  504. text: 'Cancel'
  505. }]
  506. });
  507. top.render(document.body);
  508. /*
  509. * ================ Form 4 =======================
  510. */
  511. bd.createChild({tag: 'h2', html: 'Form 4 - Forms can be a TabPanel...'});
  512. var tabs = Ext.create('Ext.form.Panel', {
  513. width: 350,
  514. border: false,
  515. bodyBorder: false,
  516. fieldDefaults: {
  517. labelWidth: 75,
  518. msgTarget: 'side'
  519. },
  520. defaults: {
  521. anchor: '100%'
  522. },
  523. items: {
  524. xtype:'tabpanel',
  525. activeTab: 0,
  526. defaults:{
  527. bodyStyle:'padding:10px'
  528. },
  529. items:[{
  530. title:'Personal Details',
  531. defaultType: 'textfield',
  532. items: [{
  533. fieldLabel: 'First Name',
  534. name: 'first',
  535. allowBlank:false,
  536. value: 'Ed'
  537. },{
  538. fieldLabel: 'Last Name',
  539. name: 'last',
  540. value: 'Spencer'
  541. },{
  542. fieldLabel: 'Company',
  543. name: 'company',
  544. value: 'Ext JS'
  545. }, {
  546. fieldLabel: 'Email',
  547. name: 'email',
  548. vtype:'email'
  549. }]
  550. },{
  551. title:'Phone Numbers',
  552. defaultType: 'textfield',
  553. items: [{
  554. fieldLabel: 'Home',
  555. name: 'home',
  556. value: '(888) 555-1212'
  557. },{
  558. fieldLabel: 'Business',
  559. name: 'business'
  560. },{
  561. fieldLabel: 'Mobile',
  562. name: 'mobile'
  563. },{
  564. fieldLabel: 'Fax',
  565. name: 'fax'
  566. }]
  567. }]
  568. },
  569. buttons: [{
  570. text: 'Save'
  571. },{
  572. text: 'Cancel'
  573. }]
  574. });
  575. tabs.render(document.body);
  576. /*
  577. * ================ Form 5 =======================
  578. */
  579. bd.createChild({tag: 'h2', html: 'Form 5 - ... and forms can contain TabPanel(s)'});
  580. var tab2 = Ext.create('Ext.form.Panel', {
  581. title: 'Inner Tabs',
  582. bodyStyle:'padding:5px',
  583. width: 600,
  584. fieldDefaults: {
  585. labelAlign: 'top',
  586. msgTarget: 'side'
  587. },
  588. defaults: {
  589. anchor: '100%'
  590. },
  591. items: [{
  592. layout:'column',
  593. border:false,
  594. items:[{
  595. columnWidth:.5,
  596. border:false,
  597. layout: 'anchor',
  598. defaultType: 'textfield',
  599. items: [{
  600. fieldLabel: 'First Name',
  601. name: 'first',
  602. anchor:'95%'
  603. }, {
  604. fieldLabel: 'Company',
  605. name: 'company',
  606. anchor:'95%'
  607. }]
  608. },{
  609. columnWidth:.5,
  610. border:false,
  611. layout: 'anchor',
  612. defaultType: 'textfield',
  613. items: [{
  614. fieldLabel: 'Last Name',
  615. name: 'last',
  616. anchor:'95%'
  617. },{
  618. fieldLabel: 'Email',
  619. name: 'email',
  620. vtype:'email',
  621. anchor:'95%'
  622. }]
  623. }]
  624. },{
  625. xtype:'tabpanel',
  626. plain:true,
  627. activeTab: 0,
  628. height:235,
  629. defaults:{bodyStyle:'padding:10px'},
  630. items:[{
  631. title:'Personal Details',
  632. defaults: {width: 230},
  633. defaultType: 'textfield',
  634. items: [{
  635. fieldLabel: 'First Name',
  636. name: 'first',
  637. allowBlank:false,
  638. value: 'Jamie'
  639. },{
  640. fieldLabel: 'Last Name',
  641. name: 'last',
  642. value: 'Avins'
  643. },{
  644. fieldLabel: 'Company',
  645. name: 'company',
  646. value: 'Ext JS'
  647. }, {
  648. fieldLabel: 'Email',
  649. name: 'email',
  650. vtype:'email'
  651. }]
  652. },{
  653. title:'Phone Numbers',
  654. defaults: {width: 230},
  655. defaultType: 'textfield',
  656. items: [{
  657. fieldLabel: 'Home',
  658. name: 'home',
  659. value: '(888) 555-1212'
  660. },{
  661. fieldLabel: 'Business',
  662. name: 'business'
  663. },{
  664. fieldLabel: 'Mobile',
  665. name: 'mobile'
  666. },{
  667. fieldLabel: 'Fax',
  668. name: 'fax'
  669. }]
  670. },{
  671. cls: 'x-plain',
  672. title: 'Biography',
  673. layout: 'fit',
  674. items: {
  675. xtype: 'htmleditor',
  676. name: 'bio2',
  677. fieldLabel: 'Biography'
  678. }
  679. }]
  680. }],
  681. buttons: [{
  682. text: 'Save'
  683. },{
  684. text: 'Cancel'
  685. }]
  686. });
  687. tab2.render(document.body);
  688. });
  689. /**
  690. * @class Ext.picker.Date
  691. * @extends Ext.Component
  692. * <p>A date picker. This class is used by the {@link Ext.form.field.Date} field to allow browsing and
  693. * selection of valid dates in a popup next to the field, but may also be used with other components.</p>
  694. * <p>Typically you will need to implement a handler function to be notified when the user chooses a color from the
  695. * picker; you can register the handler using the {@link #select} event, or by implementing the {@link #handler}
  696. * method.</p>
  697. * <p>By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate},
  698. * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs.</p>
  699. * <p>All the string values documented below may be overridden by including an Ext locale file in your page.</p>
  700. * <p>Example usage:</p>
  701. * <pre><code>new Ext.panel.Panel({
  702. title: 'Choose a future date:',
  703. width: 200,
  704. bodyPadding: 10,
  705. renderTo: Ext.getBody(),
  706. items: [{
  707. xtype: 'datepicker',
  708. minDate: new Date(),
  709. handler: function(picker, date) {
  710. // do something with the selected date
  711. }
  712. }]
  713. });</code></pre>
  714. * {@img Ext.picker.Date/Ext.picker.Date.png Ext.picker.Date component}
  715. *
  716. */
  717. Ext.define('Ext.picker.NewDate', {
  718. extend: 'Ext.Container',
  719. requires: [
  720. 'Ext.data.Model',
  721. 'Ext.view.BoundList',
  722. 'Ext.XTemplate',
  723. 'Ext.button.Button',
  724. 'Ext.button.Split',
  725. 'Ext.util.ClickRepeater',
  726. 'Ext.util.KeyNav',
  727. 'Ext.EventObject',
  728. 'Ext.fx.Manager',
  729. 'Ext.picker.Month'
  730. ],
  731. alias: 'widget.newdatepicker',
  732. alternateClassName: 'Ext.DatePicker',
  733. renderTpl: [
  734. '<div class="{cls}" id="{id}" role="grid" title="{ariaTitle} {value:this.longDay}">',
  735. '<div role="presentation" class="{baseCls}-header">',
  736. '<div class="{baseCls}-prev"><a id="{id}-prevEl" href="#" role="button" title="{prevText}"></a></div>',
  737. '<div class="{baseCls}-month" id="{id}-middleBtnEl"></div>',
  738. '<div class="{baseCls}-next"><a id="{id}-nextEl" href="#" role="button" title="{nextText}"></a></div>',
  739. '</div>',
  740. '<div id="{id}-calendarContainer"></div>',
  741. '<tpl if="showToday">',
  742. '<div id="{id}-footerEl" role="presentation" class="{baseCls}-footer"></div>',
  743. '</tpl>',
  744. '</div>',
  745. {
  746. longDay: function(value){
  747. return Ext.Date.format(value, this.longDayFormat);
  748. }
  749. }
  750. ],
  751. calendarTpl: [
  752. '<table class="{baseCls}-inner" cellspacing="0" role="presentation">' +
  753. '<thead role="presentation"><tr role="presentation">' +
  754. '<tpl for="dayNames">' +
  755. '<th role="columnheader" title="{.}"><span>{.:this.firstInitial}</span></th>' +
  756. '</tpl>' +
  757. '</tr></thead>' +
  758. '<tbody role="presentation"><tr role="presentation">' +
  759. '<tpl for="days">' +
  760. '{#:this.isEndOfWeek}' +
  761. '<td role="gridcell" id="{[Ext.id()]}" class="{[this.getCellClass(values, parent)]}" title="{date:this.titleFormat}">' +
  762. '<a role="presentation" href="#" hidefocus="on" class="{parent.baseCls}-date" tabIndex="1">' +
  763. '<em role="presentation"><span role="presentation">{.:date("j")</span></em>' +
  764. '</a>' +
  765. '</td>' +
  766. '</tpl>' +
  767. '</tr></tbody>' +
  768. '</table>', {
  769. cellClass: function(value, data) {
  770. },
  771. titleFormat: function(value) {
  772. return Ext.date.format(value, this.longDayFormat);
  773. },
  774. firstInitial: function(value) {
  775. return value.substr(0,1);
  776. },
  777. isEndOfWeek: function(value) {
  778. // convert from 1 based index to 0 based
  779. // by decrementing value once.
  780. value--;
  781. var end = value % 7 === 0 && value !== 0;
  782. return end ? '</tr><tr role="row">' : '';
  783. }
  784. }],
  785. ariaTitle: 'Date Picker',
  786. /**
  787. * @cfg {String} todayText
  788. * The text to display on the button that selects the current date (defaults to <code>'Today'</code>)
  789. */
  790. todayText : 'Today',
  791. /**
  792. * @cfg {Function} handler
  793. * Optional. A function that will handle the select event of this picker.
  794. * The handler is passed the following parameters:<div class="mdetail-params"><ul>
  795. * <li><code>picker</code> : Ext.picker.Date <div class="sub-desc">This Date picker.</div></li>
  796. * <li><code>date</code> : Date <div class="sub-desc">The selected date.</div></li>
  797. * </ul></div>
  798. */
  799. /**
  800. * @cfg {Object} scope
  801. * The scope (<code><b>this</b></code> reference) in which the <code>{@link #handler}</code>
  802. * function will be called. Defaults to this DatePicker instance.
  803. */
  804. /**
  805. * @cfg {String} todayTip
  806. * A string used to format the message for displaying in a tooltip over the button that
  807. * selects the current date. Defaults to <code>'{0} (Spacebar)'</code> where
  808. * the <code>{0}</code> token is replaced by today's date.
  809. */
  810. todayTip : '{0} (Spacebar)',
  811. /**
  812. * @cfg {String} minText
  813. * The error text to display if the minDate validation fails (defaults to <code>'This date is before the minimum date'</code>)
  814. */
  815. minText : 'This date is before the minimum date',
  816. /**
  817. * @cfg {String} maxText
  818. * The error text to display if the maxDate validation fails (defaults to <code>'This date is after the maximum date'</code>)
  819. */
  820. maxText : 'This date is after the maximum date',
  821. /**
  822. * @cfg {String} format
  823. * The default date format string which can be overriden for localization support. The format must be
  824. * valid according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}).
  825. */
  826. /**
  827. * @cfg {String} disabledDaysText
  828. * The tooltip to display when the date falls on a disabled day (defaults to <code>'Disabled'</code>)
  829. */
  830. disabledDaysText : 'Disabled',
  831. /**
  832. * @cfg {String} disabledDatesText
  833. * The tooltip text to display when the date falls on a disabled date (defaults to <code>'Disabled'</code>)
  834. */
  835. disabledDatesText : 'Disabled',
  836. /**
  837. * @cfg {Array} monthNames
  838. * An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames)
  839. */
  840. /**
  841. * @cfg {Array} dayNames
  842. * An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames)
  843. */
  844. /**
  845. * @cfg {String} nextText
  846. * The next month navigation button tooltip (defaults to <code>'Next Month (Control+Right)'</code>)
  847. */
  848. nextText : 'Next Month (Control+Right)',
  849. /**
  850. * @cfg {String} prevText
  851. * The previous month navigation button tooltip (defaults to <code>'Previous Month (Control+Left)'</code>)
  852. */
  853. prevText : 'Previous Month (Control+Left)',
  854. /**
  855. * @cfg {String} monthYearText
  856. * The header month selector tooltip (defaults to <code>'Choose a month (Control+Up/Down to move years)'</code>)
  857. */
  858. monthYearText : 'Choose a month (Control+Up/Down to move years)',
  859. /**
  860. * @cfg {Number} startDay
  861. * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
  862. */
  863. startDay : 0,
  864. /**
  865. * @cfg {Boolean} showToday
  866. * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar
  867. * that selects the current date (defaults to <code>true</code>).
  868. */
  869. showToday : true,
  870. /**
  871. * @cfg {Date} minDate
  872. * Minimum allowable date (JavaScript date object, defaults to null)
  873. */
  874. /**
  875. * @cfg {Date} maxDate
  876. * Maximum allowable date (JavaScript date object, defaults to null)
  877. */
  878. /**
  879. * @cfg {Array} disabledDays
  880. * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
  881. */
  882. /**
  883. * @cfg {RegExp} disabledDatesRE
  884. * JavaScript regular expression used to disable a pattern of dates (defaults to null). The {@link #disabledDates}
  885. * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
  886. * disabledDates value.
  887. */
  888. /**
  889. * @cfg {Array} disabledDates
  890. * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular
  891. * expression so they are very powerful. Some examples:
  892. * <ul>
  893. * <li>['03/08/2003', '09/16/2003'] would disable those exact dates</li>
  894. * <li>['03/08', '09/16'] would disable those days for every year</li>
  895. * <li>['^03/08'] would only match the beginning (useful if you are using short years)</li>
  896. * <li>['03/../2006'] would disable every day in March 2006</li>
  897. * <li>['^03'] would disable every day in every March</li>
  898. * </ul>
  899. * Note that the format of the dates included in the array should exactly match the {@link #format} config.
  900. * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to
  901. * escape the dot when restricting dates. For example: ['03\\.08\\.03'].
  902. */
  903. /**
  904. * @cfg {Boolean} disableAnim True to disable animations when showing the month picker. Defaults to <tt>false</tt>.
  905. */
  906. disableAnim: false,
  907. /**
  908. * @cfg {String} baseCls
  909. * The base CSS class to apply to this components element (defaults to <tt>'x-datepicker'</tt>).
  910. */
  911. baseCls: Ext.baseCSSPrefix + 'datepicker',
  912. /**
  913. * @cfg {String} selectedCls
  914. * The class to apply to the selected cell. Defaults to <tt>'x-datepicker-selected'</tt>
  915. */
  916. /**
  917. * @cfg {String} disabledCellCls
  918. * The class to apply to disabled cells. Defaults to <tt>'x-datepicker-disabled'</tt>
  919. */
  920. /**
  921. * @cfg {String} longDayFormat
  922. * The format for displaying a date in a longer format. Defaults to <tt>'F d, Y'</tt>
  923. */
  924. longDayFormat: 'F d, Y',
  925. /**
  926. * @cfg {Object} keyNavConfig Specifies optional custom key event handlers for the {@link Ext.util.KeyNav}
  927. * attached to this date picker. Must conform to the config format recognized by the {@link Ext.util.KeyNav}
  928. * constructor. Handlers specified in this object will replace default handlers of the same name.
  929. */
  930. /**
  931. * @cfg {Boolean} focusOnShow
  932. * True to automatically focus the picker on show. Defaults to <tt>false</tt>.
  933. */
  934. focusOnShow: false,
  935. // private
  936. // Set by other components to stop the picker focus being updated when the value changes.
  937. focusOnSelect: true,
  938. width: 178,
  939. // default value used to initialise each date in the DatePicker
  940. // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
  941. initHour: 12, // 24-hour format
  942. numDays: 42,
  943. // private, inherit docs
  944. initComponent : function() {
  945. var me = this,
  946. clearTime = Ext.Date.clearTime;
  947. me.selectedCls = me.baseCls + '-selected';
  948. me.disabledCellCls = me.baseCls + '-disabled';
  949. me.prevCls = me.baseCls + '-prevday';
  950. me.activeCls = me.baseCls + '-active';
  951. me.nextCls = me.baseCls + '-prevday';
  952. me.todayCls = me.baseCls + '-today';
  953. me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay));
  954. this.callParent();
  955. me.value = me.value ?
  956. clearTime(me.value, true) : clearTime(new Date());
  957. me.addEvents(
  958. /**
  959. * @event select
  960. * Fires when a date is selected
  961. * @param {DatePicker} this DatePicker
  962. * @param {Date} date The selected date
  963. */
  964. 'select'
  965. );
  966. me.initDisabledDays();
  967. },
  968. // private, inherit docs
  969. onRender : function(container, position){
  970. /*
  971. * days array for looping through 6 full weeks (6 weeks * 7 days)
  972. * Note that we explicitly force the size here so the template creates
  973. * all the appropriate cells.
  974. */
  975. var me = this,
  976. days = new Array(me.numDays),
  977. today = Ext.Date.format(new Date(), me.format),
  978. calendarTpl = Ext.create('Ext.XTemplate', me.calendarTpl);
  979. Ext.apply(me.renderData, {
  980. dayNames: me.dayNames,
  981. ariaTitle: me.ariaTitle,
  982. value: me.value,
  983. showToday: me.showToday,
  984. prevText: me.prevText,
  985. nextText: me.nextText,
  986. days: days
  987. });
  988. me.getTpl('renderTpl').longDayFormat = me.longDayFormat;
  989. me.addChildEls('calendarContainer', 'prevEl', 'nextEl', 'middleBtnEl', 'footerEl');
  990. this.callParent(arguments);
  991. me.el.unselectable();
  992. // The calendar is a BoundList with aen empty Store.
  993. // collectData returns the fields.
  994. calendarTpl.longDayFormat = me.longDayFormat;
  995. me.calendarView = Ext.create('Ext.view.BoundList', {
  996. store: new Ext.data.Store({fields:[]}),
  997. collectData: function() {
  998. return me.collectData();
  999. },
  1000. renderTo: me.calendarContainer,
  1001. tpl: calendarTpl,
  1002. itemSelector: 'td',
  1003. selectedItemCls: me.baseCls + '-selected'
  1004. });
  1005. me.monthBtn = Ext.create('Ext.button.Split', {
  1006. ownerCt: me,
  1007. text: '',
  1008. tooltip: me.monthYearText,
  1009. renderTo: me.middleBtnEl
  1010. });
  1011. //~ me.middleBtnEl.down('button').addCls(Ext.baseCSSPrefix + 'btn-arrow');
  1012. me.todayBtn = Ext.create('Ext.button.Button', {
  1013. renderTo: me.footerEl,
  1014. text: Ext.String.format(me.todayText, today),
  1015. tooltip: Ext.String.format(me.todayTip, today),
  1016. handler: me.selectToday,
  1017. scope: me
  1018. });
  1019. },
  1020. // Collect a data object for use by the calendar View
  1021. collectData : function() {
  1022. var me = this;
  1023. return {
  1024. today: new Date(),
  1025. daynames: me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay)),
  1026. days: Ext.AbstractView.prototype.apply(me, arguments)
  1027. };
  1028. },
  1029. // private, inherit docs
  1030. initEvents: function(){
  1031. var me = this;
  1032. this.callParent();
  1033. me.prevRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
  1034. handler: me.showPrevMonth,
  1035. scope: me,
  1036. preventDefault: true,
  1037. stopDefault: true
  1038. });
  1039. me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
  1040. handler: me.showNextMonth,
  1041. scope: me,
  1042. preventDefault:true,
  1043. stopDefault:true
  1044. });
  1045. me.keyNav = Ext.create('Ext.util.KeyNav', me.calendarView.el, Ext.apply({
  1046. scope: me,
  1047. 'left' : function(e){
  1048. if(e.ctrlKey){
  1049. me.showPrevMonth();
  1050. }
  1051. },
  1052. 'right' : function(e){
  1053. if(e.ctrlKey){
  1054. me.showNextMonth();
  1055. }
  1056. },
  1057. 'up' : function(e){
  1058. if(e.ctrlKey){
  1059. me.showNextYear();
  1060. }
  1061. },
  1062. 'down' : function(e){
  1063. if(e.ctrlKey){
  1064. me.showPrevYear();
  1065. }
  1066. },
  1067. 'pageUp' : me.showNextMonth,
  1068. 'pageDown' : me.showPrevMonth,
  1069. 'enter' : function(e){
  1070. e.stopPropagation();
  1071. return true;
  1072. }
  1073. }, me.keyNavConfig));
  1074. if(me.showToday){
  1075. me.todayKeyListener = me.calendarView.el.addKeyListener(Ext.EventObject.SPACE, me.selectToday, me);
  1076. }
  1077. me.mon(me.calendarView.el, 'mousewheel', me.handleMouseWheel, me);
  1078. me.mon(me.monthBtn, 'click', me.showMonthPicker, me);
  1079. me.mon(me.monthBtn, 'arrowclick', me.showMonthPicker, me);
  1080. me.update(me.value);
  1081. },
  1082. /**
  1083. * Setup the disabled dates regex based on config options
  1084. * @private
  1085. */
  1086. initDisabledDays : function(){
  1087. var me = this,
  1088. dd = me.disabledDates,
  1089. re = '(?:',
  1090. len;
  1091. if(!me.disabledDatesRE && dd){
  1092. len = dd.length - 1;
  1093. Ext.each(dd, function(d, i){
  1094. re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(d, me.format)) + '$' : dd[i];
  1095. if(i != len){
  1096. re += '|';
  1097. }
  1098. }, me);
  1099. me.disabledDatesRE = new RegExp(re + ')');
  1100. }
  1101. },
  1102. /**
  1103. * Replaces any existing disabled dates with new values and refreshes the DatePicker.
  1104. * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config
  1105. * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
  1106. * @return {Ext.picker.Date} this
  1107. */
  1108. setDisabledDates : function(dd){
  1109. var me = this;
  1110. if(Ext.isArray(dd)){
  1111. me.disabledDates = dd;
  1112. me.disabledDatesRE = null;
  1113. }else{
  1114. me.disabledDatesRE = dd;
  1115. }
  1116. me.initDisabledDays();
  1117. me.update(me.value, true);
  1118. return me;
  1119. },
  1120. /**
  1121. * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
  1122. * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config
  1123. * for details on supported values.
  1124. * @return {Ext.picker.Date} this
  1125. */
  1126. setDisabledDays : function(dd){
  1127. this.disabledDays = dd;
  1128. return this.update(this.value, true);
  1129. },
  1130. /**
  1131. * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
  1132. * @param {Date} value The minimum date that can be selected
  1133. * @return {Ext.picker.Date} this
  1134. */
  1135. setMinDate : function(dt){
  1136. this.minDate = dt;
  1137. return this.update(this.value, true);
  1138. },
  1139. /**
  1140. * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
  1141. * @param {Date} value The maximum date that can be selected
  1142. * @return {Ext.picker.Date} this
  1143. */
  1144. setMaxDate : function(dt){
  1145. this.maxDate = dt;
  1146. return this.update(this.value, true);
  1147. },
  1148. /**
  1149. * Sets the value of the date field
  1150. * @param {Date} value The date to set
  1151. * @return {Ext.picker.Date} this
  1152. */
  1153. setValue : function(value){
  1154. this.value = Ext.Date.clearTime(value, true);
  1155. return this.update(this.value);
  1156. },
  1157. /**
  1158. * Gets the current selected value of the date field
  1159. * @return {Date} The selected date
  1160. */
  1161. getValue : function(){
  1162. return this.value;
  1163. },
  1164. // private
  1165. focus : function(){
  1166. this.update(this.activeDate);
  1167. },
  1168. // private, inherit docs
  1169. onEnable: function(){
  1170. this.callParent();
  1171. this.setDisabledStatus(false);
  1172. this.update(this.activeDate);
  1173. },
  1174. // private, inherit docs
  1175. onDisable : function(){
  1176. this.callParent();
  1177. this.setDisabledStatus(true);
  1178. },
  1179. /**
  1180. * Set the disabled state of various internal components
  1181. * @private
  1182. * @param {Boolean} disabled
  1183. */
  1184. setDisabledStatus : function(disabled){
  1185. var me = this;
  1186. me.keyNav.setDisabled(disabled);
  1187. me.prevRepeater.setDisabled(disabled);
  1188. me.nextRepeater.setDisabled(disabled);
  1189. if (me.showToday) {
  1190. me.todayKeyListener.setDisabled(disabled);
  1191. me.todayBtn.setDisabled(disabled);
  1192. }
  1193. },
  1194. /**
  1195. * Get the current active date.
  1196. * @private
  1197. * @return {Date} The active date
  1198. */
  1199. getActive: function(){
  1200. return this.activeDate || this.value;
  1201. },
  1202. /**
  1203. * Run any animation required to hide/show the month picker.
  1204. * @private
  1205. * @param {Boolean} isHide True if it's a hide operation
  1206. */
  1207. runAnimation: function(isHide){
  1208. var options = {
  1209. duration: 200
  1210. };
  1211. if (isHide) {
  1212. this.monthPicker.el.slideOut('t', options);
  1213. } else {
  1214. this.monthPicker.el.slideIn('t', options);
  1215. }
  1216. },
  1217. /**
  1218. * Hides the month picker, if it's visible.
  1219. * @return {Ext.picker.Date} this
  1220. */
  1221. hideMonthPicker : function(){
  1222. var me = this,
  1223. picker = me.monthPicker;
  1224. if (picker) {
  1225. if (me.disableAnim) {
  1226. picker.hide();
  1227. } else {
  1228. this.runAnimation(true);
  1229. }
  1230. }
  1231. return me;
  1232. },
  1233. /**
  1234. * Show the month picker
  1235. * @return {Ext.picker.Date} this
  1236. */
  1237. showMonthPicker : function(){
  1238. var me = this,
  1239. picker;
  1240. if (me.rendered && !me.disabled) {
  1241. picker = me.createMonthPicker();
  1242. picker.setValue(me.getActive());
  1243. picker.setSize(me.getSize());
  1244. picker.setPosition(-1, -1);
  1245. if (me.disableAnim) {
  1246. picker.show();
  1247. } else {
  1248. me.runAnimation(false);
  1249. }
  1250. }
  1251. return me;
  1252. },
  1253. /**
  1254. * Create the month picker instance
  1255. * @private
  1256. * @return {Ext.picker.Month} picker
  1257. */
  1258. createMonthPicker: function(){
  1259. var me = this,
  1260. picker = me.monthPicker;
  1261. if (!picker) {
  1262. me.monthPicker = picker = Ext.create('Ext.picker.Month', {
  1263. renderTo: me.el,
  1264. floating: true,
  1265. shadow: false,
  1266. small: me.showToday === false,
  1267. listeners: {
  1268. scope: me,
  1269. cancelclick: me.onCancelClick,
  1270. okclick: me.onOkClick,
  1271. yeardblclick: me.onOkClick,
  1272. monthdblclick: me.onOkClick
  1273. }
  1274. });
  1275. if (!me.disableAnim) {
  1276. // hide the element if we're animating to prevent an initial flicker
  1277. picker.el.setStyle('display', 'none');
  1278. }
  1279. me.on('beforehide', me.hideMonthPicker, me);
  1280. }
  1281. return picker;
  1282. },
  1283. /**
  1284. * Respond to an ok click on the month picker
  1285. * @private
  1286. */
  1287. onOkClick: function(picker, value){
  1288. var me = this,
  1289. month = value[0],
  1290. year = value[1],
  1291. date = new Date(year, month, me.getActive().getDate());
  1292. if (date.getMonth() !== month) {
  1293. // 'fix' the JS rolling date conversion if needed
  1294. date = new Date(year, month, 1).getLastDateOfMonth();
  1295. }
  1296. me.update(date);
  1297. me.hideMonthPicker();
  1298. },
  1299. /**
  1300. * Respond to a cancel click on the month picker
  1301. * @private
  1302. */
  1303. onCancelClick: function(){
  1304. this.hideMonthPicker();
  1305. },
  1306. /**
  1307. * Show the previous month.
  1308. * @return {Ext.picker.Date} this
  1309. */
  1310. showPrevMonth : function(e){
  1311. return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1));
  1312. },
  1313. /**
  1314. * Show the next month.
  1315. * @return {Ext.picker.Date} this
  1316. */
  1317. showNextMonth : function(e){
  1318. return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1));
  1319. },
  1320. /**
  1321. * Show the previous year.
  1322. * @return {Ext.picker.Date} this
  1323. */
  1324. showPrevYear : function(){
  1325. this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1));
  1326. },
  1327. /**
  1328. * Show the next year.
  1329. * @return {Ext.picker.Date} this
  1330. */
  1331. showNextYear : function(){
  1332. this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1));
  1333. },
  1334. /**
  1335. * Respond to the mouse wheel event
  1336. * @private
  1337. * @param {Ext.EventObject} e
  1338. */
  1339. handleMouseWheel : function(e){
  1340. e.stopEvent();
  1341. if(!this.disabled){
  1342. var delta = e.getWheelDelta();
  1343. if(delta > 0){
  1344. this.showPrevMonth();
  1345. } else if(delta < 0){
  1346. this.showNextMonth();
  1347. }
  1348. }
  1349. },
  1350. /**
  1351. * Respond to a date being clicked in the picker
  1352. * @private
  1353. * @param {Ext.EventObject} e
  1354. * @param {HTMLElement} t
  1355. */
  1356. handleDateClick : function(e, t){
  1357. var me = this,
  1358. handler = me.handler;
  1359. e.stopEvent();
  1360. if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){
  1361. me.cancelFocus = me.focusOnSelect === false;
  1362. me.setValue(new Date(t.dateValue));
  1363. delete me.cancelFocus;
  1364. me.fireEvent('select', me, me.value);
  1365. if (handler) {
  1366. handler.call(me.scope || me, me, me.value);
  1367. }
  1368. // event handling is turned off on hide
  1369. // when we are using the picker in a field
  1370. // therefore onSelect comes AFTER the select
  1371. // event.
  1372. me.onSelect();
  1373. }
  1374. },
  1375. /**
  1376. * Perform any post-select actions
  1377. * @private
  1378. */
  1379. onSelect: function() {
  1380. if (this.hideOnSelect) {
  1381. this.hide();
  1382. }
  1383. },
  1384. /**
  1385. * Sets the current value to today.
  1386. * @return {Ext.picker.Date} this
  1387. */
  1388. selectToday : function(){
  1389. var me = this,
  1390. btn = me.todayBtn,
  1391. handler = me.handler;
  1392. if(btn && !btn.disabled){
  1393. me.setValue(Ext.Date.clearTime(new Date()));
  1394. me.fireEvent('select', me, me.value);
  1395. if (handler) {
  1396. handler.call(me.scope || me, me, me.value);
  1397. }
  1398. me.onSelect();
  1399. }
  1400. return me;
  1401. },
  1402. /**
  1403. * Update the selected cell
  1404. * @private
  1405. * @param {Date} date The new date
  1406. * @param {Date} active The active date
  1407. */
  1408. selectedUpdate: function(date, active){
  1409. var me = this,
  1410. t = date.getTime(),
  1411. cells = me.cells,
  1412. cls = me.selectedCls;
  1413. cells.removeCls(cls);
  1414. cells.each(function(c){
  1415. if (c.dom.firstChild.dateValue == t) {
  1416. me.el.dom.setAttribute('aria-activedescendent', c.dom.id);
  1417. c.addCls(cls);
  1418. if(me.isVisible() && !me.cancelFocus){
  1419. Ext.fly(c.dom.firstChild).focus(50);
  1420. }
  1421. return false;
  1422. }
  1423. }, this);
  1424. },
  1425. /**
  1426. * Update the contents of the picker for a new month
  1427. * @private
  1428. * @param {Date} date The new date
  1429. * @param {Date} active The active date
  1430. */
  1431. fullUpdate: function(date, active){
  1432. var me = this,
  1433. cells = me.cells.elements,
  1434. textNodes = me.textNodes,
  1435. disabledCls = me.disabledCellCls,
  1436. eDate = Ext.Date,
  1437. i = 0,
  1438. extraDays = 0,
  1439. visible = me.isVisible(),
  1440. sel = +eDate.clearTime(date, true),
  1441. today = +eDate.clearTime(new Date()),
  1442. min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY,
  1443. max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY,
  1444. ddMatch = me.disabledDatesRE,
  1445. ddText = me.disabledDatesText,
  1446. ddays = me.disabledDays ? me.disabledDays.join('') : false,
  1447. ddaysText = me.disabledDaysText,
  1448. format = me.format,
  1449. days = eDate.getDaysInMonth(date),
  1450. firstOfMonth = eDate.getFirstDateOfMonth(date),
  1451. startingPos = firstOfMonth.getDay() - me.startDay,
  1452. previousMonth = eDate.add(date, eDate.MONTH, -1),
  1453. longDayFormat = me.longDayFormat,
  1454. prevStart,
  1455. current,
  1456. disableToday,
  1457. tempDate,
  1458. setCellClass,
  1459. html,
  1460. cls,
  1461. formatValue,
  1462. value;
  1463. if (startingPos < 0) {
  1464. startingPos += 7;
  1465. }
  1466. days += startingPos;
  1467. prevStart = eDate.getDaysInMonth(previousMonth) - startingPos;
  1468. current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour);
  1469. if (me.showToday) {
  1470. tempDate = eDate.clearTime(new Date());
  1471. disableToday = (tempDate < min || tempDate > max ||
  1472. (ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) ||
  1473. (ddays && ddays.indexOf(tempDate.getDay()) != -1));
  1474. if (!me.disabled) {
  1475. me.todayBtn.setDisabled(disableToday);
  1476. me.todayKeyListener.setDisabled(disableToday);
  1477. }
  1478. }
  1479. setCellClass = function(cell){
  1480. value = +eDate.clearTime(current, true);
  1481. cell.title = eDate.format(current, longDayFormat);
  1482. // store dateValue number as an expando
  1483. cell.firstChild.dateValue = value;
  1484. if(value == today){
  1485. cell.className += ' ' + me.todayCls;
  1486. cell.title = me.todayText;
  1487. }
  1488. if(value == sel){
  1489. cell.className += ' ' + me.selectedCls;
  1490. me.el.dom.setAttribute('aria-activedescendant', cell.id);
  1491. if (visible && me.floating) {
  1492. Ext.fly(cell.firstChild).focus(50);
  1493. }
  1494. }
  1495. // disabling
  1496. if(value < min) {
  1497. cell.className = disabledCls;
  1498. cell.title = me.minText;
  1499. return;
  1500. }
  1501. if(value > max) {
  1502. cell.className = disabledCls;
  1503. cell.title = me.maxText;
  1504. return;
  1505. }
  1506. if(ddays){
  1507. if(ddays.indexOf(current.getDay()) != -1){
  1508. cell.title = ddaysText;
  1509. cell.className = disabledCls;
  1510. }
  1511. }
  1512. if(ddMatch && format){
  1513. formatValue = eDate.dateFormat(current, format);
  1514. if(ddMatch.test(formatValue)){
  1515. cell.title = ddText.replace('%0', formatValue);
  1516. cell.className = disabledCls;
  1517. }
  1518. }
  1519. };
  1520. for(; i < me.numDays; ++i) {
  1521. if (i < startingPos) {
  1522. html = (++prevStart);
  1523. cls = me.prevCls;
  1524. } else if (i >= days) {
  1525. html = (++extraDays);
  1526. cls = me.nextCls;
  1527. } else {
  1528. html = i - startingPos + 1;
  1529. cls = me.activeCls;
  1530. }
  1531. textNodes[i].innerHTML = html;
  1532. cells[i].className = cls;
  1533. current.setDate(current.getDate() + 1);
  1534. setCellClass(cells[i]);
  1535. }
  1536. me.monthBtn.setText(me.monthNames[date.getMonth()] + ' ' + date.getFullYear());
  1537. },
  1538. /**
  1539. * Update the contents of the picker
  1540. * @private
  1541. * @param {Date} date The new date
  1542. * @param {Boolean} forceRefresh True to force a full refresh
  1543. */
  1544. update : function(date, forceRefresh){
  1545. var me = this,
  1546. active = me.activeDate;
  1547. if (me.rendered) {
  1548. me.activeDate = date;
  1549. if(!forceRefresh && active && me.el && active.getMonth() == date.getMonth() && active.getFullYear() == date.getFullYear()){
  1550. me.selectedUpdate(date, active);
  1551. } else {
  1552. me.fullUpdate(date, active);
  1553. }
  1554. }
  1555. return me;
  1556. },
  1557. // private, inherit docs
  1558. beforeDestroy : function() {
  1559. var me = this;
  1560. if (me.rendered) {
  1561. Ext.destroy(
  1562. me.todayKeyListener,
  1563. me.keyNav,
  1564. me.monthPicker,
  1565. me.monthBtn,
  1566. me.nextRepeater,
  1567. me.prevRepeater,
  1568. me.todayBtn
  1569. );
  1570. delete me.textNodes;
  1571. delete me.cells.elements;
  1572. }
  1573. },
  1574. // private, inherit docs
  1575. onShow: function() {
  1576. this.callParent(arguments);
  1577. if (this.focusOnShow) {
  1578. this.focus();
  1579. }
  1580. }
  1581. },
  1582. // After dependencies have loaded:
  1583. function() {
  1584. var proto = this.prototype;
  1585. proto.monthNames = Ext.Date.monthNames;
  1586. proto.dayNames = Ext.Date.dayNames;
  1587. proto.format = Ext.Date.defaultFormat;
  1588. });