StructureJS
0.14.1A class based utility library for building modular and scalable web platform applications. Features opt-in classes and utilities which provide a solid foundation and toolset to build your next project.
var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; (function (factory) { if (typeof module === 'object' && typeof module.exports === 'object') { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === 'function' && define.amd) { define(["require", "exports", './DisplayObjectContainer', '../event/BaseEvent', '../util/TemplateFactory', '../util/ComponentFactory', '../plugin/jquery.eventListener'], factory); } })(function (require, exports) { "use strict"; var DisplayObjectContainer_1 = require('./DisplayObjectContainer'); var BaseEvent_1 = require('../event/BaseEvent'); var TemplateFactory_1 = require('../util/TemplateFactory'); var ComponentFactory_1 = require('../util/ComponentFactory'); var jquery_eventListener_1 = require('../plugin/jquery.eventListener'); /** * The {{#crossLink "DOMElement"}}{{/crossLink}} class is the base view class for all objects that can be placed into the HTML DOM. * * @class DOMElement * @param type [any=null] Either a jQuery object or JavaScript template string reference you want to use as the view. Check out the examples below. * @param params [any=null] Any data you would like to pass into the jQuery element or template that is being created. * @extends DisplayObjectContainer * @module StructureJS * @submodule view * @requires Extend * @requires DisplayObjectContainer * @requires BaseEvent * @requires TemplateFactory * @requires ComponentFactory * @requires jQuery * @constructor * @author Robert S. (www.codeBelt.com) * @example * // Example: Using DOMElement without extending it. * let aLink = new DOMElement('a', {text: 'Google', href: 'http://www.google.com', 'class': 'externalLink'}); * this.addChild(aLink); * * // Example: A view passing in a jQuery object. * let view = new CustomView($('.selector')); * this.addChild(view); * * // Example: A view extending DOMElement while passing in a jQuery object. * class ClassName extends DOMElement { * * constructor($element) { * super($element); * } * * create() { * super.create(); * * // Create and add your child objects to this parent class. * } * * enable() { * if (this.isEnabled === true) { return; } * * // Enable the child objects and add any event listeners. * * super.enable(); * } * * disable() { * if (this.isEnabled === false) { return; } * * // Disable the child objects and remove any event listeners. * * super.disable(); * } * * layout() { * // Layout or update the child objects in this parent class. * } * * destroy() { * this.disable(); * * // Destroy the child objects and references in this parent class to prevent memory leaks. * * super.destroy(); * } * * } * * // Example: A view extending DOMElement with a precompiled JavaScript template reference passed in. * class ClassName extends DOMElement { * * constructor() { * _super(); * } * * create() { * super.create('templates/home/homeTemplate', {data: 'some data'}); * * // Create and add your child objects to this parent class. * } * * enable() { * if (this.isEnabled === true) { return; } * * // Enable the child objects and add any event listeners. * * super.enable(); * } * * disable() { * if (this.isEnabled === false) { return; } * * // Disable the child objects and remove any event listeners. * * super.disable(); * } * * layout() { * // Layout or update the child objects in this parent class. * } * * destroy() { * this.disable(); * * // Destroy the child objects and references in this parent class to prepare for garbage collection. * * super.destroy(); * } * * } */ var DOMElement = (function (_super) { __extends(DOMElement, _super); function DOMElement(type, params) { if (type === void 0) { type = null; } if (params === void 0) { params = null; } _super.call(this); /** * Tracks number of times an element's width has been checked * in order to determine if the element has been added * to the DOM. * * @property checkCount * @type {number} * @public */ this.checkCount = 0; /** * A cached reference to the DOM Element * * @property element * @type {HTMLElement} * @default null * @public */ this.element = null; /** * A cached reference to the jQuery DOM element * * @property $element * @type {JQuery} * @default null * @public */ this.$element = null; /** * If a jQuery object was passed into the constructor this will be set as true and * this class will not try to add the view to the DOM since it already exists. * * @property _isReference * @type {boolean} * @protected */ this._isReference = false; /** * Holds onto the value passed into the constructor. * * @property _type * @type {string} * @default null * @protected */ this._type = null; /** * Holds onto the value passed into the constructor. * * @property _params * @type {any} * @default null * @protected */ this._params = null; if (type instanceof jquery_eventListener_1.default) { this.$element = type; this.element = this.$element[0]; this._isReference = true; } else if (type) { this._type = type; this._params = params; } } /** * The create function is intended to provide a consistent place for the creation and adding * of children to the view. It will automatically be called the first time that the view is added * to another DisplayObjectContainer. It is critical that all subclasses call the super for this function in * their overridden methods. * * This method gets called once when the child view is added to another view. If the child view is removed * and added to another view the create method will not be called again. * * @method create * @param type [string=div] The HTML tag you want to create or the id/class selector of the template or the pre-compiled path to a template. * @param params [any=null] Any data you would like to pass into the jQuery element or template that is being created. * @returns {any} Returns an instance of itself. * @public * @chainable * @example * // EXAMPLE 1: By default your view class will be a div element: * create() { * super.create(); * * this._childInstance = new DOMElement(); * this.addChild(this._childInstance); * } * * // EXAMPLE 2: But lets say you wanted the view to be a ul element: * create() { * super.create('ul'); * } * * // Then you could nest other elements inside this base view/element. * create() { * super.create('ul', {id: 'myId', 'class': 'myClass anotherClass'}); * * let li = new DOMElement('li', {text: 'Robert is cool'}); * this.addChild(li); * } * * // EXAMPLE 3: So that's cool but what if you wanted a block of html to be your view. Let's say you had the below * // inline Handlebar template in your html file. * <script id="todoTemplate" type="text/template"> * <div id="htmlTemplate" class="js-todo"> * <div id="input-wrapper"> * <input type="text" class="list-input" placeholder="{{ data.text }}"> * <input type="button" class="list-item-submit" value="Add"> * </div> * </div> * </script> * * // You would just pass in the id or class selector of the template which in this case is "#todoTemplate". * // There is a second optional argument where you can pass data for the Handlebar template to use. * create() { * super.create('#todoTemplate', { data: this.viewData }); * * } * * // EXAMPLE 4: Or maybe you're using grunt-contrib-handlebars, or similar, to precompile hbs templates * create() { * super.create('templates/HomeTemplate', {data: "some data"}); * * } */ DOMElement.prototype.create = function (type, params) { if (type === void 0) { type = 'div'; } if (params === void 0) { params = null; } // Use the data passed into the constructor first else use the arguments from create. type = this._type || type; params = this._params || params; if (this.isCreated === true) { throw new Error('[' + this.getQualifiedClassName() + '] You cannot call the create method manually. It is only called once automatically during the view lifecycle and should only be called once.'); } if (this.$element == null) { var html = TemplateFactory_1.default.create(type, params); if (html) { this.$element = jquery_eventListener_1.default(html); } else { this.$element = jquery_eventListener_1.default("<" + type + "/>", params); } } this.element = this.$element[0]; this.width = this.$element.width(); this.height = this.$element.height(); this.setSize(this.width, this.height); return this; }; /** * @overridden DisplayObjectContainer.addChild * @method addChild * @param child {DOMElement} The DOMElement instance to add as a child of this object instance. * @returns {any} Returns an instance of itself. * @chainable * @example * this.addChild(domElementInstance); */ DOMElement.prototype.addChild = function (child) { if (this.$element == null) { throw new Error('[' + this.getQualifiedClassName() + '] You cannot use the addChild method if the parent object is not added to the DOM.'); } _super.prototype.addChild.call(this, child); // If an empty jQuery object is passed into the constructor then don't run the code below. if (child._isReference === true && child.$element.length === 0) { return this; } if (child.isCreated === false) { child.create(); // Render the item before adding to the DOM child.isCreated = true; } // If the child object is not a reference of a jQuery object in the DOM then append it. if (child._isReference === false) { this.$element.append(child.$element); } this._onAddedToDom(child); return this; }; /** * Adds the sjsId to the DOM element so we can know what what Class object the HTMLElement belongs too. * * @method _addClientSideId * @param child {DOMElement} The DOMElement instance to add the sjsId too. * @protected */ DOMElement.prototype._addClientSideId = function (child) { var type = child.$element.attr('data-sjs-type'); var id = child.$element.attr('data-sjs-id'); if (type === void 0) { // Make them array's so the join method will work. type = [child.getQualifiedClassName()]; id = [child.sjsId]; } else { // Split them so we can push/add the new values. type = type.split(','); id = id.split(','); type.push(child.getQualifiedClassName()); id.push(child.sjsId); } // Updated list of id's and types child.$element.attr('data-sjs-id', id.join(',')); child.$element.attr('data-sjs-type', type.join(',')); }; /** * Removes the sjsId and class type from the HTMLElement. * * @method _removeClientSideId * @param child {DOMElement} The DOMElement instance to add the sjsId too. * @protected * @return {boolean} */ DOMElement.prototype._removeClientSideId = function (child) { var type = child.$element.attr('data-sjs-type'); var id = child.$element.attr('data-sjs-id'); // Split them so we can remove the child sjsId and type. var typeList = type.split(','); var idList = id.split(',').map(Number); // Convert each item into a number. var index = idList.indexOf(child.sjsId); if (index > -1) { // Remove the id and type from the array. typeList.splice(index, 1); idList.splice(index, 1); // Updated list of id's and types child.$element.attr('data-sjs-type', typeList.join(',')); child.$element.attr('data-sjs-id', idList.join(',')); } return idList.length === 0; }; /** * Called when the child object is added to the DOM. * The method will call {{#crossLink "DOMElement/layout:method"}}{{/crossLink}} and dispatch the BaseEvent.ADDED_TO_STAGE event. * * @method _onAddedToDom * @protected */ DOMElement.prototype._onAddedToDom = function (child) { var _this = this; child.checkCount++; if (child.$element.width() === 0 && child.checkCount < 5) { setTimeout(function () { _this._onAddedToDom(child); }, 100); return; } this._addClientSideId(child); child.width = child.$element.width(); child.height = child.$element.height(); child.setSize(child.width, child.height); child.enable(); child.layout(); child.dispatchEvent(new BaseEvent_1.default(BaseEvent_1.default.ADDED_TO_STAGE)); }; /** * @overridden DisplayObjectContainer.addChildAt */ DOMElement.prototype.addChildAt = function (child, index) { var children = this.$element.children(); var length = children.length; // If an empty jQuery object is passed into the constructor then don't run the code below. if (child._isReference === true && child.$element.length === 0) { return this; } if (index < 0 || index >= length) { // If the index passed in is less than 0 and greater than the total number of children then place the item at the end. this.addChild(child); } else { // Else get the child in the children array by the index passed in and place the item before that child. if (child.isCreated === false) { child.create(); // Render the item before adding to the DOM child.isCreated = true; } // Adds the child at a specific index but also will remove the child from another parent object if one exists. if (child.parent) { child.parent.removeChild(child, false); } this.children.splice(index, 0, child); this.numChildren = this.children.length; child.parent = this; // Adds the child before any child already added in the DOM. jquery_eventListener_1.default(children.get(index)).before(child.$element); this._onAddedToDom(child); } return this; }; /** * @overridden DisplayObjectContainer.swapChildren */ DOMElement.prototype.swapChildren = function (child1, child2) { var child1Index = child1.$element.index(); var child2Index = child2.$element.index(); this.addChildAt(child1, child2Index); this.addChildAt(child2, child1Index); return this; }; /** * @overridden DisplayObjectContainer.getChildAt */ DOMElement.prototype.getChildAt = function (index) { return _super.prototype.getChildAt.call(this, index); }; /** * Returns a DOMElement object with the first found DOM element by the passed in selector. * * @method getChild * @param selector {string} DOM id name, DOM class name or a DOM tag name. * @returns {DOMElement} * @public */ DOMElement.prototype.getChild = function (selector) { // Get the first match from the selector passed in. var jQueryElement = this.$element.find(selector).first(); if (jQueryElement.length === 0) { throw new TypeError('[' + this.getQualifiedClassName() + '] getChild(' + selector + ') Cannot find DOM $element'); } // Check to see if the element has a sjsId value and is a child of this parent object. var sjsId = parseInt(jQueryElement.attr('data-sjs-id')); var domElement = this.getChildByCid(sjsId); // Creates a DOMElement from the jQueryElement. if (domElement == null) { // Create a new DOMElement and assign the jQuery element to it. domElement = new DOMElement(); domElement.$element = jQueryElement; this._addClientSideId(domElement); domElement.element = jQueryElement[0]; domElement.isCreated = true; // Added to the super addChild method because we don't need to append the element to the DOM. // At this point it already exists and we are just getting a reference to the DOM element. _super.prototype.addChild.call(this, domElement); } return domElement; }; /** * Gets all the HTML elements children of this object. * * @method getChildren * @param [selector] {string} You can pass in any type of jQuery selector. If there is no selector passed in it will get all the children of this parent element. * @returns {Array.<DOMElement>} Returns a list of DOMElement's. It will grab all children HTML DOM elements of this object and will create a DOMElement for each DOM child. * If the 'data-sjs-id' property exists is on an HTML element a DOMElement will not be created for that element because it will be assumed it already exists as a DOMElement. * @public */ DOMElement.prototype.getChildren = function (selector) { if (selector === void 0) { selector = ''; } //TODO: Make sure the index of the children added is the same as the what is in the actual DOM. var $child; var domElement; var $list = this.$element.children(selector); var listLength = $list.length; for (var i = 0; i < listLength; i++) { $child = $list.eq(i); // If the jQuery element already has sjsId data property then it must be an existing DisplayObjectContainer (DOMElement) in the children array. if ($child.attr('data-sjs-id') === void 0) { domElement = new DOMElement(); domElement.$element = $child; this._addClientSideId(domElement); domElement.element = $child.get(0); domElement.isCreated = true; // Added to the super addChild method because we don't need to append the element to the DOM. // At this point it already exists and we are just getting a reference to the DOM element. _super.prototype.addChild.call(this, domElement); } } return this.children; }; /** * Removes the specified child object instance from the child list of the parent object instance. * The parent property of the removed child is set to null and the object is garbage collected if there are no other references * to the child. The index positions of any objects above the child in the parent object are decreased by 1. * * @method removeChild * @param child {DOMElement} The DisplayObjectContainer instance to remove. * @returns {any} Returns an instance of itself. * @override * @public * @chainable */ DOMElement.prototype.removeChild = function (child, destroy) { if (destroy === void 0) { destroy = true; } var remove = this._removeClientSideId(child); child.disable(); // Checks if destroy was called before removeChild so it doesn't error. if (remove === true && child.$element != null) { child.$element.unbind(); child.$element.remove(); } if (destroy === true) { child.destroy(); } _super.prototype.removeChild.call(this, child); return this; }; /** * Removes the child display object instance that exists at the specified index. * * @method removeChildAt * @param index {int} The index position of the child object. * @public * @chainable */ DOMElement.prototype.removeChildAt = function (index, destroy) { if (destroy === void 0) { destroy = true; } this.removeChild(this.getChildAt(index), destroy); return this; }; /** * Removes all child object instances from the child list of the parent object instance. * The parent property of the removed children is set to null and the objects are garbage collected if no other * references to the children exist. * * @method removeChildren * @returns {DOMElement} Returns an instance of itself. * @override * @public * @chainable */ DOMElement.prototype.removeChildren = function (destroy) { if (destroy === void 0) { destroy = true; } while (this.children.length > 0) { this.removeChild(this.children.pop(), destroy); } this.$element.empty(); return this; }; /** * @overridden DisplayObjectContainer.destroy */ DOMElement.prototype.destroy = function () { // Note: we can't just call destroy to remove the HTMLElement because there could be other views managing the same HTMLElement. /*if (this.$element != null) { this.$element.unbind(); this.$element.remove(); }*/ _super.prototype.destroy.call(this); }; /** * A way to instantiate view classes by found html selectors. * * Example: It will find all children elements of the {{#crossLink "DOMElement/$element:property"}}{{/crossLink}} property with the 'js-shareEmail' selector. * If any selectors are found the EmailShareComponent class will be instantiated and pass the found jQuery element into the contructor. * * @method createComponents * @param componentList (Array.<{ selector: string; component: DOMElement }> * @return {Array.<DOMElement>} Returns all the items created from this createComponents method. * @public * @chainable * @example * create() { * super.create(); * * this.createComponents([ * {selector: '.js-shareEmail', component: EmailShareComponent}, * {selector: '.js-pagination', component: PaginationComponent}, * {selector: '.js-carousel', component: CarouselComponent} * ]); * } */ DOMElement.prototype.createComponents = function (componentList) { var list; var createdChildren = []; var length = componentList.length; var obj; for (var i = 0; i < length; i++) { obj = componentList[i]; list = ComponentFactory_1.default.create(this.$element.find(obj.selector), obj.component, this); createdChildren = createdChildren.concat(list); } return createdChildren; }; /** * Only use this once per application and use used on your main application Class. * This selects HTML element that you want the application to have control over. * This method starts the lifecycle of the application. * * @method appendTo * @param type {any} A string value where your application will be appended. This can be an element id (#some-id), element class (.some-class) or a element tag (body). * @param [enabled=true] {boolean} Sets the enabled state of the object. * @example * <b>Instantiation Example</b><br> * This example illustrates how to instantiate your main application or root class. * * const app = new MainClass(); * app.appendTo('body'); * */ DOMElement.prototype.appendTo = function (type, enabled) { if (enabled === void 0) { enabled = true; } this.$element = (type instanceof jquery_eventListener_1.default) ? type : jquery_eventListener_1.default(type); this._addClientSideId(this); if (this.isCreated === false) { this.create(); this.isCreated = true; if (enabled === false) { this.disable(); } else { this.enable(); } this.layout(); } return this; }; return DOMElement; }(DisplayObjectContainer_1.default)); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = DOMElement; });