raptor
Version:
RaptorJS provides an AMD module loader that works in Node, Rhino and the web browser. It also includes various sub-modules to support building optimized web applications.
387 lines (339 loc) • 14.4 kB
JavaScript
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Mixins applied to the prototypes of all widget instances
* @mixin
*
* @borrows raptor/listeners/Observable#publish as #publish
* @borrows raptor/listeners/Observable#subscribe as #subscribe
*/
define(
'raptor/widgets/Widget',
['raptor'],
function(raptor, require) {
"use strict";
var listeners = require('raptor/listeners'),
dom = require('raptor/dom'),
_destroy = function(widget, removeNode, recursive) {
var message = {
widget: widget
},
rootEl = widget.getEl(),
widgets = require('raptor/widgets'),
assignedId = widget._assignedId;
widget.publish('beforeDestroy', message);
widget.__destroyed = true;
if (rootEl) {
if (recursive) {
var walkDOM = function(el) {
dom.forEachChildEl(el, function(childEl) {
if (childEl.id) {
var descendentWidget = widgets.get(childEl.id);
if (descendentWidget) {
_destroy(descendentWidget, false, false);
}
}
walkDOM(childEl);
});
};
walkDOM(rootEl);
}
if (removeNode && rootEl.parentNode) {
//Remove the widget's DOM nodes from the DOM tree if the root element is known
rootEl.parentNode.removeChild(rootEl);
}
}
widgets._remove(widget._id);
if (assignedId) {
var scopeWidget = widgets.get(widget._scope);
if (scopeWidget) {
scopeWidget.widgets._remove(widget, assignedId);
}
}
widget.publish('destroy', message);
// Have the widget unsubscribe from any messages that is currently subscribed to
// Unsubscribe all messages after publishing "destroy" otherwise the widget might not get that event
listeners.unsubscribeFromAll(widget);
},
widgetProto;
var Widget = function() {
};
Widget.makeWidget = function(widget, proto) {
if (!widget._isWidget) {
for (var k in widgetProto) {
if (!proto.hasOwnProperty(k)) {
proto[k] = widgetProto[k];
}
}
}
};
Widget.prototype = widgetProto = {
/**
*
*/
_isWidget: true,
/**
*
* @returns
*/
getObservable: function() {
return this._observable || (this._observable = listeners.createObservable());
},
/**
*
* @param allowedMessages
* @param createFuncs
* @returns
*/
registerMessages: function(allowedMessages, createFuncs) {
this.getObservable().registerMessages.apply(this, arguments);
},
/**
*
* @param message
* @param props
* @returns
*/
publish: function(message, props) {
var ob = this.getObservable();
ob.publish.apply(ob, arguments);
var pubsubEvent;
if (this._events && (pubsubEvent = this._events[message])) {
if (pubsubEvent.props) {
props = raptor.extend(props || {}, pubsubEvent.props);
}
require('raptor/pubsub').publish(pubsubEvent.target, props);
}
},
/**
*
* @param message
* @param callback
* @param thisObj
* @returns
*/
subscribe: function(message, callback, thisObj) {
var ob = this.getObservable();
return ob.subscribe.apply(ob, arguments);
},
/**
* Returns the DOM element ID corresponding to the provided
* widget element ID.
*
* @param {string} widgetElId The widget element ID.
* @returns {string} The DOM element ID corresponding tothe provided widget element ID
*/
getElId: function(widgetElId) {
return widgetElId ? this._id + "-" + widgetElId : this._id;
},
/**
* Returns a raw DOM element for the given widget element ID. If no
* widget element ID is provided then the root DOM node that the widget is bound to is returned.
* @param widgetElId
* @returns {DOMElement} The DOM element
*/
getEl: function(widgetElId) {
if (arguments.length === 1) {
return document.getElementById(this.getElId(widgetElId));
} else {
return this.el || document.getElementById(this.getElId());
}
},
/**
*
* Returns a single nested widget instance with the specified ID.
*
* NOTE: If multiple nested widgets exist with the specified ID then
* an exception will be thrown.
*
* @param nestedWidgetId
* @returns {object} The child instance widget or null if one is not found.
*/
getWidget: function(nestedWidgetId) {
return this.widgets.getWidget(nestedWidgetId);
},
/**
* Returns an array of nested widgets with the specified widget ID.
* @param nestedWidgetId
* @returns {array} An array of nested widgets (or an empty array if none are found)
*/
getWidgets: function(nestedWidgetId) {
return this.widgets.getWidgets(nestedWidgetId);
},
/**
* Destroys a widget.
*
* If the root element is specified for the widget then the widget will
* be removed from the DOM. In addition, all of the descendent widgets
* will be destroyed as well.
*
* The "beforeDestroy" message will be published by the widget before
* the widget is actually destroyed.
*
* The "destroy" message will be published after the widget
* has been destroyed.
*
* NOTE: The widget will automatically be unsubscribed from all messages
* that it has subscribed to.
*
*/
destroy: function(options) {
options = options || {};
_destroy(this, options.removeNode !== false, options.recursive !== false);
},
/**
* Returns true if this widget has been destroyed.
*
* A widget is considered destroyed if the "destroy" method
* was invoked on the widget or one of its ancestor widgets.
*
* @returns {boolean} True if this widget has been destroyed. False, otherwise.
*/
isDestroyed: function() {
return this.__destroyed;
},
/**
* This function will return the root element but unlike "getEl()" function, it will throw an error if there
* is no root element.
*/
_getRootEl : function() {
var rootEl = this.getEl();
if (!rootEl) {
throw raptor.createError(new Error("Root element missing for widget of type " + this.constructor.getName()));
}
return rootEl;
},
/**
* Re-renders a widget by replacing the widget's existing root element with
* the newly rendered HTML.
*
* <p>The widget instance is required to have a "renderer" property that defines
* the renderer to use, or, if the name ends in "Widget" then the renderer
* will be assumed to be of the name with "Widget" replaced with "Renderer"
* (e.g. "ui/buttons/Button/ButtonWidget" --> "ui/buttons/Button/ButtonRenderer")
*
* @param {Object} data The data to use as input to the renderer
* @param {raptor/render-context/Context} The render context (optional)
*
* @return {raptor/renderer/RenderResult} Returns the resulting of re-rendering the component
*/
rerender: function(data, context) {
var renderer = this.renderer,
type = this.constructor.getName(),
componentRenderer = require('raptor/renderer'),
rootEl = this._getRootEl();
if (!renderer) {
if (this.constructor.render) {
renderer = this.constructor;
}
else {
if (type.endsWith("Widget")) {
renderer = require.find(type.slice(0, -6) + "Renderer");
}
}
}
if (!renderer) {
throw raptor.createError(new Error("Renderer not found for widget " + type));
}
return componentRenderer.render(renderer, data, context).replace(rootEl);
},
/**
* This method removes the widget's root element from the DOM and saves a reference to
* it so that the widget can be re-attached to the DOM later.
*
* After detaching widget from DOM, use one of the following methods to re-attach:
* - appendTo
* - replace
* - replaceChildrenOf
* - insertBefore
* - insertAfter
* - prependTo
*
* @throws Error if widget does not have a root element
*/
detach: function() {
dom.detach(this._getRootEl());
},
/**
* Appends the widget's root element as a child of the target element.
*
* @param {DOMElement|String} targetEl The target element
* @return {void}
*/
appendTo: function(targetEl) {
dom.appendTo(this._getRootEl(), targetEl);
},
/**
* Replaces the target element with the widget's root element.
*
* @param {DOMElement|String} targetEl The target element
* @return {void}
*/
replace: function(targetEl) {
dom.replace(this._getRootEl(), targetEl);
},
/**
* Replaces the children of target element with the widget's root element.
*
* @param {DOMElement|String} targetEl The target element
* @return {void}
*/
replaceChildrenOf: function(targetEl) {
dom.replaceChildrenOf(this._getRootEl(), targetEl);
},
/**
* Inserts the widget's root element before the target element (as a sibling).
*
* @param {DOMElement|String} targetEl The target element
* @return {void}
*/
insertBefore: function(targetEl) {
dom.insertBefore(this._getRootEl(), targetEl);
},
/**
* Inserts the widget's root element after the target element (as a sibling).
*
* @param {DOMElement|String} targetEl The target element
* @return {void}
*/
insertAfter: function(targetEl) {
dom.insertAfter(this._getRootEl(), targetEl);
},
/**
* Prepends the widget's root element as a child of the target element.
*
* @param {DOMElement|String} targetEl The target element
* @return {void}
*/
prependTo: function(targetEl) {
dom.prependTo(this._getRootEl(), targetEl);
}
/**
* Subscribes to one or more events.
*
* This method is a synonym for the {@Link raptor/widgets/Widget.subscribe} method
* to maintain backwards compatibility.
* <b>This method will be removed in the future.</b>
*
* @function
* @name on
* @memberOf raptor/widgets/Widget
*/
};
widgetProto.on = widgetProto.subscribe;
widgetProto.elId = widgetProto.getElId;
return Widget;
});