form-dateselector MDL-23096 Converted the date selector to a YUI-Moodle module
[moodle.git] / lib / form / yui / dateselector / dateselector.js
1 YUI.add('moodle-form-dateselector', function(Y) {
3     /**
4      * Add some custom methods to the node class to make our lives a little
5      * easier within this module.
6      */
7     Y.mix(Y.Node.prototype, {
8         /**
9          * Gets the value of the first option in the select box
10          */
11         firstOptionValue : function() {
12             if (this.get('nodeName').toLowerCase() != 'select') {
13                 return false;
14             }
15             return this.one('option').get('value');
16         },
17         /**
18          * Gets the value of the last option in the select box
19          */
20         lastOptionValue : function() {
21             if (this.get('nodeName').toLowerCase() != 'select') {
22                 return false;
23             }
24             return this.all('option').item(this.optionSize()-1).get('value');
25         },
26         /**
27          * Gets the number of options in the select box
28          */
29         optionSize : function() {
30             if (this.get('nodeName').toLowerCase() != 'select') {
31                 return false;
32             }
33             return parseInt(this.all('option').size());
34         },
35         /**
36          * Gets the value of the selected option in the select box
37          */
38         selectedOptionValue : function() {
39             if (this.get('nodeName').toLowerCase() != 'select') {
40                 return false;
41             }
42             return this.all('option').item(this.get('selectedIndex')).get('value');
43         }
44     });
46     /**
47      * Override the default inDoc method as it is broken in IE
48      *
49      * YUI Bug: http://yuilibrary.com/projects/yui3/ticket/2529157
50      * Example location: lib/yui/3.1.1/build/dom/dom-debug.js
51      */
52     Y.DOM.inDoc = function(element, doc) {
53         // there may be multiple elements with the same ID
54         doc = doc || element['ownerDocument'];
55         var nodes = [],
56             ret = false,
57             i,
58             node;
60         if (!element.getAttribute('id')) {
61             element.setAttribute('id', Y.guid());
62         }
63         nodes = Y.DOM.allById(element.id, doc);
64         for (i = 0; node = nodes[i++];) { // check for a match
65             if (node === element) {
66                 ret = true;
67                 break;
68             }
69         }
70         return ret;
71     }
73     /**
74      * Calendar class
75      *
76      * This is our main class
77      */
78     var CALENDAR = function(config) {
79         CALENDAR.superclass.constructor.apply(this, arguments);
80     }
81     CALENDAR.prototype = {
82         panel : null,
83         yearselect : null,
84         yearselectchange : null,
85         monthselect : null,
86         monthselectchange : null,
87         dayselect : null,
88         dayselectchange : null,
89         enablecheckbox : null,
90         initializer : function(config) {
91             var controls = this.get('node').all('select');
92             controls.each(function(node){
93                 if (node.get('name').match(/\[year]/)) {
94                     this.yearselect = node;
95                 } else if (node.get('name').match(/\[month\]/)) {
96                     this.monthselect = node;
97                 } else if (node.get('name').match(/\[day]/)) {
98                     this.dayselect = node;
99                 } else {
100                     node.on('focus', this.cancel_any_timeout, this);
101                     node.on('blur', this.blur_event, this);
102                     return;
103                 }
104                 node.maskDiv = Y.Node.create('<div class="dateselector-select-mask"></div>');
105                 node.maskDiv.on('click', this.focus_event, this);
106                 node.ancestor().insert(
107                     Y.Node.create('<div class="dateselector-select-mask-point"></div>').append(
108                         node.maskDiv.setStyles({
109                             width : node.get('offsetWidth')+'px',
110                             height : node.get('offsetHeight')+'px',
111                             opacity : 0
112                         })), node);
113             }, this);
115             if (this.yearselect && this.monthselect && this.dayselect) {
116                 this.enablecheckbox = this.get('node').one('input');
117                 if (this.enablecheckbox) {
118                     this.enablecheckbox.on('focus', this.focus_event, this);
119                     this.enablecheckbox.on('change', this.focus_event, this);
120                     this.enablecheckbox.on('blur', this.blur_event, this);
121                 }
122             }
123         },
124         focus_event : function(e) {
125             M.form.dateselector.cancel_any_timeout();
126             if (this.enablecheckbox == null || this.enablecheckbox.get('checked')) {
127                 this.claim_calendar();
128             } else {
129                 if (M.form.dateselector.currentowner) {
130                     M.form.dateselector.currentowner.release_calendar();
131                 }
132             }
133         },
134         blur_event : function(e) {
135             M.form.dateselector.hidetimeout = setTimeout(M.form.dateselector.release_current, 300);
136         },
137         handle_select_change : function(e) {
138             this.set_date_from_selects();
139         },
140         claim_calendar : function() {
141             M.form.dateselector.cancel_any_timeout();
142             if (M.form.dateselector.currentowner == this) {
143                 return;
144             }
145             if (M.form.dateselector.currentowner) {
146                 M.form.dateselector.currentowner.release_calendar();
147             }
149             if (M.form.dateselector.currentowner != this) {
150                 this.connect_handlers();
151                 this.set_date_from_selects();
152             }
153             M.form.dateselector.currentowner = this;
154             M.form.dateselector.calendar.cfg.setProperty('mindate', new Date(this.yearselect.firstOptionValue(), 0, 1));
155             M.form.dateselector.calendar.cfg.setProperty('maxdate', new Date(this.yearselect.lastOptionValue(), 11, 31));
156             M.form.dateselector.panel.set('constrain', this.get('node').ancestor('form'));
157             M.form.dateselector.panel.show();
158             M.form.dateselector.fix_position();
159             setTimeout(function(){M.form.dateselector.cancel_any_timeout()}, 100);
160         },
161         set_date_from_selects : function() {
162             var year = parseInt(this.yearselect.get('value'));
163             var month = parseInt(this.monthselect.get('value')) - 1;
164             var day = parseInt(this.dayselect.get('value'));
165             M.form.dateselector.calendar.select(new Date(year, month, day));
166             M.form.dateselector.calendar.setMonth(month);
167             M.form.dateselector.calendar.setYear(year);
168             M.form.dateselector.calendar.render();
169         },
170         set_selects_from_date : function(eventtype, args) {
171             var date = args[0][0];
172             var newyear = date[0];
173             var newindex = newyear - this.yearselect.firstOptionValue();
174             this.yearselect.set('selectedIndex', newindex);
175             this.monthselect.set('selectedIndex', date[1] - this.monthselect.firstOptionValue());
176             this.dayselect.set('selectedIndex', date[2] - this.dayselect.firstOptionValue());
177         },
178         connect_handlers : function() {
179             M.form.dateselector.calendar.selectEvent.subscribe(this.set_selects_from_date, this, true);
180         },
181         release_calendar : function() {
182             M.form.dateselector.panel.hide();
183             M.form.dateselector.currentowner = null;
184             M.form.dateselector.calendar.selectEvent.unsubscribe(this.set_selects_from_date, this);
185         }
186     }
187     Y.extend(CALENDAR, Y.Base, CALENDAR.prototype, {
188         NAME : 'Date Selector',
189         ATTRS : {
190             firstdayofweek  : {
191                 validator : Y.Lang.isString
192             },
193             node : {
194                 setter : function(node) {
195                     return Y.one(node);
196                 }
197             }
198         }
199     });
201     M.form = M.form || {};
202     M.form.dateselector = {
203         panel : null,
204         calendar : null,
205         currentowner : null,
206         hidetimeout : null,
207         repositiontimeout : null,
208         init_date_selectors : function(config) {
209             if (this.panel === null) {
210                 this.initPanel(config);
211             }
212             Y.all('fieldset.fdate_time_selector').each(function(){
213                 config.node = this;
214                 new CALENDAR(config);
215             });
216             Y.all('fieldset.fdate_selector').each(function(){
217                 config.node = this;
218                 new CALENDAR(config);
219             });
220         },
221         initPanel : function(config) {
222             this.panel = new Y.Overlay({
223                 visible : false,
224                 constrain : true,
225                 bodyContent : Y.Node.create('<div id="dateselector-calendar-content"></div>'),
226                 id : 'dateselector-calendar-panel'
227             });
228             this.panel.render(document.body);
229             this.panel.on('heightChange', this.fix_position, this);
231             Y.one('#dateselector-calendar-panel').on('click', function(e){e.halt();});
232             Y.one(document.body).on('click', this.document_click, this);
234             this.calendar = new YAHOO.widget.Calendar(document.getElementById('dateselector-calendar-content'), {
235                 iframe: false,
236                 hide_blank_weeks: true,
237                 start_weekday: config.firstdayofweek
238             });
239             this.calendar.changePageEvent.subscribe(function(){
240                 this.fix_position();
241             }, this);
242         },
243         cancel_any_timeout : function() {
244             if (this.hidetimeout) {
245                 clearTimeout(this.hidetimeout);
246                 this.hidetimeout = null;
247             }
248             if (this.repositiontimeout) {
249                 clearTimeout(this.repositiontimeout);
250                 this.repositiontimeout = null;
251             }
252         },
253         delayed_reposition : function() {
254             if (this.repositiontimeout) {
255                 clearTimeout(this.repositiontimeout);
256                 this.repositiontimeout = null;
257             }
258             this.repositiontimeout = setTimeout(this.fix_position, 500);
259         },
260         fix_position : function() {
261             if (this.currentowner) {
262                 this.panel.set('constrain', this.currentowner.get('node').ancestor('form'));
263                 this.panel.set('align', {
264                     node:this.currentowner.get('node').one('select'),
265                     points:[Y.WidgetPositionAlign.BL, Y.WidgetPositionAlign.TL]
266                 });
267             }
268         },
269         release_current : function() {
270             if (this.currentowner) {
271                 this.currentowner.release_calendar();
272             }
273         },
274         document_click : function(e) {
275             if (this.currentowner) {
276                 if (this.currentowner.get('node').ancestor('div').contains(e.target)) {
277                     setTimeout(function() {M.form.dateselector.cancel_any_timeout()}, 100);
278                 } else {
279                     this.currentowner.release_calendar();
280                 }
281             }
282         }
283     }
284     
285 }, '@VERSION@', {requires:['base','node','overlay', 'yui2-calendar', 'moodle-form-dateselector-skin']});