public
Description: Click-draggable. Range-makeable. A better calendar.
Homepage: http://stephencelis.com/projects/timeframe
Clone URL: git://github.com/stephencelis/timeframe.git
Click here to lend your support to: timeframe and make a donation at www.pledgie.com !
Some fixes:

 - Multiple Timeframes should now work swimmingly
 - if(...) reeks of method invocation and bad form; if (...), anyone?
stephencelis (author)
Sun Jun 08 06:20:36 -0700 2008
commit  1674e42a7820bd4d862f67adfc06f0d98a48e7c2
tree    794ce091368b7dc188d39ada3a247f41f0d73d23
parent  401a36cfd0af8ba917ef6d5740adfc3dcfe8be3c
...
4
5
6
7
 
8
9
10
...
46
47
48
49
 
50
51
52
...
55
56
57
58
 
 
 
59
60
61
...
79
80
81
82
 
83
84
85
...
102
103
104
105
 
106
107
 
108
109
110
111
112
113
 
114
115
116
117
 
118
119
120
121
 
122
123
124
...
127
128
129
130
 
131
132
 
133
134
135
...
140
141
142
143
 
144
145
146
147
148
149
 
150
151
 
152
153
154
155
 
 
156
157
158
159
160
161
 
162
163
164
...
166
167
168
169
170
171
 
 
 
172
173
174
...
183
184
185
186
 
187
188
189
190
191
 
192
193
 
194
195
 
196
197
198
199
 
200
201
202
...
208
209
210
211
 
212
213
214
 
215
216
217
218
 
219
220
221
...
223
224
225
226
 
227
228
229
...
233
234
235
236
 
237
238
 
239
240
 
241
242
 
243
244
245
...
247
248
249
250
 
251
252
 
253
254
255
256
257
 
258
259
 
260
261
 
262
263
 
264
265
266
...
268
269
270
271
 
272
273
 
274
275
 
276
277
 
278
279
280
...
288
289
290
291
 
292
293
294
 
 
295
296
297
 
298
299
300
301
302
303
 
304
305
306
307
 
308
309
310
...
314
315
316
317
 
318
319
320
 
 
321
322
323
324
325
326
327
328
329
330
 
 
 
 
331
332
333
...
335
336
337
338
 
339
340
341
 
342
343
344
...
348
349
350
351
352
 
 
353
354
 
355
356
357
...
367
368
369
370
 
371
372
373
 
374
375
376
377
378
 
 
 
379
380
 
381
382
383
384
385
 
386
387
388
389
 
390
391
392
393
394
395
396
 
397
398
399
...
4
5
6
 
7
8
9
10
...
46
47
48
 
49
50
51
52
...
55
56
57
 
58
59
60
61
62
63
...
81
82
83
 
84
85
86
87
...
104
105
106
 
107
108
 
109
110
111
112
113
114
 
115
116
117
118
 
119
120
121
122
 
123
124
125
126
...
129
130
131
 
132
133
 
134
135
136
137
...
142
143
144
 
145
146
147
148
149
150
 
151
152
 
153
154
155
 
 
156
157
158
159
160
161
162
 
163
164
165
166
...
168
169
170
 
 
 
171
172
173
174
175
176
...
185
186
187
 
188
189
190
191
192
 
193
194
 
195
196
 
197
198
199
200
 
201
202
203
204
...
210
211
212
 
213
214
215
 
216
217
218
219
 
220
221
222
223
...
225
226
227
 
228
229
230
231
...
235
236
237
 
238
239
 
240
241
 
242
243
 
244
245
246
247
...
249
250
251
 
252
253
 
254
255
256
257
258
 
259
260
 
261
262
 
263
264
 
265
266
267
268
...
270
271
272
 
273
274
 
275
276
 
277
278
 
279
280
281
282
...
290
291
292
 
293
294
 
 
295
296
297
298
 
299
300
301
302
303
304
 
305
306
307
308
 
309
310
311
312
...
316
317
318
 
319
320
 
 
321
322
323
324
325
326
327
328
 
 
 
 
329
330
331
332
333
334
335
...
337
338
339
 
340
341
342
 
343
344
345
346
...
350
351
352
 
 
353
354
355
 
356
357
358
359
...
369
370
371
 
372
373
374
 
375
376
377
 
 
 
378
379
380
381
 
382
383
384
385
386
 
387
388
389
390
 
391
392
393
394
395
396
397
 
398
399
400
401
0
@@ -4,7 +4,7 @@
0
  * Freely distributable under the terms of an MIT-style license.
0
  * ------------------------------------------------------------- */
0
 
0
-if(typeof Prototype == 'undefined' || parseFloat(Prototype.Version.substring(0, 3)) < 1.6)
0
+if (typeof Prototype == 'undefined' || parseFloat(Prototype.Version.substring(0, 3)) < 1.6)
0
   throw 'Timeframe requires Prototype version 1.6 or greater.';
0
 
0
 // Checks for localized Datejs before defaulting to 'en-US'
0
@@ -46,7 +46,7 @@ var Timeframe = Class.create({
0
     this.latest = Date.parseToObject(this.options.get('latest'));
0
 
0
     this.calendars = [];
0
- this.element.insert(new Element('div', { id: 'calendars_container' }));
0
+ this.element.insert(new Element('div', { id: this.element.id + '_container' }));
0
     this.months.times(function(month) { this.createCalendar(month) }.bind(this));
0
 
0
     this.register().populate().refreshRange();
0
@@ -55,7 +55,9 @@ var Timeframe = Class.create({
0
   // Scaffolding
0
 
0
   createCalendar: function() {
0
- var calendar = new Element('table', { id: 'calendar_' + this.calendars.length, border: 0, cellspacing: 0, cellpadding: 5 });
0
+ var calendar = new Element('table', {
0
+ id: this.element.id + '_calendar_' + this.calendars.length, border: 0, cellspacing: 0, cellpadding: 5
0
+ });
0
     calendar.insert(new Element('caption'));
0
 
0
     var head = new Element('thead');
0
@@ -79,7 +81,7 @@ var Timeframe = Class.create({
0
     }.bind(this));
0
     calendar.insert(body);
0
 
0
- this.element.down('div#calendars_container').insert(calendar);
0
+ this.element.down('div#' + this.element.id + '_container').insert(calendar);
0
     this.calendars.push(calendar);
0
     this.months = this.calendars.length;
0
     return this;
0
@@ -102,23 +104,23 @@ var Timeframe = Class.create({
0
       var offset = (iterator.getDay() - this.weekOffset) % 7;
0
       var inactive = offset > 0 ? 'pre beyond' : false;
0
       iterator.setDate(iterator.getDate() - offset);
0
- if(iterator.getDate() > 1 && !inactive) {
0
+ if (iterator.getDate() > 1 && !inactive) {
0
         iterator.setDate(iterator.getDate() - 7);
0
- if(iterator.getDate() > 1) inactive = 'pre beyond';
0
+ if (iterator.getDate() > 1) inactive = 'pre beyond';
0
       }
0
 
0
       calendar.select('td').each(function(day) {
0
         day.date = new Date(iterator); // Is this expensive (we unload these later)? We could store the epoch time instead.
0
         day.update(day.date.getDate()).writeAttribute('class', inactive || 'active');
0
- if((this.earliest && day.date < this.earliest) || (this.latest && day.date > this.latest))
0
+ if ((this.earliest && day.date < this.earliest) || (this.latest && day.date > this.latest))
0
           day.addClassName('unselectable');
0
         else
0
           day.addClassName('selectable');
0
- if(iterator.toString() === new Date().neutral().toString()) day.addClassName('today');
0
+ if (iterator.toString() === new Date().neutral().toString()) day.addClassName('today');
0
         day.baseClass = day.readAttribute('class');
0
 
0
         iterator.setDate(iterator.getDate() + 1);
0
- if(iterator.getDate() == 1) inactive = inactive ? false : 'post beyond';
0
+ if (iterator.getDate() == 1) inactive = inactive ? false : 'post beyond';
0
       }.bind(this));
0
 
0
       month.setMonth(month.getMonth() + 1);
0
@@ -127,9 +129,9 @@ var Timeframe = Class.create({
0
   },
0
 
0
   _buildButtons: function() {
0
- var buttonList = new Element('ul', { id: 'timeframe_menu' });
0
+ var buttonList = new Element('ul', { id: this.element.id + '_menu' });
0
     this.buttons.each(function(pair) {
0
- if(pair.value.get('element'))
0
+ if (pair.value.get('element'))
0
         pair.value.get('element').addClassName('timeframe_button').addClassName(pair.key);
0
       else {
0
         var item = new Element('li');
0
@@ -140,25 +142,25 @@ var Timeframe = Class.create({
0
         buttonList.insert(item);
0
       }
0
     }.bind(this))
0
- if(buttonList.childNodes.length > 0) this.element.insert({ top: buttonList });
0
+ if (buttonList.childNodes.length > 0) this.element.insert({ top: buttonList });
0
     this.clearButton = new Element('span', { className: 'clear' }).update(new Element('span').update('X'));
0
     return this;
0
   },
0
 
0
   _buildFields: function() {
0
- var fieldset = new Element('div', { id: 'timeframe_fields' });
0
+ var fieldset = new Element('div', { id: this.element.id + '_fields' });
0
     this.fields.each(function(pair) {
0
- if(pair.value)
0
+ if (pair.value)
0
         pair.value.addClassName('timeframe_field').addClassName(pair.key);
0
       else {
0
- var container = new Element('div', { id: pair.key + 'field_container' });
0
- this.fields.set(pair.key, new Element('input', { id: pair.key + 'field', name: pair.key + 'field', type: 'text', value: '' }));
0
+ var container = new Element('div', { id: pair.key + this.element.id + '_field_container' });
0
+ this.fields.set(pair.key, new Element('input', { id: this.element.id + '_' + pair.key + 'field', name: pair.key + 'field', type: 'text', value: '' }));
0
         container.insert(new Element('label', { 'for': pair.key + 'field' }).update(pair.key));
0
         container.insert(this.fields.get(pair.key));
0
         fieldset.insert(container);
0
       }
0
     }.bind(this));
0
- if(fieldset.childNodes.length > 0) this.element.insert(fieldset);
0
+ if (fieldset.childNodes.length > 0) this.element.insert(fieldset);
0
     this.parseField('start').refreshField('start').parseField('end').refreshField('end').initDate = new Date(this.date);
0
     return this;
0
   },
0
@@ -166,9 +168,9 @@ var Timeframe = Class.create({
0
   // Event registration
0
 
0
   register: function() {
0
- document.observe('click', this.eventClick.bind(this));
0
- document.observe('mousedown', this.eventMouseDown.bind(this));
0
- document.observe('mouseover', this.eventMouseOver.bind(this));
0
+ this.element.observe('click', this.eventClick.bind(this));
0
+ this.element.observe('mousedown', this.eventMouseDown.bind(this));
0
+ this.element.observe('mouseover', this.eventMouseOver.bind(this));
0
     document.observe('mouseup', this.eventMouseUp.bind(this));
0
     document.observe('unload', this.unregister.bind(this));
0
     // mousemove listener for Opera in _disableTextSelection
0
@@ -183,20 +185,20 @@ var Timeframe = Class.create({
0
     var field = this.fields.get(fieldName);
0
     field.observe('focus', function() { field.hasFocus = true; this.parseField(fieldName, true); }.bind(this));
0
     field.observe('blur', function() { this.refreshField(fieldName); }.bind(this));
0
- new Form.Element.Observer(field, 0.2, function(element, value) { if(element.hasFocus) this.parseField(fieldName, true); }.bind(this));
0
+ new Form.Element.Observer(field, 0.2, function(element, value) { if (element.hasFocus) this.parseField(fieldName, true); }.bind(this));
0
     return this;
0
   },
0
 
0
   _disableTextSelection: function() {
0
- if(Prototype.Browser.IE) {
0
+ if (Prototype.Browser.IE) {
0
       this.element.onselectstart = function(event) {
0
- if(!/input|textarea/i.test(Event.element(event).tagName)) return false;
0
+ if (!/input|textarea/i.test(Event.element(event).tagName)) return false;
0
       };
0
- } else if(Prototype.Browser.Opera) {
0
+ } else if (Prototype.Browser.Opera) {
0
       document.observe('mousemove', this.handleMouseMove.bind(this));
0
     } else {
0
       this.element.onmousedown = function(event) {
0
- if(!/input|textarea/i.test(Event.element(event).tagName)) return false;
0
+ if (!/input|textarea/i.test(Event.element(event).tagName)) return false;
0
       };
0
     }
0
     return this;
0
@@ -208,14 +210,14 @@ var Timeframe = Class.create({
0
     var field = this.fields.get(fieldName);
0
     var date = Date.parseToObject(this.fields.get(fieldName).value);
0
     var failure = this.validateField(fieldName, date);
0
- if(failure != 'hard') {
0
+ if (failure != 'hard') {
0
       this.range.set(fieldName, date);
0
       field.removeClassName('error');
0
- } else if(field.hasFocus)
0
+ } else if (field.hasFocus)
0
       field.addClassName('error');
0
     var date = Date.parseToObject(this.range.get(fieldName));
0
     this.date = date || new Date();
0
- if(populate && date) this.populate()
0
+ if (populate && date) this.populate()
0
     this.refreshRange();
0
     return this;
0
   },
0
@@ -223,7 +225,7 @@ var Timeframe = Class.create({
0
   refreshField: function(fieldName) {
0
     var field = this.fields.get(fieldName);
0
     var initValue = field.value;
0
- if(this.range.get(fieldName)) {
0
+ if (this.range.get(fieldName)) {
0
       field.value = typeof Date.CultureInfo == 'undefined' ? this.range.get(fieldName).strftime(this.format) : this.range.get(fieldName).toString(this.format);
0
     } else
0
       field.value = '';
0
@@ -233,13 +235,13 @@ var Timeframe = Class.create({
0
   },
0
 
0
   validateField: function(fieldName, date) {
0
- if(!date) return;
0
+ if (!date) return;
0
     var error;
0
- if((this.earliest && date < this.earliest) || (this.latest && date > this.latest))
0
+ if ((this.earliest && date < this.earliest) || (this.latest && date > this.latest))
0
       error = 'hard';
0
- else if(fieldName == 'start' && this.range.get('end') && date > this.range.get('end'))
0
+ else if (fieldName == 'start' && this.range.get('end') && date > this.range.get('end'))
0
       error = 'soft';
0
- else if(fieldName == 'end' && this.range.get('start') && date < this.range.get('start'))
0
+ else if (fieldName == 'end' && this.range.get('start') && date < this.range.get('start'))
0
       error = 'soft';
0
     return error;
0
   },
0
@@ -247,20 +249,20 @@ var Timeframe = Class.create({
0
   // Event handling
0
 
0
   eventClick: function(event) {
0
- if(!event.element().ancestors) return;
0
+ if (!event.element().ancestors) return;
0
     var el;
0
- if(el = event.findElement('a.timeframe_button'))
0
+ if (el = event.findElement('a.timeframe_button'))
0
       this.handleButtonClick(event, el);
0
   },
0
 
0
   eventMouseDown: function(event) {
0
- if(!event.element().ancestors) return;
0
+ if (!event.element().ancestors) return;
0
     var el, em;
0
- if(el = event.findElement('span.clear')) {
0
+ if (el = event.findElement('span.clear')) {
0
       el.down('span').addClassName('active');
0
- if(em = event.findElement('td.selectable'))
0
+ if (em = event.findElement('td.selectable'))
0
         this.handleDateClick(em, true);
0
- } else if(el = event.findElement('td.selectable'))
0
+ } else if (el = event.findElement('td.selectable'))
0
       this.handleDateClick(el);
0
     else return;
0
   },
0
@@ -268,13 +270,13 @@ var Timeframe = Class.create({
0
   handleButtonClick: function(event, element) {
0
     var el;
0
     var movement = this.months > 1 ? this.months - 1 : 1;
0
- if(element.hasClassName('next'))
0
+ if (element.hasClassName('next'))
0
       this.date.setMonth(this.date.getMonth() + movement);
0
- else if(element.hasClassName('previous'))
0
+ else if (element.hasClassName('previous'))
0
       this.date.setMonth(this.date.getMonth() - movement);
0
- else if(element.hasClassName('today'))
0
+ else if (element.hasClassName('today'))
0
       this.date = new Date();
0
- else if(element.hasClassName('reset'))
0
+ else if (element.hasClassName('reset'))
0
       this.reset();
0
     this.populate().refreshRange();
0
   },
0
@@ -288,23 +290,23 @@ var Timeframe = Class.create({
0
 
0
   handleDateClick: function(element, couldClear) {
0
     this.mousedown = this.dragging = true;
0
- if(this.stuck)
0
+ if (this.stuck)
0
       this.stuck = false;
0
- else if(couldClear) {
0
- if(!element.hasClassName('startrange')) return;
0
+ else if (couldClear) {
0
+ if (!element.hasClassName('startrange')) return;
0
     } else {
0
       this.stuck = true;
0
- setTimeout(function() { if(this.mousedown) this.stuck = false; }.bind(this), 200);
0
+ setTimeout(function() { if (this.mousedown) this.stuck = false; }.bind(this), 200);
0
     }
0
     this.getPoint(element.date);
0
   },
0
 
0
   getPoint: function(date) {
0
- if(this.range.get('start') && this.range.get('start').toString() == date && this.range.get('end'))
0
+ if (this.range.get('start') && this.range.get('start').toString() == date && this.range.get('end'))
0
       this.startdrag = this.range.get('end');
0
     else {
0
       this.clearButton.hide();
0
- if(this.range.get('end') && this.range.get('end').toString() == date)
0
+ if (this.range.get('end') && this.range.get('end').toString() == date)
0
         this.startdrag = this.range.get('start');
0
       else
0
         this.startdrag = this.range.set('start', this.range.set('end', date));
0
@@ -314,20 +316,20 @@ var Timeframe = Class.create({
0
 
0
   eventMouseOver: function(event) {
0
     var el;
0
- if(!this.dragging)
0
+ if (!this.dragging)
0
       this.toggleClearButton(event);
0
- else if(event.findElement('span.clear span.active'));
0
- else if(el = event.findElement('td.selectable'))
0
+ else if (event.findElement('span.clear span.active'));
0
+ else if (el = event.findElement('td.selectable'))
0
       this.extendRange(el.date);
0
     else this.toggleClearButton(event);
0
   },
0
 
0
   toggleClearButton: function(event) {
0
     var el;
0
- if(event.element().ancestors && event.findElement('td.selected')) {
0
- if(el = this.element.select('#calendar_0 .pre.selected').first());
0
- else if(el = this.element.select('.active.selected').first());
0
- if(el) Element.insert(el, { top: this.clearButton });
0
+ if (event.element().ancestors && event.findElement('td.selected')) {
0
+ if (el = this.element.select('#calendar_0 .pre.selected').first());
0
+ else if (el = this.element.select('.active.selected').first());
0
+ if (el) Element.insert(el, { top: this.clearButton });
0
       this.clearButton.show().select('span').first().removeClassName('active');
0
     } else
0
       this.clearButton.hide();
0
@@ -335,10 +337,10 @@ var Timeframe = Class.create({
0
 
0
   extendRange: function(date) {
0
     this.clearButton.hide();
0
- if(date > this.startdrag) {
0
+ if (date > this.startdrag) {
0
       this.range.set('start', this.startdrag);
0
       this.range.set('end', date);
0
- } else if(date < this.startdrag) {
0
+ } else if (date < this.startdrag) {
0
       this.range.set('start', date);
0
       this.range.set('end', this.startdrag);
0
     } else {
0
@@ -348,10 +350,10 @@ var Timeframe = Class.create({
0
   },
0
 
0
   eventMouseUp: function(event) {
0
- if(!this.dragging) return;
0
- if(!this.stuck) {
0
+ if (!this.dragging) return;
0
+ if (!this.stuck) {
0
       this.dragging = false;
0
- if(event.findElement('span.clear span.active'))
0
+ if (event.findElement('span.clear span.active'))
0
         this.clearRange();
0
     }
0
     this.mousedown = false;
0
@@ -367,33 +369,33 @@ var Timeframe = Class.create({
0
   refreshRange: function() {
0
     this.element.select('td').each(function(day) {
0
       day.writeAttribute('class', day.baseClass);
0
- if(this.range.get('start') && this.range.get('end') && this.range.get('start') <= day.date && day.date <= this.range.get('end')) {
0
+ if (this.range.get('start') && this.range.get('end') && this.range.get('start') <= day.date && day.date <= this.range.get('end')) {
0
         var baseClass = day.hasClassName('beyond') ? 'beyond_' : day.hasClassName('today') ? 'today_' : null;
0
         var state = this.stuck || this.mousedown ? 'stuck' : 'selected';
0
- if(baseClass) day.addClassName(baseClass + state);
0
+ if (baseClass) day.addClassName(baseClass + state);
0
         day.addClassName(state);
0
         var rangeClass = '';
0
- if(this.range.get('start').toString() == day.date) rangeClass += 'start';
0
- if(this.range.get('end').toString() == day.date) rangeClass += 'end';
0
- if(rangeClass.length > 0) day.addClassName(rangeClass + 'range');
0
+ if (this.range.get('start').toString() == day.date) rangeClass += 'start';
0
+ if (this.range.get('end').toString() == day.date) rangeClass += 'end';
0
+ if (rangeClass.length > 0) day.addClassName(rangeClass + 'range');
0
       }
0
- if(Prototype.Browser.Opera) {
0
+ if (Prototype.Browser.Opera) {
0
         day.unselectable = 'on'; // Trick Opera into refreshing the selection (FIXME)
0
         day.unselectable = null;
0
       }
0
     }.bind(this));
0
- if(this.dragging) this.refreshField('start').refreshField('end');
0
+ if (this.dragging) this.refreshField('start').refreshField('end');
0
   },
0
 
0
   handleMouseMove: function(event) {
0
- if(event.findElement('#' + this.element.id + ' td')) window.getSelection().removeAllRanges(); // More Opera trickery
0
+ if (event.findElement('#' + this.element.id + ' td')) window.getSelection().removeAllRanges(); // More Opera trickery
0
   }
0
 });
0
 
0
 Object.extend(Date, {
0
   parseToObject: function(string) {
0
     var date = Date.parse(string);
0
- if(!date) return null;
0
+ if (!date) return null;
0
     date = new Date(date);
0
     return (date == 'Invalid Date' || date == 'NaN') ? null : date.neutral();
0
   }

Comments

    No one has commented yet.