Moritz Schlarb avatar Moritz Schlarb committed 1d56681

TimePicker and an initial try for DateTimePicker

These are both experimental...
I'm not sure if it's really a good idea to do it this way!

Comments (0)

Files changed (7)

tw2/bootstrap/samples.py

     style = 'component'
 
 
+class DemoCalendarTimePicker(twb.CalendarTimePicker):
+    style = 'dropdown'
+    defaultTime = None
+    value = "14:00"
+

tw2/bootstrap/static/timepicker/.gitignore

+*.swp

tw2/bootstrap/static/timepicker/README.md

+Timepicker for Twitter Bootstrap 2.x
+------------------------------------
+
+A simple timepicker component for Twitter Bootstrap.
+
+Documentation
+=============
+
+Read the <a href="http://jdewit.github.com/bootstrap-timepicker">documentation</a>.

tw2/bootstrap/static/timepicker/css/timepicker.css

+.bootstrap-timepicker.dropdown-menu {
+    border-radius: 4px 4px 4px 4px;
+    display: none;
+    left: 0;
+    margin-top: 1px;
+    padding: 4px;
+    top: 0;
+}
+.bootstrap-timepicker.dropdown-menu.open {
+    display: inline-block;
+}
+.bootstrap-timepicker.dropdown-menu:before {
+    border-bottom: 7px solid rgba(0, 0, 0, 0.2);
+    border-left: 7px solid transparent;
+    border-right: 7px solid transparent;
+    content: "";
+    left: 6px;
+    position: absolute;
+    top: -7px;
+}
+.bootstrap-timepicker.dropdown-menu:after {
+    border-bottom: 6px solid #FFFFFF;
+    border-left: 6px solid transparent;
+    border-right: 6px solid transparent;
+    content: "";
+    left: 7px;
+    position: absolute;
+    top: -6px;
+}
+.bootstrap-timepicker.modal {
+    margin-left: -100px;
+    margin-top: 0;
+    top: 30%;
+    width: 200px;
+}
+.bootstrap-timepicker.modal .modal-content {
+    padding: 0;
+}
+.bootstrap-timepicker table {
+    margin: 0;
+    width: 100%;
+}
+.bootstrap-timepicker td, .bootstrap-timepicker th {
+    border-radius: 4px 4px 4px 4px;
+    height: 20px;
+    text-align: center;
+}
+.bootstrap-timepicker td.separator {
+    width: 1px;
+}
+.bootstrap-timepicker td a {
+    border: 1px solid transparent;
+    display: block;
+    margin: 4px;
+    padding: 4px 0;
+}
+.bootstrap-timepicker td a:hover {
+    background-color: #EEEEEE;
+    border-color: #DDDDDD;
+    border-radius: 4px 4px 4px 4px;
+}
+

tw2/bootstrap/static/timepicker/js/bootstrap-timepicker.js

+/* =========================================================
+ * bootstrap-timepicker.js
+ * http://www.github.com/jdewit/bootstrap-timepicker
+ * =========================================================
+ * Copyright 2012
+ *
+ * Created By:
+ * Joris de Wit @joris_dewit
+ * Gilbert @mindeavor
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+!function($) {
+
+    "use strict"; // jshint ;_;
+
+    /* TIMEPICKER PUBLIC CLASS DEFINITION
+     * ================================== */
+    var Timepicker = function(element, options) {
+        this.$element = $(element);
+        this.options = $.extend({}, $.fn.timepicker.defaults, options, this.$element.data());
+        this.minuteStep = this.options.minuteStep || this.minuteStep;
+        this.showMeridian = this.options.showMeridian || this.showMeridian;
+        this.disableFocus = this.options.disableFocus || this.disableFocus;
+        this.template = this.options.template || this.template;
+        this.defaultTime = this.options.defaultTime || this.defaultTime;
+        this.open = false;
+        this.init();
+    };
+
+    Timepicker.prototype = {
+
+        constructor: Timepicker
+
+        , init: function () {
+
+            this.$element
+                .on('click', $.proxy(this.show, this))
+                .on('keyup', $.proxy(this.updateFromElementVal, this))
+            ;
+            
+            this.$widget = $(this.getTemplate()).appendTo('body');
+            
+            this.$widget.on('click', $.proxy(this.click, this));
+
+            this.setDefaultTime(this.defaultTime);
+        }
+
+        , show: function(e) {
+            e.stopPropagation();
+            e.preventDefault();
+
+            this.$element.trigger('show');
+
+            $('html').on('click.timepicker.data-api', $.proxy(this.hide, this));
+
+            if (true === this.disableFocus) {
+                this.$element.blur();
+            }
+
+            var pos = $.extend({}, this.$element.offset(), {
+                height: this.$element[0].offsetHeight
+            });
+
+            if (this.options.template === 'modal') {
+//                this.$widget.css({
+//                    top: pos.top + pos.height
+//                })
+
+                this.$widget.modal('show');
+            } else {
+                this.$widget.css({
+                    top: pos.top + pos.height
+                    , left: pos.left
+                })
+
+                if (!this.open) {
+                    this.$widget.addClass('open');
+                }
+            }
+
+            this.open = true;
+            this.$element.trigger('shown');
+
+            return this;
+        }
+
+        , hide: function(){
+            this.$element.trigger('hide');
+            
+            $('html').off('click.timepicker.data-api', $.proxy(this.hide, this));
+
+            if (this.options.template === 'modal') {
+                this.$widget.modal('hide');
+            } else {
+                this.$widget.removeClass('open');
+            }
+            this.open = false;
+            this.$element.trigger('hidden');
+
+            return this;
+        }
+
+        , setValues: function(time) {
+            var meridian, match = time.match(/(AM|PM)/i);
+            if (match) {
+                meridian = match[1];
+            }
+            time = $.trim(time.replace(/(PM|AM)/i, ''));
+            var timeArray = time.split(':');
+
+            this.meridian = meridian;
+            this.hour = parseInt(timeArray[0], 10);
+            this.minute = parseInt(timeArray[1], 10);
+        }
+
+        , setDefaultTime: function(defaultTime){
+            if (defaultTime) {
+                if (defaultTime === 'current') {
+                    var dTime = new Date();
+                    var hours = dTime.getHours();
+                    var minutes = Math.floor(dTime.getMinutes() / this.minuteStep) * this.minuteStep;
+
+                    var meridian = "AM";
+                    if (hours === 0) {
+                        hours = 12;
+                    } else if (hours > 12) {
+                        hours = hours - 12;
+                        meridian = "PM";
+                    } else {
+                       meridian = "AM";
+                    }
+
+                    this.hour = hours;
+                    this.minute = minutes;
+                    this.meridian = meridian;
+                } else {
+                    this.setValues(defaultTime);
+                }
+                this.update();
+            }
+        }
+
+        , formatTime: function(hour, minute, meridian) {
+            hour = hour < 10 ? '0' + hour : hour;
+            minute = minute < 10 ? '0' + minute : minute;
+
+            return hour + ':' + minute + ( this.showMeridian ? ' ' + meridian : '' );
+        }
+
+        , getTime: function() {
+            return this.formatTime(this.hour, this.minute, this.meridian);
+        }
+
+        , setTime: function(time) {
+            this.setValues(time);
+            this.update();
+        }
+
+        , updateElement: function() {
+            var time = this.getTime();
+
+            this.$element.val(time);
+        }
+
+        , updateWidget: function() {
+            this.$widget
+                .find('td.bootstrap-timepicker-hour').text(this.hour).end()
+                .find('td.bootstrap-timepicker-minute').text(this.minute < 10 ? '0' + this.minute : this.minute).end()
+                .find('td.bootstrap-timepicker-meridian').text(this.meridian);
+        }
+
+        , update: function() {
+            this.updateElement();
+            this.updateWidget();
+        }
+
+        , updateFromElementVal: function () {
+            var time = this.$element.val();
+            if (time) {
+                this.setValues(time);
+                this.updateWidget();
+            }
+        }
+
+        , click: function(e) {
+            e.stopPropagation();
+            e.preventDefault();
+
+            if (true !== this.disableFocus) {
+                this.$element.focus();
+            }
+
+            var action = $(e.target).closest('a').data('action');
+            if (action) {
+                this[action]();
+                this.update();
+            }
+
+        }
+
+        , incrementHour: function() {
+            if ( this.showMeridian ) {
+                if ( this.hour === 12 ) {
+                    this.hour = 1;
+                    return this.toggleMeridian();
+                }
+            }
+            if ( this.hour === 23 ) {
+                return this.hour = 0;
+            }
+            this.hour = this.hour + 1;
+        }
+
+        , decrementHour: function() {
+            if ( this.showMeridian ) {
+                if (this.hour === 1) {
+                    this.hour = 12;
+                    return this.toggleMeridian();
+                } 
+            }
+            if (this.hour === 0) {
+                return this.hour = 23;
+            }
+            this.hour = this.hour - 1;
+        }
+
+        , incrementMinute: function() {
+            var newVal = this.minute + this.minuteStep - (this.minute % this.minuteStep);
+            if (newVal > 59) {
+                this.incrementHour();
+                this.minute = newVal - 60;
+            } else {
+                this.minute = newVal;
+            }
+        }
+
+        , decrementMinute: function() {
+            var newVal = this.minute - this.minuteStep;
+            if (newVal < 0) {
+                this.decrementHour();
+                this.minute = newVal + 60;
+            } else {
+                this.minute = newVal;
+            }
+        }
+
+        , toggleMeridian: function() {
+            this.meridian = this.meridian === 'AM' ? 'PM' : 'AM';
+
+            this.update();
+        }
+        
+        , getTemplate: function() {
+            if (this.options.templates[this.options.template]) {
+                return this.options.templates[this.options.template];
+            }
+            var template;
+            switch(this.options.template) {
+                case 'modal':
+                    template = '<div class="bootstrap-timepicker modal hide fade in" style="top: 30%; margin-top: 0; width: 200px; margin-left: -100px;" data-backdrop="false">'+
+                                   '<div class="modal-header">'+
+                                       '<a href="#" class="close" data-action="hide">×</a>'+
+                                       '<h3>Pick a Time</h3>'+
+                                   '</div>'+
+                                   '<div class="modal-content">'+
+                                       '<table>'+
+                                           '<tr>'+
+                                               '<td><a href="#" data-action="incrementHour"><i class="icon-chevron-up"></i></a></td>'+
+                                               '<td class="separator"></td>'+
+                                               '<td><a href="#" data-action="incrementMinute"><i class="icon-chevron-up"></i></a></td>'+
+                                               ( this.showMeridian ? '<td><a href="#" data-action="toggleMeridian"><i class="icon-chevron-up"></i></a></td>' : '' ) +
+                                           '</tr>'+
+                                           '<tr>'+
+                                               '<td class="bootstrap-timepicker-hour"></td> '+
+                                               '<td class="separator">:</td>'+
+                                               '<td class="bootstrap-timepicker-minute"></td> '+
+                                               ( this.showMeridian ? '<td class="bootstrap-timepicker-meridian"></td>' : '' ) +
+                                           '</tr>'+
+                                           '<tr>'+
+                                               '<td><a href="#" data-action="decrementHour"><i class="icon-chevron-down"></i></a></td>'+
+                                               '<td class="separator"></td>'+
+                                               '<td><a href="#" data-action="decrementMinute"><i class="icon-chevron-down"></i></a></td>'+
+                                               ( this.showMeridian ? '<td><a href="#" data-action="toggleMeridian"><i class="icon-chevron-down"></i></a></td>' : '' ) +
+                                           '</tr>'+
+                                       '</table>'+
+                                   '</div>'+
+                                   '<div class="modal-footer">'+
+                                       '<a href="#" class="btn btn-primary" data-action="hide">Ok</a>'+
+                                   '</div>'+
+                               '</div>';
+                    
+                break;
+                case 'dropdown':
+                    template = '<div class="bootstrap-timepicker dropdown-menu">'+
+                                   '<table>'+
+                                       '<tr>'+
+                                           '<td><a href="#" data-action="incrementHour"><i class="icon-chevron-up"></i></a></td>'+
+                                           '<td class="separator"></td>'+
+                                           '<td><a href="#" data-action="incrementMinute"><i class="icon-chevron-up"></i></a></td>'+
+                                           ( this.showMeridian ? '<td><a href="#" data-action="toggleMeridian"><i class="icon-chevron-up"></i></a></td>' : '' ) +
+                                       '</tr>'+
+                                       '<tr>'+
+                                           '<td class="bootstrap-timepicker-hour"></td> '+
+                                           '<td class="separator">:</td>'+
+                                           '<td class="bootstrap-timepicker-minute"></td> '+
+                                           ( this.showMeridian ? '<td class="bootstrap-timepicker-meridian"></td>' : '' ) +
+                                       '</tr>'+
+                                       '<tr>'+
+                                           '<td><a href="#" data-action="decrementHour"><i class="icon-chevron-down"></i></a></td>'+
+                                           '<td class="separator"></td>'+
+                                           '<td><a href="#" data-action="decrementMinute"><i class="icon-chevron-down"></i></a></td>'+
+                                           ( this.showMeridian ? '<td><a href="#" data-action="toggleMeridian"><i class="icon-chevron-down"></i></a></td>' : '' ) +
+                                       '</tr>'+
+                                   '</table>'+
+                               '</div>';
+                break;
+                
+            }
+            return template;
+        }
+    };
+
+
+    /* TIMEPICKER PLUGIN DEFINITION
+     * =========================== */
+
+    $.fn.timepicker = function (option) {
+        return this.each(function () {
+            var $this = $(this)
+            , data = $this.data('timepicker')
+            , options = typeof option == 'object' && option;
+            if (!data) {
+                $this.data('timepicker', (data = new Timepicker(this, options)));
+            }
+            if (typeof option == 'string') {
+                data[option]();
+            }
+        })
+    }
+
+    $.fn.timepicker.defaults = {
+      minuteStep: 15
+    , disableFocus: false
+    , defaultTime: 'current'
+    , showMeridian: true
+    , template: 'dropdown'
+    , templates: {} // set custom templates
+    }
+
+    $.fn.timepicker.Constructor = Timepicker
+
+    /* TIMEPICKER DATA-API
+     * ================== */
+
+    $(function () {
+        $('body').on('focus.timepicker.data-api', '[data-provide="timepicker"]', function (e) {
+            var $this = $(this);
+            if ($this.data('timepicker')) {
+                return;
+            }
+            e.preventDefault();
+            $this.timepicker($this.data());
+        })
+    })
+}(window.jQuery);

tw2/bootstrap/static/timepicker/less/timepicker.less

+/*!
+ * Timepicker for Bootstrap
+ *
+ * Copyright 2012 Joris de Wit, Stefan Petre, Andrew Rowls
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ */
+.bootstrap-timepicker {
+    &.dropdown-menu {
+        top: 0;
+        left: 0;
+        padding: 4px;
+        margin-top: 1px;
+        -webkit-border-radius: 4px;
+        -moz-border-radius: 4px;
+        border-radius: 4px;
+        display: none;
+
+        &.open {
+            display: inline-block;
+        }
+
+        &:before {
+            content: '';
+            border-left: 7px solid transparent;
+            border-right: 7px solid transparent;
+            border-bottom: 7px solid #ccc;
+            border-bottom-color: rgba(0, 0, 0, 0.2);
+            position: absolute;
+            top: -7px;
+            left: 6px;
+        }
+
+        &:after {
+            content: '';
+            border-left: 6px solid transparent;
+            border-right: 6px solid transparent;
+            border-bottom: 6px solid #ffffff;
+            position: absolute;
+            top: -6px;
+            left: 7px;
+        }
+    }
+
+    &.modal {
+        top: 30%; 
+        margin-top: 0; 
+        width: 200px; 
+        margin-left: -100px; 
+
+        .modal-content {
+            padding: 0;
+        }
+    }
+
+    table {
+      width: 100%;
+      margin: 0;
+    }
+
+    td, th {
+        text-align: center;
+        height: 20px;
+        -webkit-border-radius: 4px;
+        -moz-border-radius: 4px;
+        border-radius: 4px;
+    }
+
+    td.separator {
+        width: 1px;
+    }
+
+    td a {
+        border: 1px transparent solid;
+        display: block;    
+        margin: 4px;
+        padding: 4px 0;
+
+        &:hover {
+            background-color: #eee;
+            -webkit-border-radius: 4px;
+            -moz-border-radius: 4px;
+            border-radius: 4px;
+            border-color: #ddd;
+        }
+    }
+}

tw2/bootstrap/widgets.py

     'HorizontalForm',
 
     'CalendarDatePicker',
+    'CalendarTimePicker',
     'CalendarDateTimePicker',
     'CheckBoxList',
     'CheckBoxTable',
     filename='static/datepicker/js/bootstrap-datepicker.js',
     resources=[bootstrap_js])
 
+timepicker_css = twc.CSSLink(
+    filename='static/timepicker/css/timepicker.css',
+    resources=[bootstrap_css])
+timepicker_js = twc.JSLink(
+    filename='static/timepicker/js/bootstrap-timepicker.js',
+    resources=[bootstrap_js])
+
 
 class Bootstrap(twc.Widget):
     """ Abstract base class for tw2.bootstrap widgets. """
             pass
 
 
-class CalendarDateTimePicker(Bootstrap, twf.CalendarDateTimePicker):
-    """ Not implemented.  If you want to contribute it, let us know. """
+class CalendarTimePicker(TextField):
+    resources = TextField.resources + [timepicker_js, timepicker_css]
+
+    style = twc.Param(
+        'Specify the template to use. [modal, dropdown]',
+        default='modal')
+    minuteStep = twc.Param(
+        'Specify a step for the minute field.',
+        default=15)
+    defaultTime = twc.Param(
+        'Set the initial time value. '
+        'Setting it to "current" sets it to the current time.',
+        default='current')
+    disableFocus = twc.Param(
+        'Disables the input from focusing. This is useful for touch screen '
+        'devices that display a keyboard on input focus.',
+        default=False)
 
     def prepare(self):
-        raise NotImplementedError("If you want this, let us know.")
+        super(CalendarTimePicker, self).prepare()
+        self.add_call(twj.jQuery(self.selector).timepicker(dict(
+            template=self.style,
+            minuteStep=self.minuteStep,
+            defaultTime=self.defaultTime,
+            disableFocus=self.disableFocus
+        )))
+
+
+class CalendarDateTimePicker(Bootstrap, twc.CompoundWidget):
+    date = CalendarDatePicker()
+    time = CalendarTimePicker()
+
+    def _validate(self, value, state=None):
+        """
+        Inner validation method; this is called by validate and should not be
+        called directly. Overriding this method in widgets is discouraged; a
+        custom validator should be coded instead. However, in some
+        circumstances overriding is necessary.
+        """
+        self._validated = True
+        result = ''
+        for field in self.children:
+            child_value = field.validator.to_python(field.value)
+            field.validator.validate_python(child_value, state)
+            result += child_value
+        return result
+
+    def prepare(self):
+        super(CalendarDateTimePicker, self).prepare()
 
 
 class CheckBoxList(Bootstrap, twf.CheckBoxList):
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.