diff options
author | Mahesh Gudi | 2017-05-30 14:59:05 +0530 |
---|---|---|
committer | GitHub | 2017-05-30 14:59:05 +0530 |
commit | 83d0d398fa81515ce2b6033a6f4e642e04c34a28 (patch) | |
tree | 2ccca3b4c524aaba9ef853734ac3df8bc2b9f6b0 /static/recurrence/js/recurrence-widget.js | |
parent | d73abca9000d1c90f39ed92de53302c27cc00d19 (diff) | |
parent | 626baecca1ac4cd57ce28512926d028d9b465ec7 (diff) | |
download | workshop_booking-83d0d398fa81515ce2b6033a6f4e642e04c34a28.tar.gz workshop_booking-83d0d398fa81515ce2b6033a6f4e642e04c34a28.tar.bz2 workshop_booking-83d0d398fa81515ce2b6033a6f4e642e04c34a28.zip |
Merge pull request #1 from Akshen/recurrance
Python Workshop Website
Diffstat (limited to 'static/recurrence/js/recurrence-widget.js')
-rw-r--r-- | static/recurrence/js/recurrence-widget.js | 1795 |
1 files changed, 1795 insertions, 0 deletions
diff --git a/static/recurrence/js/recurrence-widget.js b/static/recurrence/js/recurrence-widget.js new file mode 100644 index 0000000..817add8 --- /dev/null +++ b/static/recurrence/js/recurrence-widget.js @@ -0,0 +1,1795 @@ +if (!recurrence) + var recurrence = {}; + +recurrence.widget = {}; + + +recurrence.widget.Grid = function(cols, rows) { + this.init(cols, rows); +}; +recurrence.widget.Grid.prototype = { + init: function(cols, rows) { + this.disabled = false; + this.cells = []; + this.cols = cols; + this.rows = rows; + + this.init_dom(); + }, + + init_dom: function() { + var tbody = recurrence.widget.e('tbody'); + for (var y=0; y < this.rows; y++) { + var tr = recurrence.widget.e('tr'); + tbody.appendChild(tr); + for (var x=0; x < this.cols; x++) { + var td = recurrence.widget.e('td'); + tr.appendChild(td); + this.cells.push(td); + } + } + var table = recurrence.widget.e( + 'table', { + 'class': 'grid', 'cellpadding': 0, + 'cellspacing': 0, 'border': 0}, + [tbody]); + + this.elements = {'root': table, 'table': table, 'tbody': tbody}; + }, + + cell: function(col, row) { + return this.elements.tbody.childNodes[row].childNodes[col]; + }, + + enable: function () { + recurrence.widget.remove_class('disabled'); + this.disabled = false; + }, + + disable: function () { + recurrence.widget.add_class('disabled'); + this.disabled = true; + } +}; + + +recurrence.widget.Calendar = function(date, options) { + this.init(date, options); +}; +recurrence.widget.Calendar.prototype = { + init: function(date, options) { + this.date = date || recurrence.widget.date_today(); + this.month = this.date.getMonth(); + this.year = this.date.getFullYear(); + this.options = options || {}; + + if (this.options.onchange) + this.onchange = this.options.onchange; + if (this.options.onclose) + this.onclose = this.options.onclose; + + this.init_dom(); + this.show_month(this.year, this.month); + }, + + init_dom: function() { + var calendar = this; + + // navigation + + var remove = recurrence.widget.e('a', { + 'class': 'remove', + 'href': 'javascript:void(0)', + 'title': recurrence.display.labels.remove, + 'onclick': function() { + calendar.close(); + } + }, '×'); + var year_prev = recurrence.widget.e( + 'a', { + 'href': 'javascript:void(0)', 'class': 'prev-year', + 'onclick': function() {calendar.show_prev_year();}}, + '<<'); + var year_next = recurrence.widget.e( + 'a', { + 'href': 'javascript:void(0)', 'class': 'next-year', + 'onclick': function() {calendar.show_next_year();}}, + '>>'); + var month_prev = recurrence.widget.e( + 'a', { + 'href': 'javascript:void(0)', 'class': 'prev-month', + 'onclick': function() {calendar.show_prev_month();}}, + '<'); + var month_next = recurrence.widget.e( + 'a', { + 'href': 'javascript:void(0)', 'class': 'next-month', + 'onclick': function() {calendar.show_next_month();}}, + '>'); + var month_label = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, + recurrence.display.months[this.month]); + + var header_elements = [ + year_prev, month_prev, month_label, month_next, year_next]; + var header_grid = new recurrence.widget.Grid(header_elements.length, 1); + recurrence.array.foreach(header_elements, function(item, i) { + header_grid.cells[i].appendChild(item); + recurrence.widget.add_class( + header_grid.cells[i], item.className); + }); + recurrence.widget.add_class(header_grid.elements.root, 'navigation'); + + // core + + var calendar_year = recurrence.widget.e( + 'div', {'class': 'year'}, this.year); + var calendar_navigation = header_grid.elements.root; + // var calendar_week = week_grid.elements.root; + var calendar_body = recurrence.widget.e('div', {'class': 'body'}); + var calendar_footer = recurrence.widget.e('div', {'class': 'footer'}); + + var td = recurrence.widget.e( + 'td', {}, + [remove, calendar_year, calendar_navigation, + calendar_body, calendar_footer]); + var tr = recurrence.widget.e('tr', {}, [td]); + var tbody = recurrence.widget.e('tbody', {}, [tr]); + var root = recurrence.widget.e( + 'table', {'class': 'recurrence-calendar'}, [tbody]); + root.style.display = 'none'; + + this.elements = { + 'root': root, + 'year': calendar_year, + 'year_prev': year_prev, + 'year_next': year_next, + 'month_prev': month_prev, + 'month_next': month_next, + 'month_label': month_label, + 'calendar_body': calendar_body + }; + }, + + get_month_grid: function(year, month) { + var calendar = this; + + var dt = new Date(year, month, 1); + var start = dt.getDay(); + var days = recurrence.date.days_in_month(dt); + var rows = Math.ceil((days + start) / 7) + 1; + var grid = new recurrence.widget.Grid(7, rows); + + var number = 1; + recurrence.array.foreach( + grid.cells, function(cell, i) { + var cell = grid.cells[i]; + if (i < 7) { + var weekday_number = i - 1; + if (weekday_number < 0) + weekday_number = 6; + else if (weekday_number > 6) + weekday_number = 0; + cell.innerHTML = recurrence.display.weekdays_oneletter[ + weekday_number]; + recurrence.widget.add_class(cell, 'header'); + } else if (i - 7 < start || number > days) { + recurrence.widget.add_class(cell, 'empty'); + } else { + recurrence.widget.add_class(cell, 'day'); + if (this.date.getDate() == number && + this.date.getFullYear() == dt.getFullYear() && + this.date.getMonth() == dt.getMonth()) + recurrence.widget.add_class(cell, 'active'); + cell.innerHTML = number; + number = number + 1; + cell.onclick = function () { + calendar.set_date( + calendar.year, calendar.month, + parseInt(this.innerHTML, 10)); + }; + } + }, this); + + return grid; + }, + + show_month: function(year, month) { + if (this.elements.calendar_body.childNodes.length) + this.elements.calendar_body.removeChild( + this.elements.calendar_body.childNodes[0]); + this.elements.month_grid = this.get_month_grid(year, month); + this.elements.calendar_body.appendChild( + this.elements.month_grid.elements.root); + this.elements.month_label.firstChild.nodeValue = ( + recurrence.display.months[this.month]); + this.elements.year.firstChild.nodeValue = this.year; + }, + + show_prev_year: function() { + this.year = this.year - 1; + this.show_month(this.year, this.month); + }, + + show_next_year: function() { + this.year = this.year + 1; + this.show_month(this.year, this.month); + }, + + show_prev_month: function() { + this.month = this.month - 1; + if (this.month < 0) { + this.month = 11; + this.year = this.year - 1; + } + this.show_month(this.year, this.month); + }, + + show_next_month: function() { + this.month = this.month + 1; + if (this.month > 11) { + this.month = 0; + this.year = this.year + 1; + } + this.show_month(this.year, this.month); + }, + + set_date: function(year, month, day) { + if (year != this.date.getFullYear() || + month != this.date.getMonth() || + day != this.date.getDate()) { + + this.date.setFullYear(year); + this.date.setMonth(month); + this.date.setDate(day); + + recurrence.array.foreach( + this.elements.month_grid.cells, function(cell) { + if (recurrence.widget.has_class(cell, 'day')) { + var number = parseInt(cell.innerHTML, 10); + if (number == day) { + recurrence.widget.add_class(cell, 'active'); + } else { + recurrence.widget.remove_class(cell, 'active'); + } + } + }); + + if (this.onchange) + this.onchange(this.date); + } + }, + + set_position: function(x, y) { + this.elements.root.style.left = x + 'px'; + this.elements.root.style.top = y + 'px'; + }, + + show: function() { + this.elements.root.style.display = ''; + }, + + hide: function() { + this.elements.root.style.display = 'none'; + }, + + close: function() { + if (this.elements.root.parentNode) { + this.elements.root.parentNode.removeChild(this.elements.root); + if (this.onclose) + this.onclose(); + } + } +}; + + +recurrence.widget.DateSelector = function(date, options) { + this.init(date, options); +}; +recurrence.widget.DateSelector.prototype = { + init: function(date, options) { + this.disabled = false; + this.date = date; + this.calendar = null; + this.options = options || {}; + + if (this.options.onchange) + this.onchange = this.options.onchange; + + this.init_dom(); + }, + + init_dom: function() { + var dateselector = this; + + if (this.date) + var date_value = recurrence.date.format(this.date, '%Y-%m-%d'); + else + var date_value = ''; + var date_field = recurrence.widget.e( + 'input', { + 'class': 'date-field', 'size': 10, + 'value': date_value, + 'onchange': function() {dateselector.set_date(this.value);}}); + var calendar_button = recurrence.widget.e( + 'a', { + 'class': 'calendar-button', + 'href': 'javascript:void(0)', + 'title': recurrence.display.labels.calendar, + 'onclick': function() { + if (!dateselector.disabled) + dateselector.show_calendar(); + } + }, + ' '); + var root = recurrence.widget.e( + 'span', {'class': 'date-selector'}, + [date_field, calendar_button]); + + this.elements = { + 'root': root, + 'date_field': date_field, + 'calendar_button': calendar_button + }; + }, + + show_calendar: function() { + var dateselector = this; + + var calendar_blur = function(event) { + var element = event.target; + var is_in_dom = recurrence.widget.element_in_dom( + element, dateselector.calendar.elements.root); + if (!is_in_dom && + element != dateselector.elements.calendar_button) { + // clicked outside of calendar + dateselector.calendar.close(); + if (window.detachEvent) + window.detachEvent('onclick', calendar_blur); + else + window.removeEventListener('click', calendar_blur, false); + } + }; + + if (!this.calendar) { + this.calendar = new recurrence.widget.Calendar( + new Date((this.date || recurrence.widget.date_today()).valueOf()), { + 'onchange': function() { + dateselector.set_date( + recurrence.date.format(this.date, '%Y-%m-%d')); + dateselector.calendar.close(); + }, + 'onclose': function() { + if (window.detachEvent) + window.detachEvent('onclick', calendar_blur); + else + window.removeEventListener( + 'click', calendar_blur, false); + dateselector.hide_calendar(); + } + }); + document.body.appendChild(this.calendar.elements.root); + + this.calendar.show(); + this.set_calendar_position(); + + if (window.attachEvent) + window.attachEvent('onclick', calendar_blur); + else + window.addEventListener('click', calendar_blur, false); + } + }, + + set_date: function(datestring) { + var tokens = datestring.split('-'); + var year = parseInt(tokens[0], 10); + var month = parseInt(tokens[1], 10) - 1; + var day = parseInt(tokens[2], 10); + var dt = new Date(year, month, day); + + if (String(dt) == 'Invalid Date' || String(dt) == 'NaN') { + if (this.date && !this.options.allow_null) { + this.elements.date_field.value = recurrence.date.format( + this.date, '%Y-%m-%d'); + } else { + if (this.elements.date_field.value != '') { + if (this.onchange) + this.onchange(null); + } + this.elements.date_field.value = ''; + } + } else { + if (!this.date || + (year != this.date.getFullYear() || + month != this.date.getMonth() || + day != this.date.getDate())) { + + if (!this.date) + this.date = recurrence.widget.date_today(); + this.date.setFullYear(year); + this.date.setMonth(month); + this.date.setDate(day); + + this.elements.date_field.value = datestring; + + if (this.onchange) + this.onchange(this.date); + } + } + }, + + set_calendar_position: function() { + var loc = recurrence.widget.cumulative_offset( + this.elements.calendar_button); + + var calendar_x = loc[0]; + var calendar_y = loc[1]; + var calendar_right = ( + loc[0] + this.calendar.elements.root.clientWidth); + var calendar_bottom = ( + loc[1] + this.calendar.elements.root.clientHeight); + + if (calendar_right > document.scrollWidth) + calendar_x = calendar_x - ( + calendar_right - document.scrollWidth); + if (calendar_bottom > document.scrollHeight) + calendar_y = calendar_y - ( + calendar_bottom - document.scrollHeight); + + this.calendar.set_position(calendar_x, calendar_y); + }, + + hide_calendar: function() { + this.calendar = null; + }, + + enable: function () { + this.disabled = false; + this.elements.date_field.disabled = false; + }, + + disable: function () { + this.disabled = true; + this.elements.date_field.disabled = true; + if (this.calendar) + this.calendar.close(); + } +}; + + +recurrence.widget.Widget = function(textarea, options) { + this.init(textarea, options); +}; +recurrence.widget.Widget.prototype = { + init: function(textarea, options) { + if (textarea.toLowerCase) + textarea = document.getElementById(textarea); + this.selected_panel = null; + this.panels = []; + this.data = recurrence.deserialize(textarea.value); + this.textarea = textarea; + this.options = options; + + this.default_freq = options.default_freq || recurrence.WEEKLY; + + this.init_dom(); + this.init_panels(); + }, + + init_dom: function() { + var widget = this; + + var panels = recurrence.widget.e('div', {'class': 'panels'}); + var control = recurrence.widget.e('div', {'class': 'control'}); + var root = recurrence.widget.e( + 'div', {'class': this.textarea.className}, [panels, control]); + + var add_rule = new recurrence.widget.AddButton( + recurrence.display.labels.add_rule, { + 'onclick': function () {widget.add_rule();} + }); + recurrence.widget.add_class(add_rule.elements.root, 'add-rule'); + control.appendChild(add_rule.elements.root); + + var add_date = new recurrence.widget.AddButton( + recurrence.display.labels.add_date, { + 'onclick': function () {widget.add_date();} + }); + recurrence.widget.add_class(add_date.elements.root, 'add-date'); + control.appendChild(add_date.elements.root); + + this.elements = { + 'root': root, + 'panels': panels, + 'control': control + }; + + // attach immediately + this.textarea.style.display = 'none'; + this.textarea.parentNode.insertBefore( + this.elements.root, this.textarea); + }, + + init_panels: function() { + recurrence.array.foreach( + this.data.rrules, function(item) { + this.add_rule_panel(recurrence.widget.INCLUSION, item); + }, this); + recurrence.array.foreach( + this.data.exrules, function(item) { + this.add_rule_panel(recurrence.widget.EXCLUSION, item); + }, this); + recurrence.array.foreach( + this.data.rdates, function(item) { + this.add_date_panel(recurrence.widget.INCLUSION, item); + }, this); + recurrence.array.foreach( + this.data.exdates, function(item) { + this.add_date_panel(recurrence.widget.EXCLUSION, item); + }, this); + }, + + add_rule_panel: function(mode, rule) { + var panel = new recurrence.widget.Panel(this); + var form = new recurrence.widget.RuleForm(panel, mode, rule); + + panel.onexpand = function() { + if (panel.widget.selected_panel) + if (panel.widget.selected_panel != this) + panel.widget.selected_panel.collapse(); + panel.widget.selected_panel = this; + }; + panel.onremove = function() { + form.remove(); + }; + + this.elements.panels.appendChild(panel.elements.root); + this.panels.push(panel); + this.update(); + return panel; + }, + + add_date_panel: function(mode, date) { + var panel = new recurrence.widget.Panel(this); + var form = new recurrence.widget.DateForm(panel, mode, date); + + panel.onexpand = function() { + if (panel.widget.selected_panel) + if (panel.widget.selected_panel != this) + panel.widget.selected_panel.collapse(); + panel.widget.selected_panel = this; + }; + panel.onremove = function() { + form.remove(); + }; + + this.elements.panels.appendChild(panel.elements.root); + this.panels.push(panel); + this.update(); + return panel; + }, + + add_rule: function(rule) { + var rule = rule || new recurrence.Rule(this.default_freq); + this.data.rrules.push(rule); + this.add_rule_panel(recurrence.widget.INCLUSION, rule).expand(); + }, + + add_date: function(date) { + var date = date || recurrence.widget.date_today(); + this.data.rdates.push(date); + this.add_date_panel(recurrence.widget.INCLUSION, date).expand(); + }, + + update: function() { + this.textarea.value = this.data.serialize(); + } +}; + + +recurrence.widget.AddButton = function(label, options) { + this.init(label, options); +}; +recurrence.widget.AddButton.prototype = { + init: function(label, options) { + this.label = label; + this.options = options || {}; + + this.init_dom(); + }, + + init_dom: function() { + var addbutton = this; + + var plus = recurrence.widget.e( + 'span', {'class': 'plus'}, '+'); + var label = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, this.label); + var root = recurrence.widget.e( + 'a', {'class': 'add-button', 'href': 'javascript:void(0)'}, + [plus, label]); + + root.onclick = function() { + addbutton.options.onclick(); + }; + + this.elements = {'root': root, 'plus': plus, 'label': label}; + } +}; + + +recurrence.widget.Panel = function(widget, options) { + this.init(widget, options); +}; +recurrence.widget.Panel.prototype = { + init: function(widget, options) { + this.collapsed = false; + this.widget = widget; + this.options = options || {}; + + if (this.options.onremove) + this.onremove = this.options.onremove; + if (this.options.onexpand) + this.onexpand = this.options.onexpand; + if (this.options.oncollapse) + this.oncollapse = this.options.oncollapse; + + this.init_dom(); + }, + + init_dom: function() { + var panel = this; + + var remove = recurrence.widget.e('a', { + 'class': 'remove', + 'href': 'javascript:void(0)', + 'title': recurrence.display.labels.remove, + 'onclick': function() { + panel.remove(); + } + }, '×'); + var label = recurrence.widget.e('a', { + 'class': 'recurrence-label', + 'href': 'javascript:void(0)', + 'onclick': function() { + if (panel.collapsed) + panel.expand(); + else + panel.collapse(); + } + }, ' '); + var header = recurrence.widget.e( + 'div', {'class': 'header'}, [remove, label]); + var body = recurrence.widget.e( + 'div', {'class': 'body'}); + var root = recurrence.widget.e( + 'div', {'class': 'panel'}, [header, body]); + + this.elements = { + 'root': root, 'remove': remove, 'label': label, + 'header': header, 'body': body + }; + + this.collapse(); + }, + + set_label: function(label) { + this.elements.label.innerHTML = label; + }, + + set_body: function(element) { + if (this.elements.body.childNodes.length) + this.elements.body.removeChild(this.elements.body.childNodes[0]); + this.elements.body.appendChild(element); + }, + + expand: function() { + this.collapsed = false; + this.elements.body.style.display = ''; + if (this.onexpand) + this.onexpand(this); + }, + + collapse: function() { + this.collapsed = true; + this.elements.body.style.display = 'none'; + if (this.oncollapse) + this.oncollapse(this); + }, + + remove: function() { + var parent = this.elements.root.parentNode; + if (parent) + parent.removeChild(this.elements.root); + if (this.onremove) + this.onremove(parent); + } +}; + + +recurrence.widget.RuleForm = function(panel, mode, rule, options) { + this.init(panel, mode, rule, options); +}; +recurrence.widget.RuleForm.prototype = { + init: function(panel, mode, rule, options) { + this.selected_freq = rule.freq; + this.panel = panel; + this.mode = mode; + this.rule = rule; + this.options = options || {}; + + var rule_options = { + interval: rule.interval, until: rule.until, count: rule.count + }; + + this.freq_rules = [ + new recurrence.Rule(recurrence.YEARLY, rule_options), + new recurrence.Rule(recurrence.MONTHLY, rule_options), + new recurrence.Rule(recurrence.WEEKLY, rule_options), + new recurrence.Rule(recurrence.DAILY, rule_options) + ]; + this.freq_rules[this.rule.freq].update(this.rule); + + this.init_dom(); + + this.set_freq(this.selected_freq); + }, + + init_dom: function() { + var form = this; + + // mode + + var mode_checkbox = recurrence.widget.e( + 'input', {'class': 'checkbox', 'type': 'checkbox', 'name': 'mode'}); + var mode_label = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, + recurrence.display.labels.exclude_occurrences); + var mode_container = recurrence.widget.e( + 'div', {'class': 'mode'}, + [mode_checkbox, mode_label]); + if (this.mode == recurrence.widget.EXCLUSION) + // delay for ie6 compatibility + setTimeout(function() { + mode_checkbox.checked = true; + recurrence.widget.add_class(form.panel, 'exclusion'); + }, 10); + + // freq + + var freq_choices = recurrence.display.frequencies.slice(0, 4); + var freq_options = recurrence.array.foreach( + freq_choices, function(item, i) { + var option = recurrence.widget.e( + 'option', {'value': i}, + recurrence.string.capitalize(item)); + return option; + }); + var freq_select = recurrence.widget.e( + 'select', {'name': 'freq'}, freq_options); + var freq_label = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, + recurrence.display.labels.frequency + ':'); + var freq_container = recurrence.widget.e( + 'div', {'class': 'freq'}, + [freq_label, freq_select]); + + // interval + + var interval_field = recurrence.widget.e( + 'input', { + 'name': 'interval', 'size': 1, 'value': this.rule.interval}); + var interval_label1 = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, + recurrence.display.labels.every); + var interval_label2 = recurrence.widget.e( + 'span', {'class': 'label'}, + recurrence.display.timeintervals_plural[this.rule.freq]); + var interval_container = recurrence.widget.e( + 'div', {'class': 'interval'}, + [interval_label1, interval_field, interval_label2]); + + // until + + if (this.rule.until) + until_value = recurrence.date.format(this.rule.until, '%Y-%m-%d'); + else + until_value = ''; + var until_radio = recurrence.widget.e( + 'input', {'class': 'radio', 'type': 'radio', + 'name': 'until_count', 'value': 'until'}); + var until_date_selector = new recurrence.widget.DateSelector( + this.rule.until, { + 'onchange': function(date) {form.set_until(date);}, + 'allow_null': true + }); + var until_label = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, + recurrence.display.labels.date + ':'); + var until_container = recurrence.widget.e( + 'li', {'class': 'until'}, + [until_radio, until_label, until_date_selector.elements.root]); + + // count + + if (this.rule.count) + count_value = this.rule.count; + else + count_value = 1; + var count_radio = recurrence.widget.e( + 'input', { + 'class': 'radio', 'type': 'radio', + 'name': 'until_count', 'value': 'count'}); + var count_field = recurrence.widget.e( + 'input', {'name': 'count', 'size': 1, 'value': count_value}); + if (this.rule.count && this.rule.count < 2) + var token = recurrence.string.capitalize( + recurrence.display.labels.count); + else + var token = recurrence.string.capitalize( + recurrence.display.labels.count_plural); + var count_label1 = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, token.split('%(number)s')[0]); + var count_label2 = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, token.split('%(number)s')[1]); + var count_container = recurrence.widget.e( + 'li', {'class': 'count'}, + [count_radio, count_label1, count_field, count_label2]); + + // limit container + + var until_count_container = recurrence.widget.e( + 'ul', {'class': 'until-count'}, + [until_container, count_container]); + var limit_checkbox = recurrence.widget.e( + 'input', { + 'class': 'checkbox', 'type': 'checkbox', + 'name': 'limit'}); + var limit_label = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, + recurrence.display.labels.repeat_until + ':'); + var limit_container = recurrence.widget.e( + 'div', {'class': 'limit'}, + [limit_checkbox, limit_label, until_count_container]); + if (this.rule.until || this.rule.count) { + // compatibility with ie, we delay + setTimeout(function() {limit_checkbox.checked = true;}, 10); + } else { + until_radio.disabled = true; + count_radio.disabled = true; + until_date_selector.disable(); + recurrence.widget.add_class(until_count_container, 'disabled'); + } + + // core + + var freq_form_container = recurrence.widget.e( + 'div', {'class': 'form'}); + var root = recurrence.widget.e( + 'form', {}, [ + mode_container, freq_container, interval_container, + freq_form_container, limit_container]); + + // events + + mode_checkbox.onclick = function() { + if (this.checked) + form.set_mode(recurrence.widget.EXCLUSION); + else + form.set_mode(recurrence.widget.INCLUSION); + }; + + freq_select.onchange = function() { + form.set_freq(parseInt(this.value), 10); + }; + + interval_field.onchange = function() { + form.set_interval(parseInt(this.value), 10); + }; + + limit_checkbox.onclick = function () { + if (this.checked) { + recurrence.widget.remove_class( + until_count_container, 'disabled'); + until_radio.disabled = false; + count_radio.disabled = false; + if (until_radio.checked) { + until_date_selector.enable(); + form.set_until(until_date_selector.date); + } + if (count_radio.checked) { + count_field.disabled = false; + form.set_count(parseInt(count_field.value)); + } + } else { + recurrence.widget.add_class( + until_count_container, 'disabled'); + until_radio.disabled = true; + count_radio.disabled = true; + until_date_selector.disable(); + count_field.disabled = true; + recurrence.array.foreach( + form.freq_rules, function(rule) { + rule.until = null; + rule.count = null; + }); + form.update(); + } + } + + // for compatibility with ie, use timeout + setTimeout(function () { + if (form.rule.count) { + count_radio.checked = true; + until_date_selector.disable(); + } else { + until_radio.checked = true; + count_field.disabled = true; + } + }, 1); + + until_radio.onclick = function () { + this.checked = true; + until_date_selector.enable(); + count_radio.checked = false; + count_field.disabled = true; + form.set_until(until_date_selector.date); + }; + + count_radio.onclick = function () { + this.checked = true; + count_field.disabled = false; + until_radio.checked = false; + until_date_selector.disable(); + form.set_count(parseInt(count_field.value), 10); + }; + + count_field.onchange = function () { + form.set_count(parseInt(this.value), 10); + }; + + // freq forms + + var forms = [ + recurrence.widget.RuleYearlyForm, + recurrence.widget.RuleMonthlyForm, + recurrence.widget.RuleWeeklyForm, + recurrence.widget.RuleDailyForm + ]; + var freq_forms = recurrence.array.foreach( + forms, function(form, i) { + var rule = this.freq_rules[i]; + var f = new form(this, rule); + freq_form_container.appendChild(f.elements.root); + return f; + }, this); + + this.freq_forms = freq_forms; + + // install dom + + this.panel.set_label(this.get_display_text()); + this.panel.set_body(root); + + this.elements = { + 'root': root, + 'mode_checkbox': mode_checkbox, + 'freq_select': freq_select, + 'interval_field': interval_field, + 'freq_form_container': freq_form_container, + 'until_radio': until_radio, + 'count_field': count_field, + 'count_radio': count_radio, + 'limit_checkbox': limit_checkbox + }; + }, + + get_display_text: function() { + var text = this.freq_rules[this.selected_freq].get_display_text(); + if (this.mode == recurrence.widget.EXCLUSION) + text = recurrence.display.mode.exclusion + ' ' + text; + return recurrence.string.capitalize(text); + }, + + set_until: function(until) { + recurrence.array.foreach( + this.freq_rules, function(rule) { + rule.count = null; + rule.until = until; + }); + this.update(); + }, + + set_count: function(count) { + if (count < 2) + var token = recurrence.string.capitalize( + recurrence.display.labels.count); + else + var token = recurrence.string.capitalize( + recurrence.display.labels.count_plural); + var label1 = this.elements.count_field.previousSibling; + var label2 = this.elements.count_field.nextSibling; + label1.firstChild.nodeValue = token.split('%(number)s')[0]; + label2.firstChild.nodeValue = token.split('%(number)s')[1]; + recurrence.array.foreach( + this.freq_rules, function(rule) { + rule.until = null; + rule.count = count; + }); + this.update(); + }, + + set_interval: function(interval) { + interval = parseInt(interval, 10); + if (String(interval) == 'NaN') { + // invalid value, reset to previous value + this.elements.interval_field.value = ( + this.freq_rules[this.selected_freq].interval); + return; + } + + var label = this.elements.interval_field.nextSibling; + + if (interval < 2) + label.firstChild.nodeValue = ( + recurrence.display.timeintervals[this.selected_freq]); + else + label.firstChild.nodeValue = ( + recurrence.display.timeintervals_plural[this.selected_freq]); + recurrence.array.foreach( + this.freq_rules, function(rule) { + rule.interval = interval; + }); + + this.elements.interval_field.value = interval; + this.update(); + }, + + set_freq: function(freq) { + this.freq_forms[this.selected_freq].hide(); + this.freq_forms[freq].show(); + this.elements.freq_select.value = freq; + this.selected_freq = freq; + // need to update interval to display different label + this.set_interval(parseInt(this.elements.interval_field.value), 10); + this.update(); + }, + + set_mode: function(mode) { + if (this.mode != mode) { + if (this.mode == recurrence.widget.INCLUSION) { + recurrence.array.remove( + this.panel.widget.data.rrules, this.rule); + this.panel.widget.data.exrules.push(this.rule); + recurrence.widget.remove_class( + this.panel.elements.root, 'inclusion'); + recurrence.widget.add_class( + this.panel.elements.root, 'exclusion'); + } else { + recurrence.array.remove( + this.panel.widget.data.exrules, this.rule); + this.panel.widget.data.rrules.push(this.rule); + recurrence.widget.remove_class( + this.panel.elements.root, 'exclusion'); + recurrence.widget.add_class( + this.panel.elements.root, 'inclusion'); + } + this.mode = mode; + } + this.update(); + }, + + update: function() { + this.panel.set_label(this.get_display_text()); + this.rule.update(this.freq_rules[this.selected_freq]); + this.panel.widget.update(); + }, + + remove: function() { + var parent = this.elements.root.parentNode; + if (parent) + parent.removeChild(this.elements.root); + if (this.mode == recurrence.widget.INCLUSION) + recurrence.array.remove(this.panel.widget.data.rrules, this.rule); + else + recurrence.array.remove(this.panel.widget.data.exrules, this.rule); + this.panel.widget.update(); + } +}; + + +recurrence.widget.RuleYearlyForm = function(panel, rule) { + this.init(panel, rule); +}; +recurrence.widget.RuleYearlyForm.prototype = { + init: function(panel, rule) { + this.panel = panel; + this.rule = rule; + + this.init_dom(); + }, + + init_dom: function() { + var form = this; + + var grid = new recurrence.widget.Grid(4, 3); + var number = 0; + for (var y=0; y < 3; y++) { + for (var x=0; x < 4; x++) { + var cell = grid.cell(x, y); + if (this.rule.bymonth.indexOf(number + 1) > -1) + recurrence.widget.add_class(cell, 'active'); + cell.value = number + 1; + cell.innerHTML = recurrence.display.months_short[number]; + cell.onclick = function () { + if (recurrence.widget.has_class(this, 'active')) + recurrence.widget.remove_class(this, 'active'); + else + recurrence.widget.add_class(this, 'active'); + form.set_bymonth(); + }; + number += 1; + } + } + + // by weekday checkbox + + var byday_checkbox = recurrence.widget.e( + 'input', { + 'class': 'checkbox', 'type': 'checkbox', + 'name': 'byday'}); + var byday_label = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, + recurrence.string.capitalize( + recurrence.display.labels.on_the) + ':'); + var byday_container = recurrence.widget.e( + 'div', {'class': 'byday'}, + [byday_checkbox, byday_label]); + + // weekday-position + + var position_options = recurrence.array.foreach( + [1, 2, 3, 4, -1, -2, -3], function(value) { + var option = recurrence.widget.e( + 'option', {'value': value}, + recurrence.string.strip(recurrence.display.weekdays_position[ + String(value)].split('%(weekday)s')[0])); + return option; + }); + var position_select = recurrence.widget.e( + 'select', {'name': 'position'}, position_options); + var weekday_options = recurrence.array.foreach( + recurrence.display.weekdays, function(weekday, i) { + var option = recurrence.widget.e( + 'option', {'value': i}, weekday); + return option; + }); + var weekday_select = recurrence.widget.e( + 'select', {'name': 'weekday'}, weekday_options); + var weekday_position_container = recurrence.widget.e( + 'div', {'class': 'section'}, [position_select, weekday_select]); + + // core + + var year = recurrence.widget.e('div'); + year.appendChild(grid.elements.root); + + var root = recurrence.widget.e( + 'div', {'class': 'yearly'}, + [year, byday_container, weekday_position_container]); + root.style.display = 'none'; + + if (this.rule.byday.length) { + if (form.rule.bysetpos.length) { + position_select.value = String(form.rule.bysetpos[0]); + } else { + position_select.value = String(form.rule.byday[0].index); + } + weekday_select.value = String(form.rule.byday[0].number); + byday_checkbox.checked = true; + } else { + position_select.disabled = true; + weekday_select.disabled = true; + } + + // events + + byday_checkbox.onclick = function () { + if (this.checked) { + position_select.disabled = false; + weekday_select.disabled = false; + form.set_byday(); + } else { + position_select.disabled = true; + weekday_select.disabled = true; + form.rule.byday = []; + form.panel.update(); + } + }; + + position_select.onchange = function () { + form.set_byday(); + }; + + weekday_select.onchange = function () { + form.set_byday(); + }; + + this.elements = { + 'root': root, + 'grid': grid, + 'byday_checkbox': byday_checkbox, + 'position_select': position_select, + 'weekday_select': weekday_select + }; + }, + + get_weekday: function() { + var number = parseInt(this.elements.weekday_select.value, 10); + var index = parseInt(this.elements.position_select.value, 10); + return new recurrence.Weekday(number, index); + }, + + set_bymonth: function() { + var bymonth = []; + recurrence.array.foreach( + this.elements.grid.cells, function(cell) { + if (recurrence.widget.has_class(cell, 'active')) + bymonth.push(cell.value); + }) + this.rule.bymonth = bymonth; + this.panel.update(); + }, + + set_byday: function() { + this.rule.byday = [this.get_weekday()]; + this.panel.update(); + }, + + show: function() { + this.elements.root.style.display = ''; + }, + + hide: function() { + this.elements.root.style.display = 'none'; + } +}; + + +recurrence.widget.RuleMonthlyForm = function(panel, rule) { + this.init(panel, rule); +}; +recurrence.widget.RuleMonthlyForm.prototype = { + init: function(panel, rule) { + this.panel = panel; + this.rule = rule; + + this.init_dom(); + }, + + init_dom: function() { + var form = this; + + // monthday + + var monthday_grid = new recurrence.widget.Grid(7, Math.ceil(31 / 7)); + var number = 0; + for (var y=0; y < Math.ceil(31 / 7); y++) { + for (var x=0; x < 7; x++) { + number += 1; + var cell = monthday_grid.cell(x, y); + if (number > 31) { + recurrence.widget.add_class(cell, 'empty'); + continue; + } else { + cell.innerHTML = number; + if (this.rule.bymonthday.indexOf(number) > -1) + recurrence.widget.add_class(cell, 'active'); + cell.onclick = function () { + if (monthday_grid.disabled) + return; + var day = parseInt(this.innerHTML, 10) || null; + if (day) { + if (recurrence.widget.has_class(this, 'active')) + recurrence.widget.remove_class(this, 'active'); + else + recurrence.widget.add_class(this, 'active'); + form.set_bymonthday(); + } + } + } + } + } + var monthday_grid_container = recurrence.widget.e( + 'div', {'class': 'section'}); + monthday_grid_container.appendChild(monthday_grid.elements.root); + var monthday_radio = recurrence.widget.e( + 'input', { + 'class': 'radio', 'type': 'radio', + 'name': 'monthly', 'value': 'monthday'}); + var monthday_label = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, + recurrence.display.labels.each + ':'); + var monthday_container = recurrence.widget.e( + 'li', {'class': 'monthday'}, + [monthday_radio, monthday_label, monthday_grid_container]); + + // weekday-position + + var position_options = recurrence.array.foreach( + [1, 2, 3, 4, -1, -2, -3], function(value) { + var option = recurrence.widget.e( + 'option', {'value': value}, + recurrence.string.strip( + recurrence.display.weekdays_position[ + String(value)].split('%(weekday)s')[0])); + return option; + }); + var position_select = recurrence.widget.e( + 'select', {'name': 'position'}, position_options); + + var weekday_options = recurrence.array.foreach( + recurrence.display.weekdays, function(weekday, i) { + var option = recurrence.widget.e( + 'option', {'value': i}, weekday); + return option; + }); + var weekday_select = recurrence.widget.e( + 'select', {'name': 'weekday'}, weekday_options); + var weekday_position_container = recurrence.widget.e( + 'div', {'class': 'section'}, [position_select, weekday_select]); + var weekday_radio = recurrence.widget.e( + 'input', { + 'class': 'radio', 'type': 'radio', + 'name': 'monthly', 'value': 'weekday'}); + var weekday_label = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, + recurrence.display.labels.on_the + ':'); + var weekday_container = recurrence.widget.e( + 'li', {'class': 'weekday'}, + [weekday_radio, weekday_label, weekday_position_container]); + + // core + + var monthday_weekday_container = recurrence.widget.e( + 'ul', {'class': 'monthly'}, + [monthday_container, weekday_container]); + + var root = recurrence.widget.e( + 'div', {'class': 'monthly'}, [monthday_weekday_container]); + root.style.display = 'none'; + + // events + + // for compatibility with ie, use timeout + setTimeout(function () { + if (form.rule.byday.length) { + weekday_radio.checked = true; + if (form.rule.bysetpos.length) { + position_select.value = String(form.rule.bysetpos[0]); + } else { + position_select.value = String(form.rule.byday[0].index); + } + weekday_select.value = String(form.rule.byday[0].number); + monthday_grid.disable(); + } else { + monthday_radio.checked = true; + position_select.disabled = true; + weekday_select.disabled = true; + } + }, 1); + + monthday_radio.onclick = function () { + this.checked = true; + weekday_radio.checked = false; + position_select.disabled = true; + weekday_select.disabled = true; + monthday_grid.enable(); + form.set_bymonthday(); + }; + + weekday_radio.onclick = function () { + this.checked = true; + monthday_radio.checked = false; + position_select.disabled = false; + weekday_select.disabled = false; + monthday_grid.disable(); + form.set_byday(); + }; + + position_select.onchange = function () { + form.set_byday(); + }; + + weekday_select.onchange = function () { + form.set_byday(); + }; + + this.elements = { + 'root': root, + 'monthday_grid': monthday_grid, + 'monthday_radio': monthday_radio, + 'weekday_radio': weekday_radio, + 'position_select': position_select, + 'weekday_select': weekday_select + }; + }, + + get_weekday: function() { + var number = parseInt(this.elements.weekday_select.value, 10); + var index = parseInt(this.elements.position_select.value, 10); + return new recurrence.Weekday(number, index); + }, + + set_byday: function() { + this.rule.bymonthday = []; + this.rule.bysetpos = []; + this.rule.byday = [this.get_weekday()]; + this.panel.update(); + }, + + set_bymonthday: function() { + this.rule.bysetpos = []; + this.rule.byday = []; + var monthdays = []; + recurrence.array.foreach( + this.elements.monthday_grid.cells, function(cell) { + var day = parseInt(cell.innerHTML, 10) || null; + if (day && recurrence.widget.has_class(cell, 'active')) + monthdays.push(day); + }); + this.rule.bymonthday = monthdays; + this.panel.update(); + }, + + show: function() { + this.elements.root.style.display = ''; + }, + + hide: function() { + this.elements.root.style.display = 'none'; + } +}; + + +recurrence.widget.RuleWeeklyForm = function(panel, rule) { + this.init(panel, rule); +}; +recurrence.widget.RuleWeeklyForm.prototype = { + init: function(panel, rule) { + this.panel = panel; + this.rule = rule; + + this.init_dom(); + }, + + init_dom: function() { + var form = this; + + var weekday_grid = new recurrence.widget.Grid(7, 1); + var days = []; + var days = recurrence.array.foreach( + this.rule.byday, function(day) { + return recurrence.to_weekday(day).number; + }); + for (var x=0; x < 7; x++) { + var cell = weekday_grid.cell(x, 0); + if (days.indexOf(x) > -1) + recurrence.widget.add_class(cell, 'active'); + cell.value = x; + cell.innerHTML = recurrence.display.weekdays_short[x]; + cell.onclick = function () { + if (weekday_grid.disabled) + return; + if (recurrence.widget.has_class(this, 'active')) + recurrence.widget.remove_class(this, 'active'); + else + recurrence.widget.add_class(this, 'active'); + form.set_byday(); + }; + } + + var weekday_container = recurrence.widget.e( + 'div', {'class': 'section'}); + weekday_container.appendChild(weekday_grid.elements.root); + var root = recurrence.widget.e( + 'div', {'class': 'weekly'}, [weekday_container]); + root.style.display = 'none'; + + this.elements = { + 'root': root, + 'weekday_grid': weekday_grid + }; + }, + + set_byday: function() { + var byday = []; + recurrence.array.foreach( + this.elements.weekday_grid.cells, function(cell) { + if (recurrence.widget.has_class(cell, 'active')) + byday.push(new recurrence.Weekday(cell.value)); + }); + this.rule.byday = byday; + this.panel.update(); + }, + + show: function() { + this.elements.root.style.display = ''; + }, + + hide: function() { + this.elements.root.style.display = 'none'; + } +}; + + +recurrence.widget.RuleDailyForm = function(panel, rule) { + this.init(panel, rule); +}; +recurrence.widget.RuleDailyForm.prototype = { + init: function(panel, rule) { + this.panel = panel; + this.rule = rule; + + this.init_dom(); + }, + + init_dom: function() { + var root = recurrence.widget.e('div', {'class': 'daily'}); + root.style.display = 'none'; + this.elements = {'root': root}; + }, + + show: function() { + // this.elements.root.style.display = ''; + }, + + hide: function() { + // this.elements.root.style.display = 'none'; + } +}; + + +recurrence.widget.DateForm = function(panel, mode, date) { + this.init(panel, mode, date); +}; +recurrence.widget.DateForm.prototype = { + init: function(panel, mode, date) { + this.collapsed = true; + this.panel = panel; + this.mode = mode; + this.date = date; + + this.init_dom(); + }, + + init_dom: function() { + var form = this; + + // mode + + var mode_checkbox = recurrence.widget.e( + 'input', { + 'class': 'checkbox', 'type': 'checkbox', 'name': 'mode', + 'onclick': function() { + if (this.checked) + form.set_mode(recurrence.widget.EXCLUSION); + else + form.set_mode(recurrence.widget.INCLUSION); + } + }); + if (this.mode == recurrence.widget.EXCLUSION) + mode_checkbox.checked = true; + var mode_label = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, + recurrence.display.labels.exclude_date); + var mode_container = recurrence.widget.e( + 'div', {'class': 'mode'}, [mode_checkbox, mode_label]); + + // date + + var date_label = recurrence.widget.e( + 'span', {'class': 'recurrence-label'}, recurrence.display.labels.date + ':'); + var date_selector = new recurrence.widget.DateSelector( + this.date, {'onchange': function() {form.update();}}); + var date_container = recurrence.widget.e( + 'div', {'class': 'date'}, [date_label, date_selector.elements.root]); + + // core + + var root = recurrence.widget.e( + 'form', {'class': 'date'}, [mode_container, date_container]); + + // init dom + + this.panel.set_label(this.get_display_text()); + this.panel.set_body(root); + this.elements = {'root': root}; + }, + + get_display_text: function() { + var text = recurrence.date.format(this.date, pgettext('date', '%l, %F %j, %Y')); + if (this.mode == recurrence.widget.EXCLUSION) + text = recurrence.display.mode.exclusion + ' ' + text; + return recurrence.string.capitalize(text); + }, + + set_mode: function(mode) { + if (this.mode != mode) { + if (this.mode == recurrence.widget.INCLUSION) { + recurrence.array.remove( + this.panel.widget.data.rdates, this.date); + this.panel.widget.data.exdates.push(this.date); + recurrence.widget.remove_class( + this.elements.root, 'inclusion'); + recurrence.widget.add_class( + this.elements.root, 'exclusion'); + this.update(); + } else { + recurrence.array.remove( + this.panel.widget.data.exdates, this.date); + this.panel.widget.data.rdates.push(this.date); + recurrence.widget.remove_class( + this.elements.root, 'exclusion'); + recurrence.widget.add_class( + this.elements.root, 'inclusion'); + this.update(); + } + this.mode = mode; + } + this.update(); + }, + + update: function() { + this.panel.set_label(this.get_display_text()); + this.panel.widget.update(); + }, + + remove: function() { + var parent = this.elements.root.parentNode; + if (parent) + parent.removeChild(this.elements.root); + if (this.mode == recurrence.widget.INCLUSION) + recurrence.array.remove(this.panel.widget.data.rdates, this.date); + else + recurrence.array.remove(this.panel.widget.data.exdates, this.date); + this.panel.widget.update(); + } +}; + + +recurrence.widget.e = function(tag_name, attrs, inner) { + var element = document.createElement(tag_name); + if (attrs) + recurrence.widget.set_attrs(element, attrs); + if (inner) { + if (!inner.toLowerCase && inner.length) + recurrence.array.foreach( + inner, function(e) {element.appendChild(e);}); + else + element.innerHTML = inner; + } + return element; +}; + + +recurrence.widget.set_attrs = function(element, attrs) { + for (var attname in attrs) + if (attname.match(/^on/g)) + element[attname] = attrs[attname]; + else if (attname == 'class') + element.className = attrs[attname]; + else + element.setAttribute(attname, attrs[attname]); +}; + + +recurrence.widget.add_class = function(element, class_name) { + var names = (element.className || '').split(/[ \r\n\t]+/g); + if (names.indexOf(class_name) == -1) { + names.push(class_name); + element.className = names.join(' '); + } +}; + + +recurrence.widget.remove_class = function(element, class_name) { + var names = (element.className || '').split(/[ \r\n\t]+/g); + if (names.indexOf(class_name) > -1) { + recurrence.array.remove(names, class_name); + element.className = names.join(' '); + } +}; + + +recurrence.widget.has_class = function(element, class_name) { + var names = (element.className || '').split(/[ \r\n\t]+/g); + if (names.indexOf(class_name) > -1) + return true; + else + return false; +}; + + +recurrence.widget.element_in_dom = function(element, dom) { + if (element == dom) { + return true; + } else { + for (var i=0; i < dom.childNodes.length; i++) + if (recurrence.widget.element_in_dom(element, dom.childNodes[i])) + return true; + } + return false; +}; + + +recurrence.widget.cumulative_offset = function(element) { + var y = 0, x = 0; + do { + y += element.offsetTop || 0; + x += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [x, y]; +}; + + +recurrence.widget.textareas_to_widgets = function(token) { + var elements = []; + if (!token) + token = 'recurrence-widget'; + if (token.toLowerCase) { + var textareas = document.getElementsByTagName('textarea'); + recurrence.array.foreach( + textareas, function(textarea) { + if (recurrence.widget.has_class(textarea, token)) + elements.push(textarea); + }); + } + recurrence.array.foreach( + elements, function(e) { + new recurrence.widget.Widget(e, window[e.id] || {}); + }); +}; + + +recurrence.widget.date_today = function() { + var date = new Date(); + date.setHours(0); + date.setMinutes(0); + date.setSeconds(0); + return date; +}; + + +recurrence.widget.INCLUSION = true; +recurrence.widget.EXCLUSION = false; + + +// display + + +if (!recurrence.display) + recurrence.display = {}; + +recurrence.display.mode = { + 'inclusion': gettext('including'), 'exclusion': gettext('excluding') +}; + +recurrence.display.labels = { + 'frequency': gettext('Frequency'), + 'on_the': gettext('On the'), + 'each': gettext('Each'), + 'every': gettext('Every'), + 'until': gettext('Until'), + 'count': gettext('Occurs %(number)s time'), + 'count_plural': gettext('Occurs %(number)s times'), + 'date': gettext('Date'), + 'time': gettext('Time'), + 'repeat_until': gettext('Repeat until'), + 'exclude_occurrences': gettext('Exclude these occurences'), + 'exclude_date': gettext('Exclude this date'), + 'add_rule': gettext('Add recurrence'), + 'add_date': gettext('Add date'), + 'remove': gettext('Remove'), + 'calendar': gettext('Calendar') +}; |