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);
Powered by vBulletin® Version 4.1.5 Copyright © 2012 vBulletin Solutions, Inc. All rights reserved.