PDA

View Full Version : Ext.ux.form.ClockField -- TimeField with analog clock picker



NeonMonk
3 Jan 2009, 11:16 AM
Hey guys,

I've just successfully ported over and improved NoGray's Mootools TimePicker (http://www.nogray.com/time_picker.php) by Wesam Saif (released under MIT-style license)

Config options:

imagesPath: defaults to "Ext.ux.form.ClockField/images"
clockBgColor: defaults to "white"
clockBorder: defaults to "1px solid lightgrey"
format: defaults to "g:i A"

If you find any bugs/make any improvements or fixes please post back to this thread. :)

Demo (http://99thmonkey.org/extjs/examples/clockfield/)

worthy
3 Jan 2009, 12:02 PM
I don't think that http://localhost/ext/... is a valid demo website address.

NeonMonk
3 Jan 2009, 3:14 PM
Updated. Thanks.

moegal
4 Jan 2009, 5:11 AM
Hi, looks great.
Doesn't work in IE 7, and it doesnt have a way to close. Should close on enter and maybe add a x to close it or click outside of clock to close.

Thanks,
Marty

dajianshi
6 Jan 2009, 7:43 PM
Great! but the Hour arm not perform good, say not right position

AguilaLibre
23 Jan 2009, 1:57 PM
Hello,

Im trying to use your clockfield but for some reason im getting the following error: "this.hourHand is undefined" Im using the code exactly as it is, and I already check the script and nothing seems to be bad written or coded..

Thanks for your support in advance...

friendlymahi
31 Jan 2009, 11:39 AM
Even I get the same issue..Can u pls let us know what to do..

Many Thanks,
Mahi

mjlecomte
31 Jan 2009, 12:09 PM
It worked out of the box for me on FF3. I can't say the same for IE7, but I assume your errors are for FF, no? Did you run the code as is?

friendlymahi
31 Jan 2009, 2:33 PM
hii

I ran ur code as is in IE6... i got that error...

xiaojun777
3 Nov 2009, 2:42 AM
follow code fix in IE error.

clickAngle = function(pnt, coord){
var c_x = coord.width/2;
var c_y = coord.height/2;
var x = pnt.x - coord.left;
if (window.scrollX){
x += window.scrollX;
}

var y = pnt.y - coord.top;
if (window.scrollY){
y += window.scrollY;
}
var t_x = c_x;
var t_y = y;
var CA = t_x - x;
var CO = t_y - c_y;
var AO = Math.sqrt(Math.pow(CA, 2) + Math.pow(CO, 2));
var ang = Math.round((Math.acos((Math.pow(Math.abs(CA), 2) - Math.pow(Math.abs(AO), 2) - Math.pow(CO, 2))/(2 * CO * AO))) * 180/Math.PI);
if (x < c_x) { ang = 360 - ang; }
return ang;
}

follow code fix error:can't get the clock hand Accurately.
example:hour is 0 and minute is 15,It is Difficult to get the hour clock hand.


this.clockContainer.on("mousedown", function(e){
this.moveEl['move'] = true;
this.moveEl['originalTime'] = this.time;
var el = Ext.get(e.target);
var coord = this.getCoords();
var ang = this.clickAngle({x:e.xy[0], y:e.xy[1]}, coord);
var h_ang = (this.time.hour%12) * 30;
var m_ang = this.time.minute * 6;
var middle_ang_first = (h_ang + m_ang) / 2;
var middle_ang_second = (middle_ang_first + 180) % 360;
var middle_ang_max = Math.max(middle_ang_first,middle_ang_second);
var middle_ang_min = Math.min(middle_ang_first,middle_ang_second);
this.moveEl['coord'] = coord;
if ((ang >= 0 && ang < middle_ang_min) || (ang > middle_ang_max && ang < 360) ) {
if ((h_ang >= 0 && h_ang < middle_ang_min) || (h_ang > middle_ang_max && h_ang < 360) ){
this.moveEl['el'] = "hour";
}else{
this.moveEl['el'] = "minute";
}
} else if(ang >= middle_ang_min && ang < middle_ang_max ) {
if (h_ang >= middle_ang_min && h_ang < middle_ang_max){
this.moveEl['el'] = "hour";
}else{
this.moveEl['el'] = "minute";
}
} else {
if (el.getStyle("background-image").indexOf(this.hourHandImage) != -1) {
this.moveEl['el'] = "hour";
}
else {
this.moveEl['el'] = "minute";
}
}
},this);

mmanners
12 Jan 2010, 7:50 AM
Thank you for this great extension. I ran into problems with the extension when I had a value that was loaded before the field was rendered. Please note the following fixes, the code I added is in bold:



I added a rendering check to moveHands

moveHands: function(){
if(this.rendered) {
this.hourHand.setStyle({"background-position":(((this.time.hour % 12) * this.hourHandSize.width) * -1) + 'px'});
this.minuteHand.setStyle({"background-position":((this.time.minute * this.minuteHandSize.width) * -1) + 'px'});
this.updateValue();
}
},

Another problem I encountered was that time was not set when the onRender was being called. To fix this problem I made assigned the time just before I process the date value.

onRender: function(ct, position) {
Ext.ux.form.ClockField.superclass.onRender.call(this, ct, position);
this.moveEl = {};
this.moveEl['move'] = false;
if (this.value) {
if (Ext.isDate(this.value)) {
this.time = this.startTime
this.time.minute = this.value.getMinutes();
this.time.hour = this.value.getHours();
this.time.ampm = this.value.dateFormat('A');
} else if (typeof(this.value) == "object") {
this.time = this.value;
} else if (typeof(this.value) == "string") {
var t = this.parseDate(this.value);
this.time.hour = parseInt(t.formatDate('h'));
this.time.minute = parseInt(t.formatDate('i'));
this.time.ampm = t.formatDate('A');
}
...


I placed the same line of code in the setValue function, as well as assigning the value property to value passed to the function.


setValue : function(value){
this.value = value;
if (value) {
if (Ext.isDate(value)) {
this.time = this.startTime;
this.time.minute = value.getMinutes();
this.time.hour = value.getHours();
this.time.ampm = value.dateFormat('A');
} else if (typeof(value) == "object") {
this.time = value;
} else if (typeof(value) == "string") {
var t = this.parseDate(value);
this.time.hour = parseInt(t.formatDate('h'));
this.time.minute = parseInt(t.formatDate('i'));
this.time.ampm = t.formatDate('A');
}
this.updateValue();
}
this.moveHands();
},

julien_desch
22 Feb 2010, 7:45 AM
Hello,

I'm trying to use your beautifull extension but i have a mistake.

"t.formatDate is not a function"

I use the new release of extjs : 3.1.0

Thanks for your help

willf1976
11 Mar 2010, 12:17 AM
Hi All

I made some modifications to this component adding some new features, fixing a few bugs, adding documentation, and changing its structure around a bit (see inline documentation for more details).

I also made a stripped down version that just creates a floating layer next to the triggerField which users can populate anything into via listeners. Here is the modified code -- please let me know if you run into any problems with it:

Ext.ux.form.ClockField:

/*
origenal author: Neon Monk (Found in Thread: http://www.extjs.com/forum/showthread.php?t=56390)
modifying author: Will Ferrer
date: 03/11/10
history:
03/11/10 -- posted to extJs forums
11/16/10 -- fixed a problem with am and pm
*/
/**
* @class Ext.ux.form.ClockField
* @extends Ext.form.TriggerField
* An analog clock field. Modified from the orginal post. Changes:
* A parent date field which this field can supplement (As the time in changed in the parent field this field with update to match, and as time is changed in this field the hours/minutes of the parent field will be updated to match).
* Customizable input, output and display formats for dates
* In line documentation
* Added an optional close button
* Many Bug Fixes
* @constructor
* @param {Object} config The config object
*/
Ext.ns('Ext.ux.form');
Ext.ux.form.ClockField = Ext.extend(Ext.form.TriggerField, {
/**
* @cfg {Object|Null} parentField
* A parent date field component. If set changes to the parent field will be reflected in this field and changes to this field will affect the parent field. Defaults to null.
*/
parentField : null,
/**
* @cfg {String} imagesPath
* A path to where the clock images are stored. Defaults to 'images'.
*/
imagesPath: "/default-a/Ext.rtb/Form.ClockField/images",
/**
* @cfg {String} faceImage
* Name of clock face image. Defaults to 'clock_face.gif'.
*/
faceImage: "clock_face.gif",
/**
* @cfg {String} faceImage
* Name of hour hand image. Defaults to 'clock_hours.gif'.
*/
hourHandImage: "clock_hours.gif",
/**
* @cfg {String} minuteHandImage
* Name of minute hand image. Defaults to 'clock_minutes.gif'.
*/
minuteHandImage: "clock_minutes.gif",
/**
* @cfg {String} centerImage
* Name of center image. Defaults to 'gif.gif'.
*/
centerImage: "clock_center.gif",
/**
* @cfg {String} clockBgColor
* Background color of clock. Defaults to 'white'.
*/
clockBgColor: 'white',
/**
* @cfg {String} clockBorder
* border of clock. Defaults to '1px solid lightgrey'.
*/
clockBorder: '1px solid lightgrey',
/**
* @cfg {Object} clockSize
* Size of clock. Defaults to Object
*/
clockSize: {
width: 142,
height: 142
},
/**
* @cfg {Array} inputFormats
* Text formats which the clock will accept if passed to its setValue function. Defaults to ['H:i:s', 'Y-m-d H:i:s']
*/
inputFormats : ['H:i:s', 'Y-m-d H:i:s'],
/**
* @cfg {Boolean} outputRawDate
* if set to true causes the getValue function to return a date object instead of a formated date string. Defaults to false
*/
outputRawDate : false,
/**
* @cfg {String} outputFormat
* format to convert dates to when the getValue function is called (a date object will be returned instead of a string if outputRawDate is set to true). Defaults to "H:i:s"
*/
outputFormat : "H:i:s",
/**
* @cfg {String} format
* The default date format string which can be overriden for localization support. The format must be valid according to Date.parseDate. Defaults to "g:i A"
*/
format: "g:i A",
/**
* @cfg {String} displayFormat
* Format to display the date as inside the trigger field. Defaults to "g:i A"
*/
displayFormat : 'g:i A',
/**
* @cfg {Object} hourHandSize
* Size of hour hand. Defaults to Object
*/
hourHandSize: {
width: 67,
height: 68
},
/**
* @cfg {Object} minuteHandSize
* Size of minute hand. Defaults to Object
*/
minuteHandSize: {
width: 111,
height: 112
},
/**
* @cfg {Object} centerSize
* Size of center. Defaults to Object
*/
centerSize: {
width: 7,
height: 6
},
/**
* @cfg {Object} closeButtonSize
* Size of close button. Defaults to Object
*/
closeButtonSize: {
width:38,
height:25
},
/**
* @cfg {Object|Null} closeButtonConfig
* config for the close button. Set to null to disable close button. Defaults to Object
*/
closeButtonConfig: {
text : 'close'
},

/**
* @cfg {Object} closeButtonStyle
* css style for close button. Defaults to Object
*/
closeButtonStyle: {
},
/**
* @cfg {Object} ampmStyle
* css style for ampm button. Defaults to Object
*/
ampmStyle: {
'fontSize': '10pt',
'fontWeight': 'bold',
'color': '#999999',
'textDecoration': 'none',
'cursor': 'pointer'
},
/**
* @cfg {Boolean} bruteForceSetParentValue
* force value to be set on parent field -- fixes an odd bug where some times setValue will not work on the parent field. Defaults to false
*/
bruteForceSetParentValue: false,
/**
* @private internal cfg {Object} time
* time object used to configure visual component of the clock.
*/
time : {
hours : null,
minutes : null,
ampm : 'PM'
},
/**
** Public Function: getValue
* Gets the value of the field. If outputRawDate is set to true then a date object is returned. Other wise a string formated using the outputFormat property will be returned.
*/
getValue: function(){
var outputFormat = this.outputFormat,
outputRawDate = this.outputRawDate;
if (outputRawDate) {
return this.value;
} else {
return this.value.format(outputFormat);
}
},
/**
** Public Function: getRawValue
* Gets the text that is currently displayed in the field
*/
getRawValue: function(){
return this.el.dom.value;
},
/**
** Public Function: setValue
* Set the value of the clock field. If a date is passed it will be used as is, if a string is passed the code will try to parse it using the formats in the inputFormats array, if nothing is passed the current date will be used.
* @param {Date|String} value (Optional) Defaults to new Date()
*/
setValue: function(value){
var value = (Ext.isEmpty(value))?this.value:value,
autoDetectInputFormat = this.autoDetectInputFormat,
inputFormats = this.inputFormats,
rendered = this.rendered,
curFormat, n, tempValue;

if (Ext.isEmpty(value)) {
value = new Date();
} else if (Ext.isDate(value)) {
value = value;
} else if (typeof(value)=='string') {
//Use the fall backs in the inputFormats array if autoDetect failed:
if (!Ext.isDate(tempValue)) {
for (n=0;n<inputFormats.length; n++) {
curFormat = inputFormats[n];
tempValue = Date.parseDate(value, curFormat);
if (Ext.isDate(tempValue)) {
break;
}
}
}
value = tempValue;
}
this.value = value;

if (rendered) {
this.updateField();
this.updateClock();
}

},
// @private
initComponent: function(){
this.setValue(this.value);
Ext.ux.form.ClockField.superclass.initComponent.call(this);
if (!Ext.isEmpty(this.parentField)) {
this.initParentField();
}
this.addEvents(
/**
* @event updatefield
* Fires after the field has been visualy updated
* @param {Ext.ux.form.ClockField} this
*/
'updatefield'
);
},
// @private
initParentField : function(){
this.parentField.on('select', this.shareParentFieldDate, this);
this.parentField.on('show', this.shareParentFieldDate, this);
},
// @private
shareParentFieldDate : function() {
var curDate = this.parentField.getValue();
this.setValue(curDate);
},
// @private
onTriggerClick: function(){
if (this.disabled) {
return;
}
if (this.isExpanded()) {
this.collapse();
this.el.focus();
}
else {
this.onFocus({});
this.el.focus();
this.expand();
}
},
// @private
isExpanded: function(){
return (this.clockContainer.getStyle('visibility') == 'visible' ? true : false);
},
// @private
expand: function(){

if (this.disabled) {
return;
}
var w = this.el.getWidth();
this.clockContainer.alignTo(this.el, 'bl');
this.clockContainer.setSize(w, this.clockSize.height + this.closeButtonSize.height);
this.clockContainer.setStyle({
'padding-left': (w - this.clockSize.width) / 2 + 'px',
'visibility': 'visible'
});
if (this.inMenu) {
this.clockContainer.setStyle({
'display': 'block'
});
}
var cw = this.clockContainer.getWidth();
if (cw > w) {
this.clockContainer.setWidth(w);
}
else
if (cw < this.clockSize.width) {
this.clockContainer.setWidth(this.clockSize.width)
this.clockContainer.setStyle({
'padding': 0
});
}
this.ampm.update(this.time.ampm);
},
// @private
disable: function(ct, position){
Ext.ux.form.ClockField.superclass.disable.call(this);
this.collapse();
},
// @private
collapse: function(){
this.clockContainer.setStyle({
'visibility': 'hidden'
});
if (this.inMenu) {
this.clockContainer.setStyle({
'display': 'none'
});
}
},
// @private
convertValueToTime : function () {
var hours = this.value.getHours(),
minutes = this.value.getMinutes(),
ampm = this.value.format('A');

if (Ext.isEmpty(this.value)) {
this.setValue();
}

if (hours > 12) {
hours -= 12;
}

this.time.hour = hours;
this.time.minute = minutes;
this.time.ampm = ampm;
},
// @private
onRender: function(ct, position){
Ext.ux.form.ClockField.superclass.onRender.call(this, ct, position);
this.moveEl = {};
this.moveEl['move'] = false;
this.convertValueToTime();

this.clockContainer = new Ext.Layer({
shadow: this.shadow,
style: {
'-moz-user-select': 'none',
'position': 'absolute',
'z-index': 24000
}
});
//Trying to make it work in a menu item:
if (this.inMenu) {
this.clockContainer.setStyle({
'position': 'fixed'
});
this.holder = this.el.findParent('div');
this.clockContainer.appendTo(this.holder);

this.ul = Ext.get(this.el.findParent('ul'));
this.ul.setStyle({
'overflow': 'visible'
});
}
//
this.clockContainer.setStyle({
'background-color': this.clockBgColor,
'border': this.clockBorder
});

this.clockFace = Ext.DomHelper.append(this.clockContainer, {
tag: 'img',
src: this.imagesPath + "/" + this.faceImage,
style: {
'-moz-user-select': 'none',
'position': 'absolute',
'z-index': 24001
}
}, true);

this.minuteHand = Ext.DomHelper.append(this.clockContainer, {
tag: 'div',
style: {
'-moz-user-select': 'none',
'width': this.minuteHandSize.width + 'px',
'height': this.minuteHandSize.height + 'px',
'margin-top': ((this.clockSize.height - this.minuteHandSize.height) / 2) + 'px',
'margin-left': ((this.clockSize.width - this.minuteHandSize.width) / 2) + 'px',
'position': 'absolute',
'z-index': 24002,
'background': 'url(' + this.imagesPath + "/" + this.minuteHandImage + ') no-repeat top left'
}
}, true);

this.hourHand = Ext.DomHelper.append(this.clockContainer, {
tag: 'div',
style: {
'-moz-user-select': 'none',
'margin-top': ((this.clockSize.height - this.hourHandSize.height) / 2) + 'px',
'margin-left': ((this.clockSize.width - this.hourHandSize.width) / 2) + 'px',
'width': this.hourHandSize.width + 'px',
'height': this.hourHandSize.height + 'px',
'position': 'absolute',
'z-index': 24003,
'background': 'url(' + this.imagesPath + "/" + this.hourHandImage + ') no-repeat top left'
}
}, true);

this.middleImg = Ext.DomHelper.append(this.clockContainer, {
tag: 'img',
src: this.imagesPath + "/" + this.centerImage,
style: {
'-moz-user-select': 'none',
'position': 'absolute',
'z-index': 24004,
'margin-top': ((this.clockSize.height - this.centerSize.height) / 2) + 'px',
'margin-left': ((this.clockSize.width - this.centerSize.width) / 2) + 'px'
}
}, true);

this.ampm = Ext.DomHelper.append(this.clockContainer, {
tag: 'div',
style: this.ampmStyle
}, true);

this.ampm.update(this.time.ampm);

this.ampm.setStyle({
'position': 'absolute',
'z-index': 24005,
'display': 'block',
'-moz-user-select': 'none',
'margin-top': ((this.clockSize.height + (this.clockSize.height / 4)) / 2) + 'px',
'margin-left': ((this.clockSize.width - 20) / 2) + 'px'
});

if (!Ext.isEmpty(this.closeButtonConfig)) {
this.closeButtonHolder = Ext.DomHelper.append(this.clockContainer, {
tag: 'div'
}, true);

var closeButtonStyle = Ext.apply(this.closeButtonStyle, {
'position': 'absolute',
'z-index': 24005,
'display': 'block',
'-moz-user-select': 'none',
'margin-top': (this.clockSize.height) + 'px',
'margin-left': ((this.clockSize.width - this.closeButtonSize.width) / 2) + 'px',
'text-align':'center'
});

this.closeButtonHolder.setStyle(closeButtonStyle);

this.closeButtonHolder.setHeight(this.closeButtonSize.height);
this.closeButtonHolder.setWidth(this.closeButtonSize.width);

this.closeButton = new Ext.Button(Ext.apply(this.closeButtonConfig, {
renderTo : this.closeButtonHolder,
handler : this.triggerCollapse,
scope : this
}));
}

this.ampm.on("click", this.toggleAmPm, this);
this.clockContainer.on("mousedown", this.onMouseDown, this);
this.clockContainer.on("mouseup", this.onMouseUp, this);
this.clockContainer.on("mousemove", this.onMouseMove, this);
this.on('blur', this.triggerCollapse, this);
/*this.on('keydown', this.updateClock, this, {
buffer: 200
});*/

this.moveHands();
},
// @private
onMouseDown : function (e) {
this.moveEl['move'] = true;
this.moveEl['originalTime'] = this.time;
var el = Ext.get(e.target);
var coord = this.getCoords();
var ang = this.clickAngle({
x: e.xy[0],
y: e.xy[1]
}, coord);
var h_ang = (this.time.hour % 12) * 30;
var m_ang = this.time.minute * 6;
this.moveEl['coord'] = coord;
if (Math.abs(ang - m_ang) < Math.abs(ang - h_ang)) {
this.moveEl['el'] = "minute";
} else if (Math.abs(ang - m_ang) > Math.abs(ang - h_ang)) {
this.moveEl['el'] = "hour";
} else {
if (el.getStyle("background-image").indexOf(this.hourHandImage) != -1) {
this.moveEl['el'] = "hour";
} else {
this.moveEl['el'] = "minute";
}
}
},
// @private
onMouseMove : function (e) {
if (this.moveEl['move']) {
this.el.focus();
var add, ang_by;
var scrollOffset = this.getScrollXY();
//will code:
var mouseX = e.xy[0] + scrollOffset[0];
var mouseY = e.xy[1] + scrollOffset[1];
var ang = this.clickAngle({
x: mouseX,
y: mouseY
}, this.moveEl.coord);
//
if (this.moveEl.el == "hour") {
ang_by = 30;
} else {
ang_by = 6;
}
if (this.moveEl.el == "hour") {
var h = Math.round(ang / ang_by);
if (!isNaN(h)) {
this.time.hour = h;
}
} else {
var m = Math.round(ang / ang_by);
if (m == 60) {
m = 0;
}
if (!isNaN(m)) {
this.time.minute = m;
}
}
this.moveHands();
this.fireEvent("change");
}
},
// @private
onMouseUp : function (e) {
this.convertTimeToValue();
this.updateField();
this.moveEl = {};
this.moveEl['move'] = false;
},
// @private
triggerCollapse : function (e) {
if (!this.moveEl['move']) {
this.collapse();
}
},
// @private
getCoords: function(){
var position = this.clockFace.getXY(), size = this.clockFace.getSize();
var obj = {
left: this.clockFace.getLeft(),
top: Math.round(this.clockFace.getY()),
width: size.width,
height: size.height
};
obj.right = obj.left + obj.width;
obj.bottom = obj.top + obj.height;
return obj;
},
// @private
toggleAmPm: function(){
this.el.focus();
if (this.time.ampm == "AM") {
this.time.ampm = "PM";
} else {
this.time.ampm = "AM";
}
this.ampm.update(this.time.ampm);
this.convertTimeToValue();
this.updateField();
},
// @private
moveHands: function(){
this.hourHand.setStyle({
"background-position": (((this.time.hour % 12) * this.hourHandSize.width) * -1) + 'px'
});
this.minuteHand.setStyle({
"background-position": ((this.time.minute * this.minuteHandSize.width) * -1) + 'px'
});
this.updateField();
},
// @private
convertTimeToValue : function () {
var time = this.time,
hours = time.hour,
minutes = time.minute,
ampm = time.ampm,
value = new Date();
if (ampm=='PM') {
hours += 12;
}
value.setHours(hours);
value.setMinutes(minutes);
value.setSeconds(0);
this.setValue(value);
},
// @private
updateField: function(){
var displayFormat = this.displayFormat,
parentValue;
if (this.rendered) {
this.el.dom.value = this.value.format(displayFormat);
if (!Ext.isEmpty(this.parentField)) {
parentValue = this.parentField.getValue();
if (Ext.isDate(parentValue)) {
parentValue.setHours(this.value.getHours());
parentValue.setMinutes(this.value.getMinutes());
parentValue.setSeconds(0);
this.parentField.setValue(parentValue);
if (this.bruteForceSetParentValue) {
this.parentField.value = parentValue;
}
}
}
}
this.fireEvent('updatefield', this);
},
// @private
updateClock: function(e){
this.convertValueToTime();
this.moveHands();
},
// @private
validateValue: Ext.form.DateField.prototype.validateValue,
// @private
validate: Ext.form.DateField.prototype.validate,
// @private
parseDate: Ext.form.DateField.prototype.parseDate,
// @private
formatDate: Ext.form.DateField.prototype.formatDate,
// @private
clickAngle: function(pnt, coord){
var c_x = coord.width / 2;
var c_y = coord.height / 2;
var x = pnt.x + window.scrollX - coord.left;
var y = pnt.y + window.scrollY - coord.top;
var t_x = c_x;
var t_y = y;
var CA = t_x - x;
var CO = t_y - c_y;
var AO = Math.sqrt(Math.pow(CA, 2) + Math.pow(CO, 2));
var ang = Math.round((Math.acos((Math.pow(Math.abs(CA), 2) - Math.pow(Math.abs(AO), 2) - Math.pow(CO, 2)) / (2 * CO * AO))) * 180 / Math.PI);
if (x < c_x) {
ang = 360 - ang;
}
return ang;
},
// @private
getScrollXY: function () {
var scrOfX = 0, scrOfY = 0;
if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
//DOM compliant
scrOfY = document.body.scrollTop;
scrOfX = document.body.scrollLeft;
} else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
//IE6 standards compliant mode
scrOfY = document.documentElement.scrollTop;
scrOfX = document.documentElement.scrollLeft;
}
return [ scrOfX, scrOfY ];
}
});
Ext.reg('clockfield', Ext.ux.form.ClockField);





/*
addapted from: Ext.ux.form.ClockField by Neon Monk (Found in Thread: http://www.extjs.com/forum/showthread.php?t=56390)
modifying author: Will Ferrer
date: 05/11/10
history:
03/11/10 -- posted to extJs forums
05/11/10 -- fixed a few bugs
*/
/**
* @class Ext.ux.form.TriggerLayer
* @extends Ext.form.TriggerField
* A trigger field that creates a layer floating next to it when activated. Using listeners you may render different elements into the layer allowing you to easly attach any component to a trigger field.
* @constructor
* @param {Object} config The config object
*/
Ext.ns('Ext.ux.form');
Ext.ux.form.TriggerLayer = Ext.extend(Ext.form.TriggerField, {
/**
* @cfg {Object|Null} layerSize
* May contain an object with a height and width property to define the size of the layer to be created. Defaults to null.
*/
layerSize: null,
/**
* @cfg {Object|Null} layerOffset
* Offsets to apply to the layer relative to the trigger field. Defaults to [10, 0].
*/
layerOffset: [10, 0],
/**
* @cfg {Object|Null} layerAlign
* Alignment of the layer relative to the trigger field. Defaults to 'tr'.
*/
layerAlign : 'tr',
/**
* @cfg {Object} layerStyle
* Styles to apply to the css of the layer object. Defaults to {}.
*/
layerStyle : {},
/**
* @cfg {Object} layerConfig
* Config for the layer. Defaults to {}.
*/
layerConfig : {},
// @private
initComponent : function () {
Ext.ux.form.TriggerLayer.superclass.initComponent.call(this);
this.addEvents(
/**
* @event createlayer
* Fires when a layer is created
* @param {Ext.ux.form.TriggerLayer} this
* @param {Ext.Layer} the layer created
*/
'createlayer',
/**
* @event showlayer
* Fires when a layer is displayed via the exand function
* @param {Ext.ux.form.TriggerLayer} this
* @param {Ext.Layer} the layer created
*/
'showlayer',
/**
* @event hidelayer
* Fires when a layer is hidden via the collapse function
* @param {Ext.ux.form.TriggerLayer} this
* @param {Ext.Layer} the layer created
*/
'hidelayer'
);
},
// @private
onTriggerClick: function(){
if (this.disabled) {
return;
}
if (this.isExpanded()) {
this.collapse();
this.el.focus();
} else {
this.onFocus({});
this.el.focus();
this.expand();
}
},
// @private
isExpanded: function(){
return (this.layer.getStyle('visibility') == 'visible' ? true : false);
},
// @private
expand: function(){
if (this.disabled) {
return;
}
var w = this.el.getWidth();
this.layer.alignTo(this.el, this.layerAlign, this.layerOffset);
if (!Ext.isEmpty(this.layerSize)) {
this.layer.setSize(this.layerSize.width, this.layerSize.height);
/*this.layer.setStyle({
'padding-left': (w - this.layerSize.width) / 2 + 'px'
});*/
}
this.layer.setStyle({
'visibility': 'visible',
'display': 'block'
});
this.fireEvent('showlayer', this, this.layer);
},
// @private
disable: function(ct, position){
Ext.ux.form.TriggerLayer.superclass.disable.call(this);
this.collapse();
},
// @private
collapse: function(){
this.layer.setStyle({
'visibility': 'hidden'
});
this.layer.setStyle({
'display': 'none'
});
this.fireEvent('hidelayer', this, this.layer);
},
// @private
onRender: function(ct, position){
Ext.ux.form.TriggerLayer.superclass.onRender.call(this, ct, position);
var style = Ext.apply(this.layerStyle, {
'-moz-user-select': 'none',
'position': 'absolute',
'z-index': 24000
});
this.layer = new Ext.Layer(Ext.apply(this.layerConfig, {
shadow: this.shadow
}));

this.layer.setStyle(style);

this.layer.setStyle({
'position': 'fixed'
});

this.holder = this.el.findParent('div');
this.layer.appendTo(this.holder);
this.on('blur', this.collapse, this);

this.layer.setStyle({
'background-color': this.layerBgColor,
'border': this.layerBorder
});
this.fireEvent('createlayer', this, this.layer);
}
});
Ext.reg('ux-form-triggerlayer', Ext.ux.form.TriggerLayer);

kushal999
12 Jan 2011, 3:46 AM
Hi All

I made some modifications to this component adding some new features, fixing a few bugs, adding documentation, and changing its structure around a bit (see inline documentation for more details).

I also made a stripped down version that just creates a floating layer next to the triggerField which users can populate anything into via listeners. Here is the modified code -- please let me know if you run into any problems with it:


First, great work. Thanks to both of you. But Will:


It doesn't work in IE. (Doesn't cause any errors, but nothing happens when you drag the clock hands)
I'm confused as to why you posted the code for Ext.ux.form.TriggerLayer when you aren not using it within Ext.ux.form.ClockField code?

willf1976
18 Jan 2011, 12:43 PM
Hi Kushal

Thank you for the kudos and the bug report.

Trigger layer is a trimmed down version of the clock field code -- it just shows a layer and providers some ways of letting the developer put what ever they want in the layer rather than displaying a clock in it. I posted it in this thread initially because I wanted to give neon monk credit for it (since it was based off his code) but latter I realized it should also have its own thread. Now since I had posted here initially I am trying to maintain it in both threads.

I added some IE testing to my todo list and hopefully will get it to it in the next few days (when some more pressing matters are off my plate). If you happen to figure out the problem before I get it to it please let me know :).

Best regards

Will Ferrer