From 8e7cebb0d85a2d5e5b583e73da5a23e8d568062f Mon Sep 17 00:00:00 2001 From: tjhunt Date: Fri, 13 Mar 2009 09:56:53 +0000 Subject: [PATCH] formslib dates: MDL-16592 show a pop-up calendar for picking dates. Not quite finished. There is a small issue with keyboard focus. See bug. --- lib/form/dateselector.php | 1 + lib/form/datetimeselector.php | 1 + lib/formslib.php | 13 +++ lib/javascript-static.js | 191 ++++++++++++++++++++++++++++++- theme/standard/styles_layout.css | 19 ++- 5 files changed, 219 insertions(+), 6 deletions(-) diff --git a/lib/form/dateselector.php b/lib/form/dateselector.php index 799f991536..9aa9e455b7 100644 --- a/lib/form/dateselector.php +++ b/lib/form/dateselector.php @@ -179,6 +179,7 @@ class MoodleQuickForm_date_selector extends MoodleQuickForm_group function toHtml() { + form_init_date_js(); include_once('HTML/QuickForm/Renderer/Default.php'); $renderer =& new HTML_QuickForm_Renderer_Default(); $renderer->setElementTemplate('{element}'); diff --git a/lib/form/datetimeselector.php b/lib/form/datetimeselector.php index e5f4abe385..e9d480fedf 100644 --- a/lib/form/datetimeselector.php +++ b/lib/form/datetimeselector.php @@ -197,6 +197,7 @@ class MoodleQuickForm_date_time_selector extends MoodleQuickForm_group{ function toHtml() { + form_init_date_js(); include_once('HTML/QuickForm/Renderer/Default.php'); $renderer =& new HTML_QuickForm_Renderer_Default(); $renderer->setElementTemplate('{element}'); diff --git a/lib/formslib.php b/lib/formslib.php index 1e7f9d2c30..731e776800 100644 --- a/lib/formslib.php +++ b/lib/formslib.php @@ -43,6 +43,19 @@ if ($CFG->debug >= DEBUG_ALL){ PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'pear_handle_error'); } +function form_init_date_js() { + global $CFG; + static $done = false; + if (!$done) { + echo ''; + require_js(array('yui_yahoo', 'yui_dom', 'yui_event', 'yui_calendar', 'yui_container')); + print_delayed_js_call(1, 'init_date_selectors', array(get_string('firstdayofweek'))); + $done = true; + } +} /** * Moodle specific wrapper that separates quickforms syntax from moodle code. You won't directly diff --git a/lib/javascript-static.js b/lib/javascript-static.js index efb68c660f..de6996cf13 100644 --- a/lib/javascript-static.js +++ b/lib/javascript-static.js @@ -332,7 +332,194 @@ function unmaskPassword(id) { pw.parentNode.replaceChild(newpw, pw); } -/* +/** + * Search a Moodle form to find all the fdate_time_selector and fdate_selector + * elements, and add date_selector_calendar instance to each. + */ +function init_date_selectors(firstdayofweek) { + YAHOO.util.Dom.addClass(document.body, 'yui-skin-sam'); + var els = YAHOO.util.Dom.getElementsByClassName('fdate_time_selector', 'fieldset'); + for (var i = 0; i < els.length; i++) { + new date_selector_calendar(els[i], firstdayofweek); + } + els = YAHOO.util.Dom.getElementsByClassName('fdate_selector', 'fieldset'); + for (i = 0; i < els.length; i++) { + new date_selector_calendar(els[i], firstdayofweek); + } +} + +/** + * Constructor for a JavaScritp object that connects to a fdate_time_selector + * or a fdate_selector in a Moodle form, and shows a popup calendar whenever + * that element has keyboard focus. + * @param el the fieldset class="fdate_time_selector" or "fdate_selector". + */ +function date_selector_calendar(el, firstdayofweek) { + // Ensure that the shared div and calendar exist. + if (!date_selector_calendar.panel) { + date_selector_calendar.panel = new YAHOO.widget.Panel('date_selector_calendar_panel', + {visible: false, close: false, draggable: false}); + var div = document.createElement('div'); + date_selector_calendar.panel.setBody(div); + date_selector_calendar.panel.render(document.body); + + YAHOO.util.Event.addListener(document, 'click', date_selector_calendar.document_click); + date_selector_calendar.panel.showEvent.subscribe(function() { + date_selector_calendar.panel.fireEvent('changeContent'); + }); + + date_selector_calendar.calendar = new YAHOO.widget.Calendar(div, + {iframe: false, hide_blank_weeks: true, start_weekday: firstdayofweek}); + date_selector_calendar.calendar.renderEvent.subscribe(function() { + date_selector_calendar.panel.fireEvent('changeContent'); + }); + + // TODO, find a way to hide the calendar when shift-tabbing. +// date_selector_calendar.calendar.render(); +// var prev_month_link = YAHOO.util.Dom.getElementsByClassName('calnavleft', 'a', div)[0]; +// YAHOO.util.Event.addBlurListener(prev_month_link, this.blur_event, this); + } + + this.fieldset = el; + var controls = el.getElementsByTagName('select'); + for (var i = 0; i < controls.length; i++) { + YAHOO.util.Event.addFocusListener(controls[i], this.focus_event, this); + YAHOO.util.Event.addBlurListener(controls[i], this.blur_event, this); + if (/\[year\]$/.test(controls[i].name)) { + this.yearselect = controls[i]; + } + if (/\[month\]$/.test(controls[i].name)) { + this.monthselect = controls[i]; + } + if (/\[day\]$/.test(controls[i].name)) { + this.dayselect = controls[i]; + } + } + if (!(this.yearselect && this.monthselect && this.dayselect)) { + throw 'Failed to initialise calendar.'; + } + + this.enablecheckbox = el.getElementsByTagName('input')[0]; + if (this.enablecheckbox) { + YAHOO.util.Event.addFocusListener(this.enablecheckbox, this.focus_event, this); + YAHOO.util.Event.addListener(this.enablecheckbox, 'change', this.focus_event, this); + YAHOO.util.Event.addBlurListener(this.enablecheckbox, this.blur_event, this); + } +} + +/** The pop-up calendar that contains the calendar. */ +date_selector_calendar.panel = null; + +/** The shared YAHOO.widget.Calendar used by all date_selector_calendars. */ +date_selector_calendar.calendar = null; + +/** The date_selector_calendar that currently owns the shared stuff. */ +date_selector_calendar.currentowner = null; + +/** Used as a timeout when hiding the calendar on blur - so we don't hide the calendar + * if we are just jumping from on of our controls to another. */ +date_selector_calendar.hidetimeout = null; + + +date_selector_calendar.prototype.fieldset = null; +date_selector_calendar.prototype.yearselect = null; +date_selector_calendar.prototype.monthselect = null; +date_selector_calendar.prototype.dayselect = null; +date_selector_calendar.prototype.enablecheckbox = null; + +date_selector_calendar.prototype.cancel_any_timeout = function() { + if (date_selector_calendar.hidetimeout) { + clearTimeout(date_selector_calendar.hidetimeout); + date_selector_calendar.hidetimeout = null; + } +} + +date_selector_calendar.prototype.focus_event = function(e, me) { + me.cancel_any_timeout(); + if (me.enablecheckbox == null || me.enablecheckbox.checked) { + me.claim_calendar(); + } else { + if (date_selector_calendar.currentowner) { + date_selector_calendar.currentowner.release_calendar(); + } + } +} + +date_selector_calendar.prototype.blur_event = function(e, me) { + date_selector_calendar.hidetimeout = setTimeout(function() {me.release_calendar()}, 200); +} + +date_selector_calendar.prototype.handle_select_change = function(e, me) { + me.set_date_from_selects(); +} + +date_selector_calendar.document_click = function(event) { + if (date_selector_calendar.currentowner) { + var currentcontainer = date_selector_calendar.currentowner.fieldset; + var eventarget = YAHOO.util.Event.getTarget(event); + if (!YAHOO.util.Dom.isAncestor(currentcontainer, eventarget)) { + date_selector_calendar.currentowner.release_calendar(); + } + } +} + +date_selector_calendar.prototype.claim_calendar = function() { + this.cancel_any_timeout(); + if (date_selector_calendar.currentowner == this) { + return; + } + if (date_selector_calendar.currentowner) { + date_selector_calendar.currentowner.release_calendar(); + } + + date_selector_calendar.calendar.cfg.setProperty('mindate', new Date(this.yearselect.options[0].value, 0, 1)); + date_selector_calendar.calendar.cfg.setProperty('maxdate', new Date(this.yearselect.options[this.yearselect.options.length - 1].value, 11, 31)); + this.fieldset.insertBefore(date_selector_calendar.panel.element, this.fieldset.firstChild); + this.set_date_from_selects(); + date_selector_calendar.panel.show(); + var me = this; + setTimeout(function() {me.cancel_any_timeout()}, 100); + + if (date_selector_calendar.currentowner != this) { + this.connect_handlers(); + } + + date_selector_calendar.currentowner = this; +} + +date_selector_calendar.prototype.set_date_from_selects = function() { + var year = parseInt(this.yearselect.value); + var month = parseInt(this.monthselect.value) - 1; + var day = parseInt(this.dayselect.value); + date_selector_calendar.calendar.select(new Date(year, month, day)); + date_selector_calendar.calendar.setMonth(month); + date_selector_calendar.calendar.setYear(year); + date_selector_calendar.calendar.render(); + date_selector_calendar.panel.cfg.setProperty('context', [this.fieldset, 'bl', 'tl']); +} + +date_selector_calendar.prototype.set_selects_from_date = function(eventtype, args) { + var date = args[0][0]; + var newyear = date[0]; + var newindex = newyear - this.yearselect.options[0].value; + this.yearselect.selectedIndex = newindex; + this.monthselect.selectedIndex = date[1] - this.monthselect.options[0].value; + this.dayselect.selectedIndex = date[2] - this.dayselect.options[0].value; +} + +date_selector_calendar.prototype.connect_handlers = function() { + YAHOO.util.Event.addListener([this.yearselect, this.monthselect, this.dayselect], 'change', this.handle_select_change, this); + date_selector_calendar.calendar.selectEvent.subscribe(this.set_selects_from_date, this, true); +} + +date_selector_calendar.prototype.release_calendar = function() { + date_selector_calendar.panel.hide(); + date_selector_calendar.currentowner = null; + YAHOO.util.Event.removeListener([this.yearselect, this.monthselect, this.dayselect], this.handle_select_change); + date_selector_calendar.calendar.selectEvent.unsubscribe(this.set_selects_from_date, this); +} + +/** elementToggleHide (element, elementFinder) If elementFinder is not provided, toggles the "hidden" class for the specified element. @@ -747,4 +934,4 @@ function close_window_reloading_opener() { close_window(); // Intentionally, only try to close the window if there is some evidence we are in a popup. } -} \ No newline at end of file +} diff --git a/theme/standard/styles_layout.css b/theme/standard/styles_layout.css index 306943a7ec..d145d57f2b 100644 --- a/theme/standard/styles_layout.css +++ b/theme/standard/styles_layout.css @@ -655,6 +655,19 @@ fieldset.fdate_selector label { width: auto; } +#date_selector_calendar_panel .bd { + padding: 0; +} + +#date_selector_calendar_panel .yui-calcontainer { + border: none; + float: none; +} +/* Prevent border-collapse:collapse from bleeding through in IE6, IE7 */ +#date_selector_calendar_panel.yui-overlay-hidden table { + *display:none; +} + .mform div.felement, .mform fieldset.felement{ display: block; float: left; @@ -4333,7 +4346,6 @@ body#mod-forum-search .introcontent { text-align: left; margin-top: 1.5em; } -#mod-quiz-summary #quiz-timer, #mod-quiz-summary .submitbtns { margin-top: 1.5em; } @@ -4345,11 +4357,10 @@ body#mod-forum-search .introcontent { height: 16px; vertical-align: middle; } -#mod-quiz-summary #quiz-timer, #mod-quiz-attempt #quiz-timer { - display: none; + display: none; } -#quiz-timer #quiz-time-left { +#mod-quiz-attempt #quiz-time-left { font-weight: bold; } -- 2.39.5