You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1297 lines
34 KiB
1297 lines
34 KiB
2 years ago
|
/*
|
||
|
YUI 3.17.2 (build 9c3c78e)
|
||
|
Copyright 2014 Yahoo! Inc. All rights reserved.
|
||
|
Licensed under the BSD License.
|
||
|
http://yuilibrary.com/license/
|
||
|
*/
|
||
|
|
||
|
YUI.add('widget-base', function (Y, NAME) {
|
||
|
|
||
|
/**
|
||
|
* Provides the base Widget class, with HTML Parser support
|
||
|
*
|
||
|
* @module widget
|
||
|
* @main widget
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Provides the base Widget class
|
||
|
*
|
||
|
* @module widget
|
||
|
* @submodule widget-base
|
||
|
*/
|
||
|
var L = Y.Lang,
|
||
|
Node = Y.Node,
|
||
|
|
||
|
ClassNameManager = Y.ClassNameManager,
|
||
|
|
||
|
_getClassName = ClassNameManager.getClassName,
|
||
|
_getWidgetClassName,
|
||
|
|
||
|
_toInitialCap = Y.cached(function(str) {
|
||
|
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||
|
}),
|
||
|
|
||
|
// K-Weight, IE GC optimizations
|
||
|
CONTENT = "content",
|
||
|
VISIBLE = "visible",
|
||
|
HIDDEN = "hidden",
|
||
|
DISABLED = "disabled",
|
||
|
FOCUSED = "focused",
|
||
|
WIDTH = "width",
|
||
|
HEIGHT = "height",
|
||
|
BOUNDING_BOX = "boundingBox",
|
||
|
CONTENT_BOX = "contentBox",
|
||
|
PARENT_NODE = "parentNode",
|
||
|
OWNER_DOCUMENT = "ownerDocument",
|
||
|
AUTO = "auto",
|
||
|
SRC_NODE = "srcNode",
|
||
|
BODY = "body",
|
||
|
TAB_INDEX = "tabIndex",
|
||
|
ID = "id",
|
||
|
RENDER = "render",
|
||
|
RENDERED = "rendered",
|
||
|
DESTROYED = "destroyed",
|
||
|
STRINGS = "strings",
|
||
|
DIV = "<div></div>",
|
||
|
CHANGE = "Change",
|
||
|
LOADING = "loading",
|
||
|
|
||
|
_UISET = "_uiSet",
|
||
|
|
||
|
EMPTY_STR = "",
|
||
|
EMPTY_FN = function() {},
|
||
|
|
||
|
TRUE = true,
|
||
|
FALSE = false,
|
||
|
|
||
|
UI,
|
||
|
ATTRS = {},
|
||
|
UI_ATTRS = [VISIBLE, DISABLED, HEIGHT, WIDTH, FOCUSED, TAB_INDEX],
|
||
|
|
||
|
WEBKIT = Y.UA.webkit,
|
||
|
|
||
|
// Widget nodeid-to-instance map.
|
||
|
_instances = {};
|
||
|
|
||
|
/**
|
||
|
* A base class for widgets, providing:
|
||
|
* <ul>
|
||
|
* <li>The render lifecycle method, in addition to the init and destroy
|
||
|
* lifecycle methods provide by Base</li>
|
||
|
* <li>Abstract methods to support consistent MVC structure across
|
||
|
* widgets: renderer, renderUI, bindUI, syncUI</li>
|
||
|
* <li>Support for common widget attributes, such as boundingBox, contentBox, visible,
|
||
|
* disabled, focused, strings</li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @param config {Object} Object literal specifying widget configuration properties.
|
||
|
*
|
||
|
* @class Widget
|
||
|
* @constructor
|
||
|
* @extends Base
|
||
|
*/
|
||
|
function Widget(config) {
|
||
|
|
||
|
// kweight
|
||
|
var widget = this,
|
||
|
parentNode,
|
||
|
render,
|
||
|
constructor = widget.constructor;
|
||
|
|
||
|
widget._strs = {};
|
||
|
widget._cssPrefix = constructor.CSS_PREFIX || _getClassName(constructor.NAME.toLowerCase());
|
||
|
|
||
|
// We need a config for HTML_PARSER to work.
|
||
|
config = config || {};
|
||
|
|
||
|
Widget.superclass.constructor.call(widget, config);
|
||
|
|
||
|
render = widget.get(RENDER);
|
||
|
|
||
|
if (render) {
|
||
|
// Render could be a node or boolean
|
||
|
if (render !== TRUE) {
|
||
|
parentNode = render;
|
||
|
}
|
||
|
widget.render(parentNode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Static property provides a string to identify the class.
|
||
|
* <p>
|
||
|
* Currently used to apply class identifiers to the bounding box
|
||
|
* and to classify events fired by the widget.
|
||
|
* </p>
|
||
|
*
|
||
|
* @property NAME
|
||
|
* @type String
|
||
|
* @static
|
||
|
*/
|
||
|
Widget.NAME = "widget";
|
||
|
|
||
|
/**
|
||
|
* Constant used to identify state changes originating from
|
||
|
* the DOM (as opposed to the JavaScript model).
|
||
|
*
|
||
|
* @property UI_SRC
|
||
|
* @type String
|
||
|
* @static
|
||
|
* @final
|
||
|
*/
|
||
|
UI = Widget.UI_SRC = "ui";
|
||
|
|
||
|
/**
|
||
|
* Static property used to define the default attribute
|
||
|
* configuration for the Widget.
|
||
|
*
|
||
|
* @property ATTRS
|
||
|
* @type Object
|
||
|
* @static
|
||
|
*/
|
||
|
Widget.ATTRS = ATTRS;
|
||
|
|
||
|
// Trying to optimize kweight by setting up attrs this way saves about 0.4K min'd
|
||
|
|
||
|
/**
|
||
|
* @attribute id
|
||
|
* @writeOnce
|
||
|
* @default Generated using guid()
|
||
|
* @type String
|
||
|
*/
|
||
|
|
||
|
ATTRS[ID] = {
|
||
|
valueFn: "_guid",
|
||
|
writeOnce: TRUE
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Flag indicating whether or not this Widget
|
||
|
* has been through the render lifecycle phase.
|
||
|
*
|
||
|
* @attribute rendered
|
||
|
* @readOnly
|
||
|
* @default false
|
||
|
* @type boolean
|
||
|
*/
|
||
|
ATTRS[RENDERED] = {
|
||
|
value:FALSE,
|
||
|
readOnly: TRUE
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @attribute boundingBox
|
||
|
* @description The outermost DOM node for the Widget, used for sizing and positioning
|
||
|
* of a Widget as well as a containing element for any decorator elements used
|
||
|
* for skinning.
|
||
|
* @type String | Node
|
||
|
* @writeOnce
|
||
|
*/
|
||
|
ATTRS[BOUNDING_BOX] = {
|
||
|
valueFn:"_defaultBB",
|
||
|
setter: "_setBB",
|
||
|
writeOnce: TRUE
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @attribute contentBox
|
||
|
* @description A DOM node that is a direct descendant of a Widget's bounding box that
|
||
|
* houses its content.
|
||
|
* @type String | Node
|
||
|
* @writeOnce
|
||
|
*/
|
||
|
ATTRS[CONTENT_BOX] = {
|
||
|
valueFn:"_defaultCB",
|
||
|
setter: "_setCB",
|
||
|
writeOnce: TRUE
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @attribute tabIndex
|
||
|
* @description Number (between -32767 to 32767) indicating the widget's
|
||
|
* position in the default tab flow. The value is used to set the
|
||
|
* "tabIndex" attribute on the widget's bounding box. Negative values allow
|
||
|
* the widget to receive DOM focus programmatically (by calling the focus
|
||
|
* method), while being removed from the default tab flow. A value of
|
||
|
* null removes the "tabIndex" attribute from the widget's bounding box.
|
||
|
* @type Number
|
||
|
* @default null
|
||
|
*/
|
||
|
ATTRS[TAB_INDEX] = {
|
||
|
value: null,
|
||
|
validator: "_validTabIndex"
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @attribute focused
|
||
|
* @description Boolean indicating if the Widget, or one of its descendants,
|
||
|
* has focus.
|
||
|
* @readOnly
|
||
|
* @default false
|
||
|
* @type boolean
|
||
|
*/
|
||
|
ATTRS[FOCUSED] = {
|
||
|
value: FALSE,
|
||
|
readOnly:TRUE
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @attribute disabled
|
||
|
* @description Boolean indicating if the Widget should be disabled. The disabled implementation
|
||
|
* is left to the specific classes extending widget.
|
||
|
* @default false
|
||
|
* @type boolean
|
||
|
*/
|
||
|
ATTRS[DISABLED] = {
|
||
|
value: FALSE
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @attribute visible
|
||
|
* @description Boolean indicating whether or not the Widget is visible.
|
||
|
* @default TRUE
|
||
|
* @type boolean
|
||
|
*/
|
||
|
ATTRS[VISIBLE] = {
|
||
|
value: TRUE
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @attribute height
|
||
|
* @description String with units, or number, representing the height of the Widget. If a number is provided,
|
||
|
* the default unit, defined by the Widgets DEF_UNIT, property is used.
|
||
|
* @default EMPTY_STR
|
||
|
* @type {String | Number}
|
||
|
*/
|
||
|
ATTRS[HEIGHT] = {
|
||
|
value: EMPTY_STR
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @attribute width
|
||
|
* @description String with units, or number, representing the width of the Widget. If a number is provided,
|
||
|
* the default unit, defined by the Widgets DEF_UNIT, property is used.
|
||
|
* @default EMPTY_STR
|
||
|
* @type {String | Number}
|
||
|
*/
|
||
|
ATTRS[WIDTH] = {
|
||
|
value: EMPTY_STR
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @attribute strings
|
||
|
* @description Collection of strings used to label elements of the Widget's UI.
|
||
|
* @default null
|
||
|
* @type Object
|
||
|
*/
|
||
|
ATTRS[STRINGS] = {
|
||
|
value: {},
|
||
|
setter: "_strSetter",
|
||
|
getter: "_strGetter"
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Whether or not to render the widget automatically after init, and optionally, to which parent node.
|
||
|
*
|
||
|
* @attribute render
|
||
|
* @type boolean | Node
|
||
|
* @writeOnce
|
||
|
*/
|
||
|
ATTRS[RENDER] = {
|
||
|
value:FALSE,
|
||
|
writeOnce:TRUE
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* The css prefix which the static Widget.getClassName method should use when constructing class names
|
||
|
*
|
||
|
* @property CSS_PREFIX
|
||
|
* @type String
|
||
|
* @default Widget.NAME.toLowerCase()
|
||
|
* @private
|
||
|
* @static
|
||
|
*/
|
||
|
Widget.CSS_PREFIX = _getClassName(Widget.NAME.toLowerCase());
|
||
|
|
||
|
/**
|
||
|
* Generate a standard prefixed classname for the Widget, prefixed by the default prefix defined
|
||
|
* by the <code>Y.config.classNamePrefix</code> attribute used by <code>ClassNameManager</code> and
|
||
|
* <code>Widget.NAME.toLowerCase()</code> (e.g. "yui-widget-xxxxx-yyyyy", based on default values for
|
||
|
* the prefix and widget class name).
|
||
|
* <p>
|
||
|
* The instance based version of this method can be used to generate standard prefixed classnames,
|
||
|
* based on the instances NAME, as opposed to Widget.NAME. This method should be used when you
|
||
|
* need to use a constant class name across different types instances.
|
||
|
* </p>
|
||
|
* @method getClassName
|
||
|
* @param {String*} args* 0..n strings which should be concatenated, using the default separator defined by ClassNameManager, to create the class name
|
||
|
*/
|
||
|
Widget.getClassName = function() {
|
||
|
// arguments needs to be array'fied to concat
|
||
|
return _getClassName.apply(ClassNameManager, [Widget.CSS_PREFIX].concat(Y.Array(arguments), true));
|
||
|
};
|
||
|
|
||
|
_getWidgetClassName = Widget.getClassName;
|
||
|
|
||
|
/**
|
||
|
* Returns the widget instance whose bounding box contains, or is, the given node.
|
||
|
* <p>
|
||
|
* In the case of nested widgets, the nearest bounding box ancestor is used to
|
||
|
* return the widget instance.
|
||
|
* </p>
|
||
|
* @method getByNode
|
||
|
* @static
|
||
|
* @param node {Node | String} The node for which to return a Widget instance. If a selector
|
||
|
* string is passed in, which selects more than one node, the first node found is used.
|
||
|
* @return {Widget} Widget instance, or null if not found.
|
||
|
*/
|
||
|
Widget.getByNode = function(node) {
|
||
|
var widget,
|
||
|
widgetMarker = _getWidgetClassName();
|
||
|
|
||
|
node = Node.one(node);
|
||
|
if (node) {
|
||
|
node = node.ancestor("." + widgetMarker, true);
|
||
|
if (node) {
|
||
|
widget = _instances[Y.stamp(node, true)];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return widget || null;
|
||
|
};
|
||
|
|
||
|
Y.extend(Widget, Y.Base, {
|
||
|
|
||
|
/**
|
||
|
* Returns a class name prefixed with the the value of the
|
||
|
* <code>YUI.config.classNamePrefix</code> attribute + the instances <code>NAME</code> property.
|
||
|
* Uses <code>YUI.config.classNameDelimiter</code> attribute to delimit the provided strings.
|
||
|
* e.g.
|
||
|
* <code>
|
||
|
* <pre>
|
||
|
* // returns "yui-slider-foo-bar", for a slider instance
|
||
|
* var scn = slider.getClassName('foo','bar');
|
||
|
*
|
||
|
* // returns "yui-overlay-foo-bar", for an overlay instance
|
||
|
* var ocn = overlay.getClassName('foo','bar');
|
||
|
* </pre>
|
||
|
* </code>
|
||
|
*
|
||
|
* @method getClassName
|
||
|
* @param {String} [classnames*] One or more classname bits to be joined and prefixed
|
||
|
*/
|
||
|
getClassName: function () {
|
||
|
return _getClassName.apply(ClassNameManager, [this._cssPrefix].concat(Y.Array(arguments), true));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Initializer lifecycle implementation for the Widget class. Registers the
|
||
|
* widget instance, and runs through the Widget's HTML_PARSER definition.
|
||
|
*
|
||
|
* @method initializer
|
||
|
* @protected
|
||
|
* @param config {Object} Configuration object literal for the widget
|
||
|
*/
|
||
|
initializer: function(config) {
|
||
|
|
||
|
var bb = this.get(BOUNDING_BOX);
|
||
|
|
||
|
if (bb instanceof Node) {
|
||
|
this._mapInstance(Y.stamp(bb));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Notification event, which widget implementations can fire, when
|
||
|
* they change the content of the widget. This event has no default
|
||
|
* behavior and cannot be prevented, so the "on" or "after"
|
||
|
* moments are effectively equivalent (with on listeners being invoked before
|
||
|
* after listeners).
|
||
|
*
|
||
|
* @event widget:contentUpdate
|
||
|
* @preventable false
|
||
|
* @param {EventFacade} e The Event Facade
|
||
|
*/
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Utility method used to add an entry to the boundingBox id to instance map.
|
||
|
*
|
||
|
* This method can be used to populate the instance with lazily created boundingBox Node references.
|
||
|
*
|
||
|
* @method _mapInstance
|
||
|
* @param {String} The boundingBox id
|
||
|
* @protected
|
||
|
*/
|
||
|
_mapInstance : function(id) {
|
||
|
_instances[id] = this;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Destructor lifecycle implementation for the Widget class. Purges events attached
|
||
|
* to the bounding box and content box, removes them from the DOM and removes
|
||
|
* the Widget from the list of registered widgets.
|
||
|
*
|
||
|
* @method destructor
|
||
|
* @protected
|
||
|
*/
|
||
|
destructor: function() {
|
||
|
|
||
|
var boundingBox = this.get(BOUNDING_BOX),
|
||
|
bbGuid;
|
||
|
|
||
|
if (boundingBox instanceof Node) {
|
||
|
bbGuid = Y.stamp(boundingBox,true);
|
||
|
|
||
|
if (bbGuid in _instances) {
|
||
|
delete _instances[bbGuid];
|
||
|
}
|
||
|
|
||
|
this._destroyBox();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* <p>
|
||
|
* Destroy lifecycle method. Fires the destroy
|
||
|
* event, prior to invoking destructors for the
|
||
|
* class hierarchy.
|
||
|
*
|
||
|
* Overrides Base's implementation, to support arguments to destroy
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* Subscribers to the destroy
|
||
|
* event can invoke preventDefault on the event object, to prevent destruction
|
||
|
* from proceeding.
|
||
|
* </p>
|
||
|
* @method destroy
|
||
|
* @param destroyAllNodes {Boolean} If true, all nodes contained within the Widget are
|
||
|
* removed and destroyed. Defaults to false due to potentially high run-time cost.
|
||
|
* @return {Widget} A reference to this object
|
||
|
* @chainable
|
||
|
*/
|
||
|
destroy: function(destroyAllNodes) {
|
||
|
this._destroyAllNodes = destroyAllNodes;
|
||
|
return Widget.superclass.destroy.apply(this);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Removes and destroys the widgets rendered boundingBox, contentBox,
|
||
|
* and detaches bound UI events.
|
||
|
*
|
||
|
* @method _destroyBox
|
||
|
* @protected
|
||
|
*/
|
||
|
_destroyBox : function() {
|
||
|
|
||
|
var boundingBox = this.get(BOUNDING_BOX),
|
||
|
contentBox = this.get(CONTENT_BOX),
|
||
|
deep = this._destroyAllNodes,
|
||
|
same;
|
||
|
|
||
|
same = boundingBox && boundingBox.compareTo(contentBox);
|
||
|
|
||
|
if (this.UI_EVENTS) {
|
||
|
this._destroyUIEvents();
|
||
|
}
|
||
|
|
||
|
this._unbindUI(boundingBox);
|
||
|
|
||
|
if (contentBox) {
|
||
|
if (deep) {
|
||
|
contentBox.empty();
|
||
|
}
|
||
|
contentBox.remove(TRUE);
|
||
|
}
|
||
|
|
||
|
if (!same) {
|
||
|
if (deep) {
|
||
|
boundingBox.empty();
|
||
|
}
|
||
|
boundingBox.remove(TRUE);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Establishes the initial DOM for the widget. Invoking this
|
||
|
* method will lead to the creating of all DOM elements for
|
||
|
* the widget (or the manipulation of existing DOM elements
|
||
|
* for the progressive enhancement use case).
|
||
|
* <p>
|
||
|
* This method should only be invoked once for an initialized
|
||
|
* widget.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* It delegates to the widget specific renderer method to do
|
||
|
* the actual work.
|
||
|
* </p>
|
||
|
*
|
||
|
* @method render
|
||
|
* @chainable
|
||
|
* @final
|
||
|
* @param parentNode {Object | String} Optional. The Node under which the
|
||
|
* Widget is to be rendered. This can be a Node instance or a CSS selector string.
|
||
|
* <p>
|
||
|
* If the selector string returns more than one Node, the first node will be used
|
||
|
* as the parentNode. NOTE: This argument is required if both the boundingBox and contentBox
|
||
|
* are not currently in the document. If it's not provided, the Widget will be rendered
|
||
|
* to the body of the current document in this case.
|
||
|
* </p>
|
||
|
*/
|
||
|
render: function(parentNode) {
|
||
|
|
||
|
if (!this.get(DESTROYED) && !this.get(RENDERED)) {
|
||
|
/**
|
||
|
* Lifecycle event for the render phase, fired prior to rendering the UI
|
||
|
* for the widget (prior to invoking the widget's renderer method).
|
||
|
* <p>
|
||
|
* Subscribers to the "on" moment of this event, will be notified
|
||
|
* before the widget is rendered.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* Subscribers to the "after" moment of this event, will be notified
|
||
|
* after rendering is complete.
|
||
|
* </p>
|
||
|
*
|
||
|
* @event render
|
||
|
* @preventable _defRenderFn
|
||
|
* @param {EventFacade} e The Event Facade
|
||
|
*/
|
||
|
this.publish(RENDER, {
|
||
|
queuable:FALSE,
|
||
|
fireOnce:TRUE,
|
||
|
defaultTargetOnly:TRUE,
|
||
|
defaultFn: this._defRenderFn
|
||
|
});
|
||
|
|
||
|
this.fire(RENDER, {parentNode: (parentNode) ? Node.one(parentNode) : null});
|
||
|
}
|
||
|
return this;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Default render handler
|
||
|
*
|
||
|
* @method _defRenderFn
|
||
|
* @protected
|
||
|
* @param {EventFacade} e The Event object
|
||
|
* @param {Node} parentNode The parent node to render to, if passed in to the <code>render</code> method
|
||
|
*/
|
||
|
_defRenderFn : function(e) {
|
||
|
this._parentNode = e.parentNode;
|
||
|
|
||
|
this.renderer();
|
||
|
this._set(RENDERED, TRUE);
|
||
|
|
||
|
this._removeLoadingClassNames();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Creates DOM (or manipulates DOM for progressive enhancement)
|
||
|
* This method is invoked by render() and is not chained
|
||
|
* automatically for the class hierarchy (unlike initializer, destructor)
|
||
|
* so it should be chained manually for subclasses if required.
|
||
|
*
|
||
|
* @method renderer
|
||
|
* @protected
|
||
|
*/
|
||
|
renderer: function() {
|
||
|
// kweight
|
||
|
var widget = this;
|
||
|
|
||
|
widget._renderUI();
|
||
|
widget.renderUI();
|
||
|
|
||
|
widget._bindUI();
|
||
|
widget.bindUI();
|
||
|
|
||
|
widget._syncUI();
|
||
|
widget.syncUI();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Configures/Sets up listeners to bind Widget State to UI/DOM
|
||
|
*
|
||
|
* This method is not called by framework and is not chained
|
||
|
* automatically for the class hierarchy.
|
||
|
*
|
||
|
* @method bindUI
|
||
|
* @protected
|
||
|
*/
|
||
|
bindUI: EMPTY_FN,
|
||
|
|
||
|
/**
|
||
|
* Adds nodes to the DOM
|
||
|
*
|
||
|
* This method is not called by framework and is not chained
|
||
|
* automatically for the class hierarchy.
|
||
|
*
|
||
|
* @method renderUI
|
||
|
* @protected
|
||
|
*/
|
||
|
renderUI: EMPTY_FN,
|
||
|
|
||
|
/**
|
||
|
* Refreshes the rendered UI, based on Widget State
|
||
|
*
|
||
|
* This method is not called by framework and is not chained
|
||
|
* automatically for the class hierarchy.
|
||
|
*
|
||
|
* @method syncUI
|
||
|
* @protected
|
||
|
*
|
||
|
*/
|
||
|
syncUI: EMPTY_FN,
|
||
|
|
||
|
/**
|
||
|
* @method hide
|
||
|
* @description Hides the Widget by setting the "visible" attribute to "false".
|
||
|
* @chainable
|
||
|
*/
|
||
|
hide: function() {
|
||
|
return this.set(VISIBLE, FALSE);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @method show
|
||
|
* @description Shows the Widget by setting the "visible" attribute to "true".
|
||
|
* @chainable
|
||
|
*/
|
||
|
show: function() {
|
||
|
return this.set(VISIBLE, TRUE);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @method focus
|
||
|
* @description Causes the Widget to receive the focus by setting the "focused"
|
||
|
* attribute to "true".
|
||
|
* @chainable
|
||
|
*/
|
||
|
focus: function () {
|
||
|
return this._set(FOCUSED, TRUE);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @method blur
|
||
|
* @description Causes the Widget to lose focus by setting the "focused" attribute
|
||
|
* to "false"
|
||
|
* @chainable
|
||
|
*/
|
||
|
blur: function () {
|
||
|
return this._set(FOCUSED, FALSE);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @method enable
|
||
|
* @description Set the Widget's "disabled" attribute to "false".
|
||
|
* @chainable
|
||
|
*/
|
||
|
enable: function() {
|
||
|
return this.set(DISABLED, FALSE);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @method disable
|
||
|
* @description Set the Widget's "disabled" attribute to "true".
|
||
|
* @chainable
|
||
|
*/
|
||
|
disable: function() {
|
||
|
return this.set(DISABLED, TRUE);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @method _uiSizeCB
|
||
|
* @protected
|
||
|
* @param {boolean} expand
|
||
|
*/
|
||
|
_uiSizeCB : function(expand) {
|
||
|
this.get(CONTENT_BOX).toggleClass(_getWidgetClassName(CONTENT, "expanded"), expand);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Helper method to collect the boundingBox and contentBox and append to the provided parentNode, if not
|
||
|
* already a child. The owner document of the boundingBox, or the owner document of the contentBox will be used
|
||
|
* as the document into which the Widget is rendered if a parentNode is node is not provided. If both the boundingBox and
|
||
|
* the contentBox are not currently in the document, and no parentNode is provided, the widget will be rendered
|
||
|
* to the current document's body.
|
||
|
*
|
||
|
* @method _renderBox
|
||
|
* @private
|
||
|
* @param {Node} parentNode The parentNode to render the widget to. If not provided, and both the boundingBox and
|
||
|
* the contentBox are not currently in the document, the widget will be rendered to the current document's body.
|
||
|
*/
|
||
|
_renderBox: function(parentNode) {
|
||
|
|
||
|
// TODO: Performance Optimization [ More effective algo to reduce Node refs, compares, replaces? ]
|
||
|
|
||
|
var widget = this, // kweight
|
||
|
contentBox = widget.get(CONTENT_BOX),
|
||
|
boundingBox = widget.get(BOUNDING_BOX),
|
||
|
srcNode = widget.get(SRC_NODE),
|
||
|
defParentNode = widget.DEF_PARENT_NODE,
|
||
|
|
||
|
doc = (srcNode && srcNode.get(OWNER_DOCUMENT)) || boundingBox.get(OWNER_DOCUMENT) || contentBox.get(OWNER_DOCUMENT);
|
||
|
|
||
|
// If srcNode (assume it's always in doc), have contentBox take its place (widget render responsible for re-use of srcNode contents)
|
||
|
if (srcNode && !srcNode.compareTo(contentBox) && !contentBox.inDoc(doc)) {
|
||
|
srcNode.replace(contentBox);
|
||
|
}
|
||
|
|
||
|
if (!boundingBox.compareTo(contentBox.get(PARENT_NODE)) && !boundingBox.compareTo(contentBox)) {
|
||
|
// If contentBox box is already in the document, have boundingBox box take it's place
|
||
|
if (contentBox.inDoc(doc)) {
|
||
|
contentBox.replace(boundingBox);
|
||
|
}
|
||
|
boundingBox.appendChild(contentBox);
|
||
|
}
|
||
|
|
||
|
parentNode = parentNode || (defParentNode && Node.one(defParentNode));
|
||
|
|
||
|
if (parentNode) {
|
||
|
parentNode.appendChild(boundingBox);
|
||
|
} else if (!boundingBox.inDoc(doc)) {
|
||
|
Node.one(BODY).insert(boundingBox, 0);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Setter for the boundingBox attribute
|
||
|
*
|
||
|
* @method _setBB
|
||
|
* @private
|
||
|
* @param {Node|String} node
|
||
|
* @return Node
|
||
|
*/
|
||
|
_setBB: function(node) {
|
||
|
return this._setBox(this.get(ID), node, this.BOUNDING_TEMPLATE, true);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Setter for the contentBox attribute
|
||
|
*
|
||
|
* @method _setCB
|
||
|
* @private
|
||
|
* @param {Node|String} node
|
||
|
* @return Node
|
||
|
*/
|
||
|
_setCB: function(node) {
|
||
|
return (this.CONTENT_TEMPLATE === null) ? this.get(BOUNDING_BOX) : this._setBox(null, node, this.CONTENT_TEMPLATE, false);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Returns the default value for the boundingBox attribute.
|
||
|
*
|
||
|
* For the Widget class, this will most commonly be null (resulting in a new
|
||
|
* boundingBox node instance being created), unless a srcNode was provided
|
||
|
* and CONTENT_TEMPLATE is null, in which case it will be srcNode.
|
||
|
* This behavior was introduced in 3.17.2 to accomodate single-box widgets
|
||
|
* whose BB & CB both point to srcNode (e.g. Y.Button).
|
||
|
*
|
||
|
* @method _defaultBB
|
||
|
* @protected
|
||
|
*/
|
||
|
_defaultBB : function() {
|
||
|
var node = this.get(SRC_NODE),
|
||
|
nullCT = (this.CONTENT_TEMPLATE === null);
|
||
|
|
||
|
return ((node && nullCT) ? node : null);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Returns the default value for the contentBox attribute.
|
||
|
*
|
||
|
* For the Widget class, this will be the srcNode if provided, otherwise null (resulting in
|
||
|
* a new contentBox node instance being created)
|
||
|
*
|
||
|
* @method _defaultCB
|
||
|
* @protected
|
||
|
*/
|
||
|
_defaultCB : function(node) {
|
||
|
return this.get(SRC_NODE) || null;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Helper method to set the bounding/content box, or create it from
|
||
|
* the provided template if not found.
|
||
|
*
|
||
|
* @method _setBox
|
||
|
* @private
|
||
|
*
|
||
|
* @param {String} id The node's id attribute
|
||
|
* @param {Node|String} node The node reference
|
||
|
* @param {String} template HTML string template for the node
|
||
|
* @param {boolean} isBounding true if this is the boundingBox, false if it's the contentBox
|
||
|
* @return {Node} The node
|
||
|
*/
|
||
|
_setBox : function(id, node, template, isBounding) {
|
||
|
|
||
|
node = Node.one(node);
|
||
|
|
||
|
if (!node) {
|
||
|
node = Node.create(template);
|
||
|
|
||
|
if (isBounding) {
|
||
|
this._bbFromTemplate = true;
|
||
|
} else {
|
||
|
this._cbFromTemplate = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!node.get(ID)) {
|
||
|
node.set(ID, id || Y.guid());
|
||
|
}
|
||
|
|
||
|
return node;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Initializes the UI state for the Widget's bounding/content boxes.
|
||
|
*
|
||
|
* @method _renderUI
|
||
|
* @protected
|
||
|
*/
|
||
|
_renderUI: function() {
|
||
|
this._renderBoxClassNames();
|
||
|
this._renderBox(this._parentNode);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Applies standard class names to the boundingBox and contentBox
|
||
|
*
|
||
|
* @method _renderBoxClassNames
|
||
|
* @protected
|
||
|
*/
|
||
|
_renderBoxClassNames : function() {
|
||
|
var classes = this._getClasses(),
|
||
|
cl,
|
||
|
boundingBox = this.get(BOUNDING_BOX),
|
||
|
i;
|
||
|
|
||
|
boundingBox.addClass(_getWidgetClassName());
|
||
|
|
||
|
// Start from Widget Sub Class
|
||
|
for (i = classes.length-3; i >= 0; i--) {
|
||
|
cl = classes[i];
|
||
|
boundingBox.addClass(cl.CSS_PREFIX || _getClassName(cl.NAME.toLowerCase()));
|
||
|
}
|
||
|
|
||
|
// Use instance based name for content box
|
||
|
this.get(CONTENT_BOX).addClass(this.getClassName(CONTENT));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Removes class names representative of the widget's loading state from
|
||
|
* the boundingBox.
|
||
|
*
|
||
|
* @method _removeLoadingClassNames
|
||
|
* @protected
|
||
|
*/
|
||
|
_removeLoadingClassNames: function () {
|
||
|
|
||
|
var boundingBox = this.get(BOUNDING_BOX),
|
||
|
contentBox = this.get(CONTENT_BOX),
|
||
|
instClass = this.getClassName(LOADING),
|
||
|
widgetClass = _getWidgetClassName(LOADING);
|
||
|
|
||
|
boundingBox.removeClass(widgetClass)
|
||
|
.removeClass(instClass);
|
||
|
|
||
|
contentBox.removeClass(widgetClass)
|
||
|
.removeClass(instClass);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets up DOM and CustomEvent listeners for the widget.
|
||
|
*
|
||
|
* @method _bindUI
|
||
|
* @protected
|
||
|
*/
|
||
|
_bindUI: function() {
|
||
|
this._bindAttrUI(this._UI_ATTRS.BIND);
|
||
|
this._bindDOM();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @method _unbindUI
|
||
|
* @protected
|
||
|
*/
|
||
|
_unbindUI : function(boundingBox) {
|
||
|
this._unbindDOM(boundingBox);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets up DOM listeners, on elements rendered by the widget.
|
||
|
*
|
||
|
* @method _bindDOM
|
||
|
* @protected
|
||
|
*/
|
||
|
_bindDOM : function() {
|
||
|
var oDocument = this.get(BOUNDING_BOX).get(OWNER_DOCUMENT),
|
||
|
focusHandle = Widget._hDocFocus;
|
||
|
|
||
|
// Shared listener across all Widgets.
|
||
|
if (!focusHandle) {
|
||
|
focusHandle = Widget._hDocFocus = oDocument.on("focus", this._onDocFocus, this);
|
||
|
focusHandle.listeners = {
|
||
|
count: 0
|
||
|
};
|
||
|
}
|
||
|
|
||
|
focusHandle.listeners[Y.stamp(this, true)] = true;
|
||
|
focusHandle.listeners.count++;
|
||
|
|
||
|
// Fix for Webkit:
|
||
|
// Document doesn't receive focus in Webkit when the user mouses
|
||
|
// down on it, so the "focused" attribute won't get set to the
|
||
|
// correct value. Keeping this instance based for now, potential better performance.
|
||
|
// Otherwise we'll end up looking up widgets from the DOM on every mousedown.
|
||
|
if (WEBKIT){
|
||
|
this._hDocMouseDown = oDocument.on("mousedown", this._onDocMouseDown, this);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @method _unbindDOM
|
||
|
* @protected
|
||
|
*/
|
||
|
_unbindDOM : function(boundingBox) {
|
||
|
|
||
|
var focusHandle = Widget._hDocFocus,
|
||
|
yuid = Y.stamp(this, true),
|
||
|
focusListeners,
|
||
|
mouseHandle = this._hDocMouseDown;
|
||
|
|
||
|
if (focusHandle) {
|
||
|
|
||
|
focusListeners = focusHandle.listeners;
|
||
|
|
||
|
if (focusListeners[yuid]) {
|
||
|
delete focusListeners[yuid];
|
||
|
focusListeners.count--;
|
||
|
}
|
||
|
|
||
|
if (focusListeners.count === 0) {
|
||
|
focusHandle.detach();
|
||
|
Widget._hDocFocus = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (WEBKIT && mouseHandle) {
|
||
|
mouseHandle.detach();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Updates the widget UI to reflect the attribute state.
|
||
|
*
|
||
|
* @method _syncUI
|
||
|
* @protected
|
||
|
*/
|
||
|
_syncUI: function() {
|
||
|
this._syncAttrUI(this._UI_ATTRS.SYNC);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets the height on the widget's bounding box element
|
||
|
*
|
||
|
* @method _uiSetHeight
|
||
|
* @protected
|
||
|
* @param {String | Number} val
|
||
|
*/
|
||
|
_uiSetHeight: function(val) {
|
||
|
this._uiSetDim(HEIGHT, val);
|
||
|
this._uiSizeCB((val !== EMPTY_STR && val !== AUTO));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets the width on the widget's bounding box element
|
||
|
*
|
||
|
* @method _uiSetWidth
|
||
|
* @protected
|
||
|
* @param {String | Number} val
|
||
|
*/
|
||
|
_uiSetWidth: function(val) {
|
||
|
this._uiSetDim(WIDTH, val);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @method _uiSetDim
|
||
|
* @private
|
||
|
* @param {String} dim The dimension - "width" or "height"
|
||
|
* @param {Number | String} val The value to set
|
||
|
*/
|
||
|
_uiSetDim: function(dimension, val) {
|
||
|
this.get(BOUNDING_BOX).setStyle(dimension, L.isNumber(val) ? val + this.DEF_UNIT : val);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets the visible state for the UI
|
||
|
*
|
||
|
* @method _uiSetVisible
|
||
|
* @protected
|
||
|
* @param {boolean} val
|
||
|
*/
|
||
|
_uiSetVisible: function(val) {
|
||
|
this.get(BOUNDING_BOX).toggleClass(this.getClassName(HIDDEN), !val);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets the disabled state for the UI
|
||
|
*
|
||
|
* @method _uiSetDisabled
|
||
|
* @protected
|
||
|
* @param {boolean} val
|
||
|
*/
|
||
|
_uiSetDisabled: function(val) {
|
||
|
this.get(BOUNDING_BOX).toggleClass(this.getClassName(DISABLED), val);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets the focused state for the UI
|
||
|
*
|
||
|
* @method _uiSetFocused
|
||
|
* @protected
|
||
|
* @param {boolean} val
|
||
|
* @param {string} src String representing the source that triggered an update to
|
||
|
* the UI.
|
||
|
*/
|
||
|
_uiSetFocused: function(val, src) {
|
||
|
var boundingBox = this.get(BOUNDING_BOX);
|
||
|
boundingBox.toggleClass(this.getClassName(FOCUSED), val);
|
||
|
|
||
|
if (src !== UI) {
|
||
|
if (val) {
|
||
|
boundingBox.focus();
|
||
|
} else {
|
||
|
boundingBox.blur();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Set the tabIndex on the widget's rendered UI
|
||
|
*
|
||
|
* @method _uiSetTabIndex
|
||
|
* @protected
|
||
|
* @param Number
|
||
|
*/
|
||
|
_uiSetTabIndex: function(index) {
|
||
|
var boundingBox = this.get(BOUNDING_BOX);
|
||
|
|
||
|
if (L.isNumber(index)) {
|
||
|
boundingBox.set(TAB_INDEX, index);
|
||
|
} else {
|
||
|
boundingBox.removeAttribute(TAB_INDEX);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @method _onDocMouseDown
|
||
|
* @description "mousedown" event handler for the owner document of the
|
||
|
* widget's bounding box.
|
||
|
* @protected
|
||
|
* @param {EventFacade} evt The event facade for the DOM focus event
|
||
|
*/
|
||
|
_onDocMouseDown: function (evt) {
|
||
|
if (this._domFocus) {
|
||
|
this._onDocFocus(evt);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* DOM focus event handler, used to sync the state of the Widget with the DOM
|
||
|
*
|
||
|
* @method _onDocFocus
|
||
|
* @protected
|
||
|
* @param {EventFacade} evt The event facade for the DOM focus event
|
||
|
*/
|
||
|
_onDocFocus: function (evt) {
|
||
|
var widget = Widget.getByNode(evt.target),
|
||
|
activeWidget = Widget._active;
|
||
|
|
||
|
if (activeWidget && (activeWidget !== widget)) {
|
||
|
activeWidget._domFocus = false;
|
||
|
activeWidget._set(FOCUSED, false, {src:UI});
|
||
|
|
||
|
Widget._active = null;
|
||
|
}
|
||
|
|
||
|
if (widget) {
|
||
|
widget._domFocus = true;
|
||
|
widget._set(FOCUSED, true, {src:UI});
|
||
|
|
||
|
Widget._active = widget;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Generic toString implementation for all widgets.
|
||
|
*
|
||
|
* @method toString
|
||
|
* @return {String} The default string value for the widget [ displays the NAME of the instance, and the unique id ]
|
||
|
*/
|
||
|
toString: function() {
|
||
|
// Using deprecated name prop for kweight squeeze.
|
||
|
return this.name + "[" + this.get(ID) + "]";
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Default unit to use for dimension values
|
||
|
*
|
||
|
* @property DEF_UNIT
|
||
|
* @type String
|
||
|
*/
|
||
|
DEF_UNIT : "px",
|
||
|
|
||
|
/**
|
||
|
* Default node to render the bounding box to. If not set,
|
||
|
* will default to the current document body.
|
||
|
*
|
||
|
* @property DEF_PARENT_NODE
|
||
|
* @type String | Node
|
||
|
*/
|
||
|
DEF_PARENT_NODE : null,
|
||
|
|
||
|
/**
|
||
|
* Property defining the markup template for content box. If your Widget doesn't
|
||
|
* need the dual boundingBox/contentBox structure, set CONTENT_TEMPLATE to null,
|
||
|
* and contentBox and boundingBox will both point to the same Node.
|
||
|
*
|
||
|
* @property CONTENT_TEMPLATE
|
||
|
* @type String
|
||
|
*/
|
||
|
CONTENT_TEMPLATE : DIV,
|
||
|
|
||
|
/**
|
||
|
* Property defining the markup template for bounding box.
|
||
|
*
|
||
|
* @property BOUNDING_TEMPLATE
|
||
|
* @type String
|
||
|
*/
|
||
|
BOUNDING_TEMPLATE : DIV,
|
||
|
|
||
|
/**
|
||
|
* @method _guid
|
||
|
* @protected
|
||
|
*/
|
||
|
_guid : function() {
|
||
|
return Y.guid();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @method _validTabIndex
|
||
|
* @protected
|
||
|
* @param {Number} tabIndex
|
||
|
*/
|
||
|
_validTabIndex : function (tabIndex) {
|
||
|
return (L.isNumber(tabIndex) || L.isNull(tabIndex));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Binds after listeners for the list of attributes provided
|
||
|
*
|
||
|
* @method _bindAttrUI
|
||
|
* @private
|
||
|
* @param {Array} attrs
|
||
|
*/
|
||
|
_bindAttrUI : function(attrs) {
|
||
|
var i,
|
||
|
l = attrs.length;
|
||
|
|
||
|
for (i = 0; i < l; i++) {
|
||
|
this.after(attrs[i] + CHANGE, this._setAttrUI);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Invokes the _uiSet=ATTR NAME> method for the list of attributes provided
|
||
|
*
|
||
|
* @method _syncAttrUI
|
||
|
* @private
|
||
|
* @param {Array} attrs
|
||
|
*/
|
||
|
_syncAttrUI : function(attrs) {
|
||
|
var i, l = attrs.length, attr;
|
||
|
for (i = 0; i < l; i++) {
|
||
|
attr = attrs[i];
|
||
|
this[_UISET + _toInitialCap(attr)](this.get(attr));
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @method _setAttrUI
|
||
|
* @private
|
||
|
* @param {EventFacade} e
|
||
|
*/
|
||
|
_setAttrUI : function(e) {
|
||
|
if (e.target === this) {
|
||
|
this[_UISET + _toInitialCap(e.attrName)](e.newVal, e.src);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* The default setter for the strings attribute. Merges partial sets
|
||
|
* into the full string set, to allow users to partial sets of strings
|
||
|
*
|
||
|
* @method _strSetter
|
||
|
* @protected
|
||
|
* @param {Object} strings
|
||
|
* @return {String} The full set of strings to set
|
||
|
*/
|
||
|
_strSetter : function(strings) {
|
||
|
return Y.merge(this.get(STRINGS), strings);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Helper method to get a specific string value
|
||
|
*
|
||
|
* @deprecated Used by deprecated WidgetLocale implementations.
|
||
|
* @method getString
|
||
|
* @param {String} key
|
||
|
* @return {String} The string
|
||
|
*/
|
||
|
getString : function(key) {
|
||
|
return this.get(STRINGS)[key];
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Helper method to get the complete set of strings for the widget
|
||
|
*
|
||
|
* @deprecated Used by deprecated WidgetLocale implementations.
|
||
|
* @method getStrings
|
||
|
* @param {String} key
|
||
|
* @return {String} The strings
|
||
|
*/
|
||
|
getStrings : function() {
|
||
|
return this.get(STRINGS);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* The lists of UI attributes to bind and sync for widget's _bindUI and _syncUI implementations
|
||
|
*
|
||
|
* @property _UI_ATTRS
|
||
|
* @type Object
|
||
|
* @private
|
||
|
*/
|
||
|
_UI_ATTRS : {
|
||
|
BIND: UI_ATTRS,
|
||
|
SYNC: UI_ATTRS
|
||
|
}
|
||
|
});
|
||
|
|
||
|
Y.Widget = Widget;
|
||
|
|
||
|
|
||
|
}, '3.17.2', {
|
||
|
"requires": [
|
||
|
"attribute",
|
||
|
"base-base",
|
||
|
"base-pluginhost",
|
||
|
"classnamemanager",
|
||
|
"event-focus",
|
||
|
"node-base",
|
||
|
"node-style"
|
||
|
],
|
||
|
"skinnable": true
|
||
|
});
|