UNPKG

alloy

Version:

TiDev Titanium MVC Framework

518 lines (451 loc) 15.4 kB
var Alloy = require('/alloy'), Backbone = Alloy.Backbone, _ = Alloy._; /** * @class Alloy.Controller * @extends Backbone.Events * The base class for Alloy controllers. * * Each controller is associated with a UI hierarchy, defined in an XML file in the * `views` folder. Each element in the view hierarchy is either a Titanium {@link Titanium.UI.View View} * or another Alloy controller or widget. Each Alloy controller or widget can additionally contain * Titanium Views and/or more controllers and widgets. * */ var Controller = function() { var roots = []; var self = this; function getControllerParam() { return self.__widgetId ? { widgetId: self.__widgetId, name: self.__controllerPath } : self.__controllerPath; } this.__iamalloy = true; _.extend(this, Backbone.Events, { __views: {}, __events: [], __proxyProperties: {}, setParent: function(parent) { var len = roots.length; if (!len) { return; } if (parent.__iamalloy) { this.parent = parent.parent; } else { this.parent = parent; } for (var i = 0; i < len; i++) { if (roots[i].__iamalloy) { roots[i].setParent(this.parent); } else { this.parent.add(roots[i]); } } }, addTopLevelView: function(view) { roots.push(view); }, addProxyProperty: function(key, value) { this.__proxyProperties[key] = value; }, removeProxyProperty: function(key) { delete this.__proxyProperties[key]; }, /** * @method getTopLevelViews * Returns a list of the root view elements associated with this controller. * #### Example * The following example displays the `id` of each top-level view associated with the * controller: // index.js var views = $.getTopLevelViews(); for (each in views) { var view = views[each]; console.log(view.id); } * * * @return {Array.<(Titanium.UI.View|Alloy.Controller)>} */ getTopLevelViews: function() { return roots; }, /** * @method getView * Returns the specified view associated with this controller. * * If no `id` is specified, returns the first top-level view. * * #### Example * The following example gets a reference to a `<Window/>` object * with the `id` of "loginWin" and then calls its [open()](Titanium.UI.Window) method. var loginWindow = $.getView('loginWin'); loginWindow.open(); * * @param {String} [id] ID of the view to return. * @return {Titanium.UI.View/Alloy.Controller} */ getView: function(id) { if (typeof id === 'undefined' || id === null) { return roots[0]; } return this.__views[id]; }, removeView: function(id) { delete this[id]; delete this.__views[id]; }, getProxyProperty: function(name) { return this.__proxyProperties[name]; }, /** * @method getViews * Returns a list of all IDed view elements associated with this controller. * * #### Example * Given the following XML view: <Alloy> <TabGroup id="tabs"> <Tab title="Tab 1" icon="KS_nav_ui.png" id="tab1"> <Window title="Tab 1" id="win1"> <Label id="label1">I am Window 1</Label> </Window> </Tab> <Tab title="Tab 2" icon="KS_nav_views.png" id="tab2"> <Window title="Tab 2" id="wind2"> <Label id="label2">I am Window 2</Label> </Window> </Tab> </TabGroup> <View id="otherview"></View> </Alloy> * The following view-controller outputs the id of each view in the hierarchy. var views = $.getViews(); for (each in views) { var view = views[each]; console.log(view.id); } [INFO] : win1 [INFO] : label1 [INFO] : tab1 [INFO] : wind2 [INFO] : label2 [INFO] : tab2 [INFO] : tabs [INFO] : otherview * @return {Array.<(Titanium.UI.View|Alloy.Controller)>} */ getViews: function() { return this.__views; }, /** * @method destroy * Frees binding resources associated with this controller and its * UI components. It is critical that this is called when employing * model/collection binding in order to avoid potential memory leaks. * $.destroy() should be called whenever a controller's UI is to * be "closed" or removed from the app. See the [Destroying Data Bindings](#!/guide/Destroying_Data_Bindings) * test application for an example of this approach. * #### Example * In the following example the view-controller for a {@link Titanium.UI.Window Window} object named `dialog` * calls its `destroy()` method in response to the Window object being closed. $.dialog.addEventListener('close', function() { $.destroy(); }); */ destroy: function() { // destroy() is defined during the compile process based on // the UI components and binding contained within the controller. }, // getViewEx for advanced parsing and element traversal getViewEx: function(opts) { var recurse = opts.recurse || false; if (recurse) { var view = this.getView(); if (!view) { return null; } else if (view.__iamalloy) { return view.getViewEx({ recurse: true }); } else { return view; } } else { return this.getView(); } }, // getProxyPropertyEx for advanced parsing and element traversal getProxyPropertyEx: function(name, opts) { var recurse = opts.recurse || false; if (recurse) { var view = this.getProxyProperty(name); if (!view) { return null; } else if (view.__iamalloy) { return view.getProxyProperty(name, { recurse: true }); } else { return view; } } else { return this.getView(name); } }, /** * @method createStyle * Creates a dictionary of properties based on the specified styles. * * * You can use this dictionary with the view object's * {@link Titanium.UI.View#method-applyProperties applyProperties} method * or a create object method, such as {@link Titanium.UI#method-createView Titanium.UI.createView}. * #### Examples * The following creates a new style object that is passed as a parameter * to the {@link Titanium.UI#method-createLabel Ti.UI.createLabel()} method. var styleArgs = { apiName: 'Ti.UI.Label', classes: ['blue','shadow','large'], id: 'tester', borderWidth: 2, borderRadius: 16, borderColor: '#000' }; var styleObject = $.createStyle(styleArgs); testLabel = Ti.UI.createLabel(styleObject); * The next example uses the {@link Titanium#method-applyProperties applyProperties()} method * to apply a style object to an existing Button control (button not shown). var style = $.createStyle({ classes: args.button, apiName: 'Button', color: 'blue' }); $.button.applyProperties(style); * @param {AlloyStyleDict} opts Dictionary of styles to apply. * * @return {Dictionary} * @since 1.2.0 */ createStyle: function(opts) { return Alloy.createStyle(getControllerParam(), opts); }, /* * Documented in docs/apidoc/controller.js */ UI: { create: function(apiName, opts) { return Alloy.UI.create(getControllerParam(), apiName, opts); } }, /** * @method addClass * Adds a TSS class to the specified view object. * * You can apply additional styles with the `opts` parameter. To use this method * effectively you may need to enable autostyling * on the target XML view. See [Autostyle](#!/guide/Dynamic_Styles-section-37530415_DynamicStyles-Autostyle) * in the Alloy developer guide. * #### Example * The following adds the TSS classes ".redbg" and ".bigger" to a {@link Titanium.UI.Label} * object proxy `label1`, and also sets the label's `text` property to "Cancel". // index.js $.addClass($.label1, 'redbg bigger', {text: "Cancel"}); The 'redbg' and 'bigger' classes are shown below: // index.tss ".redbg" : { color: 'red' } ".bigger": { font : { fontSize: '36' } } * @param {Object} proxy View object to which to add class(es). * @param {Array<String>/String} classes Array or space-separated list of classes to apply. * @param {Dictionary} [opts] Dictionary of properties to apply after classes have been added. * @since 1.2.0 */ addClass: function(proxy, classes, opts) { return Alloy.addClass(getControllerParam(), proxy, classes, opts); }, /** * @method removeClass * Removes a TSS class from the specified view object. * * You can apply additional styles after the removal with the `opts` parameter. * To use this method effectively you may need to enable autostyling * on the target XML view. See [Autostyle](#!/guide/Dynamic_Styles-section-37530415_DynamicStyles-Autostyle) * in the Alloy developer guide. * #### Example * The following removes the "redbg" and "bigger" TSS classes from a {@link Titanium.UI.Label} * object proxy `label1`, and also sets the label's `text` property to "...". $.removeClass($.label1, 'redbg bigger', {text: "..."}); * @param {Object} proxy View object from which to remove class(es). * @param {Array<String>/String} classes Array or space-separated list of classes to remove. * @param {Dictionary} [opts] Dictionary of properties to apply after the class removal. * @since 1.2.0 */ removeClass: function(proxy, classes, opts) { return Alloy.removeClass(getControllerParam(), proxy, classes, opts); }, /** * @method resetClass * Sets the array of TSS classes for the target View object, adding the classes specified and * removing any applied classes that are not specified. * * You can apply classes or styles after the reset using the `classes` or `opts` parameters. * To use this method effectively you may need to enable autostyling * on the target XML view. See [Autostyle](#!/guide/Dynamic_Styles-section-37530415_DynamicStyles-Autostyle) * in the Alloy developer guide. * #### Example * The following removes all previously applied styles on `label1` and then applies * the TSS class 'no-style'. $.resetClass($.label1, 'no-style'); * @param {Object} proxy View object to reset. * @param {Array<String>/String} [classes] Array or space-separated list of classes to apply after the reset. * @param {Dictionary} [opts] Dictionary of properties to apply after the reset. * @since 1.2.0 */ resetClass: function(proxy, classes, opts) { return Alloy.resetClass(getControllerParam(), proxy, classes, opts); }, /** * @method updateViews * Applies a set of properties to view elements associated with this controller. * This method is useful for setting properties on repeated elements such as * {@link Titanium.UI.TableViewRow TableViewRow} objects, rather than needing to have a controller * for those child controllers. * #### Example * The following example uses this method to update a Label inside a TableViewRow object * before adding it to a TableView. * View-controller file: controllers/index.js for (var i=0; i < 10; i++) { var row = Alloy.createController("tablerow"); row.updateViews({ "#theLabel": { text: "I am row #" + i } }); $.tableView.appendRow(row.getView()); }; * XML view: views/tablerow.xml <Alloy> <TableViewRow> <Label id="theLabel"></Label> </TableViewRow> </Alloy> * XML view: views/index.xml <TableView id="tableView"> </TableView> * @param {Object} args An object whose keys are the IDs (in form '#id') of views to which the styles will be applied. * @since 1.4.0 */ updateViews: function(args) { var views = this.getViews(); if (_.isObject(args)) { _.each(_.keys(args), function(key) { var elem = views[key.substring(1)]; if (key.indexOf('#') === 0 && key !== '#' && _.isObject(elem) && typeof elem.applyProperties === 'function') { // apply the properties but make sure we're applying them to a Ti.UI object (not a controller) elem.applyProperties(args[key]); } }); } return this; }, /** * @method addListener * Adds a tracked event listeners to a view proxy object. * By default, any event listener declared in XML is tracked by Alloy. * * #### Example * Add an event to the tracking target. $.addListener($.aView, 'click', onClick); * @param {Object} proxy Proxy view object to listen to. * @param {String} type Name of the event. * @param {Function} callback Callback function to invoke when the event is fired. * @returns {String} ID attribute of the view object. If one does not exist, Alloy will create a unique ID. * @since 1.7.0 */ addListener: function(proxy, type, callback) { if (!proxy) { throw new Error(`Trying to pass proxy, but proxy is null. Attempted to add event listener for type = "${type}"`); } if (!proxy.id) { proxy.id = _.uniqueId('__trackId'); if (_.has(this.__views, proxy.id)) { Ti.API.error('$.addListener: ' + proxy.id + ' was conflict.'); return; } } // Improve error reporting when passing something wrong to an event listener if (typeof proxy.addEventListener !== 'function') { throw new Error(`Passed proxy "${proxy.id}" is not a valid Alloy proxy. Attempted to add event listener for type = "${type}"`); } proxy.addEventListener(type, callback); this.__events.push({ id: proxy.id, view: proxy, type: type, handler: callback }); return proxy.id; }, /** * @method getListener * Gets all the tracked event listeners of the view-controller or * only the ones specified by the parameters. Passing no parameters, * retrieves all tracked event listeners. Set a parameter to `null` * if you do not want to restrict the match to that parameter. * * #### Example * Get all events bound to the view-controller. var listener = $.getListener(); * @param {Object} [proxy] Proxy view object. * @param {String} [type] Name of the event. * @returns {Array<TrackedEventListener>} List of tracked event listeners. * @since 1.7.0 */ getListener: function(proxy, type) { return _.filter(this.__events, function(event, index) { if ((!proxy || proxy.id === event.id) && (!type || type === event.type)) { return true; } return false; }); }, /** * @method removeListener * Removes all tracked event listeners or only the ones * specified by the parameters. Passing no parameters, * removes all tracked event listeners. Set a parameter to `null` * if you do not want to restrict the match to that parameter. * * #### Example * When the window is closed, remove all tracked event listeners. <Alloy> <Window onOpen="doOpen" onClose="doClose"> <Label id="label" onClick="doClick">Hello, world</Label> </Window> </Alloy> function doClose() { $.removeListener(); } * @param {Object} [proxy] Proxy view object to remove event listeners from. * @param {String} [type] Name of the event to remove. * @param {Function} [callback] Callback to remove. * @returns {Alloy.Controller} Controller instance. * @since 1.7.0 */ removeListener: function(proxy, type, callback) { this.__events.forEach(function(event, index) { if ((!proxy || proxy.id === event.id) && (!type || type === event.type) && (!callback || callback === event.handler)) { event.view.removeEventListener(event.type, event.handler); delete self.__events[index]; } }); return this; } }); }; module.exports = Controller;