Permalink
Browse files

Initialization

  • Loading branch information...
0 parents commit 7fff7556fde246fd8e85ffe56241e8a745c1b29d @stephencelis committed Apr 21, 2008
1 .gitignore
@@ -0,0 +1 @@
+.DS_Store
20 MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008 Stephen Celis
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
70 README.markdown
@@ -0,0 +1,70 @@
+Timeframe
+=========
+
+Click-draggable. Range-makeable. A better calendar.
+
+
+The code:
+---------
+
+ new Timeframe(element, options);
+
+
+### Options available:
+
+* `calendars`:
+ The number of calendar months showing at once (default: `2`).
+
+* `format`:
+ The strftime format for the dates in the input fields (default:
+ `%b %d, %Y`).
+
+* `weekoffset`:
+ The weekday offset (use `1` to start the week on Monday).
+
+* `startfield`, `endfield`:
+ Declare the range start and end input tags (by default, these are generated
+ with the timeframe).
+
+* `previousbutton`, `todaybutton`, `nextbutton`, `resetbutton`:
+ Declare the navigational buttons (these are also generated by default with
+ the timeframe).
+
+
+### Also!!:
+
+The startfield and endfield are parsed with `Date.parse()`, so feel free to
+use [Datejs.js](http://datejs.com) for ninja-quick date-parsing.
+
+
+An example:
+-----------
+
+ <script type="text/javascript" charset="utf-8">
+ //<![CDATA[
+ new Timeframe('calendars', {
+ startfield: 'start',
+ endfield: 'end',
+ previousbutton: 'previous',
+ todaybutton: 'today',
+ nextbutton: 'next',
+ resetbutton: 'reset' });
+ //]]>
+ </script>
+
+
+Dependencies
+------------
+
+Timeframe requires [Prototype](http://prototypejs.org) 1.6.
+
+
+Download
+--------
+
+Find the latest version of Timeframe on
+[Github](http://github.com/stephencelis/timeframe).
+
+
+Copyright (c) 2008 [Stephen Celis](http://stephencelis.com), released under
+the MIT license.
15 TODO
@@ -0,0 +1,15 @@
+THINGS TODO (mostly in order of importance)
+
+* Let's unit test
+
+* Oh, and verify this works in all modern browsers
+
+* Internationalization
+
+* Range options: minrange, maxrange, defaultrange
+
+* Prettier initial styles, especially for non-Safari browsers
+
+* iPhone optimization
+
+* Never too late for code cleanup and optimizations
119 example/example.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
+ <title>Timeframe</title>
+
+ <link rel="stylesheet" href="stylesheets/timeframe.css" type="text/css" media="screen" title="timeframe" charset="utf-8"/>
+</head>
+<body>
+ <div id="page_container">
+ <h1><a href="http://github.com/stephencelis/timeframe">Timeframe</a><small>, by Stephen Celis</small></h1>
+ <p>
+ <em>Click-draggable. Range-makeable. A better calendar.</em>
+ </p>
+ <h2>
+ The code:
+ </h2>
+ <pre><code>
+ new Timeframe(element, options);
+ </code></pre>
+ <h3>
+ Options available:
+ </h3>
+ <dl>
+ <dt><code>calendars</code></dt>
+ <dd>The number of calendar months showing at once (default: <code>2</code>).</dd>
+ <dt><code>format</code></dt>
+ <dd>The <a href="http://developer.apple.com/documentation/Darwin/Reference/ManPages/man3/strftime.3.html">strftime</a> format for the dates in the input fields (default: <code>%b %d, %Y</code>).</dd>
+ <dt><code>weekoffset</code></dt>
+ <dd>The weekday offset (use <code>1</code> to start the week on Monday).</dd>
+ <dt><code>startfield</code>, <code>endfield</code></dt>
+ <dd>Declare the range start and end <code>input</code> tags (by default, these are generated with the timeframe).</dd>
+ <dt><code>previousbutton</code>, <code>todaybutton</code>, <code>nextbutton</code>, <code>resetbutton</code></dt>
+ <dd>Declare the navigational buttons (by default, these are also generated with the timeframe).</dd>
+ </dl>
+ <h3>
+ Also!!:
+ </h3>
+ <p>
+ The <code>startfield</code> and <code>endfield</code> are parsed with <code>Date.parse()</code>, so feel free to use <a href="http://datejs.com/">Datejs.js</a> for ninja-quick date-parsing.
+ </p>
+ <h2>
+ An example:
+ </h2>
+ <div id="example">
+ <ul id="calendar_navigation">
+ <li><a href="#" onclick="return false;" id="previous">&larr;</a></li>
+ <li><a href="#" onclick="return false;" id="today">T</a></li>
+ <li><a href="#" onclick="return false;" id="next">&rarr;</a></li>
+ </ul>
+ <div id="calendars"></div>
+ <div id="calendar_form">
+ <div id="labels">
+ <label for="start">Embarkation</label> and <label for="end">return</label> (<a href="#" id="reset">reset</a>):
+ </div>
+ <div id="fields">
+ <span>
+ <input type="text" name="start" value="" id="start"/>
+ &ndash;
+ <input type="text" name="end" value="" id="end"/>
+ </span>
+ </div>
+ </div>
+ </div>
+ <p>
+ Generated from this code (see the source for more detail):
+ </p>
+ <pre><code>
+ &lt;script type=&quot;text/javascript&quot; charset=&quot;utf-8&quot;&gt;
+ //&lt;![CDATA[
+ new Timeframe(&#x27;calendars&#x27;, {
+ startfield: &#x27;start&#x27;,
+ endfield: &#x27;end&#x27;,
+ previousbutton: &#x27;previous&#x27;,
+ todaybutton: &#x27;today&#x27;,
+ nextbutton: &#x27;next&#x27;,
+ resetbutton: &#x27;reset&#x27; });
+ //]]&gt;
+ &lt;/script&gt;
+ </code></pre>
+ <h2>Contact/contribution:</h2>
+ <p>
+ Timeframe is open source and available for forking, pushing, and pulling at <a href="http://github.com">Github</a>:
+ </p>
+ <p>
+ <a href="http://github.com/stephencelis/timeframe">http://github.com/stephencelis/timeframe</a>
+ </p>
+ <p>
+ Contact me with questions/comments at
+ <script type="text/javascript">
+ //<![CDATA[
+ document.write(
+"<n uers=\"znvygb:fgrcura\100fgrcurapryvf\056pbz\">fgrcura\100fgrcurapryvf\056pbz<\057n>".replace(/[a-zA-Z]/g, function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);}));
+ //]]>
+ </script>.
+ </p>
+ <p>
+ Learn more about me at <a href="http://stephencelis.com">http://stephencelis.com</a>.
+ </p>
+ <p>
+ Copyright &copy; 2008 Stephen Celis. Provided under the MIT License.
+ </p>
+ </div>
+ <script type="text/javascript" charset="utf-8" src="scripts/prototype.js"></script>
+ <script type="text/javascript" charset="utf-8" src="../timeframe.js"></script>
+ <script type="text/javascript" charset="utf-8">
+ //<![CDATA[
+ new Timeframe('calendars', {
+ startfield: 'start',
+ endfield: 'end',
+ previousbutton: 'previous',
+ todaybutton: 'today',
+ nextbutton: 'next',
+ resetbutton: 'reset' });
+ //]]>
+ </script>
+</body>
+</html>
BIN example/images/closebox.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN example/images/closebox_selected.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4,225 example/scripts/prototype.js
4,225 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
209 example/stylesheets/timeframe.css
@@ -0,0 +1,209 @@
+body {
+ font-family: Arial, Helvetica, sans-serif;
+ text-shadow: 0 0 0 #fff;
+ margin: 0;
+}
+ #page_container {
+ margin: auto;
+ width: 600px;
+ }
+ p {
+ line-height: 1.4;
+ }
+ dd {
+ padding: 0 0 10px;
+ }
+ #example {
+ padding: 5px 130px 40px;
+ background: #eee;
+ border: 3px solid #fff;
+
+ -webkit-box-shadow: 0 1px 10px #999;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 7px;
+ }
+
+/* Navigation */
+
+#calendar_navigation {
+ list-style-type: none;
+ padding: 6px 136px;
+
+ text-shadow: none;
+}
+ #calendar_navigation li {
+ float: left;
+ width: 20px;
+ height: 20px;
+ }
+ #calendar_navigation a {
+ display: block;
+ padding: 1px 0 2px;
+ text-align: center;
+ text-decoration: none;
+
+ -webkit-box-shadow: 0 1px 2px #999;
+ }
+ #calendar_navigation a.previous, #calendar_navigation a.next {
+ background: #fff;
+ color: #468966;
+ display: block;
+ text-align: center;
+ text-decoration: none;
+ }
+ #calendar_navigation a.today:hover {
+ background: #246744;
+ }
+ #calendar_navigation a.today:active {
+ background: #024522;
+ }
+ #calendar_navigation a.previous:hover, #calendar_navigation a.next:hover {
+ background: #ccc;
+ }
+ #calendar_navigation a.previous:active, #calendar_navigation a.next:active {
+ background: #aaa;
+ }
+ #calendar_navigation a.previous {
+ -webkit-border-top-left-radius: 10px;
+ -webkit-border-bottom-left-radius: 10px;
+ -moz-border-radius-topleft: 11px;
+ -moz-border-radius-bottomleft: 11px;
+ }
+ #calendar_navigation a.next {
+ -webkit-border-top-right-radius: 10px;
+ -webkit-border-bottom-right-radius: 10px;
+ -moz-border-radius-topright: 11px;
+ -moz-border-radius-bottomright: 11px;
+ }
+
+/* Calendar*/
+
+table {
+ margin: 0 6px 15px;
+ float: left;
+ font-size: 15px;
+
+ text-shadow: none;
+ -webkit-box-shadow: 0px 2px 6px #999;
+}
+ caption {
+ text-shadow: 0 0 0 #fff;
+ }
+ thead {
+ background: #222;
+ color: #eee;
+ }
+ tbody {
+ background: #fff;
+ }
+ #calendar_0 {
+ clear: both;
+ }
+ th, td {
+ height: 18px;
+ margin: 0;
+ padding: 3px 1px 1px;
+ text-align: center;
+ width: 20px;
+ }
+ td {
+ cursor: pointer;
+ }
+ td:hover {
+ background: #bbb;
+ }
+ td.selected:hover {
+ background: #e99a27;
+ }
+ .pre, .post {
+ background: #aaa;
+ color: #ccc;
+ }
+ .today {
+ background: #468966;
+ color: #eee;
+ }
+ .selected {
+ background: #ffb03b;
+ }
+ .active.startrange {
+ -webkit-border-top-left-radius: 10px;
+ -webkit-border-bottom-left-radius: 10px;
+ -moz-border-radius-topleft: 11px;
+ -moz-border-radius-bottomleft: 11px;
+ }
+ .active.endrange {
+ -webkit-border-top-right-radius: 10px;
+ -webkit-border-bottom-right-radius: 10px;
+ -moz-border-radius-topright: 11px;
+ -moz-border-radius-bottomright: 11px;
+ }
+ .startrange, .endrange {
+ cursor: ew-resize;
+ }
+ .pre.selected, .post.selected {
+ background: #999 ;
+ }
+ .today.selected {
+ background: #b64926;
+ }
+ .stuck {
+ background: #e99a27;
+ }
+ .pre.stuck, .post.stuck {
+ background: #888 ;
+ }
+ .today.stuck {
+ background: #8e2800;
+ }
+
+ a.clear {
+ color: transparent;
+ display: block;
+ height: 0;
+ position: absolute;
+ width: 0;
+ }
+ a.clear span {
+ background-image: url(../images/closebox.png);
+ display: block;
+ height: 30px;
+ left: -18px;
+ position: relative;
+ top: -18px;
+ width: 30px;
+ }
+ a.clear.active span {
+ background-image: url(../images/closebox_selected.png);
+ }
+
+/* Form */
+
+#calendar_form {
+ clear: both;
+}
+ #calendar_form #labels label {
+ font-weight: bold;
+ }
+ #calendar_form #fields span {
+ background: #ccc;
+ border-bottom: 1px solid #999;
+ display: inline-block;
+ padding: 0 4px;
+
+ -webkit-box-shadow: 0 1px 1px #ccc;
+ }
+ #calendar_form #fields span input {
+ border-style: none;
+ background-color: transparent;
+ font-size: 14px;
+ padding: 2px 6px;
+ width: 96px;
+
+ -webkit-border-radius: 10px;
+ -moz-border-radius: 11px;
+ }
+ #calendar_form #fields span input:focus {
+ outline: none;
+ background: #aaa;
+ }
391 timeframe.js
@@ -0,0 +1,391 @@
+Date.weekdays = $w('Sunday Monday Tuesday Wednesday Thursday Friday Saturday');
+Date.months = $w('January February March April May June July August September October November December');
+
+var Timeframes = [];
+
+var Timeframe = Class.create({
+ /**
+ * Syntax:
+ *
+ * new Timeframe('el_id', {
+ * calendars: 2,
+ * weekoffset: 0
+ * });
+ */
+ Version: '0.1 Calamari',
+
+ initialize: function(element, options) {
+ Timeframes.push(this);
+
+ this.container = $(element);
+ this.disableSelection(this.container);
+
+ this.options = $H({
+ calendars: 2,
+ format: '%b %d, %Y',
+ weekoffset: 0
+ }).merge(options || {});
+
+ this.calendars = this.options.get('calendars');
+
+ // Look for custom buttons:
+ this.buttons = $H({
+ previous: $(this.options.get('previousbutton')),
+ today: $(this.options.get('todaybutton')),
+ next: $(this.options.get('nextbutton')),
+ reset: $(this.options.get('resetbutton'))
+ });
+
+ // Look for custom fields:
+ this.fields = $H({
+ start: $(this.options.get('startfield')),
+ end: $(this.options.get('endfield'))
+ });
+
+ this.format = this.options.get('format');
+ this.weekoffset = this.options.get('weekoffset');
+ this.weekdayNames = Date.weekdays; // this.weekdayNames = Date.internationalizations.get(this.options.get('locale'));
+ this.monthNames = Date.months; // this.monthNames = Date.internationalizations.get(this.options.get('locale'));
+ this.date = new Date();
+ this.defaultDate = new Date(this.date)
+
+ this.buildButtons();
+ this.calendars.times(function(calendar) { this.buildCalendar(calendar) }.bind(this));
+ this.buildFields();
+ this.populate();
+ this.activate();
+ },
+
+ // Make sure dragging doesn't select any calendar text
+ disableSelection: function(element) {
+ element.onselectstart = function() { return false; };
+ element.unselectable = 'on';
+ element.style.MozUserSelect = 'none';
+ element.style.cursor = 'default';
+ },
+
+ buildButtons: function() {
+ var list = new Element('ul', { id: 'timeframe_menu' });
+ this.buttons.each(function(pair) {
+ if(pair.value)
+ pair.value.addClassName('timeframe_button').addClassName(pair.key);
+ else {
+ var item = new Element('li');
+ this.buttons.set(pair.key, new Element('a', { className: 'timeframe_button ' + pair.key, href: '#', onclick: 'return false;' }).update(pair.key));
+ Element.insert(item, { bottom: this.buttons.get(pair.key) });
@Caged
Caged Apr 23, 2008

In 1.6.* this can be done using item.insert(content); bottom is the default if not specified with a hash.

+ Element.insert(list, { bottom: item });
+ }
+ }.bind(this))
+ if(list.childNodes.length > 0) Element.insert(this.container, { bottom: list });
+
+ this.clearButton = new Element('a', { className: 'clear', href: '#', onclick: 'return false;' }).update(new Element('span').update('X'));
+ },
+
+ buildCalendar: function(calendarNumber) {
+ var calendar = new Element('table', { id: 'calendar_' + calendarNumber, border: 0, cellspacing: 0, cellpadding: 5 });
+ Element.insert(calendar, { top: new Element('caption') });
+ Element.insert(calendar, { bottom: this.buildHead() });
+ Element.insert(calendar, { bottom: this.buildBody() });
+ Element.insert(this.container, { bottom: calendar });
+ },
+
+ buildHead: function() {
+ var head = new Element('thead');
+ var row = new Element('tr');
+ this.weekdayNames.length.times(function(column) {
+ var weekday = this.weekdayNames[(column + this.weekoffset) % 7];
+ var cell = new Element('th', { scope: 'col', abbr: weekday }).update(weekday.substring(0,1));
+ Element.insert(row, { bottom: cell });
+ }.bind(this));
+ Element.insert(head, { bottom: row });
+ return head;
+ },
+
+ buildBody: function() {
+ var body = new Element('tbody');
+ (6).times(function(rowNumber) {
+ var row = new Element('tr');
+ this.weekdayNames.length.times(function(column) {
+ // var weekday = this.weekdayNames[column]; # => use rowNumber
+ var cell = new Element('td');
+ Element.insert(row, { bottom: cell });
+ }); //.bind(this));
+ Element.insert(body, { bottom: row });
+ }.bind(this));
+ return body;
+ },
+
+ buildFields: function() {
+ var fieldset = new Element('div', { id: 'timeframe_fields' });
+ this.fields.each(function(pair) {
+ if(pair.value)
+ pair.value.addClassName('timeframe_field').addClassName(pair.key);
+ else {
+ var container = new Element('div', { id: pair.key + 'field_container' });
+ Element.insert(container, { bottom: new Element('label', { 'for': pair.key + 'field' }) });
+ Element.insert(container, { bottom: new Element('input', { id: pair.key + 'field', name: pair.key + 'field', type: 'text', value: '' }) });
+ }
+ }.bind(this));
+ this.startfield = this.fields.get('start');
+ this.endfield = this.fields.get('end');
+ },
+
+ populate: function() {
+ var month = this.date.neutral();
+ month.setDate(1);
+ this.calendars.times(function(n) {
+ var calendar = $('calendar_' + n);
+ var caption = calendar.select('caption').first();
+ caption.update(this.monthNames[month.getMonth()] + ' ' + month.getFullYear());
+
+ var iterator = new Date(month);
+ var offset = (iterator.getDay() - this.weekoffset) % 7;
+ var inactive = offset > 0 ? 'pre' : false;
+ iterator.setDate(iterator.getDate() - offset);
+ if(iterator.getDate() > 1 && !inactive) {
+ iterator.setDate(iterator.getDate() - 7);
+ if(iterator.getDate() > 1) inactive = 'pre';
+ }
+
+ calendar.select('td').each(function(day) {
+ day.date = new Date(iterator); // Is this expensive (we unload these later)? We could store the epoch time instead.
+ day.update(day.date.getDate()).setAttribute('class', inactive || 'active');
+ if(iterator.toString() === new Date().neutral().toString()) day.addClassName('today');
+ iterator.setDate(iterator.getDate() + 1);
+ if(iterator.getDate() == 1) inactive = inactive ? false : 'post';
+ });
+
+ month.setMonth(month.getMonth() + 1);
+ }.bind(this));
+ this.refreshRange();
+ },
+
+ activate: function() {
+ document.observe('click', this.handleClick.bindAsEventListener(this));
@Caged
Caged Apr 23, 2008

In 1.6.* bindAsEventListener isn’t really needed unless your using on* events. bind is the preferred way.

+ document.observe('mousedown', this.handleMousedown.bindAsEventListener(this));
+ document.observe('mouseover', this.handleMouseover.bindAsEventListener(this));
+ document.observe('mouseup', this.setPoint.bindAsEventListener(this));
+ document.observe('unload', this.deactivate.bindAsEventListener(this));
+ this.aimFieldObservers();
+ },
+
+ aimFieldObservers: function(field, assignment) {
+ [this.startfield, this.endfield].invoke('observe', 'focus', this.declareFocus.bindAsEventListener(this));
+ [this.startfield, this.endfield].invoke('observe', 'blur', this.declareBlur.bindAsEventListener(this));
+ new Form.Element.Observer(this.startfield, 0.2, function(element, value) {
+ if(element.hasFocus) {
+ var date = new Date(Date.parse(value)).neutral();
+ this.startdate = date == 'Invalid Date' ? null : date;
+ this.refreshRange();
+ }
+ }.bind(this));
+ new Form.Element.Observer(this.endfield, 0.2, function(element, value) {
+ if(element.hasFocus) {
+ var date = new Date(Date.parse(value)).neutral();
+ this.enddate = date == 'Invalid Date' ? null : date;
+ this.refreshRange();
+ }
+ }.bind(this));
+ },
+
+ declareFocus: function(event) {
+ event.element().hasFocus = true;
+ if(this.startdate && event.element() == this.startfield) {
+ this.date = new Date(this.startdate)
+ this.populate();
+ }
+ else if(this.enddate && event.element() == this.endfield) {
+ this.date = new Date(this.enddate)
+ this.populate();
+ }
+ },
+
+ declareBlur: function(event) {
+ event.element().hasFocus = false;
+ this.refreshFields();
+ },
+
+ deactivate: function() {
+ this.container.select('td').each(function(day) { day.date = null; });
+ },
+
+ handleClick: function(event) {
+ if(!event.element().ancestors) return;
+ var el;
+ if(el = event.findElement('a.timeframe_button'))
+ this.handleButtonClick(event, el);
+ },
+
+ handleMousedown: function(event) {
+ if(!event.element().ancestors) return;
+ var el, em;
+ if(el = event.findElement('a.clear')) {
+ el.addClassName('active');
+ if(em = event.findElement('td'))
+ this.handleDateClick(em, true);
+ }
+ else if(el = event.findElement('td'))
+ this.handleDateClick(el);
+ else return;
+ },
+
+ handleButtonClick: function(event, element) {
+ var el;
+ var movement = this.calendars > 1 ? this.calendars - 1 : 1;
+ if(element.hasClassName('next'))
+ this.date.setMonth(this.date.getMonth() + movement);
+ else if(element.hasClassName('previous'))
+ this.date.setMonth(this.date.getMonth() - movement);
+ else if(element.hasClassName('today'))
+ this.date = new Date();
+ else if(element.hasClassName('reset')) {
+ this.resetFields();
+ }
+ this.populate();
+ },
+
+ resetFields: function() {
+ this.startfield.value = this.startfield.defaultValue;
+ this.endfield.value = this.endfield.defaultValue;
+ this.date = new Date(this.defaultDate);
+ var startdate = new Date(Date.parse(this.startfield.value)).neutral();
+ this.startdate = startdate == 'Invalid Date' ? null : startdate;
+ var enddate = new Date(Date.parse(this.endfield)).neutral();
+ this.enddate = enddate == 'Invalid Date' ? null : enddate;
+ },
+
+ handleDateClick: function(element, couldClear) {
+ this.mousedown = this.dragging = true;
+ if(this.stuck)
+ this.stuck = false;
+ else if(couldClear) {
+ if(!element.hasClassName('startrange')) return;
+ }
+ else {
+ this.stuck = true;
+ setTimeout(function() { if(this.mousedown) this.stuck = false; }.bind(this), 200);
+ }
+ this.getPoint(element.date);
+ },
+
+ getPoint: function(date) {
+ if(this.startdate && this.startdate.toString() == date && this.enddate)
+ this.startdrag = this.enddate;
+ else {
+ this.clearButton.hide();
+ if(this.enddate && this.enddate.toString() == date)
+ this.startdrag = this.startdate;
+ else
+ this.startdrag = this.startdate = this.enddate = date;
+ }
+ this.refreshRange();
+ },
+
+ handleMouseover: function(event) {
+ var el;
+ if(!this.dragging)
+ this.toggleClearButton(event);
+ else if(el = event.findElement('td'))
+ this.extendRange(el.date);
+ },
+
+ toggleClearButton: function(event) {
+ if(event.element().ancestors && event.findElement('td.selected')) {
+ if(el = this.container.select('#calendar_0 .pre.selected').first());
+ else if(el = this.container.select('.active.selected').first());
+ Element.insert(el, { top: this.clearButton });
+ this.clearButton.removeClassName('active').show();
+ }
+ else {
+ this.clearButton.hide();
+ }
+ },
+
+ extendRange: function(date) {
+ this.clearButton.hide();
+ if(date > this.startdrag) {
+ this.startdate = this.startdrag;
+ this.enddate = date;
+ }
+ else if(date < this.startdrag) {
+ this.startdate = date;
+ this.enddate = this.startdrag;
+ }
+ else {
+ this.startdate = this.enddate = date;
+ }
+ this.refreshRange();
+ },
+
+ setPoint: function(event) {
+ if(!this.dragging) return;
+ if(!this.stuck) {
+ var el;
+ this.dragging = false;
+ if(el = event.findElement('a.clear.active')) {
+ el.hide().removeClassName('active');
+ this.startdate = this.enddate = null;
+ this.refreshRange();
+ this.refreshFields();
+ }
+ }
+ this.mousedown = false;
+ this.refreshRange();
+ },
+
+ refreshRange: function() {
+ this.container.select('td').each(function(day) {
+ if(this.startdate <= day.date && day.date <= this.enddate) {
+ day.addClassName('selected');
+ this.stuck || this.mousedown ? day.addClassName('stuck') : day.removeClassName('stuck');
+ this.startdate.toString() == day.date ? day.addClassName('startrange') : day.removeClassName('startrange');
+ this.enddate.toString() == day.date ? day.addClassName('endrange') : day.removeClassName('endrange');
+ }
+ else day.removeClassName('selected').removeClassName('startrange').removeClassName('endrange').removeClassName('stuck');
+ }.bind(this));
+ if(this.dragging) this.refreshFields();
+ },
+
+ refreshFields: function() {
+ this.startfield.value = this.startdate ? this.startdate.strftime(this.format) : null;
+ this.endfield.value = this.enddate ? this.enddate.strftime(this.format) : null;
+ }
+});
+
+Object.extend(Date.prototype, {
+ // modified from http://alternateidea.com/blog/articles/2008/2/8/a-strftime-for-prototype
+ strftime: function(format) {
+ var day = this.getDay(), month = this.getMonth();
+ var hours = this.getHours(), minutes = this.getMinutes();
+ function pad(num) { return num.toPaddedString(2); };
+
+ return format.gsub(/\%([aAbBcdHImMpSwyY])/, function(part) {
+ switch(part[1]) {
+ case 'a': return Date.weekdays.invoke('substring', 0, 3)[day]; break;
+ case 'A': return Date.weekdays[day]; break;
+ case 'b': return Date.months.invoke('substring', 0, 3)[month]; break;
+ case 'B': return Date.months[month]; break;
+ case 'c': return this.toString(); break;
+ case 'd': return pad(this.getDate()); break;
+ case 'H': return pad(hours); break;
+ case 'I': return (hours % 12 == 0) ? 12 : pad(hours % 12); break;
+ case 'm': return pad(month + 1); break;
+ case 'M': return pad(minutes); break;
+ case 'p': return hours >= 12 ? 'PM' : 'AM'; break;
+ case 'S': return pad(this.getSeconds()); break;
+ case 'w': return day; break;
+ case 'y': return pad(this.getFullYear() % 100); break;
+ case 'Y': return this.getFullYear().toString(); break;
+ }
+ }.bind(this));
+ },
+
+ neutral: function() {
+ var neutered = new Date(this);
+ neutered.setHours(12);
+ neutered.setMinutes(0);
+ neutered.setSeconds(0);
+ neutered.setMilliseconds(0);
+ return neutered;
+ }
+});

2 comments on commit 7fff755

@Caged

Great stuff, Stephen! I added a couple comments relating to 1.6.

@stephencelis

Thanks for the pointers, Justin! I’ll make these changes shortly.

Please sign in to comment.