This class implements a "virtual element" at a relative size and position
* within an existing element. It provides mouse events from a zone of an element of
* defined dimensions.
* The zone is defined using top
, right
, bottom
,
* left
, width
and height
options which specify
* the bounds of the zone in a similar manner to the CSS style properties of those names.
* @cfg {String|HtmlElement} el The element in which to create the zone.
* @cfg {Array} points An Array of points within the element defining the event zone.
* @cfg {Number} top The top of the zone. If negative means an offset from the bottom.
* @cfg {Number} right The right of the zone. If negative means an offset from the right.
* @cfg {Number} left The left of the zone. If negative means an offset from the right.
* @cfg {Number} bottom The bottom of the zone. If negative means an offset from the bottom.
* @cfg {Number} width The width of the zone.
* @cfg {Number} height The height of the zone.
* @constructor
* Create a new EventZone
* @param {Object} config The config object.
*/
Ext.ux.EventZone = Ext.extend(Ext.util.Observable, {
constructor: function(config) {
this.initialConfig = config;
this.addEvents(
/**
* @event mouseenter
* This event fires when the mouse enters the zone.
* @param {EventObject} e the underlying mouse event.
* @param {EventZone} this
*/
'mouseenter',
/**
* @event mousedown
* This event fires when the mouse button is depressed within the zone.
* @param {EventObject} e the underlying mouse event.
* @param {EventZone} this
*/
'mousedown',
/**
* @event mousemove
* This event fires when the mouse moves within the zone.
* @param {EventObject} e the underlying mouse event.
* @param {EventZone} this
*/
'mousemove',
/**
* @event mouseup
* This event fires when the mouse button is released within the zone.
* @param {EventObject} e the underlying mouse event.
* @param {EventZone} this
*/
'mouseup',
/**
* @event mouseenter
* This event fires when the mouse is clicked within the zone.
* @param {EventObject} e the underlying mouse event.
* @param {EventZone} this
*/
'click',
/**
* @event mouseleave
* This event fires when the mouse leaves the zone.
* @param {EventObject} e the underlying mouse event.
* @param {EventZone} this
*/
'mouseleave'
);
Ext.apply(this, config);
this.el = Ext.get(this.el);
// If a polygon within the element is specified...
if (this.points) {
this.polygon = new Ext.lib.Polygon(this.points);
this.points = this.polygon.points;
}
Ext.ux.EventZone.superclass.constructor.call(this);
this.el.on({
mouseenter: this.handleMouseEvent,
mousedown: this.handleMouseEvent,
mousemove: this.handleMouseEvent,
mouseup: this.handleMouseEvent,
click: this.handleMouseEvent,
mouseleave: this.handleMouseEvent,
scope: this
});
},
handleMouseEvent: function(e) {
var r = this.polygon ? this.getPolygon() : this.getRegion();
var inBounds = r.contains(e.getPoint());
switch (e.type) {
// mouseenter fires this
case 'mouseover':
if (inBounds) {
this.mouseIn = true;
this.fireEvent('mouseenter', e, this);
}
break;
// mouseleave fires this
case 'mouseout':
this.mouseIn = false;
this.fireEvent('mouseleave', e, this);
break;
case 'mousemove':
if (inBounds) {
if (this.mouseIn) {
this.fireEvent('mousemove', e, this);
} else {
this.mouseIn = true;
this.fireEvent('mouseenter', e, this);
}
} else {
if (this.mouseIn) {
this.mouseIn = false;
this.fireEvent('mouseleave', e, this);
}
}
break;
default:
if (inBounds) {
this.fireEvent(e.type, e, this);
}
}
},
getPolygon: function() {
var xy = this.el.getXY();
return this.polygon.translate(xy[0], xy[1]);
},
getRegion: function() {
var r = this.el.getRegion();
// Adjust left boundary of region
if (Ext.isNumber(this.left)) {
if (this.left < 0) {
r.left = r.right + this.left;
} else {
r.left += this.left;
}
}
// Adjust right boundary of region
if (Ext.isNumber(this.width)) {
r.right = r.left + this.width;
} else if (Ext.isNumber(this.right)) {
r.right = (this.right < 0) ? r.right + this.right : r.left + this.right;
}
// Adjust top boundary of region
if (Ext.isNumber(this.top)) {
if (this.top < 0) {
r.top = r.bottom + this.top;
} else {
r.top += this.top;
}
}
// Adjust bottom boundary of region
if (Ext.isNumber(this.height)) {
r.bottom = r.top + this.height;
} else if (Ext.isNumber(this.bottom)) {
r.bottom = (this.bottom < 0) ? r.bottom + this.bottom : r.top + this.bottom;
}
return r;
}
});
/**
* @class Ext.lib.Polygon
* This class encapsulates an absolute area of the document bounded by a list of points.
* @constructor
* Create a new Polygon
* @param {Object} points An Array of [n,n]
point specification Arrays, or
* an Array of Ext.lib.Points, or an HtmlElement, or an Ext.lib.Region.
*/
Ext.lib.Polygon = Ext.extend(Ext.lib.Region, {
constructor: function(points) {
var i, l, el;
if (l = points.length) {
if (points[0].x) {
for (i = 0; i < l; i++) {
points[i] = [ points[i].x, points[i].y ];
}
}
this.points = points;
} else {
if (el = Ext.get(points)) {
points = Ext.lib.Region.getRegion(el.dom);
}
if (points instanceof Ext.lib.Region) {
this.points = [
[points.left, points.top],
[points.right, points.top],
[points.right, points.bottom],
[points.left, points.bottom]
];
}
}
},
/**
* Returns a new Polygon translated by the specified X
and Y
increments.
* @param xDelta {Number} The X
translation increment.
* @param xDelta {Number} The Y
translation increment.
* @return {Polygon} The resulting Polygon.
*/
translate: function(xDelta, yDelta) {
var r = [], p = this.points, l = p.length, i;
for (i = 0; i < l; i++) {
r[i] = [ p[i][0] + xDelta, p[i][1] + yDelta ];
}
return new Ext.lib.Polygon(r);
},
/**
* Returns the area of this Polygon.
*/
getArea: function() {
var p = this.points, l = p.length, area = 0, i, j = 0;
for (i = 0; i < l; i++) {
j++;
if (j == l) {
j = 0;
}
area += (p[i][0] + p[j][0]) * (p[i][1] - p[j][1]);
}
return area * 0.5;
},
/**
* Returns true
if this Polygon contains the specified point. Thanks
* to http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html for the algorithm.
* @param pt {Point|Number} Either an Ext.lib.Point object, or the X
coordinate to test.
* @param py {Number} Optional. If the first parameter was an X
coordinate, this is the Y
coordinate.
*/
contains: function(pt, py) {
var f = (arguments.length == 1),
testX = f ? pt.x : pt,
testY = f ? pt.y : py,
p = this.points,
nvert = p.length,
j = nvert - 1,
i, j, c = false;
for (i = 0; i < nvert; j = i++) {
if ( ((p[i][1] > testY) != (p[j][1] > testY)) &&
(testX < (p[j][0]-p[i][0]) * (testY-p[i][1]) / (p[j][1]-p[i][1]) + p[i][0])) {
c = !c;
}
}
return c;
}
});
/**
* @class Ext.Resizable
* @extends Ext.util.Observable
* This is an override of Ext.Resizable to make usage of the Ex.ux.EventZone
* Applies virtual drag handles to an element to make it resizable.
* Here is the list of valid resize handles:
*
Value Description
------ -------------------
'n' north
's' south
'e' east
'w' west
'nw' northwest
'sw' southwest
'se' southeast
'ne' northeast
'all' all
* Here's an example showing the creation of a typical Resizable:
*
var resizer = new Ext.Resizable('element-id', {
handles: 'all',
minWidth: 200,
minHeight: 100,
maxWidth: 500,
maxHeight: 400,
pinned: true
});
resizer.on('resize', myHandler);
* To hide a particular handle, set its display to none in CSS, or through script:
* resizer.east.setDisplayed(false);
* @constructor
* Create a new resizable component
* @param {Mixed} el The id or element to resize
* @param {Object} config configuration options
*/
Ext.Resizable = function(el, config){
this.el = Ext.get(el);
/**
* The proxy Element that is resized in place of the real Element during the resize operation.
* This may be queried using {@link Ext.Element#getBox} to provide the new area to resize to.
* Read only.
* @type Ext.Element.
* @property proxy
*/
this.proxy = this.el.createProxy({tag: 'div', cls: 'x-resizable-proxy', id: this.el.id + '-rzproxy'}, Ext.getBody());
this.proxy.unselectable();
this.proxy.enableDisplayMode('block');
Ext.apply(this, config);
if(this.pinned){
this.disableTrackOver = true;
this.el.addClass('x-resizable-pinned');
}
// if the element isn't positioned, make it relative
var position = this.el.getStyle('position');
if(position != 'absolute' && position != 'fixed'){
this.el.setStyle('position', 'relative');
}
if(!this.handles){ // no handles passed, must be legacy style
this.handles = 's,e,se';
if(this.multiDirectional){
this.handles += ',n,w';
}
}
if(this.handles == 'all'){
this.handles = 'n s e w ne nw se sw';
}
var hs = this.handles.split(/\s*?[,;]\s*?| /);
var ps = Ext.Resizable.positions;
for(var i = 0, len = hs.length; i < len; i++){
if(hs[i] && ps[hs[i]]){
var pos = ps[hs[i]];
this[pos] = new Ext.Resizable.Handle(this, pos);
}
}
// legacy
this.corner = this.southeast;
if(this.handles.indexOf('n') != -1 || this.handles.indexOf('w') != -1){
this.updateBox = true;
}
this.activeHandle = null;
if(this.adjustments == 'auto'){
var hw = this.west, he = this.east, hn = this.north, hs = this.south;
this.adjustments = [
(he.el ? -he.el.getWidth() : 0) + (hw.el ? -hw.el.getWidth() : 0),
(hn.el ? -hn.el.getHeight() : 0) + (hs.el ? -hs.el.getHeight() : 0) -1
];
}
if(this.draggable){
this.dd = this.dynamic ?
this.el.initDD(null) : this.el.initDDProxy(null, {dragElId: this.proxy.id});
this.dd.setHandleElId(this.el.id);
}
this.addEvents(
/**
* @event beforeresize
* Fired before resize is allowed. Set {@link #enabled} to false to cancel resize.
* @param {Ext.Resizable} this
* @param {Ext.EventObject} e The mousedown event
*/
'beforeresize',
/**
* @event resize
* Fired after a resize.
* @param {Ext.Resizable} this
* @param {Number} width The new width
* @param {Number} height The new height
* @param {Ext.EventObject} e The mouseup event
*/
'resize'
);
if(this.width !== null && this.height !== null){
this.resizeTo(this.width, this.height);
}
if(Ext.isIE){
this.el.dom.style.zoom = 1;
}
Ext.Resizable.superclass.constructor.call(this);
};
Ext.extend(Ext.Resizable, Ext.util.Observable, {
/**
* @cfg {Array/String} adjustments String 'auto' or an array [width, height] with values to be added to the
* resize operation's new size (defaults to [0, 0])
*/
adjustments : [0, 0],
/**
* @cfg {Boolean} animate True to animate the resize (not compatible with dynamic sizing, defaults to false)
*/
animate : false,
/**
* @cfg {Mixed} constrainTo Constrain the resize to a particular element
*/
/**
* @cfg {Boolean} draggable Convenience to initialize drag drop (defaults to false)
*/
draggable: false,
/**
* @cfg {Number} duration Animation duration if animate = true (defaults to 0.35)
*/
duration : 0.35,
/**
* @cfg {Boolean} dynamic True to resize the element while dragging instead of using a proxy (defaults to false)
*/
dynamic : false,
/**
* @cfg {String} easing Animation easing if animate = true (defaults to 'easingOutStrong')
*/
easing : 'easeOutStrong',
/**
* @cfg {Boolean} enabled False to disable resizing (defaults to true)
*/
enabled : true,
/**
* @property enabled Writable. False if resizing is disabled.
* @type Boolean
*/
/**
* @cfg {String} handles String consisting of the resize handles to display (defaults to undefined).
* Specify either 'all' or any of 'n s e w ne nw se sw'.
*/
handles : false,
/**
* @cfg {Boolean} multiDirectional Deprecated. Deprecated style of adding multi-direction resize handles.
*/
multiDirectional : false,
/**
* @cfg {Number} height The height of the element in pixels (defaults to null)
*/
height : null,
/**
* @cfg {Number} width The width of the element in pixels (defaults to null)
*/
width : null,
/**
* @cfg {Number} heightIncrement The increment to snap the height resize in pixels
* (only applies if {@link #dynamic}==true
). Defaults to 0.
*/
heightIncrement : 0,
/**
* @cfg {Number} widthIncrement The increment to snap the width resize in pixels
* (only applies if {@link #dynamic}==true
). Defaults to 0.
*/
widthIncrement : 0,
/**
* @cfg {Number} minHeight The minimum height for the element (defaults to 5)
*/
minHeight : 5,
/**
* @cfg {Number} minWidth The minimum width for the element (defaults to 5)
*/
minWidth : 5,
/**
* @cfg {Number} maxHeight The maximum height for the element (defaults to 10000)
*/
maxHeight : 10000,
/**
* @cfg {Number} maxWidth The maximum width for the element (defaults to 10000)
*/
maxWidth : 10000,
/**
* @cfg {Number} minX The minimum x for the element (defaults to 0)
*/
minX: 0,
/**
* @cfg {Number} minY The minimum x for the element (defaults to 0)
*/
minY: 0,
/**
* @cfg {Boolean} pinned True to ensure that the resize handles are always visible, false to display them only when the
* user mouses over the resizable borders. This is only applied at config time. (defaults to false)
*/
pinned : false,
/**
* @cfg {Boolean} preserveRatio True to preserve the original ratio between height
* and width during resize (defaults to false)
*/
preserveRatio : false,
/**
* @cfg {Ext.lib.Region} resizeRegion Constrain the resize to a particular region
*/
/**
* Perform a manual resize and fires the 'resize' event.
* @param {Number} width
* @param {Number} height
*/
resizeTo : function(width, height){
this.el.setSize(width, height);
this.fireEvent('resize', this, width, height, null);
},
// private
startSizing : function(e, handle){
this.fireEvent('beforeresize', this, e);
if(this.enabled){ // 2nd enabled check in case disabled before beforeresize handler
e.stopEvent();
Ext.getDoc().on({
scope: this,
mousemove: this.onMouseMove,
mouseup: {
fn: this.onMouseUp,
single: true,
scope: this
}
});
Ext.getBody().addClass('ux-resizable-handle-' + handle.position);
this.resizing = true;
this.startBox = this.el.getBox();
this.startPoint = e.getXY();
this.offsets = [(this.startBox.x + this.startBox.width) - this.startPoint[0],
(this.startBox.y + this.startBox.height) - this.startPoint[1]];
if(this.constrainTo) {
var ct = Ext.get(this.constrainTo);
this.resizeRegion = ct.getRegion().adjust(
ct.getFrameWidth('t'),
ct.getFrameWidth('l'),
-ct.getFrameWidth('b'),
-ct.getFrameWidth('r')
);
}
this.proxy.setStyle('visibility', 'hidden'); // workaround display none
this.proxy.show();
this.proxy.setBox(this.startBox);
if(!this.dynamic){
this.proxy.setStyle('visibility', 'visible');
}
}
},
// private
onMouseDown : function(handle, e){
if(this.enabled && !this.activeHandle){
e.stopEvent();
this.activeHandle = handle;
this.startSizing(e, handle);
}
},
// private
onMouseUp : function(e){
Ext.getBody().removeClass('ux-resizable-handle-' + this.activeHandle.position)
.un('mousemove', this.onMouseMove, this);
var size = this.resizeElement();
this.resizing = false;
this.handleOut(this.activeHandle);
this.proxy.hide();
this.fireEvent('resize', this, size.width, size.height, e);
this.activeHandle = null;
},
// private
snap : function(value, inc, min){
if(!inc || !value){
return value;
}
var newValue = value;
var m = value % inc;
if(m > 0){
if(m > (inc/2)){
newValue = value + (inc-m);
}else{
newValue = value - m;
}
}
return Math.max(min, newValue);
},
/**
* Performs resizing of the associated Element. This method is called internally by this
* class, and should not be called by user code.
* If a Resizable is being used to resize an Element which encapsulates a more complex UI
* component such as a Panel, this method may be overridden by specifying an implementation
* as a config option to provide appropriate behaviour at the end of the resize operation on
* mouseup, for example resizing the Panel, and relaying the Panel's content.
* The new area to be resized to is available by examining the state of the {@link #proxy}
* Element. Example:
new Ext.Panel({
title: 'Resize me',
x: 100,
y: 100,
renderTo: Ext.getBody(),
floating: true,
frame: true,
width: 400,
height: 200,
listeners: {
render: function(p) {
new Ext.Resizable(p.getEl(), {
handles: 'all',
pinned: true,
transparent: true,
resizeElement: function() {
var box = this.proxy.getBox();
p.updateBox(box);
if (p.layout) {
p.doLayout();
}
return box;
}
});
}
}
}).show();
*/
resizeElement : function(){
var box = this.proxy.getBox();
if(this.updateBox){
this.el.setBox(box, false, this.animate, this.duration, null, this.easing);
}else{
this.el.setSize(box.width, box.height, this.animate, this.duration, null, this.easing);
}
if(!this.dynamic){
this.proxy.hide();
}
return box;
},
// private
constrain : function(v, diff, m, mx){
if(v - diff < m){
diff = v - m;
}else if(v - diff > mx){
diff = v - mx;
}
return diff;
},
// private
onMouseMove : function(e){
if(this.enabled && this.activeHandle){
try{// try catch so if something goes wrong the user doesn't get hung
if(this.resizeRegion && !this.resizeRegion.contains(e.getPoint())) {
return;
}
//var curXY = this.startPoint;
var curSize = this.curSize || this.startBox,
x = this.startBox.x, y = this.startBox.y,
ox = x,
oy = y,
w = curSize.width,
h = curSize.height,
ow = w,
oh = h,
mw = this.minWidth,
mh = this.minHeight,
mxw = this.maxWidth,
mxh = this.maxHeight,
wi = this.widthIncrement,
hi = this.heightIncrement,
eventXY = e.getXY(),
diffX = -(this.startPoint[0] - Math.max(this.minX, eventXY[0])),
diffY = -(this.startPoint[1] - Math.max(this.minY, eventXY[1])),
pos = this.activeHandle.position,
tw,
th;
switch(pos){
case 'east':
w += diffX;
w = Math.min(Math.max(mw, w), mxw);
break;
case 'south':
h += diffY;
h = Math.min(Math.max(mh, h), mxh);
break;
case 'southeast':
w += diffX;
h += diffY;
w = Math.min(Math.max(mw, w), mxw);
h = Math.min(Math.max(mh, h), mxh);
break;
case 'north':
diffY = this.constrain(h, diffY, mh, mxh);
y += diffY;
h -= diffY;
break;
case 'west':
diffX = this.constrain(w, diffX, mw, mxw);
x += diffX;
w -= diffX;
break;
case 'northeast':
w += diffX;
w = Math.min(Math.max(mw, w), mxw);
diffY = this.constrain(h, diffY, mh, mxh);
y += diffY;
h -= diffY;
break;
case 'northwest':
diffX = this.constrain(w, diffX, mw, mxw);
diffY = this.constrain(h, diffY, mh, mxh);
y += diffY;
h -= diffY;
x += diffX;
w -= diffX;
break;
case 'southwest':
diffX = this.constrain(w, diffX, mw, mxw);
h += diffY;
h = Math.min(Math.max(mh, h), mxh);
x += diffX;
w -= diffX;
break;
}
var sw = this.snap(w, wi, mw);
var sh = this.snap(h, hi, mh);
if(sw != w || sh != h){
switch(pos){
case 'northeast':
y -= sh - h;
break;
case 'north':
y -= sh - h;
break;
case 'southwest':
x -= sw - w;
break;
case 'west':
x -= sw - w;
break;
case 'northwest':
x -= sw - w;
y -= sh - h;
break;
}
w = sw;
h = sh;
}
if(this.preserveRatio){
switch(pos){
case 'southeast':
case 'east':
h = oh * (w/ow);
h = Math.min(Math.max(mh, h), mxh);
w = ow * (h/oh);
break;
case 'south':
w = ow * (h/oh);
w = Math.min(Math.max(mw, w), mxw);
h = oh * (w/ow);
break;
case 'northeast':
w = ow * (h/oh);
w = Math.min(Math.max(mw, w), mxw);
h = oh * (w/ow);
break;
case 'north':
tw = w;
w = ow * (h/oh);
w = Math.min(Math.max(mw, w), mxw);
h = oh * (w/ow);
x += (tw - w) / 2;
break;
case 'southwest':
h = oh * (w/ow);
h = Math.min(Math.max(mh, h), mxh);
tw = w;
w = ow * (h/oh);
x += tw - w;
break;
case 'west':
th = h;
h = oh * (w/ow);
h = Math.min(Math.max(mh, h), mxh);
y += (th - h) / 2;
tw = w;
w = ow * (h/oh);
x += tw - w;
break;
case 'northwest':
tw = w;
th = h;
h = oh * (w/ow);
h = Math.min(Math.max(mh, h), mxh);
w = ow * (h/oh);
y += th - h;
x += tw - w;
break;
}
}
this.proxy.setBounds(x, y, w, h);
if(this.dynamic){
this.resizeElement();
}
}catch(ex){}
}
},
// private
handleOver : function(handle){
if(this.enabled){
Ext.getBody().addClass('ux-resizable-handle-' + handle.position);
}
},
// private
handleOut : function(handle){
if(!this.resizing){
Ext.getBody().removeClass('ux-resizable-handle-' + handle.position);
}
},
/**
* Returns the element this component is bound to.
* @return {Ext.Element}
*/
getEl : function(){
return this.el;
},
/**
* Destroys this resizable. If the element was wrapped and
* removeEl is not true then the element remains.
* @param {Boolean} removeEl (optional) true to remove the element from the DOM
*/
destroy : function(removeEl){
Ext.destroy(this.dd, this.proxy);
this.proxy = null;
var ps = Ext.Resizable.positions;
for(var k in ps){
if(typeof ps[k] != 'function' && this[ps[k]]){
this[ps[k]].destroy();
}
}
if(removeEl){
this.el.update('');
Ext.destroy(this.el);
this.el = null;
}
this.purgeListeners();
},
syncHandleHeight : function(){
var h = this.el.getHeight(true);
if(this.west.el){
this.west.el.setHeight(h);
}
if(this.east.el){
this.east.el.setHeight(h);
}
}
});
// private
// hash to map config positions to true positions
Ext.Resizable.positions = {
n: 'north', s: 'south', e: 'east', w: 'west', se: 'southeast', sw: 'southwest', nw: 'northwest', ne: 'northeast'
};
Ext.Resizable.cfg = {
north: {left: 7, right: -7, height: 7},
south: {left: 7, right: -7, top: -7},
east: {top: 7, bottom: -7, left: -7},
west: {top: 7, bottom: -7, width: 7},
southeast: {top: -7, left: -7},
southwest: {top: -7, width: 7},
northwest: {height: 7, width: 7},
northeast: {left: -7, height: 7}
};
// private
Ext.Resizable.Handle = function(rz, pos){
this.position = pos;
this.rz = rz;
var cfg = Ext.Resizable.cfg[pos] || Ext.Resizable.cfg[Ext.Resizable.positions[pos]];
this.ez = new Ext.ux.EventZone(Ext.apply({
position: pos,
el: rz.el
}, cfg));
this.ez.on({
mousedown: this.onMouseDown,
mouseenter: this.onMouseOver,
mouseleave: this.onMouseOut,
scope: this
});
};
// private
Ext.Resizable.Handle.prototype = {
cursor: 'move',
// private
afterResize : function(rz){
// do nothing
},
// private
onMouseDown : function(e){
this.rz.onMouseDown(this, e);
},
// private
onMouseOver : function(e){
this.rz.handleOver(this, e);
},
// private
onMouseOut : function(e){
this.rz.handleOut(this, e);
},
// private
destroy : function(){
Ext.destroy(this.el);
this.el = null;
}
};
/**
*
* Ext.ux.elasticTextArea Extension Class for Ext 3.x Library
*
* @author Steffen Kamper
*
* @license Ext.ux.elasticTextArea is licensed under the terms of
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* License details: http://www.gnu.org/licenses/lgpl.html
*
*/
Ext.ux.elasticTextArea = function(){
var defaultConfig = function(){
return {
minHeight : 0
,maxHeight : 0
,growBy: 12
}
}
var processOptions = function(config){
var o = defaultConfig();
var options = {};
Ext.apply(options, config, o);
return options;
}
return {
div : null
,applyTo: function(elementId, options){
var el = Ext.get(elementId);
var width = el.getWidth();
var height = el.getHeight();
var styles = el.getStyles('padding-top', 'padding-bottom', 'padding-left', 'padding-right', 'line-height', 'font-size', 'font-family', 'font-weight');
if(! this.div){
var options = processOptions(options);
this.div = Ext.DomHelper.append(Ext.getBody() || document.body, {
'id':elementId + '-preview-div'
,'tag' : 'div'
,'background': 'red'
,'style' : 'position: absolute; top: -100000px; left: -100000px;'
}, true)
Ext.DomHelper.applyStyles(this.div, styles);
el.on('keyup', function() {
this.applyTo(elementId, options);
}, this);
}
this.div.setWidth(parseInt(el.getStyle('width')));
//replace \n with
so that the enter key can trigger and height increase
//but first remove all previous entries, so that the height measurement can be as accurate as possible
this.div.update(
el.dom.value.replace(/
/, '
')
.replace(/<|>/g, ' ')
.replace(/&/g,"&")
.replace(/\n/g, '
')
);
var growBy = parseInt(el.getStyle('line-height'));
growBy = growBy ? growBy + 1 : 1;
if (growBy === 1) {
growBy = options.growBy;
}
var textHeight = this.div.getHeight();
textHeight = textHeight ? textHeight + growBy : growBy;
if ( (textHeight > options.maxHeight ) && (options.maxHeight > 0) ){
textHeight = options.maxHeight;
el.setStyle('overflow', 'auto');
}
if ( (textHeight < options.minHeight ) && (options.minHeight > 0) ) {
textHeight = options.minHeight;
el.setStyle('overflow', 'auto');
}
el.setHeight(textHeight , true);
}
}
}
/**
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* Evaluation of TYPO3 form field content
*/
function evalFunc() {
this.input = evalFunc_input;
this.output = evalFunc_output;
this.parseInt = evalFunc_parseInt;
this.getNumChars = evalFunc_getNumChars;
this.parseDouble = evalFunc_parseDouble;
this.noSpace = evalFunc_noSpace;
this.getSecs = evalFunc_getSecs;
this.getYear = evalFunc_getYear;
this.getTimeSecs = evalFunc_getTimeSecs;
this.getTime = evalFunc_getTime;
this.getDate = evalFunc_getDate;
this.getTimestamp = evalFunc_getTimestamp;
this.caseSwitch = evalFunc_caseSwitch;
this.evalObjValue = evalFunc_evalObjValue;
this.outputObjValue = evalFunc_outputObjValue;
this.split = evalFunc_splitStr;
this.pol = evalFunc_pol;
this.convertClientTimestampToUTC = evalFunc_convertClientTimestampToUTC;
this.ltrim = evalFunc_ltrim;
this.btrim = evalFunc_btrim;
var today = new Date();
this.lastYear = this.getYear(today);
this.lastDate = this.getDate(today);
this.lastTime = 0;
this.refDate = today;
this.isInString = '';
this.USmode = 0;
}
function evalFunc_pol(foreign, value) {
return eval (((foreign=="-")?'-':'')+value);
}
function evalFunc_evalObjValue(FObj,value) {
var evallist = FObj.evallist;
this.isInString = (FObj.is_in) ? ''+FObj.is_in : '';
var index=1;
var theEvalType = (FObj.evallist) ? this.split(evallist, ",", index) : false;
var newValue=value;
while (theEvalType) {
if (typeof TBE_EDITOR == 'object' && TBE_EDITOR.customEvalFunctions[theEvalType] && typeof TBE_EDITOR.customEvalFunctions[theEvalType] == 'function') {
newValue = TBE_EDITOR.customEvalFunctions[theEvalType](newValue);
} else {
newValue = evalFunc.input(theEvalType, newValue);
}
index++;
theEvalType = this.split(evallist, ",", index);
}
return newValue;
}
function evalFunc_outputObjValue(FObj,value) {
var evallist = FObj.evallist;
var index=1;
var theEvalType = this.split(evallist, ",", index);
var newValue=value;
while (theEvalType) {
if (theEvalType != 'required') {
newValue = evalFunc.output(theEvalType, value, FObj);
}
index++;
theEvalType = this.split(evallist, ",", index);
}
return newValue;
}
function evalFunc_caseSwitch(type,inVal) {
var theVal = ''+inVal;
var newString = '';
switch (type) {
case "alpha":
case "num":
case "alphanum":
case "alphanum_x":
for (var a=0;a= 'a' && theChar <= 'z') || (theChar >= 'A' && theChar <= 'Z');
var num = (theChar>='0' && theChar<='9');
switch(type) {
case "alphanum": special=0; break;
case "alpha": num=0; special=0; break;
case "num": alpha=0; special=0; break;
}
if (alpha || num || theChar==' ' || special) {
newString+=theChar;
}
}
break;
case "is_in":
if (this.isInString) {
for (var a=0;a 0; a--) {
if (theVal.substr(a-1,1)!=' ') {
return theVal.substr(0,a);
}
}
return '';
}
function evalFunc_splitSingle(value) {
var theVal = ''+value;
this.values = new Array();
this.pointer = 3;
this.values[1]=theVal.substr(0,2);
this.values[2]=theVal.substr(2,2);
this.values[3]=theVal.substr(4,10);
}
function evalFunc_split(value) {
this.values = new Array();
this.valPol = new Array();
this.pointer = 0;
var numberMode = 0;
var theVal = "";
value+=" ";
for (var a=0;a"9") {
if (numberMode) {
this.pointer++;
this.values[this.pointer]=theVal;
theVal = "";
numberMode=0;
}
if (theChar=="+" || theChar=="-") {
this.valPol[this.pointer+1] = theChar;
}
} else {
theVal+=theChar;
numberMode=1;
}
}
}
function evalFunc_input(type,inVal) {
if (type=="md5") {
return MD5(inVal);
}
if (type=="trim") {
return this.ltrim(this.btrim(inVal));
}
if (type=="int") {
return this.parseInt(inVal);
}
if (type=="double2") {
return this.parseDouble(inVal);
}
var today = new Date();
var add=0;
var value = this.ltrim(inVal);
var values = new evalFunc_split(value);
var theCmd = value.substr(0,1);
value = this.caseSwitch(type,value);
if (value=="") {
return "";
return 0; // Why would I ever return a zero??? (20/12/01)
}
switch (type) {
case "datetime":
switch (theCmd) {
case "d":
case "t":
case "n":
this.lastTime = this.convertClientTimestampToUTC(this.getTimestamp(today), 0);
if (values.valPol[1]) {
add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
}
break;
case "+":
case "-":
if (this.lastTime == 0) {
this.lastTime = this.convertClientTimestampToUTC(this.getTimestamp(today), 0);
}
if (values.valPol[1]) {
add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
}
break;
default:
var index = value.indexOf(' ');
if (index!=-1) {
var dateVal = this.input("date",value.substr(index,value.length));
// set refDate so that evalFunc_input on time will work with correct DST information
this.refDate = new Date(dateVal*1000);
this.lastTime = dateVal + this.input("time",value.substr(0,index));
} else {
// only date, no time
this.lastTime = this.input("date", value);
}
}
this.lastTime+=add*24*60*60;
return this.lastTime;
break;
case "year":
switch (theCmd) {
case "d":
case "t":
case "n":
this.lastYear = this.getYear(today);
if (values.valPol[1]) {
add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
}
break;
case "+":
case "-":
if (values.valPol[1]) {
add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
}
break;
default:
if (values.valPol[2]) {
add = this.pol(values.valPol[2],this.parseInt(values.values[2]));
}
var year = (values.values[1])?this.parseInt(values.values[1]):this.getYear(today);
if ((year >= 0 && year < 38) || (year >= 70 && year<100) || (year >= 1902 && year < 2038)) {
if (year<100) {
year = (year<38) ? year+=2000 : year+=1900;
}
} else {
year = this.getYear(today);
}
this.lastYear = year;
}
this.lastYear+=add;
return this.lastYear;
break;
case "date":
switch (theCmd) {
case "d":
case "t":
case "n":
this.lastDate = this.getTimestamp(today);
if (values.valPol[1]) {
add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
}
break;
case "+":
case "-":
if (values.valPol[1]) {
add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
}
break;
default:
var index = 4;
if (values.valPol[index]) {
add = this.pol(values.valPol[index],this.parseInt(values.values[index]));
}
if (values.values[1] && values.values[1].length>2) {
if (values.valPol[2]) {
add = this.pol(values.valPol[2],this.parseInt(values.values[2]));
}
var temp = values.values[1];
values = new evalFunc_splitSingle(temp);
}
var year = (values.values[3])?this.parseInt(values.values[3]):this.getYear(today);
if ((year >= 0 && year < 38) || (year >= 70 && year < 100) || (year >= 1902 && year<2038)) {
if (year<100) {
year = (year<38) ? year+=2000 : year+=1900;
}
} else {
year = this.getYear(today);
}
var month = (values.values[this.USmode?1:2])?this.parseInt(values.values[this.USmode?1:2]):today.getUTCMonth()+1;
var day = (values.values[this.USmode?2:1])?this.parseInt(values.values[this.USmode?2:1]):today.getUTCDate();
var theTime = new Date(parseInt(year), parseInt(month)-1, parseInt(day));
// Substract timezone offset from client
this.lastDate = this.convertClientTimestampToUTC(this.getTimestamp(theTime), 0);
}
this.lastDate+=add*24*60*60;
return this.lastDate;
break;
case "time":
case "timesec":
switch (theCmd) {
case "d":
case "t":
case "n":
this.lastTime = this.getTimeSecs(today);
if (values.valPol[1]) {
add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
}
break;
case "+":
case "-":
if (this.lastTime == 0) {
this.lastTime = this.getTimeSecs(today);
}
if (values.valPol[1]) {
add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
}
break;
default:
var index = (type=="timesec")?4:3;
if (values.valPol[index]) {
add = this.pol(values.valPol[index],this.parseInt(values.values[index]));
}
if (values.values[1] && values.values[1].length>2) {
if (values.valPol[2]) {
add = this.pol(values.valPol[2],this.parseInt(values.values[2]));
}
var temp = values.values[1];
values = new evalFunc_splitSingle(temp);
}
var sec = (values.values[3])?this.parseInt(values.values[3]):today.getUTCSeconds();
if (sec > 59) {sec=59;}
var min = (values.values[2])?this.parseInt(values.values[2]):today.getUTCMinutes();
if (min > 59) {min=59;}
var hour = (values.values[1])?this.parseInt(values.values[1]):today.getUTCHours();
if (hour >= 24) {hour=0;}
var theTime = new Date(this.getYear(this.refDate), this.refDate.getUTCMonth(), this.refDate.getUTCDate(), hour, min, ((type=="timesec")?sec:0));
// Substract timezone offset from client
this.lastTime = this.convertClientTimestampToUTC(this.getTimestamp(theTime), 1);
}
this.lastTime+=add*60;
if (this.lastTime<0) {this.lastTime+=24*60*60;}
return this.lastTime;
break;
default:
return value;
}
}
function evalFunc_output(type,value,FObj) {
var theString = "";
switch (type) {
case "date":
if (!parseInt(value)) {return '';}
var theTime = new Date(parseInt(value) * 1000);
if (this.USmode) {
theString = (theTime.getUTCMonth()+1)+'-'+theTime.getUTCDate()+'-'+this.getYear(theTime);
} else {
theString = theTime.getUTCDate()+'-'+(theTime.getUTCMonth()+1)+'-'+this.getYear(theTime);
}
break;
case "datetime":
if (!parseInt(value)) {return '';}
theString = this.output("time",value)+' '+this.output("date",value);
break;
case "time":
case "timesec":
if (!parseInt(value)) {return '';}
var theTime = new Date(parseInt(value) * 1000);
var h = theTime.getUTCHours();
var m = theTime.getUTCMinutes();
var s = theTime.getUTCSeconds();
theString = h+':'+((m<10)?'0':'')+m + ((type=="timesec")?':'+((s<10)?'0':'')+s:'');
break;
case "password":
theString = (value) ? TS.passwordDummy : "";
break;
case "int":
theString = (FObj.checkbox && value==FObj.checkboxValue)?'':value;
break;
default:
theString = value;
}
return theString;
}
function evalFunc_getSecs(timeObj) {
return timeObj.getUTCSeconds();
}
// Seconds since midnight:
function evalFunc_getTime(timeObj) {
return timeObj.getUTCHours() * 60 * 60 + timeObj.getUTCMinutes() * 60 + this.getSecs(timeObj);
}
function evalFunc_getYear(timeObj) {
return timeObj.getUTCFullYear();
}
// Seconds since midnight with client timezone offset:
function evalFunc_getTimeSecs(timeObj) {
return timeObj.getHours()*60*60+timeObj.getMinutes()*60+timeObj.getSeconds();
}
function evalFunc_getDate(timeObj) {
var theTime = new Date(this.getYear(timeObj), timeObj.getUTCMonth(), timeObj.getUTCDate());
return this.getTimestamp(theTime);
}
function evalFunc_dummy (evallist,is_in,checkbox,checkboxValue) {
this.evallist = evallist;
this.is_in = is_in;
this.checkboxValue = checkboxValue;
this.checkbox = checkbox;
}
function evalFunc_splitStr(theStr1, delim, index) {
var theStr = ''+theStr1;
var lengthOfDelim = delim.length;
sPos = -lengthOfDelim;
if (index<1) {index=1;}
for (a=1; a
* @coauthor Oliver Hader
*/
var TBE_EDITOR = {
/* Example:
elements: {
'data-parentPid-table-uid': {
'field': {
'range': [0, 100],
'rangeImg': '',
'required': true,
'requiredImg': ''
}
}
},
*/
elements: {},
nested: {'field':{}, 'level':{}},
ignoreElements: [],
recentUpdatedElements: {},
actionChecks: { submit: [] },
formname: '',
formnameUENC: '',
loadTime: 0,
isChanged: 0,
auth_timeout_field: 0,
backPath: '',
prependFormFieldNames: 'data',
prependFormFieldNamesUENC: 'data',
prependFormFieldNamesCnt: 0,
isPalettedoc: null,
doSaveFieldName: 0,
labels: {},
images: {
req: new Image(),
cm: new Image(),
sel: new Image(),
clear: new Image()
},
clearBeforeSettingFormValueFromBrowseWin: [],
// Handling of data structures:
addElements: function(elements) {
TBE_EDITOR.recentUpdatedElements = elements;
TBE_EDITOR.elements = $H(TBE_EDITOR.elements).merge(elements).toObject();
},
addNested: function(elements) {
// Merge data structures:
if (elements) {
$H(elements).each(function(element) {
var levelMax, i, currentLevel, subLevel;
var nested = element.value;
if (nested.level && nested.level.length) {
// If the first level is of type 'inline', it could be created by a AJAX request to IRRE.
// So, try to get the upper levels this dynamic level is nested in:
if (typeof inline!='undefined' && nested.level[0][0]=='inline') {
nested.level = inline.findContinuedNestedLevel(nested.level, nested.level[0][1]);
}
levelMax = nested.level.length-1;
for (i=0; i<=levelMax; i++) {
currentLevel = TBE_EDITOR.getNestedLevelIdent(nested.level[i]);
if (typeof TBE_EDITOR.nested.level[currentLevel] == 'undefined') {
TBE_EDITOR.nested.level[currentLevel] = { 'clean': true, 'item': {}, 'sub': {} };
}
// Add next sub level to the current level:
if (i=0; i--) {
TBE_EDITOR.removeElement(removeStack[i]);
}
TBE_EDITOR.ignoreElements = [];
}
},
getElement: function(record, field, type) {
var result = null;
var element;
if (TBE_EDITOR.elements && TBE_EDITOR.elements[record] && TBE_EDITOR.elements[record][field]) {
element = TBE_EDITOR.elements[record][field];
if (type) {
if (element[type]) result = element;
} else {
result = element;
}
}
return result;
},
checkElements: function(type, recentUpdated, record, field) {
var result = 1;
var elementName, elementData, elementRecord, elementField;
var source = (recentUpdated ? TBE_EDITOR.recentUpdatedElements : TBE_EDITOR.elements);
if (TBE_EDITOR.ignoreElements.length && TBE_EDITOR.ignoreElements.indexOf(record)!=-1) {
return result;
}
if (type) {
if (record && field) {
elementName = record+'['+field+']';
elementData = TBE_EDITOR.getElement(record, field, type);
if (elementData) {
if (!TBE_EDITOR.checkElementByType(type, elementName, elementData, recentUpdated)) {
result = 0;
}
}
} else {
var elementFieldList, elRecIndex, elRecCnt, elFldIndex, elFldCnt;
var elementRecordList = $H(source).keys();
for (elRecIndex=0, elRecCnt=elementRecordList.length; elRecIndex=0; i--) {
nestedLevelType = fieldLevels[i][0];
nestedLevelName = fieldLevels[i][1];
fieldLevelIdent = TBE_EDITOR.getNestedLevelIdent(fieldLevels[i]);
// Construct the CSS id strings of the image/icon tags showing the notification:
if (nestedLevelType == 'tab') {
element = nestedLevelName+'-REQ';
} else if (nestedLevelType == 'inline') {
element = nestedLevelName+'_req';
} else {
continue;
}
// Set the icons:
if (resolved) {
if (TBE_EDITOR.checkNested(fieldLevelIdent)) {
TBE_EDITOR.setImage(element, TBE_EDITOR.images.clear);
} else {
break;
}
} else {
if (TBE_EDITOR.nested.level && TBE_EDITOR.nested.level[fieldLevelIdent]) {
TBE_EDITOR.nested.level[fieldLevelIdent].clean = false;
}
TBE_EDITOR.setImage(element, TBE_EDITOR.images.req);
}
}
}
},
// Check all the input fields on a given level of nesting - if only on is unfilled, the whole level is marked as required:
checkNested: function(nestedLevelIdent) {
var nestedLevel, isClean;
if (nestedLevelIdent && TBE_EDITOR.nested.level && TBE_EDITOR.nested.level[nestedLevelIdent]) {
nestedLevel = TBE_EDITOR.nested.level[nestedLevelIdent];
if (!nestedLevel.clean) {
if (typeof nestedLevel.item == 'object') {
$H(nestedLevel.item).each(
function(pair) {
if (isClean || typeof isClean == 'undefined') {
isClean = (
TBE_EDITOR.checkElements('required', false, pair.value[0], pair.value[1]) &&
TBE_EDITOR.checkElements('range', false, pair.value[0], pair.value[1])
);
}
}
);
if (typeof isClean != 'undefined' && !isClean) {
return false;
}
}
if (typeof nestedLevel.sub == 'object') {
$H(nestedLevel.sub).each(
function(pair) {
if (isClean || typeof isClean == 'undefined') {
isClean = TBE_EDITOR.checkNested(pair.key);
}
}
);
if (typeof isClean != 'undefined' && !isClean) {
return false;
}
}
// Store the result, that this level (the fields on this and the sub levels) are clean:
nestedLevel.clean = true;
}
}
return true;
},
getNestedLevelIdent: function(level) {
return level.join('::');
},
addActionChecks: function(type, checks) {
TBE_EDITOR.actionChecks[type].push(checks);
},
// Regular TCEforms JSbottom scripts:
loginRefreshed: function() {
var date = new Date();
TBE_EDITOR.loadTime = Math.floor(date.getTime()/1000);
if (top.busy && top.busy.loginRefreshed) { top.busy.loginRefreshed(); }
},
checkLoginTimeout: function() {
var date = new Date();
var theTime = Math.floor(date.getTime()/1000);
if (theTime > TBE_EDITOR.loadTime+TBE_EDITOR.auth_timeout_field-10) {
return true;
}
},
fieldChanged_fName: function(fName,el) {
var idx=2+TBE_EDITOR.prependFormFieldNamesCnt;
var table = TBE_EDITOR.split(fName, "[", idx);
var uid = TBE_EDITOR.split(fName, "[", idx+1);
var field = TBE_EDITOR.split(fName, "[", idx+2);
table = table.substr(0,table.length-1);
uid = uid.substr(0,uid.length-1);
field = field.substr(0,field.length-1);
TBE_EDITOR.fieldChanged(table,uid,field,el);
},
fieldChanged: function(table,uid,field,el) {
var theField = TBE_EDITOR.prependFormFieldNames+'['+table+']['+uid+']['+field+']';
var theRecord = TBE_EDITOR.prependFormFieldNames+'['+table+']['+uid+']';
TBE_EDITOR.isChanged = 1;
// Set change image:
var imgObjName = "cm_"+table+"_"+uid+"_"+field;
TBE_EDITOR.setImage(imgObjName,TBE_EDITOR.images.cm);
// Set change image
if (document[TBE_EDITOR.formname][theField] && document[TBE_EDITOR.formname][theField].type=="select-one" && document[TBE_EDITOR.formname][theField+"_selIconVal"]) {
var imgObjName = "selIcon_"+table+"_"+uid+"_"+field+"_";
TBE_EDITOR.setImage(imgObjName+document[TBE_EDITOR.formname][theField+"_selIconVal"].value,TBE_EDITOR.images.clear);
document[TBE_EDITOR.formname][theField+"_selIconVal"].value = document[TBE_EDITOR.formname][theField].selectedIndex;
TBE_EDITOR.setImage(imgObjName+document[TBE_EDITOR.formname][theField+"_selIconVal"].value,TBE_EDITOR.images.sel);
}
// Set required flag:
var imgReqObjName = "req_"+table+"_"+uid+"_"+field;
if (TBE_EDITOR.getElement(theRecord,field,'required') && document[TBE_EDITOR.formname][theField]) {
if (TBE_EDITOR.checkElements('required', false, theRecord, field)) {
TBE_EDITOR.setImage(imgReqObjName,TBE_EDITOR.images.clear);
TBE_EDITOR.notifyNested(theField, true);
} else {
TBE_EDITOR.setImage(imgReqObjName,TBE_EDITOR.images.req);
TBE_EDITOR.notifyNested(theField, false);
}
}
if (TBE_EDITOR.getElement(theRecord,field,'range') && document[TBE_EDITOR.formname][theField]) {
if (TBE_EDITOR.checkElements('range', false, theRecord, field)) {
TBE_EDITOR.setImage(imgReqObjName,TBE_EDITOR.images.clear);
TBE_EDITOR.notifyNested(theField, true);
} else {
TBE_EDITOR.setImage(imgReqObjName,TBE_EDITOR.images.req);
TBE_EDITOR.notifyNested(theField, false);
}
}
if (TBE_EDITOR.isPalettedoc) { TBE_EDITOR.setOriginalFormFieldValue(theField) };
},
setOriginalFormFieldValue: function(theField) {
if (TBE_EDITOR.isPalettedoc && (TBE_EDITOR.isPalettedoc).document[TBE_EDITOR.formname] && (TBE_EDITOR.isPalettedoc).document[TBE_EDITOR.formname][theField]) {
(TBE_EDITOR.isPalettedoc).document[TBE_EDITOR.formname][theField].value = document[TBE_EDITOR.formname][theField].value;
}
},
isFormChanged: function(noAlert) {
if (TBE_EDITOR.isChanged && !noAlert && confirm(TBE_EDITOR.labels.fieldsChanged)) {
return 0;
}
return TBE_EDITOR.isChanged;
},
checkAndDoSubmit: function(sendAlert) {
if (TBE_EDITOR.checkSubmit(sendAlert)) { TBE_EDITOR.submitForm(); }
},
/**
* Checks if the form can be submitted according to any possible restrains like required values, item numbers etc.
* Returns true if the form can be submitted, otherwise false (and might issue an alert message, if "sendAlert" is 1)
* If "sendAlert" is false, no error message will be shown upon false return value (if "1" then it will).
* If "sendAlert" is "-1" then the function will ALWAYS return true regardless of constraints (except if login has expired) - this is used in the case where a form field change requests a form update and where it is accepted that constraints are not observed (form layout might change so other fields are shown...)
*/
checkSubmit: function(sendAlert) {
var funcIndex, funcMax, funcRes;
var OK=1;
// $this->additionalJS_submit:
if (TBE_EDITOR.actionChecks && TBE_EDITOR.actionChecks.submit) {
for (funcIndex=0, funcMax=TBE_EDITOR.actionChecks.submit.length; funcIndex= lower && numberOfElements <= upper) {
return true;
} else {
return false;
}
},
initRequired: function() {
// $reqLinesCheck
TBE_EDITOR.checkElements('required', true);
// $reqRangeCheck
TBE_EDITOR.checkElements('range', true);
},
setImage: function(name,image) {
var object;
if (document[name]) {
object = document[name];
} else if (document.getElementById(name)) {
object = document.getElementById(name);
}
if (object) {
if (typeof image == 'object') {
document[name].src = image.src;
} else {
document[name].src = eval(image+'.src');
}
}
},
submitForm: function() {
if (TBE_EDITOR.doSaveFieldName) {
document[TBE_EDITOR.formname][TBE_EDITOR.doSaveFieldName].value=1;
}
// Set a short timeout to allow other JS processes to complete, in particular those from
// EXT:backend/Resources/Public/JavaScript/FormEngine.js (reference: http://forge.typo3.org/issues/58755).
// TODO: This should be solved in a better way when this script is refactored.
window.setTimeout('document[TBE_EDITOR.formname].submit()', 10);
},
split: function(theStr1, delim, index) {
var theStr = ""+theStr1;
var lengthOfDelim = delim.length;
sPos = -lengthOfDelim;
if (index<1) {index=1;}
for (var a=1; a i; i++) {
var el = split[i].replace(/ /g, '');
if (el == 'datetime' || el == 'date') {
var now = new Date();
checkSetValue = Date.parse(now)/1000 - now.getTimezoneOffset()*60;
break;
} else if (el == 'time' || el == 'timesec') {
checkSetValue = evalFunc_getTimeSecs(new Date());
break;
}
}
document[TBE_EDITOR.formname][theField].value=checkSetValue;
} else {
document[TBE_EDITOR.formname][theField].value=checkboxValue;
}
}else{
document[TBE_EDITOR.formname][theField].value = evalFunc.evalObjValue(theFObj, document[TBE_EDITOR.formname][theField+"_hr"].value);
}
typo3form.fieldSet(theField, evallist, is_in, checkbox, checkboxValue);
}
}
};
// backwards compatibility for extensions
var typo3FormFieldSet = typo3form.fieldSet;
var typo3FormFieldGet = typo3form.fieldGet;
/**
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
Ext.onReady(function() {
// Only use placeholder JavaScript fallback in Internet Explorer
if (!Ext.isIE) {
return;
}
// TODO rewrite in ExtJS
$$('[placeholder]').each(function(el) {
if (el.getAttribute('placeholder') != "") {
el.observe('TYPO3:focus', function() {
var input = Ext.get(this);
if (this.getValue() == this.getAttribute('placeholder')) {
this.setValue('');
input.removeClass('placeholder');
}
});
el.observe('focus', function() { el.fire('TYPO3:focus'); });
el.observe('TYPO3:blur', function() {
var input = Ext.get(this);
if (input.getValue() == '' || this.getValue() == this.getAttribute('placeholder')) {
this.setValue(this.getAttribute('placeholder'));
input.addClass('placeholder');
}
});
el.observe('blur', function() { el.fire('TYPO3:blur'); });
el.fire('TYPO3:blur');
}
});
});
Ext.ns('Ext.ux', 'Ext.ux.menu', 'Ext.ux.form');
Ext.ux.DateTimePicker = Ext.extend(Ext.DatePicker, {
timeFormat: 'H:i',
initComponent: function() {
var t = this.timeFormat.split(':');
this.hourFormat = t[0];
this.minuteFormat = t[1];
Ext.ux.DateTimePicker.superclass.initComponent.call(this);
},
/**
* Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
* @param {Date} value The minimum date that can be selected
*/
setMinTime: function(dt) {
this.minTime = dt;
this.update(this.value, true);
},
/**
* Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
* @param {Date} value The maximum date that can be selected
*/
setMaxTime: function(dt) {
this.maxTime = dt;
this.update(this.value, true);
},
/**
* Returns the value of the date/time field
*/
getValue: function() {
return this.addTimeToValue(this.value);
},
/**
* Sets the value of the date/time field
* @param {Date} value The date to set
*/
setValue: function(value) {
var old = this.value;
this.value = value.clearTime(true);
if (this.el) {
this.update(this.value);
}
this.hourField.setValue(value.format(this.hourFormat));
this.minuteField.setValue(value.format(this.minuteFormat));
},
/**
* Sets the value of the time field
* @param {Date} value The date to set
*/
setTime: function(value) {
this.hourField.setValue(value.format(this.hourFormat));
this.minuteField.setValue(value.format(this.minuteFormat));
},
/**
* Updates the date value with the time entered
* @param {Date} value The date to which time should be added
*/
addTimeToValue: function(date) {
return date.clearTime().add(Date.HOUR, this.hourField.getValue()).add(Date.MINUTE, this.minuteField.getValue());
},
onRender: function(container, position) {
var m = [
'',
' | | |
',
''
];
var dn = this.dayNames;
for (var i = 0; i < 7; i++) {
var d = this.startDay + i;
if (d > 6) {
d = d - 7;
}
m.push('', dn[d].substr(0, 1), ' | ');
}
m[m.length] = " ";
for (var i = 0; i < 42; i++) {
if (i % 7 == 0 && i != 0) {
m[m.length] = " ";
}
m[m.length] = ' | ';
}
m.push('
|
',
this.showToday ? ' |
' : '',
'
'
);
var el = document.createElement("div");
el.className = "x-date-picker";
el.innerHTML = m.join("");
container.dom.insertBefore(el, position);
this.el = Ext.get(el);
this.eventEl = Ext.get(el.firstChild);
new Ext.util.ClickRepeater(this.el.child("td.x-date-left a"), {
handler: this.showPrevMonth,
scope: this,
preventDefault:true,
stopDefault:true
});
new Ext.util.ClickRepeater(this.el.child("td.x-date-right a"), {
handler: this.showNextMonth,
scope: this,
preventDefault:true,
stopDefault:true
});
this.mon(this.eventEl, "mousewheel", this.handleMouseWheel, this);
this.monthPicker = this.el.down('div.x-date-mp');
this.monthPicker.enableDisplayMode('block');
var kn = new Ext.KeyNav(this.eventEl, {
"left": function(e) {
e.ctrlKey ?
this.showPrevMonth() :
this.update(this.activeDate.add("d", -1));
},
"right": function(e) {
e.ctrlKey ?
this.showNextMonth() :
this.update(this.activeDate.add("d", 1));
},
"up": function(e) {
e.ctrlKey ?
this.showNextYear() :
this.update(this.activeDate.add("d", -7));
},
"down": function(e) {
e.ctrlKey ?
this.showPrevYear() :
this.update(this.activeDate.add("d", 7));
},
"pageUp": function(e) {
this.showNextMonth();
},
"pageDown": function(e) {
this.showPrevMonth();
},
"enter": function(e) {
e.stopPropagation();
this.fireEvent("select", this, this.value);
return true;
},
scope : this
});
this.mon(this.eventEl, "click", this.handleDateClick, this, {delegate: "a.x-date-date"});
this.el.select("table.x-date-inner").unselectable();
this.cells = this.el.select("table.x-date-inner tbody td");
this.textNodes = this.el.query("table.x-date-inner tbody span");
this.mbtn = new Ext.Button({
text: " ",
tooltip: this.monthYearText,
renderTo: this.el.child("td.x-date-middle", true)
});
this.mon(this.mbtn, 'click', this.showMonthPicker, this);
this.mbtn.el.child('em').addClass("x-btn-arrow");
if (this.showToday) {
this.todayKeyListener = this.eventEl.addKeyListener(Ext.EventObject.SPACE, this.selectToday, this);
var today = (new Date()).dateFormat(this.format);
this.todayBtn = new Ext.Button({
text: String.format(this.todayText, today),
tooltip: String.format(this.todayTip, today),
handler: this.selectToday,
scope: this
});
}
this.formPanel = new Ext.form.FormPanel({
layout: 'table',
layoutConfig: {
// The total column count must be specified here
columns: 3
},
renderTo: this.el.child("td.x-date-bottom", true),
baseCls: 'x-plain',
hideBorders: true,
labelAlign: 'left',
labelWidth: 10,
forceLayout: true,
items: [
{
columnWidth: .4,
layout: 'form',
baseCls: 'x-plain',
items: [
{
xtype: 'textfield',
id: this.getId() + '_hour',
maxLength: 2,
fieldLabel: '',
labelWidth: 30,
width: 30,
minValue: 0,
maxValue: 24,
allowBlank: false,
labelSeparator: '',
tabIndex: 1,
maskRe: /[0-9]/
}
]
},
{
columnWidth: .3,
layout: 'form',
baseCls: 'x-plain',
items: [
{
xtype: 'textfield',
id: this.getId() + '_minute',
maxLength: 2,
fieldLabel: ':',
labelWidth: 10,
width: 30,
minValue: 0,
maxValue: 59,
allowBlank: false,
labelSeparator: '',
tabIndex: 2,
maskRe: /[0-9]/
}
]
},
{
columnWidth: .3,
layout: 'form',
baseCls: 'x-plain',
items: [this.todayBtn]
}
]
});
this.hourField = Ext.getCmp(this.getId() + '_hour');
this.minuteField = Ext.getCmp(this.getId() + '_minute');
this.hourField.on('blur', function(field) {
var old = field.value;
var h = parseInt(field.getValue());
if (h > 23) {
field.setValue(old);
}
});
this.minuteField.on('blur', function(field) {
var old = field.value;
var h = parseInt(field.getValue());
if (h > 59) {
field.setValue(old);
}
});
if (Ext.isIE) {
this.el.repaint();
}
this.update(this.value);
},
// private
handleDateClick : function(e, t) {
e.stopEvent();
if (t.dateValue && !Ext.fly(t.parentNode).hasClass("x-date-disabled")) {
this.setValue(this.addTimeToValue(new Date(t.dateValue)));
this.fireEvent("select", this, this.value);
}
},
selectToday : function() {
if (this.todayBtn && !this.todayBtn.disabled) {
this.setValue(new Date());
this.fireEvent("select", this, this.value);
}
},
update : function(date, forceRefresh) {
Ext.ux.DateTimePicker.superclass.update.call(this, date, forceRefresh);
if (this.showToday) {
this.setTime(new Date());
}
}
});
Ext.reg('datetimepicker', Ext.ux.DateTimePicker);
Ext.ux.menu.DateTimeMenu = Ext.extend(Ext.menu.Menu, {
enableScrolling : false,
hideOnClick : true,
cls: 'x-date-menu x-datetime-menu',
initComponent : function() {
Ext.apply(this, {
plain: true,
showSeparator: false,
items: this.picker = new Ext.ux.DateTimePicker(Ext.apply({
internalRender: this.strict || !Ext.isIE,
ctCls: 'x-menu-datetime-item x-menu-date-item'
}, this.initialConfig))
});
this.picker.purgeListeners();
Ext.ux.menu.DateTimeMenu.superclass.initComponent.call(this);
this.relayEvents(this.picker, ['select']);
this.on('select', this.menuHide, this);
if (this.handler) {
this.on('select', this.handler, this.scope || this)
}
},
menuHide: function() {
if (this.hideOnClick) {
this.hide(true);
}
}
});
Ext.reg('datetimemenu', Ext.ux.menu.DateTimeMenu);
/**
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
Ext.ns('TYPO3');
// class to manipulate TCEFORMS
TYPO3.TCEFORMS = {
init: function() {
Ext.QuickTips.init();
this.update();
},
update: function() {
this.convertDateFieldsToDatePicker();
this.convertTextareasResizable();
},
convertDateFieldsToDatePicker: function() {
var dateFields = Ext.select("input[id^=tceforms-date]"), minDate, maxDate, lowerMatch, upperMatch;
dateFields.each(function(element) {
var index = element.dom.id.match(/tceforms-datefield-/) ? 0 : 1;
var format = TYPO3.settings.datePickerUSmode ? TYPO3.settings.dateFormatUS : TYPO3.settings.dateFormat;
var datepicker = element.next('span'), menu;
// check for daterange
var lowerMatch = element.dom.className.match(/lower-(\d+)\b/);
minDate = Ext.isArray(lowerMatch) ? new Date(lowerMatch[1] * 1000) : null;
var upperMatch = element.dom.className.match(/upper-(\d+)\b/);
maxDate = Ext.isArray(upperMatch) ? new Date(upperMatch[1] * 1000) : null;
if (index === 0) {
menu = new Ext.menu.DateMenu({
id: 'p' + element.dom.id,
format: format[index],
value: Date.parseDate(element.dom.value, format[index]),
minDate: minDate,
maxDate: maxDate,
handler: function(picker, date){
var relElement = Ext.getDom(picker.ownerCt.id.substring(1));
relElement.value = date.format(format[index]);
if (Ext.isFunction(relElement.onchange)) {
relElement.onchange.call(relElement);
}
},
listeners: {
beforeshow: function(obj) {
var relElement = Ext.getDom(obj.picker.ownerCt.id.substring(1));
if (relElement.value) {
obj.picker.setValue(Date.parseDate(relElement.value, format[index]));
}
}
}
});
} else {
menu = new Ext.ux.menu.DateTimeMenu({
id: 'p' + element.dom.id,
format: format[index],
value: Date.parseDate(element.dom.value, format[index]),
minDate: minDate,
maxDate: maxDate,
listeners: {
beforeshow: function(obj) {
var relElement = Ext.getDom(obj.picker.ownerCt.id.substring(1));
if (relElement.value) {
obj.picker.setValue(Date.parseDate(relElement.value, format[index]));
}
},
select: function(picker) {
var relElement = Ext.getDom(picker.ownerCt.id.substring(1));
relElement.value = picker.getValue().format(format[index]);
if (Ext.isFunction(relElement.onchange)) {
relElement.onchange.call(relElement);
}
}
}
});
}
datepicker.removeAllListeners();
datepicker.on('click', function(){
menu.show(datepicker);
});
});
},
convertTextareasResizable: function() {
var textAreas = Ext.select("textarea[id^=tceforms-textarea-]");
textAreas.each(function(element) {
if (TYPO3.settings.textareaFlexible) {
var elasticTextarea = new Ext.ux.elasticTextArea().applyTo(element.dom.id, {
minHeight: 50,
maxHeight: TYPO3.settings.textareaMaxHeight
});
}
if (TYPO3.settings.textareaResize) {
element.addClass('resizable');
var dwrapped = new Ext.Resizable(element.dom.id, {
minWidth: 300,
minHeight: 50,
dynamic: true
});
}
});
}
}
Ext.onReady(TYPO3.TCEFORMS.init, TYPO3.TCEFORMS);
// Fix for slider TCA control in IE9
Ext.override(Ext.dd.DragTracker, {
onMouseMove:function (e, target) {
var isIE9 = Ext.isIE && (/msie 9/.test(navigator.userAgent.toLowerCase())) && document.documentMode != 6;
if (this.active && Ext.isIE && !isIE9 && !e.browserEvent.button) {
e.preventDefault();
this.onMouseUp(e);
return;
}
e.preventDefault();
var xy = e.getXY(), s = this.startXY;
this.lastXY = xy;
if (!this.active) {
if (Math.abs(s[0] - xy[0]) > this.tolerance || Math.abs(s[1] - xy[1]) > this.tolerance) {
this.triggerStart(e);
} else {
return;
}
}
this.fireEvent('mousemove', this, e);
this.onDrag(e);
this.fireEvent('drag', this, e);
}
});
/**
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* Class for JS handling of suggest fields in TCEforms.
*
* @author Andreas Wolf
* @author Benni Mack
*/
if (!TCEForms) {
var TCEForms = {};
}
TCEForms.Suggest = Class.create({
objectId: '',
suggestField: '',
suggestResultList: '',
minimumCharacters: 2,
defaultValue: '',
fieldType: '',
/**
* Wrapper for script.aculo.us' Autocompleter functionality: Assigns a new autocompletion object to
* the input field of a given Suggest selector.
*
* @param string objectId The ID of the object to assign the completer to
* @param string table The table of the record which is currently edited
* @param string field The field which is currently edited
* @param integer uid The uid of the record which is currently edited
* @param integer pid The pid of the record which is currently edited
* @param integer minimumCharacters the minimum characters that is need to trigger the initial search
* @param string fieldType The TCA type of the field (e.g. group, select, ...)
* @param string newRecordRow JSON encoded new content element. Only set when new record is inside flexform
*/
initialize: function(objectId, table, field, uid, pid, minimumCharacters, fieldType, newRecordRow) {
var PATH_typo3 = top.TS.PATH_typo3 || window.opener.top.TS.PATH_typo3;
this.objectId = objectId;
this.suggestField = objectId + 'Suggest';
this.suggestResultList = objectId + 'SuggestChoices';
this.fieldType = fieldType;
new Ajax.Autocompleter(this.suggestField, this.suggestResultList, PATH_typo3 + TYPO3.settings.ajaxUrls['t3lib_TCEforms_suggest::searchRecord'], {
paramName: 'value',
minChars: (minimumCharacters ? minimumCharacters : this.minimumCharacters),
updateElement: this.addElementToList.bind(this),
parameters: 'table=' + table + '&field=' + field + '&uid=' + uid + '&pid=' + pid + '&newRecordRow=' + newRecordRow,
indicator: objectId + 'SuggestIndicator'
}
);
$(this.suggestField).observe('focus', this.checkDefaultValue.bind(this));
$(this.suggestField).observe('keydown', this.checkDefaultValue.bind(this));
},
/**
* check for default value of the input field and set it to empty once somebody wants to type something in
*/
checkDefaultValue: function() {
if ($(this.suggestField).value == this.defaultValue) {
$(this.suggestField).value = '';
}
},
/**
* Adds an element to the list of items in a group selector.
*
* @param object item The item to add to the list (usually an li element)
* @return void
*/
addElementToList: function(item) {
if (item.className.indexOf('noresults') == -1) {
var arr = item.id.split('-');
var ins_table = arr[0];
var ins_uid = arr[1];
var ins_uid_string = (this.fieldType == 'select') ? ins_uid : (ins_table + '_' + ins_uid);
var rec_table = arr[2];
var rec_uid = arr[3];
var rec_field = arr[4];
var formEl = this.objectId;
var suggestLabelNode = Element.select(item, '.suggest-label')[0];
var label = suggestLabelNode.textContent ? suggestLabelNode.textContent : suggestLabelNode.innerText;
var suggestLabelTitleNode = Element.select(suggestLabelNode, '[title]')[0];
var title = suggestLabelTitleNode ? suggestLabelTitleNode.readAttribute('title') : '';
setFormValueFromBrowseWin(formEl, ins_uid_string, label, title);
TBE_EDITOR.fieldChanged(rec_table, rec_uid, rec_field, formEl);
$(this.suggestField).value = this.defaultValue;
}
}
});
/*
* This code has been copied from Project_CMS
* Copyright (c) 2005 by Phillip Berndt (www.pberndt.com)
*
* Extended Textarea for IE and Firefox browsers
* Features:
* - Possibility to place tabs in