4 * Copyright, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://www.tinymce.com/license
8 * Contributing: http://www.tinymce.com/contributing
12 * This is the base class for all controls and containers. All UI control instances inherit
13 * from this one as it has the base logic needed by all of them.
15 * @class tinymce.ui.Control
17 define("tinymce/ui/Control", [
20 "tinymce/ui/Collection",
22 ], function(Class, Tools, Collection, DomUtils) {
25 var nativeEvents = Tools.makeMap("focusin focusout scroll click dblclick mousedown mouseup mousemove mouseover" +
26 " mouseout mouseenter mouseleave wheel keydown keypress keyup contextmenu", " ");
28 var elementIdCache = {};
29 var hasMouseWheelEventSupport = "onmousewheel" in document;
30 var hasWheelEventSupport = false;
32 var Control = Class.extend({
38 * Class/id prefix to use for all controls.
41 * @field {String} classPrefix
46 * Constructs a new control instance with the specified settings.
49 * @param {Object} settings Name/value object with settings.
50 * @setting {String} style Style CSS properties to add.
51 * @setting {String} border Border box values example: 1 1 1 1
52 * @setting {String} padding Padding box values example: 1 1 1 1
53 * @setting {String} margin Margin box values example: 1 1 1 1
54 * @setting {Number} minWidth Minimal width for the control.
55 * @setting {Number} minHeight Minimal height for the control.
56 * @setting {String} classes Space separated list of classes to add.
57 * @setting {String} role WAI-ARIA role to use for control.
58 * @setting {Boolean} hidden Is the control hidden by default.
59 * @setting {Boolean} disabled Is the control disabled by default.
60 * @setting {String} name Name of the control instance.
62 init: function(settings) {
63 var self = this, classes, i;
65 self.settings = settings = Tools.extend({}, self.Defaults, settings);
68 self._id = DomUtils.id();
69 self._text = self._name = '';
70 self._width = self._height = 0;
71 self._aria = {role: settings.role};
74 classes = settings.classes;
76 classes = classes.split(' ');
80 classes.map[classes[i]] = true;
84 self._classes = classes || [];
87 // Set some properties
88 Tools.each('title text width height name classes visible disabled active value'.split(' '), function(name) {
89 var value = settings[name], undef;
91 if (value !== undef) {
93 } else if (self['_' + name] === undef) {
94 self['_' + name] = false;
98 self.on('click', function() {
99 if (self.disabled()) {
104 // TODO: Is this needed duplicate code see above?
105 if (settings.classes) {
106 Tools.each(settings.classes.split(' '), function(cls) {
112 * Name/value object with settings for the current control.
114 * @field {Object} settings
116 self.settings = settings;
118 self._borderBox = self.parseBox(settings.border);
119 self._paddingBox = self.parseBox(settings.padding);
120 self._marginBox = self.parseBox(settings.margin);
122 if (settings.hidden) {
127 // Will generate getter/setter methods for these properties
128 Properties: 'parent,title,text,width,height,disabled,active,name,value',
130 // Will generate empty dummy functions for these
131 Methods: 'renderHtml',
134 * Returns the root element to render controls into.
136 * @method getContainerElm
137 * @return {Element} HTML DOM element to render into.
139 getContainerElm: function() {
140 return document.body;
144 * Returns a control instance for the current DOM element.
146 * @method getParentCtrl
147 * @param {Element} elm HTML dom element to get parent control from.
148 * @return {tinymce.ui.Control} Control instance or undefined.
150 getParentCtrl: function(elm) {
154 ctrl = Control.controlIdLookup[elm.id];
159 elm = elm.parentNode;
166 * Parses the specified box value. A box value contains 1-4 properties in clockwise order.
169 * @param {String/Number} value Box value "0 1 2 3" or "0" etc.
170 * @return {Object} Object with top/right/bottom/left properties.
173 parseBox: function(value) {
180 if (typeof(value) === "number") {
191 value = value.split(' ');
195 value[1] = value[2] = value[3] = value[0];
196 } else if (len === 2) {
199 } else if (len === 3) {
204 top: parseInt(value[0], radix) || 0,
205 right: parseInt(value[1], radix) || 0,
206 bottom: parseInt(value[2], radix) || 0,
207 left: parseInt(value[3], radix) || 0
211 borderBox: function() {
212 return this._borderBox;
215 paddingBox: function() {
216 return this._paddingBox;
219 marginBox: function() {
220 return this._marginBox;
223 measureBox: function(elm, prefix) {
224 function getStyle(name) {
225 var defaultView = document.defaultView;
229 name = name.replace(/[A-Z]/g, function(a) {
233 return defaultView.getComputedStyle(elm, null).getPropertyValue(name);
236 return elm.currentStyle[name];
239 function getSide(name) {
240 var val = parseInt(getStyle(name), 10);
242 return isNaN(val) ? 0 : val;
246 top: getSide(prefix + "TopWidth"),
247 right: getSide(prefix + "RightWidth"),
248 bottom: getSide(prefix + "BottomWidth"),
249 left: getSide(prefix + "LeftWidth")
254 * Initializes the current controls layout rect.
255 * This will be executed by the layout managers to determine the
256 * default minWidth/minHeight etc.
258 * @method initLayoutRect
259 * @return {Object} Layout rect instance.
261 initLayoutRect: function() {
262 var self = this, settings = self.settings, borderBox, layoutRect;
263 var elm = self.getEl(), width, height, minWidth, minHeight, autoResize;
264 var startMinWidth, startMinHeight;
267 borderBox = self._borderBox = self._borderBox || self.measureBox(elm, 'border');
268 self._paddingBox = self._paddingBox || self.measureBox(elm, 'padding');
269 self._marginBox = self._marginBox || self.measureBox(elm, 'margin');
271 // Setup minWidth/minHeight and width/height
272 startMinWidth = settings.minWidth;
273 startMinHeight = settings.minHeight;
274 minWidth = startMinWidth || elm.offsetWidth;
275 minHeight = startMinHeight || elm.offsetHeight;
276 width = settings.width;
277 height = settings.height;
278 autoResize = settings.autoResize;
279 autoResize = typeof(autoResize) != "undefined" ? autoResize : !width && !height;
281 width = width || minWidth;
282 height = height || minHeight;
284 var deltaW = borderBox.left + borderBox.right;
285 var deltaH = borderBox.top + borderBox.bottom;
287 var maxW = settings.maxWidth || 0xFFFF;
288 var maxH = settings.maxHeight || 0xFFFF;
290 // Setup initial layout rect
291 self._layoutRect = layoutRect = {
298 contentW: width - deltaW,
299 contentH: height - deltaH,
300 innerW: width - deltaW,
301 innerH: height - deltaH,
302 startMinWidth: startMinWidth || 0,
303 startMinHeight: startMinHeight || 0,
304 minW: Math.min(minWidth, maxW),
305 minH: Math.min(minHeight, maxH),
308 autoResize: autoResize,
312 self._lastLayoutRect = {};
318 * Getter/setter for the current layout rect.
321 * @param {Object} [newRect] Optional new layout rect.
322 * @return {tinymce.ui.Control/Object} Current control or rect object.
324 layoutRect: function(newRect) {
325 var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls;
327 // Initialize default layout rect
329 curRect = self.initLayoutRect();
332 // Set new rect values
334 // Calc deltas between inner and outer sizes
335 deltaWidth = curRect.deltaW;
336 deltaHeight = curRect.deltaH;
339 if (newRect.x !== undef) {
340 curRect.x = newRect.x;
344 if (newRect.y !== undef) {
345 curRect.y = newRect.y;
349 if (newRect.minW !== undef) {
350 curRect.minW = newRect.minW;
354 if (newRect.minH !== undef) {
355 curRect.minH = newRect.minH;
358 // Set new width and calculate inner width
360 if (size !== undef) {
361 size = size < curRect.minW ? curRect.minW : size;
362 size = size > curRect.maxW ? curRect.maxW : size;
364 curRect.innerW = size - deltaWidth;
367 // Set new height and calculate inner height
369 if (size !== undef) {
370 size = size < curRect.minH ? curRect.minH : size;
371 size = size > curRect.maxH ? curRect.maxH : size;
373 curRect.innerH = size - deltaHeight;
376 // Set new inner width and calculate width
377 size = newRect.innerW;
378 if (size !== undef) {
379 size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size;
380 size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size;
381 curRect.innerW = size;
382 curRect.w = size + deltaWidth;
385 // Set new height and calculate inner height
386 size = newRect.innerH;
387 if (size !== undef) {
388 size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size;
389 size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size;
390 curRect.innerH = size;
391 curRect.h = size + deltaHeight;
395 if (newRect.contentW !== undef) {
396 curRect.contentW = newRect.contentW;
400 if (newRect.contentH !== undef) {
401 curRect.contentH = newRect.contentH;
404 // Compare last layout rect with the current one to see if we need to repaint or not
405 lastLayoutRect = self._lastLayoutRect;
406 if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y ||
407 lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) {
408 repaintControls = Control.repaintControls;
410 if (repaintControls) {
411 if (repaintControls.map && !repaintControls.map[self._id]) {
412 repaintControls.push(self);
413 repaintControls.map[self._id] = true;
417 lastLayoutRect.x = curRect.x;
418 lastLayoutRect.y = curRect.y;
419 lastLayoutRect.w = curRect.w;
420 lastLayoutRect.h = curRect.h;
430 * Repaints the control after a layout operation.
434 repaint: function() {
435 var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect;
437 style = self.getEl().style;
438 rect = self._layoutRect;
439 lastRepaintRect = self._lastRepaintRect || {};
441 borderBox = self._borderBox;
442 borderW = borderBox.left + borderBox.right;
443 borderH = borderBox.top + borderBox.bottom;
445 if (rect.x !== lastRepaintRect.x) {
446 style.left = rect.x + 'px';
447 lastRepaintRect.x = rect.x;
450 if (rect.y !== lastRepaintRect.y) {
451 style.top = rect.y + 'px';
452 lastRepaintRect.y = rect.y;
455 if (rect.w !== lastRepaintRect.w) {
456 style.width = (rect.w - borderW) + 'px';
457 lastRepaintRect.w = rect.w;
460 if (rect.h !== lastRepaintRect.h) {
461 style.height = (rect.h - borderH) + 'px';
462 lastRepaintRect.h = rect.h;
465 // Update body if needed
466 if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) {
467 bodyStyle = self.getEl('body').style;
468 bodyStyle.width = (rect.innerW) + 'px';
469 lastRepaintRect.innerW = rect.innerW;
472 if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) {
473 bodyStyle = bodyStyle || self.getEl('body').style;
474 bodyStyle.height = (rect.innerH) + 'px';
475 lastRepaintRect.innerH = rect.innerH;
478 self._lastRepaintRect = lastRepaintRect;
479 self.fire('repaint', {}, false);
483 * Binds a callback to the specified event. This event can both be
484 * native browser events like "click" or custom ones like PostRender.
486 * The callback function will be passed a DOM event like object that enables yout do stop propagation.
489 * @param {String} name Name of the event to bind. For example "click".
490 * @param {String/function} callback Callback function to execute ones the event occurs.
491 * @return {tinymce.ui.Control} Current control object.
493 on: function(name, callback) {
494 var self = this, bindings, handlers, names, i;
496 function resolveCallbackName(name) {
501 self.parents().each(function(ctrl) {
502 var callbacks = ctrl.settings.callbacks;
504 if (callbacks && (callback = callbacks[name])) {
511 return callback.call(scope, e);
516 if (typeof(callback) == 'string') {
517 callback = resolveCallbackName(callback);
520 names = name.toLowerCase().split(' ');
525 bindings = self._bindings;
527 bindings = self._bindings = {};
530 handlers = bindings[name];
532 handlers = bindings[name] = [];
535 handlers.push(callback);
537 if (nativeEvents[name]) {
538 if (!self._nativeEvents) {
539 self._nativeEvents = {name: true};
541 self._nativeEvents[name] = true;
544 if (self._rendered) {
545 self.bindPendingEvents();
555 * Unbinds the specified event and optionally a specific callback. If you omit the name
556 * parameter all event handlers will be removed. If you omit the callback all event handles
557 * by the specified name will be removed.
560 * @param {String} [name] Name for the event to unbind.
561 * @param {function} [callback] Callback function to unbind.
562 * @return {mxex.ui.Control} Current control object.
564 off: function(name, callback) {
565 var self = this, i, bindings = self._bindings, handlers, bindingName, names, hi;
569 names = name.toLowerCase().split(' ');
573 handlers = bindings[name];
575 // Unbind all handlers
577 for (bindingName in bindings) {
578 bindings[bindingName].length = 0;
585 // Unbind all by name
589 // Unbind specific ones
590 hi = handlers.length;
592 if (handlers[hi] === callback) {
593 handlers.splice(hi, 1);
608 * Fires the specified event by name and arguments on the control. This will execute all
609 * bound event handlers.
612 * @param {String} name Name of the event to fire.
613 * @param {Object} [args] Arguments to pass to the event.
614 * @param {Boolean} [bubble] Value to control bubbeling. Defaults to true.
615 * @return {Object} Current arguments object.
617 fire: function(name, args, bubble) {
618 var self = this, i, l, handlers, parentCtrl;
620 name = name.toLowerCase();
622 // Dummy function that gets replaced on the delegation state functions
623 function returnFalse() {
627 // Dummy function that gets replaced on the delegation state functions
628 function returnTrue() {
632 // Setup empty object if args is omited
635 // Stick type into event object
640 // Stick control into event
645 // Add event delegation methods if they are missing
646 if (!args.preventDefault) {
647 // Add preventDefault method
648 args.preventDefault = function() {
649 args.isDefaultPrevented = returnTrue;
652 // Add stopPropagation
653 args.stopPropagation = function() {
654 args.isPropagationStopped = returnTrue;
657 // Add stopImmediatePropagation
658 args.stopImmediatePropagation = function() {
659 args.isImmediatePropagationStopped = returnTrue;
662 // Add event delegation states
663 args.isDefaultPrevented = returnFalse;
664 args.isPropagationStopped = returnFalse;
665 args.isImmediatePropagationStopped = returnFalse;
668 if (self._bindings) {
669 handlers = self._bindings[name];
672 for (i = 0, l = handlers.length; i < l; i++) {
673 // Execute callback and break if the callback returns a false
674 if (!args.isImmediatePropagationStopped() && handlers[i].call(self, args) === false) {
681 // Bubble event up to parent controls
682 if (bubble !== false) {
683 parentCtrl = self.parent();
684 while (parentCtrl && !args.isPropagationStopped()) {
685 parentCtrl.fire(name, args, false);
686 parentCtrl = parentCtrl.parent();
694 * Returns a control collection with all parent controls.
697 * @param {String} selector Optional selector expression to find parents.
698 * @return {tinymce.ui.Collection} Collection with all parent controls.
700 parents: function(selector) {
701 var ctrl = this, parents = new Collection();
703 // Add each parent to collection
704 for (ctrl = ctrl.parent(); ctrl; ctrl = ctrl.parent()) {
708 // Filter away everything that doesn't match the selector
710 parents = parents.filter(selector);
717 * Returns the control next to the current control.
720 * @return {tinymce.ui.Control} Next control instance.
723 var parentControls = this.parent().items();
725 return parentControls[parentControls.indexOf(this) + 1];
729 * Returns the control previous to the current control.
732 * @return {tinymce.ui.Control} Previous control instance.
735 var parentControls = this.parent().items();
737 return parentControls[parentControls.indexOf(this) - 1];
741 * Find the common ancestor for two control instances.
743 * @method findCommonAncestor
744 * @param {tinymce.ui.Control} ctrl1 First control.
745 * @param {tinymce.ui.Control} ctrl2 Second control.
746 * @return {tinymce.ui.Control} Ancestor control instance.
748 findCommonAncestor: function(ctrl1, ctrl2) {
754 while (parentCtrl && ctrl1 != parentCtrl) {
755 parentCtrl = parentCtrl.parent();
758 if (ctrl1 == parentCtrl) {
762 ctrl1 = ctrl1.parent();
769 * Returns true/false if the specific control has the specific class.
772 * @param {String} cls Class to check for.
773 * @param {String} [group] Sub element group name.
774 * @return {Boolean} True/false if the control has the specified class.
776 hasClass: function(cls, group) {
777 var classes = this._classes[group || 'control'];
779 cls = this.classPrefix + cls;
781 return classes && !!classes.map[cls];
785 * Adds the specified class to the control
788 * @param {String} cls Class to check for.
789 * @param {String} [group] Sub element group name.
790 * @return {tinymce.ui.Control} Current control object.
792 addClass: function(cls, group) {
793 var self = this, classes, elm;
795 cls = this.classPrefix + cls;
796 classes = self._classes[group || 'control'];
801 self._classes[group || 'control'] = classes;
804 if (!classes.map[cls]) {
805 classes.map[cls] = cls;
808 if (self._rendered) {
809 elm = self.getEl(group);
812 elm.className = classes.join(' ');
821 * Removes the specified class from the control.
823 * @method removeClass
824 * @param {String} cls Class to remove.
825 * @param {String} [group] Sub element group name.
826 * @return {tinymce.ui.Control} Current control object.
828 removeClass: function(cls, group) {
829 var self = this, classes, i, elm;
831 cls = this.classPrefix + cls;
832 classes = self._classes[group || 'control'];
833 if (classes && classes.map[cls]) {
834 delete classes.map[cls];
838 if (classes[i] === cls) {
839 classes.splice(i, 1);
844 if (self._rendered) {
845 elm = self.getEl(group);
848 elm.className = classes.join(' ');
856 * Toggles the specified class on the control.
858 * @method toggleClass
859 * @param {String} cls Class to remove.
860 * @param {Boolean} state True/false state to add/remove class.
861 * @param {String} [group] Sub element group name.
862 * @return {tinymce.ui.Control} Current control object.
864 toggleClass: function(cls, state, group) {
868 self.addClass(cls, group);
870 self.removeClass(cls, group);
877 * Returns the class string for the specified group name.
880 * @param {String} [group] Group to get clases by.
881 * @return {String} Classes for the specified group.
883 classes: function(group) {
884 var classes = this._classes[group || 'control'];
886 return classes ? classes.join(' ') : '';
890 * Sets the inner HTML of the control element.
893 * @param {String} html Html string to set as inner html.
894 * @return {tinymce.ui.Control} Current control object.
896 innerHtml: function(html) {
897 DomUtils.innerHtml(this.getEl(), html);
902 * Returns the control DOM element or sub element.
905 * @param {String} [suffix] Suffix to get element by.
906 * @param {Boolean} [dropCache] True if the cache for the element should be dropped.
907 * @return {Element} HTML DOM element for the current control or it's children.
909 getEl: function(suffix, dropCache) {
910 var elm, id = suffix ? this._id + '-' + suffix : this._id;
912 elm = elementIdCache[id] = (dropCache === true ? null : elementIdCache[id]) || DomUtils.get(id);
918 * Sets/gets the visible for the control.
921 * @param {Boolean} state Value to set to control.
922 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
924 visible: function(state) {
925 var self = this, parentCtrl;
927 if (typeof(state) !== "undefined") {
928 if (self._visible !== state) {
929 if (self._rendered) {
930 self.getEl().style.display = state ? '' : 'none';
933 self._visible = state;
935 // Parent container needs to reflow
936 parentCtrl = self.parent();
938 parentCtrl._lastRect = null;
941 self.fire(state ? 'show' : 'hide');
947 return self._visible;
951 * Sets the visible state to true.
954 * @return {tinymce.ui.Control} Current control instance.
957 return this.visible(true);
961 * Sets the visible state to false.
964 * @return {tinymce.ui.Control} Current control instance.
967 return this.visible(false);
971 * Focuses the current control.
974 * @return {tinymce.ui.Control} Current control instance.
978 this.getEl().focus();
987 * Blurs the current control.
990 * @return {tinymce.ui.Control} Current control instance.
999 * Sets the specified aria property.
1002 * @param {String} name Name of the aria property to set.
1003 * @param {String} value Value of the aria property.
1004 * @return {tinymce.ui.Control} Current control instance.
1006 aria: function(name, value) {
1007 var self = this, elm = self.getEl();
1009 if (typeof(value) === "undefined") {
1010 return self._aria[name];
1012 self._aria[name] = value;
1015 if (self._rendered) {
1016 if (name == 'label') {
1017 elm.setAttribute('aria-labeledby', self._id);
1020 elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
1027 * Encodes the specified string with HTML entities. It will also
1028 * translate the string to different languages.
1031 * @param {String/Object/Array} text Text to entity encode.
1032 * @param {Boolean} [translate=true] False if the contents shouldn't be translated.
1033 * @return {String} Encoded and possible traslated string.
1035 encode: function(text, translate) {
1036 if (translate !== false && Control.translate) {
1037 text = Control.translate(text);
1040 return (text || '').replace(/[&<>"]/g, function(match) {
1041 return '&#' + match.charCodeAt(0) + ';';
1046 * Adds items before the current control.
1049 * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control.
1050 * @return {tinymce.ui.Control} Current control instance.
1052 before: function(items) {
1053 var self = this, parent = self.parent();
1056 parent.insert(items, parent.items().indexOf(self), true);
1063 * Adds items after the current control.
1066 * @param {Array/tinymce.ui.Collection} items Array of items to append after this control.
1067 * @return {tinymce.ui.Control} Current control instance.
1069 after: function(items) {
1070 var self = this, parent = self.parent();
1073 parent.insert(items, parent.items().indexOf(self));
1080 * Removes the current control from DOM and from UI collections.
1083 * @return {tinymce.ui.Control} Current control instance.
1085 remove: function() {
1086 var self = this, elm = self.getEl(), parent = self.parent(), newItems;
1089 var controls = self.items().toArray();
1090 var i = controls.length;
1092 controls[i].remove();
1096 if (parent && parent.items) {
1099 parent.items().each(function(item) {
1100 if (item !== self) {
1101 newItems.push(item);
1105 parent.items().set(newItems);
1106 parent._lastRect = null;
1109 if (self._eventsRoot && self._eventsRoot == self) {
1113 delete Control.controlIdLookup[self._id];
1115 if (elm.parentNode) {
1116 elm.parentNode.removeChild(elm);
1123 * Renders the control before the specified element.
1125 * @method renderBefore
1126 * @param {Element} elm Element to render before.
1127 * @return {tinymce.ui.Control} Current control instance.
1129 renderBefore: function(elm) {
1132 elm.parentNode.insertBefore(DomUtils.createFragment(self.renderHtml()), elm);
1139 * Renders the control to the specified element.
1141 * @method renderBefore
1142 * @param {Element} elm Element to render to.
1143 * @return {tinymce.ui.Control} Current control instance.
1145 renderTo: function(elm) {
1148 elm = elm || self.getContainerElm();
1149 elm.appendChild(DomUtils.createFragment(self.renderHtml()));
1156 * Post render method. Called after the control has been rendered to the target.
1158 * @method postRender
1159 * @return {tinymce.ui.Control} Current control instance.
1161 postRender: function() {
1162 var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot;
1164 // Bind on<event> settings
1165 for (name in settings) {
1166 if (name.indexOf("on") === 0) {
1167 self.on(name.substr(2), settings[name]);
1171 if (self._eventsRoot) {
1172 for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) {
1173 parentEventsRoot = parent._eventsRoot;
1176 if (parentEventsRoot) {
1177 for (name in parentEventsRoot._nativeEvents) {
1178 self._nativeEvents[name] = true;
1183 self.bindPendingEvents();
1185 if (settings.style) {
1188 elm.setAttribute('style', settings.style);
1189 elm.style.cssText = settings.style;
1193 if (!self._visible) {
1194 DomUtils.css(self.getEl(), 'display', 'none');
1197 if (self.settings.border) {
1198 box = self.borderBox();
1199 DomUtils.css(self.getEl(), {
1200 'border-top-width': box.top,
1201 'border-right-width': box.right,
1202 'border-bottom-width': box.bottom,
1203 'border-left-width': box.left
1207 // Add instance to lookup
1208 Control.controlIdLookup[self._id] = self;
1210 for (var key in self._aria) {
1211 self.aria(key, self._aria[key]);
1214 self.fire('postrender', {}, false);
1218 * Scrolls the current control into view.
1220 * @method scrollIntoView
1221 * @param {String} align Alignment in view top|center|bottom.
1222 * @return {tinymce.ui.Control} Current control instance.
1224 scrollIntoView: function(align) {
1225 function getOffset(elm, rootElm) {
1226 var x, y, parent = elm;
1229 while (parent && parent != rootElm && parent.nodeType) {
1230 x += parent.offsetLeft || 0;
1231 y += parent.offsetTop || 0;
1232 parent = parent.offsetParent;
1235 return {x: x, y: y};
1238 var elm = this.getEl(), parentElm = elm.parentNode;
1239 var x, y, width, height, parentWidth, parentHeight;
1240 var pos = getOffset(elm, parentElm);
1244 width = elm.offsetWidth;
1245 height = elm.offsetHeight;
1246 parentWidth = parentElm.clientWidth;
1247 parentHeight = parentElm.clientHeight;
1249 if (align == "end") {
1250 x -= parentWidth - width;
1251 y -= parentHeight - height;
1252 } else if (align == "center") {
1253 x -= (parentWidth / 2) - (width / 2);
1254 y -= (parentHeight / 2) - (height / 2);
1257 parentElm.scrollLeft = x;
1258 parentElm.scrollTop = y;
1264 * Binds pending DOM events.
1268 bindPendingEvents: function() {
1269 var self = this, i, l, parents, eventRootCtrl, nativeEvents, name;
1271 function delegate(e) {
1272 var control = self.getParentCtrl(e.target);
1275 control.fire(e.type, e);
1279 function mouseLeaveHandler() {
1280 var ctrl = eventRootCtrl._lastHoverCtrl;
1283 ctrl.fire("mouseleave", {target: ctrl.getEl()});
1285 ctrl.parents().each(function(ctrl) {
1286 ctrl.fire("mouseleave", {target: ctrl.getEl()});
1289 eventRootCtrl._lastHoverCtrl = null;
1293 function mouseEnterHandler(e) {
1294 var ctrl = self.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents;
1296 // Over on a new control
1297 if (ctrl !== lastCtrl) {
1298 eventRootCtrl._lastHoverCtrl = ctrl;
1300 parents = ctrl.parents().toArray().reverse();
1304 lastParents = lastCtrl.parents().toArray().reverse();
1305 lastParents.push(lastCtrl);
1307 for (idx = 0; idx < lastParents.length; idx++) {
1308 if (parents[idx] !== lastParents[idx]) {
1313 for (i = lastParents.length - 1; i >= idx; i--) {
1314 lastCtrl = lastParents[i];
1315 lastCtrl.fire("mouseleave", {
1316 target : lastCtrl.getEl()
1321 for (i = idx; i < parents.length; i++) {
1323 ctrl.fire("mouseenter", {
1324 target : ctrl.getEl()
1330 function fixWheelEvent(e) {
1333 if (e.type == "mousewheel") {
1334 e.deltaY = - 1/40 * e.wheelDelta;
1336 if (e.wheelDeltaX) {
1337 e.deltaX = -1/40 * e.wheelDeltaX;
1341 e.deltaY = e.detail;
1344 e = self.fire("wheel", e);
1347 self._rendered = true;
1349 nativeEvents = self._nativeEvents;
1351 // Find event root element if it exists
1352 parents = self.parents().toArray();
1353 parents.unshift(self);
1354 for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) {
1355 eventRootCtrl = parents[i]._eventsRoot;
1358 // Event root wasn't found the use the root control
1359 if (!eventRootCtrl) {
1360 eventRootCtrl = parents[parents.length - 1] || self;
1363 // Set the eventsRoot property on children that didn't have it
1364 self._eventsRoot = eventRootCtrl;
1365 for (l = i, i = 0; i < l; i++) {
1366 parents[i]._eventsRoot = eventRootCtrl;
1369 // Bind native event delegates
1370 for (name in nativeEvents) {
1371 if (!nativeEvents) {
1375 if (name === "wheel" && !hasWheelEventSupport) {
1376 if (hasMouseWheelEventSupport) {
1377 DomUtils.on(self.getEl(), "mousewheel", fixWheelEvent);
1379 DomUtils.on(self.getEl(), "DOMMouseScroll", fixWheelEvent);
1385 // Special treatment for mousenter/mouseleave since these doesn't bubble
1386 if (name === "mouseenter" || name === "mouseleave") {
1387 // Fake mousenter/mouseleave
1388 if (!eventRootCtrl._hasMouseEnter) {
1389 DomUtils.on(eventRootCtrl.getEl(), "mouseleave", mouseLeaveHandler);
1390 DomUtils.on(eventRootCtrl.getEl(), "mouseover", mouseEnterHandler);
1391 eventRootCtrl._hasMouseEnter = 1;
1393 } else if (!eventRootCtrl[name]) {
1394 DomUtils.on(eventRootCtrl.getEl(), name, delegate);
1395 eventRootCtrl[name] = true;
1398 // Remove the event once it's bound
1399 nativeEvents[name] = false;
1405 * Reflows the current control and it's parents.
1406 * This should be used after you for example append children to the current control so
1407 * that the layout managers know that they need to reposition everything.
1410 * container.append({type: 'button', text: 'My button'}).reflow();
1413 * @return {tinymce.ui.Control} Current control instance.
1415 reflow: function() {
1422 * Sets/gets the parent container for the control.
1425 * @param {tinymce.ui.Container} parent Optional parent to set.
1426 * @return {tinymce.ui.Control} Parent control or the current control on a set action.
1428 // parent: function(parent) {} -- Generated
1431 * Sets/gets the text for the control.
1434 * @param {String} value Value to set to control.
1435 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
1437 // text: function(value) {} -- Generated
1440 * Sets/gets the width for the control.
1443 * @param {Number} value Value to set to control.
1444 * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
1446 // width: function(value) {} -- Generated
1449 * Sets/gets the height for the control.
1452 * @param {Number} value Value to set to control.
1453 * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
1455 // height: function(value) {} -- Generated
1458 * Sets/gets the disabled state on the control.
1461 * @param {Boolean} state Value to set to control.
1462 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
1464 // disabled: function(state) {} -- Generated
1467 * Sets/gets the active for the control.
1470 * @param {Boolean} state Value to set to control.
1471 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
1473 // active: function(state) {} -- Generated
1476 * Sets/gets the name for the control.
1479 * @param {String} value Value to set to control.
1480 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
1482 // name: function(value) {} -- Generated
1485 * Sets/gets the title for the control.
1488 * @param {String} value Value to set to control.
1489 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
1491 // title: function(value) {} -- Generated