UNPKG

fuelux

Version:

Base Fuel UX styles and controls

698 lines (576 loc) 22.8 kB
/* * Fuel UX Scheduler * https://github.com/ExactTarget/fuelux * * Copyright (c) 2014 ExactTarget * Licensed under the BSD New license. */ // -- BEGIN UMD WRAPPER PREFACE -- // For more information on UMD visit: // https://github.com/umdjs/umd/blob/master/jqueryPlugin.js (function (factory) { if (typeof define === 'function' && define.amd) { // if AMD loader is available, register as an anonymous module. define(['jquery', 'fuelux/combobox', 'fuelux/datepicker', 'fuelux/radio', 'fuelux/selectlist', 'fuelux/spinbox'], factory); } else if (typeof exports === 'object') { // Node/CommonJS module.exports = factory(require('jquery'), require('./combobox'), require('./datepicker'), require('./radio'), require('./selectlist'), require('./spinbox') ); } else { // OR use browser globals if AMD is not present factory(jQuery); } }(function ($) { if (!$.fn.combobox || !$.fn.datepicker || !$.fn.radio || !$.fn.selectlist || !$.fn.spinbox) { throw new Error('Fuel UX scheduler control requires combobox, datepicker, radio, selectlist, and spinbox.'); } // -- END UMD WRAPPER PREFACE -- // -- BEGIN MODULE CODE HERE -- var old = $.fn.scheduler; // SCHEDULER CONSTRUCTOR AND PROTOTYPE var Scheduler = function (element, options) { var self = this; this.$element = $(element); this.options = $.extend({}, $.fn.scheduler.defaults, options); // cache elements this.$startDate = this.$element.find('.start-datetime .start-date'); this.$startTime = this.$element.find('.start-datetime .start-time'); this.$timeZone = this.$element.find('.timezone-container .timezone'); this.$repeatIntervalPanel = this.$element.find('.repeat-every-panel'); this.$repeatIntervalSelect = this.$element.find('.repeat-options'); this.$repeatIntervalSpinbox = this.$element.find('.repeat-every'); this.$repeatIntervalTxt = this.$element.find('.repeat-every-text'); this.$end = this.$element.find('.repeat-end'); this.$endSelect = this.$end.find('.end-options'); this.$endAfter = this.$end.find('.end-after'); this.$endDate = this.$end.find('.end-on-date'); // panels this.$recurrencePanels = this.$element.find('.repeat-panel'); this.$repeatIntervalSelect.selectlist(); //initialize sub-controls this.$element.find('.selectlist').selectlist(); this.$startDate.datepicker(this.options.startDateOptions); this.$startTime.combobox(); // init start time if (this.$startTime.find('input').val() === '') { this.$startTime.combobox('selectByIndex', 0); } // every 0 days/hours doesn't make sense, change if not set if (this.$repeatIntervalSpinbox.find('input').val() === '0') { this.$repeatIntervalSpinbox.spinbox({ 'value': 1, 'min': 1 }); } else { this.$repeatIntervalSpinbox.spinbox({ 'min': 1 }); } this.$endAfter.spinbox({ 'value': 1, 'min': 1 }); this.$endDate.datepicker(this.options.endDateOptions); this.$element.find('.radio-custom').radio(); // bind events: 'change' is a Bootstrap JS fired event this.$repeatIntervalSelect.on('changed.fu.selectlist', $.proxy(this.repeatIntervalSelectChanged, this)); this.$endSelect.on('changed.fu.selectlist', $.proxy(this.endSelectChanged, this)); this.$element.find('.repeat-days-of-the-week .btn-group .btn').on('change.fu.scheduler', function (e, data) { self.changed(e, data, true); }); this.$element.find('.combobox').on('changed.fu.combobox', $.proxy(this.changed, this)); this.$element.find('.datepicker').on('changed.fu.datepicker', $.proxy(this.changed, this)); this.$element.find('.datepicker').on('dateClicked.fu.datepicker', $.proxy(this.changed, this)); this.$element.find('.selectlist').on('changed.fu.selectlist', $.proxy(this.changed, this)); this.$element.find('.spinbox').on('changed.fu.spinbox', $.proxy(this.changed, this)); this.$element.find('.repeat-monthly .radio-custom, .repeat-yearly .radio-custom').on('change.fu.scheduler', $.proxy(this.changed, this)); }; Scheduler.prototype = { constructor: Scheduler, destroy: function () { var markup; // set input value attribute this.$element.find('input').each(function () { $(this).attr('value', $(this).val()); }); // empty elements to return to original markup and store this.$element.find('.datepicker .calendar').empty(); markup = this.$element[0].outerHTML; // destroy components this.$element.find('.combobox').combobox('destroy'); this.$element.find('.datepicker').datepicker('destroy'); this.$element.find('.selectlist').selectlist('destroy'); this.$element.find('.spinbox').spinbox('destroy'); this.$element.find('.radio-custom').radio('destroy'); this.$element.remove(); // any external bindings // [none] return markup; }, changed: function (e, data, propagate) { if (!propagate) { e.stopPropagation(); } this.$element.trigger('changed.fu.scheduler', { data: (data !== undefined) ? data : $(e.currentTarget).data(), originalEvent: e, value: this.getValue() }); }, disable: function () { this.toggleState('disable'); }, enable: function () { this.toggleState('enable'); }, setUtcTime: function (d, t, offset) { var date = d.split('-'); var time = t.split(':'); function z(n) { return (n < 10 ? '0' : '') + n; } var utcDate = new Date(Date.UTC(date[0], (date[1] - 1), date[2], time[0], time[1], (time[2] ? time[2] : 0))); if (offset === 'Z') { utcDate.setUTCHours(utcDate.getUTCHours() + 0); } else { var re1 = '(.)';// Any Single Character 1 var re2 = '.*?';// Non-greedy match on filler var re3 = '\\d';// Uninteresting: d var re4 = '.*?';// Non-greedy match on filler var re5 = '(\\d)';// Any Single Digit 1 var p = new RegExp(re1 + re2 + re3 + re4 + re5, ["i"]); var m = p.exec(offset); if (m !== null) { var c1 = m[1]; var d1 = m[2]; var modifier = (c1 === '+') ? 1 : -1; utcDate.setUTCHours(utcDate.getUTCHours() + (modifier * parseInt(d1, 10))); } } var localDifference = utcDate.getTimezoneOffset(); utcDate.setMinutes(localDifference); return utcDate; }, // called when the end range changes // (Never, After, On date) endSelectChanged: function (e, data) { var selectedItem, val; if (!data) { selectedItem = this.$endSelect.selectlist('selectedItem'); val = selectedItem.value; } else { val = data.value; } // hide all panels this.$endAfter.parent().addClass('hidden'); this.$endAfter.parent().attr('aria-hidden', 'true'); this.$endDate.parent().addClass('hidden'); this.$endDate.parent().attr('aria-hidden', 'true'); if (val === 'after') { this.$endAfter.parent().removeClass('hide hidden'); // hide is deprecated this.$endAfter.parent().attr('aria-hidden', 'false'); } else if (val === 'date') { this.$endDate.parent().removeClass('hide hidden'); // hide is deprecated this.$endDate.parent().attr('aria-hidden', 'false'); } }, getValue: function () { // FREQ = frequency (secondly, minutely, hourly, daily, weekdays, weekly, monthly, yearly) // BYDAY = when picking days (MO,TU,WE,etc) // BYMONTH = when picking months (Jan,Feb,March) - note the values should be 1,2,3... // BYMONTHDAY = when picking days of the month (1,2,3...) // BYSETPOS = when picking First,Second,Third,Fourth,Last (1,2,3,4,-1) var interval = this.$repeatIntervalSpinbox.spinbox('value'); var pattern = ''; var repeat = this.$repeatIntervalSelect.selectlist('selectedItem').value; var startTime; if (this.$startTime.combobox('selectedItem').value) { startTime = this.$startTime.combobox('selectedItem').value; startTime = startTime.toLowerCase(); } else { startTime = this.$startTime.combobox('selectedItem').text.toLowerCase(); } var timeZone = this.$timeZone.selectlist('selectedItem'); var getFormattedDate; getFormattedDate = function (dateObj, dash) { var fdate = ''; var item; fdate += dateObj.getFullYear(); fdate += dash; item = dateObj.getMonth() + 1;//because 0 indexing makes sense when dealing with months /sarcasm fdate += (item < 10) ? '0' + item : item; fdate += dash; item = dateObj.getDate(); fdate += (item < 10) ? '0' + item : item; return fdate; }; var day, days, hasAm, hasPm, month, pos, startDateTime, type; startDateTime = '' + getFormattedDate(this.$startDate.datepicker('getDate'), '-'); startDateTime += 'T'; hasAm = (startTime.search('am') >= 0); hasPm = (startTime.search('pm') >= 0); startTime = $.trim(startTime.replace(/am/g, '').replace(/pm/g, '')).split(':'); startTime[0] = parseInt(startTime[0], 10); startTime[1] = parseInt(startTime[1], 10); if (hasAm && startTime[0] > 11) { startTime[0] = 0; } else if (hasPm && startTime[0] < 12) { startTime[0] += 12; } startDateTime += (startTime[0] < 10) ? '0' + startTime[0] : startTime[0]; startDateTime += ':'; startDateTime += (startTime[1] < 10) ? '0' + startTime[1] : startTime[1]; startDateTime += (timeZone.offset === '+00:00') ? 'Z' : timeZone.offset; if (repeat === 'none') { pattern = 'FREQ=DAILY;INTERVAL=1;COUNT=1;'; } else if (repeat === 'secondly') { pattern = 'FREQ=SECONDLY;'; pattern += 'INTERVAL=' + interval + ';'; } else if (repeat === 'minutely') { pattern = 'FREQ=MINUTELY;'; pattern += 'INTERVAL=' + interval + ';'; } else if (repeat === 'hourly') { pattern = 'FREQ=HOURLY;'; pattern += 'INTERVAL=' + interval + ';'; } else if (repeat === 'daily') { pattern += 'FREQ=DAILY;'; pattern += 'INTERVAL=' + interval + ';'; } else if (repeat === 'weekdays') { pattern += 'FREQ=DAILY;'; pattern += 'BYDAY=MO,TU,WE,TH,FR;'; pattern += 'INTERVAL=1;'; } else if (repeat === 'weekly') { days = []; this.$element.find('.repeat-days-of-the-week .btn-group input:checked').each(function () { days.push($(this).data().value); }); pattern += 'FREQ=WEEKLY;'; pattern += 'BYDAY=' + days.join(',') + ';'; pattern += 'INTERVAL=' + interval + ';'; } else if (repeat === 'monthly') { pattern += 'FREQ=MONTHLY;'; pattern += 'INTERVAL=' + interval + ';'; type = this.$element.find('input[name=repeat-monthly]:checked').val(); if (type === 'bymonthday') { day = parseInt(this.$element.find('.repeat-monthly-date .selectlist').selectlist('selectedItem').text, 10); pattern += 'BYMONTHDAY=' + day + ';'; } else if (type === 'bysetpos') { days = this.$element.find('.repeat-monthly-day .month-days').selectlist('selectedItem').value; pos = this.$element.find('.repeat-monthly-day .month-day-pos').selectlist('selectedItem').value; pattern += 'BYDAY=' + days + ';'; pattern += 'BYSETPOS=' + pos + ';'; } } else if (repeat === 'yearly') { pattern += 'FREQ=YEARLY;'; type = this.$element.find('input[name=repeat-yearly]:checked').val(); if (type === 'bymonthday') { // there are multiple .year-month classed elements in scheduler markup month = this.$element.find('.repeat-yearly-date .year-month').selectlist('selectedItem').value; day = this.$element.find('.repeat-yearly-date .year-month-day').selectlist('selectedItem').text; pattern += 'BYMONTH=' + month + ';'; pattern += 'BYMONTHDAY=' + day + ';'; } else if (type === 'bysetpos') { days = this.$element.find('.repeat-yearly-day .year-month-days').selectlist('selectedItem').value; pos = this.$element.find('.repeat-yearly-day .year-month-day-pos').selectlist('selectedItem').value; // there are multiple .year-month classed elements in scheduler markup month = this.$element.find('.repeat-yearly-day .year-month').selectlist('selectedItem').value; pattern += 'BYDAY=' + days + ';'; pattern += 'BYSETPOS=' + pos + ';'; pattern += 'BYMONTH=' + month + ';'; } } var end = this.$endSelect.selectlist('selectedItem').value; var duration = ''; // if both UNTIL and COUNT are not specified, the recurrence will repeat forever // http://tools.ietf.org/html/rfc2445#section-4.3.10 if (repeat !== 'none') { if (end === 'after') { duration = 'COUNT=' + this.$endAfter.spinbox('value') + ';'; } else if (end === 'date') { duration = 'UNTIL=' + getFormattedDate(this.$endDate.datepicker('getDate'), '') + ';'; } } pattern += duration; // remove trailing semicolon pattern = pattern.substring(pattern.length - 1) === ';' ? pattern.substring(0, pattern.length - 1) : pattern; var data = { startDateTime: startDateTime, timeZone: timeZone, recurrencePattern: pattern }; return data; }, // called when the repeat interval changes // (None, Hourly, Daily, Weekdays, Weekly, Monthly, Yearly repeatIntervalSelectChanged: function (e, data) { var selectedItem, val, txt; if (!data) { selectedItem = this.$repeatIntervalSelect.selectlist('selectedItem'); val = selectedItem.value || ""; txt = selectedItem.text || ""; } else { val = data.value; txt = data.text; } // set the text this.$repeatIntervalTxt.text(txt); switch (val.toLowerCase()) { case 'hourly': case 'daily': case 'weekly': case 'monthly': this.$repeatIntervalPanel.removeClass('hide hidden'); // hide is deprecated this.$repeatIntervalPanel.attr('aria-hidden', 'false'); break; default: this.$repeatIntervalPanel.addClass('hidden'); // hide is deprecated this.$repeatIntervalPanel.attr('aria-hidden', 'true'); break; } // hide all panels this.$recurrencePanels.addClass('hidden'); this.$recurrencePanels.attr('aria-hidden', 'true'); // show panel for current selection this.$element.find('.repeat-' + val).removeClass('hide hidden'); // hide is deprecated this.$element.find('.repeat-' + val).attr('aria-hidden', 'false'); // the end selection should only be shown when // the repeat interval is not "None (run once)" if (val === 'none') { this.$end.addClass('hidden'); this.$end.attr('aria-hidden', 'true'); } else { this.$end.removeClass('hide hidden'); // hide is deprecated this.$end.attr('aria-hidden', 'false'); } }, setValue: function (options) { var hours, i, item, l, minutes, period, recur, temp, startDate, startTime, timeOffset; if (options.startDateTime) { temp = options.startDateTime.split('T'); startDate = temp[0]; if (temp[1]) { temp[1] = temp[1].split(':'); hours = parseInt(temp[1][0], 10); minutes = (temp[1][1]) ? parseInt(temp[1][1].split('+')[0].split('-')[0].split('Z')[0], 10) : 0; period = (hours < 12) ? 'AM' : 'PM'; if (hours === 0) { hours = 12; } else if (hours > 12) { hours -= 12; } minutes = (minutes < 10) ? '0' + minutes : minutes; startTime = hours + ':' + minutes; temp = hours + ':' + minutes + ' ' + period; this.$startTime.find('input').val(temp); this.$startTime.combobox('selectByText', temp); } else { startTime = '00:00'; } } else { startTime = '00:00'; var currentDate = this.$startDate.datepicker('getDate'); startDate = currentDate.getFullYear() + '-' + currentDate.getMonth() + '-' + currentDate.getDate(); } // create jQuery selection string for timezone object // based on data-attributes and pass to selectlist item = 'li'; if (options.timeZone) { if (typeof (options.timeZone) === 'string') { item += '[data-name="' + options.timeZone + '"]'; } else { $.each(options.timeZone, function(key, value) { item += '[data-' + key + '="' + value + '"]'; }); } timeOffset = options.timeZone.offset; this.$timeZone.selectlist('selectBySelector', item); } else if (options.startDateTime) { temp = options.startDateTime.split('T')[1]; if (temp) { if (temp.search(/\+/) > -1) { temp = '+' + $.trim(temp.split('+')[1]); } else if (temp.search(/\-/) > -1) { temp = '-' + $.trim(temp.split('-')[1]); } else { temp = '+00:00'; } } else { temp = '+00:00'; } timeOffset = (temp === '+00:00') ? 'Z' : temp; item += '[data-offset="' + temp + '"]'; this.$timeZone.selectlist('selectBySelector', item); } else { timeOffset = 'Z'; } if (options.recurrencePattern) { recur = {}; temp = options.recurrencePattern.toUpperCase().split(';'); for (i = 0, l = temp.length; i < l; i++) { if (temp[i] !== '') { item = temp[i].split('='); recur[item[0]] = item[1]; } } if (recur.FREQ === 'DAILY') { if (recur.BYDAY === 'MO,TU,WE,TH,FR') { item = 'weekdays'; } else { if (recur.INTERVAL === '1' && recur.COUNT === '1') { item = 'none'; } else { item = 'daily'; } } } else if (recur.FREQ === 'SECONDLY') { item = 'secondly'; } else if (recur.FREQ === 'MINUTELY') { item = 'minutely'; } else if (recur.FREQ === 'HOURLY') { item = 'hourly'; } else if (recur.FREQ === 'WEEKLY') { if (recur.BYDAY) { item = this.$element.find('.repeat-days-of-the-week .btn-group'); item.find('label').removeClass('active'); temp = recur.BYDAY.split(','); for (i = 0, l = temp.length; i < l; i++) { item.find('input[data-value="' + temp[i] + '"]').prop('checked',true).parent().addClass('active'); } } item = 'weekly'; } else if (recur.FREQ === 'MONTHLY') { this.$element.find('.repeat-monthly input').removeAttr('checked').removeClass('checked'); this.$element.find('.repeat-monthly label.radio-custom').removeClass('checked'); if (recur.BYMONTHDAY) { temp = this.$element.find('.repeat-monthly-date'); temp.find('input').addClass('checked').prop('checked', true); temp.find('label.radio-custom').addClass('checked'); temp.find('.selectlist').selectlist('selectByValue', recur.BYMONTHDAY); } else if (recur.BYDAY) { temp = this.$element.find('.repeat-monthly-day'); temp.find('input').addClass('checked').prop('checked', true); temp.find('label.radio-custom').addClass('checked'); if (recur.BYSETPOS) { temp.find('.month-day-pos').selectlist('selectByValue', recur.BYSETPOS); } temp.find('.month-days').selectlist('selectByValue', recur.BYDAY); } item = 'monthly'; } else if (recur.FREQ === 'YEARLY') { this.$element.find('.repeat-yearly input').removeAttr('checked').removeClass('checked'); this.$element.find('.repeat-yearly label.radio-custom').removeClass('checked'); if (recur.BYMONTHDAY) { temp = this.$element.find('.repeat-yearly-date'); temp.find('input').addClass('checked').prop('checked', true); temp.find('label.radio-custom').addClass('checked'); if (recur.BYMONTH) { temp.find('.year-month').selectlist('selectByValue', recur.BYMONTH); } temp.find('.year-month-day').selectlist('selectByValue', recur.BYMONTHDAY); } else if (recur.BYSETPOS) { temp = this.$element.find('.repeat-yearly-day'); temp.find('input').addClass('checked').prop('checked', true); temp.find('label.radio-custom').addClass('checked'); temp.find('.year-month-day-pos').selectlist('selectByValue', recur.BYSETPOS); if (recur.BYDAY) { temp.find('.year-month-days').selectlist('selectByValue', recur.BYDAY); } if (recur.BYMONTH) { temp.find('.year-month').selectlist('selectByValue', recur.BYMONTH); } } item = 'yearly'; } else { item = 'none'; } if (recur.COUNT) { this.$endAfter.spinbox('value', parseInt(recur.COUNT, 10)); this.$endSelect.selectlist('selectByValue', 'after'); } else if (recur.UNTIL) { temp = recur.UNTIL; if (temp.length === 8) { temp = temp.split(''); temp.splice(4, 0, '-'); temp.splice(7, 0, '-'); temp = temp.join(''); } var timeZone = this.$timeZone.selectlist('selectedItem'); var timezoneOffset = (timeZone.offset === '+00:00') ? 'Z' : timeZone.offset; var utcEndHours = this.setUtcTime(temp, startTime, timezoneOffset); this.$endDate.datepicker('setDate', utcEndHours); this.$endSelect.selectlist('selectByValue', 'date'); } else { this.$endSelect.selectlist('selectByValue', 'never'); } this.endSelectChanged(); if (recur.INTERVAL) { this.$repeatIntervalSpinbox.spinbox('value', parseInt(recur.INTERVAL, 10)); } this.$repeatIntervalSelect.selectlist('selectByValue', item); this.repeatIntervalSelectChanged(); } var utcStartHours = this.setUtcTime(startDate, startTime, timeOffset); this.$startDate.datepicker('setDate', utcStartHours); }, toggleState: function (action) { this.$element.find('.combobox').combobox(action); this.$element.find('.datepicker').datepicker(action); this.$element.find('.selectlist').selectlist(action); this.$element.find('.spinbox').spinbox(action); this.$element.find('.radio-custom').radio(action); if (action === 'disable') { action = 'addClass'; } else { action = 'removeClass'; } this.$element.find('.repeat-days-of-the-week .btn-group')[action]('disabled'); }, value: function (options) { if (options) { return this.setValue(options); } else { return this.getValue(); } } }; // SCHEDULER PLUGIN DEFINITION $.fn.scheduler = function (option) { var args = Array.prototype.slice.call(arguments, 1); var methodReturn; var $set = this.each(function () { var $this = $(this); var data = $this.data('fu.scheduler'); var options = typeof option === 'object' && option; if (!data) { $this.data('fu.scheduler', (data = new Scheduler(this, options))); } if (typeof option === 'string') { methodReturn = data[option].apply(data, args); } }); return (methodReturn === undefined) ? $set : methodReturn; }; $.fn.scheduler.defaults = {}; $.fn.scheduler.Constructor = Scheduler; $.fn.scheduler.noConflict = function () { $.fn.scheduler = old; return this; }; // DATA-API $(document).on('mousedown.fu.scheduler.data-api', '[data-initialize=scheduler]', function (e) { var $control = $(e.target).closest('.scheduler'); if (!$control.data('fu.scheduler')) { $control.scheduler($control.data()); } }); // Must be domReady for AMD compatibility $(function () { $('[data-initialize=scheduler]').each(function () { var $this = $(this); if ($this.data('scheduler')) return; $this.scheduler($this.data()); }); }); // -- BEGIN UMD WRAPPER AFTERWORD -- })); // -- END UMD WRAPPER AFTERWORD --