1. #1
    Sencha User
    Join Date
    Jul 2011
    Location
    The Netherlands
    Posts
    24
    Vote Rating
    5
    renato01 is on a distinguished road

      3  

    Default Ext.ux.picker.Time [timepickerfield]

    Ext.ux.picker.Time [timepickerfield]


    Timepicker for hours and minutes. The TimePickerField is derived from the Ext.field.DatePicker. So it inherits al default behavior. The value returned by timepicker is a full date object, with time portion.
    Here is a touch version of time picker:


    The picker:
    Code:
    /**
    * The picker with hours and minutes slots
    */
    Ext.define('Ext.ux.picker.Time', {
        extend:'Ext.picker.Picker',
        xtype:'timepicker',
    
    
        config:{
            /**
             * @cfg {Number} increment The number of minutes between each minute value in the list.
             * Defaults to: 5
             */
            increment:5,
    
    
            /**
             * @cfg {Number} start value of hours
             */
            minHours:0,
    
    
            /**
             * @cfg {Number} end value of hours.
             */
            maxHours:23,
    
    
            /**
             * @cfg {String} title to show above hour slot
             * Note: for titles to show set the {useTitle} config to true.
             */
            hoursTitle:'Hours',
    
    
            /**
             * @cfg {String} title to show above hour slot
             * Note: for this to show set the {useTitle} config to true.
             */
            minutesTitle:'Minutes',
    
    
            /**
             * @cfg {boolean} show/hide title headers.
             * Note: defaults to false (framework default 'Ext.picker.Picker')
             */
    
    
            slots: []
        },
    
    
        /**
         *
         * @param value
         * @param animated
         */
        setValue:function (value, animated) {
            var increment = this.getInitialConfig().increment,
                modulo;
    
    
            if (Ext.isDate(value)) {
                value = {
                    hours:value.getHours(),
                    minutes:value.getMinutes()
                };
            }
    
    
            //Round minutes
            modulo = value.minutes % increment;
            if (modulo > 0) {
                value.minutes = Math.round(value.minutes / increment) * increment;
            }
            this.callParent([value, animated]);
        },
    
    
        /**
         * @override
         * @returns {Date} A date object containing the selected hours and minutes. Year, month, day default to the current date..
         */
        getValue:function () {
            var value = this.callParent(arguments),
                date = new Date();
            value = new Date(date.getFullYear(), date.getMonth(), date.getDate(), value.hours, value.minutes);
            return value;
        },
    
    
        applySlots:function (slots) {
            var me = this,
                hours = me.createHoursSlot(),
                minutes = me.createMinutesSlot();
    
    
            return [hours, minutes];
        },
    
    
        createHoursSlot:function () {
            var me = this,
                initialConfig = me.getInitialConfig(),
                title = initialConfig.hoursTitle ,
                minHours = initialConfig.minHours,
                maxHours = initialConfig.maxHours,
                hours = [],
                slot;
    
    
            for (var i = minHours; i <= maxHours; i++) {
                var text = (i < 10) ? ('0' + i) : i; //Add leading zero
                hours.push({text:text, value:i});
            }
    
    
            slot = {
                name:'hours',
                align:'center',
                title:title,
                data:hours,
                flex:1
            };
    
    
            return slot;
        },
    
    
        createMinutesSlot:function () {
            var me = this,
                initialConfig = me.getInitialConfig(),
                title = initialConfig.minutesTitle ,
                increment = initialConfig.increment,
                minutes = [],
                slot;
    
    
            for (var j = 0; j < 60; j += increment) {
                var text;
                text = (j < 10) ? ('0' + j) : j; //Add leading zero
                minutes.push({text:text, value:j});
            }
    
    
            slot = {
                name:'minutes',
                align:'center',
                title:title,
                data:minutes,
                flex:1
            };
            return slot;
        }
    });

    The picker text field (extends from the datepickerfield)
    Code:
    /**
    * TimePickerfield. Extends from datepickerfield
    */
    Ext.define('Ext.ux.field.TimePicker', {
        extend:'Ext.field.DatePicker',
        xtype:'timepickerfield',
    
    
        requires:['Ext.ux.picker.Time'],
    
    
        config:{
            dateFormat:'H:i', //Default format show time only
            picker:true
        },
    
    
        /**
         * @override
         * @param value
         * Source copied, small modification
         */
        applyValue:function (value) {
            if (!Ext.isDate(value) && !Ext.isObject(value)) {
                value = null;
            }
    
    
            // Begin modified section
            if (Ext.isObject(value)) {
                var date = new Date(),
                    year = value.year || date.getFullYear(), // Defaults to current year if year was not supplied etc..
                    month = value.month || date.getMonth(),
                    day = value.day || date.getDate();
    
    
                value = new Date(year, month, day, value.hours, value.minutes); //Added hour and minutes
            }
            // End modfied section!
            return value;
        },
    
    
        applyPicker:function (picker) {
            picker = Ext.factory(picker, 'Ext.ux.picker.Time');
            picker.setHidden(true); // Do not show picker on creeation
            Ext.Viewport.add(picker);
            return picker;
        },
    
    
        updatePicker:function (picker) {
            picker.on({
                scope:this,
                change:'onPickerChange',
                hide:'onPickerHide'
            });
            picker.setValue(this.getValue());
            return picker;
        }
    });

    Usage:
    Code:
    {
        xtype: 'timepickerfield',
        label: 'time',
        value: new Date(), // object also possible {hours:12, minutes:25},
        name: 'time',
        picker:{
            height:300
            minHours:9, //(optional)Selectable hours will be between 9-18
            maxHours:18 // (optional) These values default to 0-24
        }
    }

    Code:
    //* Notes:
    getValue() // will return a {Date} object
    getFormattedValue() //will return H:i (example16:40)
    That's all. Any thoughts?


    [EDIT]
    - Refactored picker code.
    - Implemented mithcell's comments
    Attached Images
    Last edited by renato01; 26 Mar 2012 at 7:43 AM. Reason: added image, changed namespace

  2. #2
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    37,330
    Vote Rating
    847
    mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute

      0  

    Default


    You should make picker a proper config of Ext.ux.picker.Time and use applyPicker method to do the Ext.factory to create the slot instead of doing it in getPicker and in updatePicker add the listener and set value.
    Mitchell Simoens @SenchaMitch
    Sencha Inc, Senior Forum Manager
    ________________
    Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
    https://github.com/mitchellsimoens

    Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/

    Need more help with your app? Hire Sencha Services services@sencha.com

    Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is in print!

    When posting code, please use BBCode's CODE tags.

  3. #3
    Sencha User
    Join Date
    Jul 2011
    Location
    The Netherlands
    Posts
    24
    Vote Rating
    5
    renato01 is on a distinguished road

      0  

    Default Update

    Update


    Took @mitchellsimoens comments and modified code.
    Although, one question to @mitchell:
    How would you do this part? The setHidden and then add to Viewport part?
    Otherwise the picker won't show or immediately show after you add to Viewport..


    Code:
    applyPicker:function (picker) {
        picker = Ext.factory(picker, 'Ext.ux.picker.Time');
        picker.setHidden(true); // Do not show picker on creeation
        Ext.Viewport.add(picker);
        return picker;
    }

    I think that's why the Ext.field.DatePicker does the creation of the picker inside the getPicker(), when its first called..

  4. #4
    Sencha User
    Join Date
    Dec 2011
    Posts
    21
    Vote Rating
    1
    supersaiyen is on a distinguished road

      1  

    Default


    Here's my attempt at the same notion, not sure i'm loving the input/output value format, but it works.
    Code:
    /**
     * @author RWeneck
     * 
     * Adapted from https://github.com/ghuntley/Ext.ux.touch.DateTimePicker
     * 
     */
    Ext.define('Application.view.TimePicker', {
    	extend : 'Ext.Picker',
    	xtype : 'timepicker',
    
    
    	config : {
    		/**
             * @cfg {String/Number} value The value to initialize the picker with
             * @accessor
             */
            value: "11:00 AM",
    		/**
    		 * @cfg {String} hourText
    		 * The label to show for the hour column. Defaults to 'Hour'.
    		 */
    		hourText : 'Hour',
    		/**
    		 * @cfg {String} minuteText
    		 * The label to show for the minute column. Defaults to 'Minute'.
    		 */
    		minuteText : 'Minute',
    		/**
    		 * @cfg {String} daynightText
    		 * The label to show for the daynight column. Defaults to 'AM/PM'.
    		 */
    		daynightText : 'AM/PM',
    		/**
    		 * @cfg {Array} slotOrder
    		 * An array of strings that specifies the order of the slots. Defaults to <tt>['hour', 'minute', 'daynight']</tt>.
    		 */
    		slotOrder : ['hour', 'minute', 'daynight']
    
    
    	},
    	// @private
    	initialize: function() {
    		var me = this;
    		
    		var hours = [], 
    			minutes = [], 
    			daynight = [], 
    			i;
    			
    		for( i = 1; i <= 12; i++) {
    			hours.push({
    				text : i,
    				value : i
    			});
    		}
    
    
    		for( i = 0; i < 60; i += 5) {
    			minutes.push({
    				text : i < 10 ? '0' + i : i,
    				value : i
    			});
    		}
    
    
    		daynight.push({
    			text : 'AM',
    			value : 'AM'
    		}, {
    			text : 'PM',
    			value : 'PM'
    		});
    
    
    		
    		var newSlots = [];
    		Ext.each(this.getSlotOrder(), function(item) {
    			newSlots.push(this.createSlot(item, hours, minutes, daynight));
    		}, this);
    		
    		this.setSlots(newSlots);
    		this.callParent(arguments);
    	},
    	createSlot : function(name, hours, minutes, daynight) {
    		switch (name) {
    			case 'hour':
    				return {
    					name : 'hour',
    					align : 'center',
    					data : hours,
    					title : this.getUseTitles() ? this.getHourText() : false,
    					flex : 2
    				};
    			case 'minute':
    				return {
    					name : 'minute',
    					align : 'center',
    					data : minutes,
    					title : this.getUseTitles() ? this.getMinuteText() : false,
    					flex : 2
    				};
    			case 'daynight':
    				return {
    					name : 'daynight',
    					align : 'center',
    					data : daynight,
    					title : this.getUseTitles() ? this.getDaynightText() : false,
    					flex : 2
    				};
    		}
    	},
    
    
    	/**
    	 * Takes a String or an object that represents time,
    	 * The object should contain the keys; hour, minute, daynight
    	 * or the string should be in the format of "11:00 AM"
    	 */
    	setValue : function(value, animated) {
    		if(!value){
    			value = {
    				hour : 11,
    				minute : 00,
    				daynight : "AM"
    			}
    		}
    		if(typeof value == "string"){
    			var hour, minute, daynight;
    			value = value.trim();
    			hour = value.substring(0, value.indexOf(":"));
    			minute = value.substring(value.indexOf(":")+1, value.indexOf(" "));
    			daynight = value.substring(value.indexOf(" ")+1);
    			
    			value = {
    				hour : hour,
    				minute : minute,
    				daynight : daynight
    			}
    		}
    		this.callParent(arguments);
    		
    		for(key in value) {
    			slot = this.child('[name=' + key + ']');
    			if(slot) {
    				if(key === 'hour' && value[key] > 12) {
    					daynightVal = 'PM';
    					value[key] -= 12;
    				}
    				slot.setValue(value[key], animated);
    			}
    		}
    		return this;
    	}
    });
    Can be used like so:
    Code:
    var picker = Ext.create('Application.view.TimePicker', {
    			useTitles : true,
    			value : "11:30 AM"
    		});
    		Ext.Viewport.add(picker);
    		picker.show();
    Screen Shot 2012-04-02 at 2.23.02 PM.png

  5. #5
    Sencha User
    Join Date
    Apr 2012
    Location
    Denmark
    Posts
    71
    Vote Rating
    1
    hjeDK is on a distinguished road

      0  

    Default


    This looks great, but how do you add it to an app and use it?
    I'm using Sencha Touch 2 with the MVC-file structure and have read a bit about the Ext.Loader, but I still get
    Uncaught Error: [Ext.createByAlias] Cannot create an instance of unrecognized alias: widget.timepickerfield

  6. #6
    Sencha User
    Join Date
    Dec 2011
    Posts
    21
    Vote Rating
    1
    supersaiyen is on a distinguished road

      0  

    Default


    Create a TimePicker.js in your app/view folder. Change the Ext.define line to your applications name:
    PHP Code:
    Ext.define('YOURAPPLICATON.view.TimePicker', {... 
    Then instantiate it like:
    PHP Code:
    var picker Ext.create('YOURAPPLICATION.view.TimePicker', {            
                
    useTitles true,            
                
    value "11:30 AM"        
    });        
    Ext.Viewport.add(picker);        
    picker.show(); 
    This extends from datepicker, not datepickerfield, so you'll need to add it to the viewport and call show, though it wouldnt take much to assign it to a pickerfield's picker value.

  7. #7
    Sencha User
    Join Date
    Apr 2012
    Location
    Denmark
    Posts
    71
    Vote Rating
    1
    hjeDK is on a distinguished road

      0  

    Default


    I was trying to implement @renato01 version as I need a 24-hour clock. and that makes use of Ext.ux-folders.

  8. #8
    Sencha User
    Join Date
    Jul 2011
    Location
    The Netherlands
    Posts
    24
    Vote Rating
    5
    renato01 is on a distinguished road

      1  

    Default


    Quote Originally Posted by hjeDK View Post
    This looks great, but how do you add it to an app and use it?
    I'm using Sencha Touch 2 with the MVC-file structure and have read a bit about the Ext.Loader, but I still get
    Uncaught Error: [Ext.createByAlias] Cannot create an instance of unrecognized alias: widget.timepickerfield
    It looks like the files didn't load correctly.
    Make sure the two files are in the correct folder:
    - ext/ux/picker/Time.js
    - ext/ux/field/TimePicker.js

    Add the requires to your code and make sure they load correctly:
    Code:
    requires:['Ext.ux.field.TimePicker']
    You may also need to configure the loader correctly.
    Add this before you init your application

    Code:
    Ext.Loader.setConfig({
        enabled: true,
        paths:{
            'Ext': '../../Scripts/ext' //This should be the url to your script folder..
        }
    });
    That should do it!

    Once you get that working, you can try this (better solution/more advanced):
    Rename all occurrences of 'Ext' to 'YourAppName' and put the files in a 'ux' folder. The 'ux' should be on the same level as 'controller' & 'view'.
    Example:
    Code:
    'Ext.ux.field.TimePicker' to > 'YOUAPPNAME.ux.field.TimePicker'
    'Ext.ux.picker.Picker' to > 'YOUAPPNAME.ux.picker.Time'
    - /ux/picker/Time.js
    - /ux/field/TimePicker.js

  9. #9
    Sencha User
    Join Date
    Apr 2012
    Location
    Denmark
    Posts
    71
    Vote Rating
    1
    hjeDK is on a distinguished road

      0  

    Default


    Thanks now I made it to show up on the view, but the sheet won't show up when I click the field.
    I put the example code in as an item in a fieldset along with a datepicker and a selectfield

    EDIT: the sheet turned up behind my panel that I have made as a modal popup. Any idea how to solve that?

  10. #10
    Sencha User
    Join Date
    Jul 2011
    Location
    The Netherlands
    Posts
    24
    Vote Rating
    5
    renato01 is on a distinguished road

      0  

    Default


    That might be a problem.. Because the picker is itself modal..
    Maybe look into zIndex...