├── publish.sh ├── .gitignore ├── .npmignore ├── dist ├── alloyxl-commonjs-0.9.0.zip ├── alloyxl-commonjs-0.9.1.zip ├── alloyxl-commonjs-0.9.5.zip ├── alloyxl-commonjs-0.9.6.zip ├── alloyxl-commonjs-0.9.7.zip ├── alloyxl-commonjs-0.9.8.zip ├── alloyxl-commonjs-0.9.9.zip ├── alloyxl-commonjs-1.0.0.zip ├── alloyxl-commonjs-1.0.1.zip └── alloyxl-commonjs-1.0.2.zip ├── package.json ├── alloyXL.js └── readme.md /publish.sh: -------------------------------------------------------------------------------- 1 | npm publish 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/reste.js 2 | lib/alloyXL.js 3 | lib/crux*.js 4 | 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git 3 | alloyXL.js symlink 4 | dist 5 | publish.sh 6 | readme.md -------------------------------------------------------------------------------- /dist/alloyxl-commonjs-0.9.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonkneen/AlloyXL/HEAD/dist/alloyxl-commonjs-0.9.0.zip -------------------------------------------------------------------------------- /dist/alloyxl-commonjs-0.9.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonkneen/AlloyXL/HEAD/dist/alloyxl-commonjs-0.9.1.zip -------------------------------------------------------------------------------- /dist/alloyxl-commonjs-0.9.5.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonkneen/AlloyXL/HEAD/dist/alloyxl-commonjs-0.9.5.zip -------------------------------------------------------------------------------- /dist/alloyxl-commonjs-0.9.6.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonkneen/AlloyXL/HEAD/dist/alloyxl-commonjs-0.9.6.zip -------------------------------------------------------------------------------- /dist/alloyxl-commonjs-0.9.7.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonkneen/AlloyXL/HEAD/dist/alloyxl-commonjs-0.9.7.zip -------------------------------------------------------------------------------- /dist/alloyxl-commonjs-0.9.8.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonkneen/AlloyXL/HEAD/dist/alloyxl-commonjs-0.9.8.zip -------------------------------------------------------------------------------- /dist/alloyxl-commonjs-0.9.9.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonkneen/AlloyXL/HEAD/dist/alloyxl-commonjs-0.9.9.zip -------------------------------------------------------------------------------- /dist/alloyxl-commonjs-1.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonkneen/AlloyXL/HEAD/dist/alloyxl-commonjs-1.0.0.zip -------------------------------------------------------------------------------- /dist/alloyxl-commonjs-1.0.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonkneen/AlloyXL/HEAD/dist/alloyxl-commonjs-1.0.1.zip -------------------------------------------------------------------------------- /dist/alloyxl-commonjs-1.0.2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonkneen/AlloyXL/HEAD/dist/alloyxl-commonjs-1.0.2.zip -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alloyxl", 3 | "version": "1.0.4", 4 | "description": "A Titanium Alloy JavaScript library that enhances Alloy.", 5 | "titaniumManifest": { 6 | "guid": "e9b31b59-333e-4755-a996-04ac761ad652" 7 | }, 8 | "main": "alloyXL.js", 9 | "scripts": {}, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/jasonkneen/AlloyXL.git" 13 | }, 14 | "keywords": [ 15 | "alloy", 16 | "appc-lib", 17 | "appc-npm", 18 | "appcelerator", 19 | "arrow", 20 | "controllers", 21 | "titanium", 22 | "titanium-module" 23 | ], 24 | "author": "Jason Kneen", 25 | "license": "Apache 2.0", 26 | "bugs": { 27 | "url": "https://github.com/jasonkneen/AlloyXL/issues" 28 | }, 29 | "homepage": "https://github.com/jasonkneen/AlloyXL", 30 | "dependencies": {}, 31 | "devDependencies": {} 32 | } 33 | -------------------------------------------------------------------------------- /alloyXL.js: -------------------------------------------------------------------------------- 1 | Alloy.App = _.clone(Backbone.Events); 2 | 3 | // added to make webpack compatible 4 | var createController = Alloy.createController; 5 | 6 | var debug = true; 7 | var touchTimeout = 750; 8 | 9 | exports.options = (args) => { 10 | debug = args.debug; 11 | touchTimeout = args.touchTimeout; 12 | }; 13 | 14 | Alloy.createController = function (name, args) { 15 | 16 | function cleanUpController(controller) { 17 | Alloy.Controllers[controller.id] = null; 18 | 19 | if (controller.__views) { 20 | _.each(controller.__views, function (value) { 21 | cleanUpController(value); 22 | }); 23 | } 24 | 25 | if (controller.__iamalloy) { 26 | controller.off(); 27 | controller.destroy(); 28 | } 29 | 30 | controller = null; 31 | } 32 | 33 | // added to make webpack compatible 34 | var controller = createController(name, args); 35 | 36 | // original non-webpack implementation 37 | // var controller = new (require("alloy/controllers/" + name).default)(args); 38 | 39 | if (Object.keys(controller.__views).length > 0) { 40 | var view = controller.getView(); 41 | 42 | Alloy.Controllers = Alloy.Controllers || {}; 43 | 44 | var path = name.split('/'); 45 | 46 | if (path.length > 0) name = path[path.length - 1]; 47 | 48 | if (Alloy.Controllers[name] && !controller.getView().id) { 49 | console.warn('::AlloyXL:: The controller Alloy.Controllers.' + name + ' (' + controller.__controllerPath + ') exists, and will be overwriten because it\'s conflicting with another controller already instanciated with the same name. Please add a unique ID on the top parent view within that controller view so you can use this as the controller name within AlloyXL to avoid this.'); 50 | } 51 | 52 | if (controller.getView().id) { 53 | name = controller.getView().id; 54 | } 55 | 56 | Alloy.Controllers[name] = controller; 57 | 58 | controller.once = function (eventName, callback) { 59 | controller.on(eventName, function () { 60 | controller.off(eventName); 61 | callback(); 62 | }); 63 | return controller; 64 | }; 65 | 66 | if (typeof view.addEventListener === 'function') { 67 | if (typeof view.open === 'function') { 68 | view.addEventListener('open', function open(e) { 69 | view.removeEventListener('open', open); 70 | 71 | controller.trigger('open', e); 72 | if (ENV_DEV && debug) console.log('::AlloyXL:: controller ' + name + ' was opened'); 73 | }); 74 | 75 | view.addEventListener('close', function close() { 76 | view.removeEventListener('close', close); 77 | 78 | view = null; 79 | 80 | cleanUpController(controller); 81 | 82 | controller = null; 83 | 84 | if (ENV_DEV && debug) console.log('::AlloyXL:: Controller ' + name + ' cleaned up!'); 85 | }); 86 | 87 | view.addEventListener('postlayout', function postlayout(e) { 88 | view.removeEventListener('postlayout', postlayout); 89 | controller.trigger('postlayout', e); 90 | 91 | if (ENV_DEV && debug) console.log('::AlloyXL:: controller ' + name + ' layout finished'); 92 | }); 93 | } else { 94 | view.addEventListener('postlayout', function postlayout(e) { 95 | view.removeEventListener('postlayout', postlayout); 96 | controller.trigger('postlayout', e); 97 | 98 | if (ENV_DEV && debug) console.log('::AlloyXL:: controller ' + name + ' layout finished'); 99 | }); 100 | } 101 | } 102 | } 103 | if (controller & controller.getView()) { 104 | controller.getView().addEventListener('click', function clickBlocker(e) { 105 | setTimeout(function () { 106 | e.source.touchEnabled = false; 107 | }, touchTimeout); 108 | e.source.touchEnabled = true; 109 | }); 110 | } 111 | 112 | return controller; 113 | }; -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # AlloyXL 2 | 3 | Better controller management and workflow for [Appcelerator](http://www.appcelerator.com) [Titanium](https://github.com/appcelerator/titanium_mobile) [Alloy MVC framework](https://github.com/appcelerator/alloy). 4 | 5 | ## Why? 6 | 7 | I build a lot of apps where I use Alloy MVC. In developing apps I end up requiring some additional functionality when it comes to using controllers for example: 8 | 9 | - cleaning up after a controller / view is closed 10 | - knowing when a view is opened / closed and acting on it 11 | - accessing controllers globally across the app 12 | - analytics / logging 13 | 14 | ### The Old Way 15 | 16 | So previously I’d end up writing code to clean up a view / controller within say, a close function associated with hitting back or close button, like this: 17 | 18 | ```JS 19 | function doClose() { 20 | $.getView().close(); 21 | $.destroy(); 22 | $.off(); 23 | } 24 | ``` 25 | 26 | This is fine when **you** handle the event of closing a controller / view — but if you’re using say, a Navigation Window or TabGroup and you’re using the default behavoir of the back button, then this code wouldn’t run. You’d have to associate code with the “close” event on the Window. 27 | 28 | _(All of this led to lots of templated code in controllers, repeative code and could end up with some views being closed outside of the code I was writing and so the clean up wouldn’t happen.)_ 29 | 30 | ## A New Way - Using AlloyXL 31 | 32 | The idea behind AlloyXL was to tackle this at the source, by overriding the **Alloy.createController** method, I could do all this at source, ensuring that every controller is created in the same way, without changing any other code. 33 | 34 | The main things I wanted to achieve with AlloyXL were:- 35 | 36 | * Providing open / close events on Window views 37 | * Cleaning up memory after a controller is closed 38 | * Reduce code throughout the app 39 | * Allow access to controllers globally like Alloy.Collections 40 | 41 | ## Quick Start 42 | 43 | * [Install from NPM the latest version](https://www.npmjs.com/package/alloyxl) 44 | or 45 | * [Download the latest version](https://github.com/jasonkneen/AlloyXL) and place in your project (lib folder for Alloy). 46 | 47 | Wherever you want to initialise the API interface, put this (ideally this should go in your **alloy.js** file):- 48 | 49 | ```javascript 50 | require("alloyXL"); 51 | ``` 52 | *(note you don’t have to assign it to a variable)* 53 | 54 | If you want to set some options you can do:- 55 | 56 | ```javascript 57 | require("alloyXL").options({ 58 | debug: false, // turns off debug messages in ENV_DEV 59 | touchTimeout: 250 // disables a view when clicked for X ms to prevent double taps (default 750) 60 | }); 61 | ``` 62 | 63 | Once done, AlloyXL will automatically override the **Alloy.createController** method adding the following: 64 | 65 | - open and close events for Windows / Navigation Windows and TabGroups 66 | - a “once” event handler 67 | - global access to the controller from **Alloy.Controllers.name_of_controller** 68 | - model / collection, event and pointer clean up on close 69 | - debug messages on open, close, cleanup and postlayout (if debug = true -- the default) 70 | 71 | So, you can do something like this: 72 | 73 | _(where the view we’re in is a Navigation Window or TabGroup and “subview” is a Window)_ 74 | 75 | ```javascript 76 | $.getView().openWindow(Alloy.createController("subview").on("open", function(){ 77 | Alloy.Controllers.subview.getView().setBackgroundColor("#F00"); 78 | }).getView()); 79 | ``` 80 | In this example, without creating any local pointers to the controller or view, we’re responding to the open event on the window, and setting the background color of the view to red. 81 | 82 | You can also combine this with Alloy event chaning: 83 | 84 | ```javascript 85 | $.getView().openWindow(Alloy.createController("subview").on("open", function(){ 86 | Alloy.Controllers.subview.getView().setBackgroundColor("red"); 87 | }).on("anotherEvent", function(){ 88 | // handle the anotherEvent here -- you have to fire this yourself 89 | }).once("oneTime", function(){ 90 | // this event will only ever fire once -- you have to fire this yourself 91 | }).getView()); 92 | ``` 93 | 94 | Because AlloyXL is intercepting and overriding the **Alloy.createController** method, it’s able to do all this at the source, ensuring that everything is cleaned up afterwards! 95 | 96 | ## Same name controllers conflicts 97 | 98 | The **Alloy.Controllers** object stores the *last* instance to a controller — so normally, if you’re creating controllers with different names it’s all fine — however if you create two instances to a controller at once, only the *last* one is in **Alloy.Controllers**. 99 | 100 | If you have two controllers having the same names but located in different folders (`app/controllers/registration/index.js` and `app/controllers/home/index.js` for example), this will cause a conflict within AlloyXL. 101 | 102 | However, you can avoid this issue by either using different and unique names for all of your controllers **or** by using a unique ID on the top parent ``, `` or `` within your controllers and use it as the controller name within AlloyXL: 103 | 104 | File: `app/views/registration/index.xml` 105 | ```xml 106 | 107 | 108 | 109 | 110 | ``` 111 | 112 | ```javascript 113 | // Now access the controller using 114 | Alloy.Controllers.myUniqueID; 115 | // instead of 116 | Alloy.Controllers.index; 117 | ``` 118 | 119 | ### Global event handlers 120 | 121 | AlloyXL introduces a global event handler at `Alloy.App` — in order to use this you can trigger an event from anywhere the app and then handle this using the `Alloy.App` object. 122 | 123 | So: 124 | 125 | ```js 126 | Alloy.App.trigger("logout"); 127 | ``` 128 | 129 | Could be used to logout of the app anywhere and could be picked up by: 130 | 131 | ```js 132 | Alloy.App.on("logout", function(){ 133 | // do your log out here 134 | }); 135 | ``` 136 | 137 | You could even pass callbacks or other functions around events so you could handle say the logging out of a user (or an invalid API token), sending them back to a login screen. 138 | 139 | ### Taking the advantage of overriding controllers 140 | 141 | By applying the above, you can easily override a controller already instantiated within `Alloy.Controllers`, no matter what. 142 | 143 | This is very useful for `` if you want to re-use the same “ID” or controller name across your project. Here is a simple example: 144 | 145 | File: `app/views/login.xml` Login screen 146 | ```xml 147 | 148 | 149 | ... 150 | 151 | 152 | ``` 153 | 154 | File: `app/views/home.xml` Home screen once connected 155 | ```xml 156 | 157 | 158 | ... 159 | 160 | 161 | ``` 162 | 163 | File: `app/views/welcome.xml` Welcome window displayed no matter if you’re logged in or not 164 | ```xml 165 | 166 | 167 | ... 168 | 169 | 170 | ``` 171 | 172 | ```javascript 173 | // This will work no matter if the controller used is login or home. 174 | Alloy.Controllers.navigationWindow.getView().openWindow( 175 | Alloy.createController("welcome").getView() 176 | ); 177 | ``` 178 | 179 | ## License 180 |
181 | Copyright 2020-2022 Jason Kneen
182 | 
183 | Licensed under the Apache License, Version 2.0 (the “License”);
184 | you may not use this file except in compliance with the License.
185 | You may obtain a copy of the License at
186 | 
187 |    http://www.apache.org/licenses/LICENSE-2.0
188 | 
189 | Unless required by applicable law or agreed to in writing, software
190 | distributed under the License is distributed on an “AS IS” BASIS,
191 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
192 | See the License for the specific language governing permissions and
193 | limitations under the License.
194 | 
195 | --------------------------------------------------------------------------------