4 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
5 * @author Cornelius Weiss <c.weiss@metaways.de>
6 * @copyright Copyright (c) 2007-2015 Metaways Infosystems GmbH (http://www.metaways.de)
9 Ext.ns('Tine.Calendar');
11 Tine.Calendar.RrulePanel = Ext.extend(Ext.Panel, {
16 wkdays: ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'],
23 * the event edit dialog (parent)
24 * @type Tine.Calendar.EventEditDialog
26 eventEditDialog: null,
31 initComponent: function() {
32 this.app = Tine.Tinebase.appMgr.get('Calendar');
34 this.title = this.app.i18n._('Recurrances');
40 this.NONEcard = new Ext.Panel({
42 html: this.app.i18n._('No recurring rule defined')
44 this.NONEcard.setRule = Ext.emptyFn;
45 this.NONEcard.fillDefaults = Ext.emptyFn;
46 this.NONEcard.getRule = function() {
49 this.NONEcard.isValid = function() {
53 this.DAILYcard = new Tine.Calendar.RrulePanel.DAILYcard({rrulePanel: this});
54 this.WEEKLYcard = new Tine.Calendar.RrulePanel.WEEKLYcard({rrulePanel: this});
55 this.MONTHLYcard = new Tine.Calendar.RrulePanel.MONTHLYcard({rrulePanel: this});
56 this.YEARLYcard = new Tine.Calendar.RrulePanel.YEARLYcard({rrulePanel: this});
58 this.ruleCards = new Ext.Panel({
61 style: 'padding: 10px 0 0 10px;',
71 this.idPrefix = Ext.id();
74 id: this.idPrefix + 'tglbtn' + 'NONE',
75 xtype: 'tbbtnlockedtoggle',
77 text: this.app.i18n._('None'),
78 handler: this.onFreqChange.createDelegate(this, ['NONE']),
79 toggleGroup: this.idPrefix + 'freqtglgroup'
81 id: this.idPrefix + 'tglbtn' + 'DAILY',
82 xtype: 'tbbtnlockedtoggle',
84 text: this.app.i18n._('Daily'),
85 handler: this.onFreqChange.createDelegate(this, ['DAILY']),
86 toggleGroup: this.idPrefix + 'freqtglgroup'
88 id: this.idPrefix + 'tglbtn' + 'WEEKLY',
89 xtype: 'tbbtnlockedtoggle',
91 text: this.app.i18n._('Weekly'),
92 handler: this.onFreqChange.createDelegate(this, ['WEEKLY']),
93 toggleGroup: this.idPrefix + 'freqtglgroup'
95 id: this.idPrefix + 'tglbtn' + 'MONTHLY',
96 xtype: 'tbbtnlockedtoggle',
98 text: this.app.i18n._('Monthly'),
99 handler: this.onFreqChange.createDelegate(this, ['MONTHLY']),
100 toggleGroup: this.idPrefix + 'freqtglgroup'
102 id: this.idPrefix + 'tglbtn' + 'YEARLY',
103 xtype: 'tbbtnlockedtoggle',
105 text: this.app.i18n._('Yearly'),
106 handler: this.onFreqChange.createDelegate(this, ['YEARLY']),
107 toggleGroup: this.idPrefix + 'freqtglgroup'
114 Tine.Calendar.RrulePanel.superclass.initComponent.call(this);
117 isValid: function() {
118 return this.activeRuleCard.isValid(this.record);
121 onFreqChange: function(freq) {
122 this.ruleCards.layout.setActiveItem(this[freq + 'card']);
123 this.ruleCards.layout.layout();
124 this.activeRuleCard = this[freq + 'card'];
128 * disable contents not panel
130 setDisabled: function(v) {
131 this.items.each(function(item) {
136 onRecordLoad: function(record) {
137 this.record = record;
139 if (! this.record.get('editGrant') || this.record.isRecurException()) {
140 this.setDisabled(true);
143 this.rrule = this.record.get('rrule');
145 var dtstart = this.record.get('dtstart');
146 if (Ext.isDate(dtstart)) {
147 var byday = Tine.Calendar.RrulePanel.prototype.wkdays[dtstart.format('w')];
148 var bymonthday = dtstart.format('j');
149 var bymonth = dtstart.format('n');
151 this.WEEKLYcard.setRule({
155 this.MONTHLYcard.setRule({
158 bymonthday: bymonthday
160 this.YEARLYcard.setRule({
162 bymonthday: bymonthday,
167 var freq = this.rrule && this.rrule.freq ? this.rrule.freq : 'NONE';
169 var freqBtn = Ext.getCmp(this.idPrefix + 'tglbtn' + freq);
170 freqBtn.toggle(true);
172 this.activeRuleCard = this[freq + 'card'];
173 this.ruleCards.activeItem = this.activeRuleCard;
175 this.activeRuleCard.setRule(this.rrule);
177 if (this.record.isRecurException()) {
178 this.activeRuleCard = this.NONEcard;
179 this.items.each(function(item) {
180 item.setDisabled(true);
183 this.NONEcard.html = this.app.i18n._("Exceptions of reccuring events can't have recurrences themselves.");
187 onRecordUpdate: function(record) {
188 var rrule = this.activeRuleCard.rendered ? this.activeRuleCard.getRule() : this.rrule;
190 if (! this.rrule && rrule) {
191 // mark as new rule to avoid series confirm dlg
192 rrule.newrule = true;
195 record.set('rrule', '');
196 record.set('rrule', rrule);
200 Tine.Calendar.RrulePanel.AbstractCard = Ext.extend(Ext.Panel, {
206 getRule: function() {
210 interval: this.interval.getValue()
213 if (this.untilRadio.checked) {
214 rrule.until = this.until.getRawValue();
215 rrule.until = rrule.until ? Date.parseDate(rrule.until, this.until.format) : null;
218 if (Ext.isDate(rrule.until)) {
219 // make sure, last reccurance is included
220 rrule.until = rrule.until.clearTime(true).add(Date.HOUR, 24).add(Date.SECOND, -1).format(Date.patterns.ISO8601Long);
223 rrule.count = this.count.getValue() || 1;
230 onAfterUnitTriggerClick: function() {
231 if (! this.until.getValue()) {
232 var dtstart = this.rrulePanel.record.get('dtstart');
233 this.until.menu.picker.setValue(dtstart);
237 initComponent: function() {
238 this.app = Tine.Tinebase.appMgr.get('Calendar');
240 this.limitId = Ext.id();
242 this.untilRadio = new Ext.form.Radio({
243 requiredGrant : 'editGrant',
245 boxLabel : this.app.i18n._('at'),
246 name : this.limitId + 'LimitRadioGroup',
247 inputValue : 'UNTIL',
250 check: this.onLimitRadioCheck.createDelegate(this)
254 this.until = new Ext.form.DateField({
255 requiredGrant : 'editGrant',
257 emptyText : this.app.i18n._('never'),
258 onTriggerClick: Ext.form.DateField.prototype.onTriggerClick.createSequence(this.onAfterUnitTriggerClick, this),
262 render: function(f) {f.wrap.setWidth.defer(100, f.wrap, [f.initialConfig.width]);}
266 var countStringParts = this.app.i18n._('after {0} occurrences').split('{0}'),
267 countBeforeString = countStringParts[0],
268 countAfterString = countStringParts[1];
270 this.countRadio = new Ext.form.Radio({
271 requiredGrant : 'editGrant',
273 boxLabel : countBeforeString,
274 name : this.limitId + 'LimitRadioGroup',
275 inputValue : 'COUNT',
277 check: this.onLimitRadioCheck.createDelegate(this)
281 this.count = new Ext.form.NumberField({
282 requiredGrant : 'editGrant',
283 style : 'text-align:right;',
284 //fieldLabel : this.intervalBeforeString,
291 var intervalPars = this.intervalString.split('{0}');
292 var intervalBeforeString = intervalPars[0];
293 var intervalAfterString = intervalPars[1];
295 this.interval = new Ext.form.NumberField({
296 requiredGrant : 'editGrant',
297 style : 'text-align:right;',
298 //fieldLabel : this.intervalBeforeString,
309 if (this.freq != 'YEARLY') {
314 html: intervalBeforeString
318 style: 'padding-top: 2px;',
319 html: intervalAfterString
321 }].concat(this.items);
324 this.items = this.items.concat({
326 html: '<div style="padding-top: 5px;">' + this.app.i18n._('End') + '</div>' +
327 '<div style="position: relative;">' +
328 '<div style="position: relative;">' +
330 '<td width="65" id="' + this.limitId + 'untilRadio"></td>' +
331 '<td width="100" id="' + this.limitId + 'until"></td>' +
334 '<div style="position: relative;">' +
336 '<td width="65" id="' + this.limitId + 'countRadio"></td>' +
337 '<td width="40" id="' + this.limitId + 'count"></td>' +
338 '<td width="40" style="padding-left: 5px" >' + countAfterString + '</td>' +
344 render: this.onLimitRender
348 Tine.Calendar.RrulePanel.AbstractCard.superclass.initComponent.call(this);
351 onLimitRender: function() {
352 var untilradioel = Ext.get(this.limitId + 'untilRadio');
353 var untilel = Ext.get(this.limitId + 'until');
355 var countradioel = Ext.get(this.limitId + 'countRadio');
356 var countel = Ext.get(this.limitId + 'count');
358 if (! (untilradioel && countradioel)) {
359 return this.onLimitRender.defer(100, this, arguments);
362 this.untilRadio.render(untilradioel);
363 this.until.render(untilel);
364 this.until.wrap.setWidth(80);
366 this.countRadio.render(countradioel);
367 this.count.render(countel);
370 onLimitRadioCheck: function(radio, checked) {
371 switch(radio.inputValue) {
373 this.count.setDisabled(checked);
376 this.until.setDisabled(checked);
381 isValid: function(record) {
382 var until = this.until.getValue(),
385 if (Ext.isDate(until) && Ext.isDate(record.get('dtstart'))) {
386 if (until.getTime() < record.get('dtstart').getTime()) {
387 this.until.markInvalid(this.app.i18n._('Until has to be after event start'));
392 if (Ext.isDate(record.get('dtend')) && Ext.isDate(record.get('dtstart'))) {
393 var dayDifference = (record.get('dtend').getTime() - record.get('dtstart').getTime()) / 1000 / 60 / 60 / 24,
394 dtendField = this.rrulePanel.eventEditDialog.getForm().findField('dtend');
396 if(freq == 'DAILY' && dayDifference >= 1) {
397 dtendField.markInvalid(this.app.i18n._('The event is longer than the recurring interval'));
399 } else if(freq == 'WEEKLY' && dayDifference >= 7) {
400 dtendField.markInvalid(this.app.i18n._('The event is longer than the recurring interval'));
402 } else if(freq == 'MONTHLY' && dayDifference >= 28) {
403 dtendField.markInvalid(this.app.i18n._('The event is longer than the recurring interval'));
405 } else if(freq == 'YEARLY' && dayDifference >= 365) {
406 dtendField.markInvalid(this.app.i18n._('The event is longer than the recurring interval'));
414 setRule: function(rrule) {
415 this.interval.setValue(rrule.interval || 1);
416 var date = Date.parseDate(rrule.until, Date.patterns.ISO8601Long);
417 this.until.value = date;
420 this.count.value = rrule.count;
422 this.untilRadio.setValue(false);
423 this.countRadio.setValue(true);
424 this.onLimitRadioCheck(this.untilRadio, false);
425 this.onLimitRadioCheck(this.countRadio, true);
430 Tine.Calendar.RrulePanel.DAILYcard = Ext.extend(Tine.Calendar.RrulePanel.AbstractCard, {
434 initComponent: function() {
435 this.app = Tine.Tinebase.appMgr.get('Calendar');
437 this.intervalString = this.app.i18n._('Every {0}. Day');
439 Tine.Calendar.RrulePanel.DAILYcard.superclass.initComponent.call(this);
443 Tine.Calendar.RrulePanel.WEEKLYcard = Ext.extend(Tine.Calendar.RrulePanel.AbstractCard, {
447 getRule: function() {
448 var rrule = Tine.Calendar.RrulePanel.WEEKLYcard.superclass.getRule.call(this);
451 this.byday.items.each(function(cb) {
453 bydayArray.push(cb.name);
457 rrule.byday = bydayArray.join();
459 rrule.byday = this.byDayValue;
462 rrule.wkst = this.wkst || Tine.Calendar.RrulePanel.prototype.wkdays[Ext.DatePicker.prototype.startDay];
467 initComponent: function() {
468 this.app = Tine.Tinebase.appMgr.get('Calendar');
470 this.intervalString = this.app.i18n._('Every {0}. Week at');
473 for (var i=0,d; i<7; i++) {
474 d = (i+Ext.DatePicker.prototype.startDay)%7
476 boxLabel: Date.dayNames[d],
477 name: Tine.Calendar.RrulePanel.prototype.wkdays[d]
481 this.byday = new Ext.form.CheckboxGroup({
482 requiredGrant : 'editGrant',
483 style: 'padding-top: 5px;',
488 this.items = [this.byday];
490 Tine.Calendar.RrulePanel.WEEKLYcard.superclass.initComponent.call(this);
493 setRule: function(rrule) {
494 Tine.Calendar.RrulePanel.WEEKLYcard.superclass.setRule.call(this, rrule);
495 this.wkst = rrule.wkst;
498 this.byDayValue = rrule.byday;
500 var bydayArray = rrule.byday.split(',');
502 if (Ext.isArray(this.byday.items)) {
503 // on initialisation items are not renderd
504 Ext.each(this.byday.items, function(cb) {
505 if (bydayArray.indexOf(cb.name) != -1) {
510 // after items are rendered
511 this.byday.items.each(function(cb) {
512 if (bydayArray.indexOf(cb.name) != -1) {
521 Tine.Calendar.RrulePanel.MONTHLYcard = Ext.extend(Tine.Calendar.RrulePanel.AbstractCard, {
525 getRule: function() {
526 var rrule = Tine.Calendar.RrulePanel.MONTHLYcard.superclass.getRule.call(this);
528 if (this.bydayRadio.checked) {
529 rrule.byday = this.wkNumber.getValue() + this.wkDay.getValue();
531 rrule.bymonthday = this.bymonthdayday.getValue();
537 initComponent: function() {
538 this.app = Tine.Tinebase.appMgr.get('Calendar');
540 this.intervalString = this.app.i18n._('Every {0}. Month');
542 this.idPrefix = Ext.id();
544 this.bydayRadio = new Ext.form.Radio({
546 boxLabel: this.app.i18n._('at the'),
547 name: this.idPrefix + 'byRadioGroup',
551 check: this.onByRadioCheck.createDelegate(this)
555 this.wkNumber = new Ext.form.ComboBox({
556 requiredGrant : 'editGrant',
559 triggerAction : 'all',
565 [1, this.app.i18n._('first') ],
566 [2, this.app.i18n._('second') ],
567 [3, this.app.i18n._('third') ],
568 [4, this.app.i18n._('fourth') ],
569 [-1, this.app.i18n._('last') ]
574 for (var i=0,d; i<7; i++) {
575 d = (i+Ext.DatePicker.prototype.startDay)%7
576 Tine.Calendar.RrulePanel.prototype.wkdays[d];
577 wkdayItems.push([Tine.Calendar.RrulePanel.prototype.wkdays[d], Date.dayNames[d]]);
580 this.wkDay = new Ext.form.ComboBox({
581 requiredGrant : 'editGrant',
584 triggerAction : 'all',
586 value : Tine.Calendar.RrulePanel.prototype.wkdays[Ext.DatePicker.prototype.startDay],
592 this.bymonthdayRadio = new Ext.form.Radio({
593 requiredGrant : 'editGrant',
595 boxLabel : this.app.i18n._('at the'),
596 name : this.idPrefix + 'byRadioGroup',
597 inputValue : 'BYMONTHDAY',
599 check: this.onByRadioCheck.createDelegate(this)
603 this.bymonthdayday = new Ext.form.NumberField({
604 requiredGrant : 'editGrant',
605 style : 'text-align:right;',
613 html: '<div style="padding-top: 5px;">' +
614 '<div style="position: relative;">' +
616 '<td style="position: relative;" width="65" id="' + this.idPrefix + 'bydayradio"></td>' +
617 '<td width="100" id="' + this.idPrefix + 'bydaywknumber"></td>' +
618 '<td width="110" id="' + this.idPrefix + 'bydaywkday"></td>' +
621 '<div style="position: relative;">' +
623 '<td width="65" id="' + this.idPrefix + 'bymonthdayradio"></td>' +
624 '<td width="40" id="' + this.idPrefix + 'bymonthdayday"></td>' +
631 render: this.onByRender
635 Tine.Calendar.RrulePanel.MONTHLYcard.superclass.initComponent.call(this);
638 onByRadioCheck: function(radio, checked) {
639 switch(radio.inputValue) {
641 this.bymonthdayday.setDisabled(checked);
644 this.wkNumber.setDisabled(checked);
645 this.wkDay.setDisabled(checked);
650 onByRender: function() {
651 var bybayradioel = Ext.get(this.idPrefix + 'bydayradio');
652 var bybaywknumberel = Ext.get(this.idPrefix + 'bydaywknumber');
653 var bybaywkdayel = Ext.get(this.idPrefix + 'bydaywkday');
655 var bymonthdayradioel = Ext.get(this.idPrefix + 'bymonthdayradio');
656 var bymonthdaydayel = Ext.get(this.idPrefix + 'bymonthdayday');
658 if (! (bybayradioel && bymonthdayradioel)) {
659 return this.onByRender.defer(100, this, arguments);
662 this.bydayRadio.render(bybayradioel);
663 this.wkNumber.render(bybaywknumberel);
664 this.wkNumber.wrap.setWidth(80);
665 this.wkDay.render(bybaywkdayel);
666 this.wkDay.wrap.setWidth(100);
668 this.bymonthdayRadio.render(bymonthdayradioel);
669 this.bymonthdayday.render(bymonthdaydayel);
672 setRule: function(rrule) {
673 Tine.Calendar.RrulePanel.MONTHLYcard.superclass.setRule.call(this, rrule);
676 this.bydayRadio.setValue(true);
677 this.bymonthdayRadio.setValue(false);
678 this.onByRadioCheck(this.bydayRadio, true);
679 this.onByRadioCheck(this.bymonthdayRadio, false);
681 var parts = rrule.byday.match(/([\-\d]{1,2})([A-Z]{2})/);
682 this.wkNumber.setValue(parts[1]);
683 this.wkDay.setValue(parts[2]);
687 if (rrule.bymonthday) {
688 this.bydayRadio.setValue(false);
689 this.bymonthdayRadio.setValue(true);
690 this.onByRadioCheck(this.bydayRadio, false);
691 this.onByRadioCheck(this.bymonthdayRadio, true);
693 this.bymonthdayday.setValue(rrule.bymonthday);
700 Tine.Calendar.RrulePanel.YEARLYcard = Ext.extend(Tine.Calendar.RrulePanel.AbstractCard, {
704 getRule: function() {
705 var rrule = Tine.Calendar.RrulePanel.MONTHLYcard.superclass.getRule.call(this);
707 if (this.bydayRadio.checked) {
708 rrule.byday = this.wkNumber.getValue() + this.wkDay.getValue();
710 rrule.bymonthday = this.bymonthdayday.getValue();
713 rrule.bymonth = this.bymonth.getValue();
717 initComponent: function() {
718 this.app = Tine.Tinebase.appMgr.get('Calendar');
720 this.intervalString = this.app.i18n._('Every {0}. Year');
722 this.idPrefix = Ext.id();
724 this.bydayRadio = new Ext.form.Radio({
725 requiredGrant : 'editGrant',
727 boxLabel : this.app.i18n._('at the'),
728 name : this.idPrefix + 'byRadioGroup',
729 inputValue : 'BYDAY',
731 check: this.onByRadioCheck.createDelegate(this)
735 this.wkNumber = new Ext.form.ComboBox({
736 requiredGrant : 'editGrant',
739 triggerAction : 'all',
746 [1, this.app.i18n._('first') ],
747 [2, this.app.i18n._('second') ],
748 [3, this.app.i18n._('third') ],
749 [4, this.app.i18n._('fourth') ],
750 [-1, this.app.i18n._('last') ]
755 for (var i=0,d; i<7; i++) {
756 d = (i+Ext.DatePicker.prototype.startDay)%7
757 Tine.Calendar.RrulePanel.prototype.wkdays[d];
758 wkdayItems.push([Tine.Calendar.RrulePanel.prototype.wkdays[d], Date.dayNames[d]]);
761 this.wkDay = new Ext.form.ComboBox({
762 requiredGrant : 'editGrant',
765 triggerAction : 'all',
767 value : Tine.Calendar.RrulePanel.prototype.wkdays[Ext.DatePicker.prototype.startDay],
774 this.bymonthdayRadio = new Ext.form.Radio({
775 requiredGrant : 'editGrant',
777 boxLabel : this.app.i18n._('at the'),
778 name : this.idPrefix + 'byRadioGroup',
779 inputValue : 'BYMONTHDAY',
782 check: this.onByRadioCheck.createDelegate(this)
786 this.bymonthdayday = new Ext.form.NumberField({
787 requiredGrant : 'editGrant',
788 style : 'text-align:right;',
795 for (var i=0; i<Date.monthNames.length; i++) {
796 monthItems.push([i+1, Date.monthNames[i]]);
799 this.bymonth = new Ext.form.ComboBox({
800 requiredGrant : 'editGrant',
803 triggerAction : 'all',
812 html: '<div style="padding-top: 5px;">' +
813 '<div style="position: relative;">' +
815 '<td style="position: relative;" width="65" id="' + this.idPrefix + 'bydayradio"></td>' +
816 '<td width="100" id="' + this.idPrefix + 'bydaywknumber"></td>' +
817 '<td width="110" id="' + this.idPrefix + 'bydaywkday"></td>' +
818 //'<td style="padding-left: 10px">' + this.app.i18n._('of') + '</td>' +
821 '<div style="position: relative;">' +
823 '<td width="65" id="' + this.idPrefix + 'bymonthdayradio"></td>' +
824 '<td width="40" id="' + this.idPrefix + 'bymonthdayday"></td>' +
826 '<td width="15" style="padding-left: 37px">' + this.app.i18n._('of') + '</td>' +
827 '<td width="100" id="' + this.idPrefix + 'bymonth"></td>' +
833 render: this.onByRender
836 Tine.Calendar.RrulePanel.YEARLYcard.superclass.initComponent.call(this);
839 onByRadioCheck: function(radio, checked) {
840 switch(radio.inputValue) {
842 this.bymonthdayday.setDisabled(checked);
845 this.wkNumber.setDisabled(checked);
846 this.wkDay.setDisabled(checked);
851 onByRender: function() {
852 var bybayradioel = Ext.get(this.idPrefix + 'bydayradio');
853 var bybaywknumberel = Ext.get(this.idPrefix + 'bydaywknumber');
854 var bybaywkdayel = Ext.get(this.idPrefix + 'bydaywkday');
856 var bymonthdayradioel = Ext.get(this.idPrefix + 'bymonthdayradio');
857 var bymonthdaydayel = Ext.get(this.idPrefix + 'bymonthdayday');
859 var bymonthel = Ext.get(this.idPrefix + 'bymonth');
861 if (! (bybayradioel && bymonthdayradioel)) {
862 return this.onByRender.defer(100, this, arguments);
865 this.bydayRadio.render(bybayradioel);
866 this.wkNumber.render(bybaywknumberel);
867 this.wkNumber.wrap.setWidth(80);
868 this.wkDay.render(bybaywkdayel);
869 this.wkDay.wrap.setWidth(100);
871 this.bymonthdayRadio.render(bymonthdayradioel);
872 this.bymonthdayday.render(bymonthdaydayel);
874 this.bymonth.render(bymonthel);
875 this.bymonth.wrap.setWidth(100);
878 setRule: function(rrule) {
879 Tine.Calendar.RrulePanel.MONTHLYcard.superclass.setRule.call(this, rrule);
882 this.bydayRadio.setValue(true);
883 this.bymonthdayRadio.setValue(false);
884 this.onByRadioCheck(this.bydayRadio, true);
885 this.onByRadioCheck(this.bymonthdayRadio, false);
887 var parts = rrule.byday.match(/([\-\d]{1,2})([A-Z]{2})/);
888 this.wkNumber.setValue(parts[1]);
889 this.wkDay.setValue(parts[2]);
893 if (rrule.bymonthday) {
894 this.bydayRadio.setValue(false);
895 this.bymonthdayRadio.setValue(true);
896 this.onByRadioCheck(this.bydayRadio, false);
897 this.onByRadioCheck(this.bymonthdayRadio, true);
899 this.bymonthdayday.setValue(rrule.bymonthday);
902 this.bymonth.setValue(rrule.bymonth);