Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initialization

  • Loading branch information...
commit 7fff7556fde246fd8e85ffe56241e8a745c1b29d 0 parents
Stephen Celis authored
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
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) });
Justin Palmer
Caged added a note

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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));
Justin Palmer
Caged added a note

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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

Justin Palmer

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

Justin Palmer

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

Justin Palmer

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

Stephen Celis

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

Please sign in to comment.
Something went wrong with that request. Please try again.