@maccesar/titools 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS-TEMPLATE.md +173 -0
- package/README.md +867 -0
- package/agents/ti-researcher.md +108 -0
- package/bin/titools.js +53 -0
- package/lib/commands/agents.js +126 -0
- package/lib/commands/install.js +188 -0
- package/lib/commands/uninstall.js +215 -0
- package/lib/commands/update.js +159 -0
- package/lib/config.js +119 -0
- package/lib/downloader.js +153 -0
- package/lib/installer.js +253 -0
- package/lib/platform.js +108 -0
- package/lib/symlink.js +142 -0
- package/lib/utils.js +270 -0
- package/package.json +67 -0
- package/skills/alloy-expert/SKILL.md +247 -0
- package/skills/alloy-expert/assets/ControllerAutoCleanup.js +182 -0
- package/skills/alloy-expert/references/alloy-structure.md +381 -0
- package/skills/alloy-expert/references/anti-patterns.md +133 -0
- package/skills/alloy-expert/references/code-conventions.md +469 -0
- package/skills/alloy-expert/references/contracts.md +280 -0
- package/skills/alloy-expert/references/controller-patterns.md +520 -0
- package/skills/alloy-expert/references/error-handling.md +484 -0
- package/skills/alloy-expert/references/examples.md +735 -0
- package/skills/alloy-expert/references/migration-patterns.md +298 -0
- package/skills/alloy-expert/references/patterns.md +448 -0
- package/skills/alloy-expert/references/performance-patterns.md +855 -0
- package/skills/alloy-expert/references/security-patterns.md +847 -0
- package/skills/alloy-expert/references/state-management.md +779 -0
- package/skills/alloy-expert/references/testing.md +872 -0
- package/skills/alloy-guides/SKILL.md +214 -0
- package/skills/alloy-guides/references/CLI_TASKS.md +243 -0
- package/skills/alloy-guides/references/CONCEPTS.md +191 -0
- package/skills/alloy-guides/references/CONTROLLERS.md +298 -0
- package/skills/alloy-guides/references/MODELS.md +1028 -0
- package/skills/alloy-guides/references/PURGETSS.md +56 -0
- package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +242 -0
- package/skills/alloy-guides/references/VIEWS_STYLES.md +388 -0
- package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +109 -0
- package/skills/alloy-guides/references/VIEWS_XML.md +558 -0
- package/skills/alloy-guides/references/WIDGETS.md +176 -0
- package/skills/alloy-howtos/SKILL.md +203 -0
- package/skills/alloy-howtos/references/best_practices.md +138 -0
- package/skills/alloy-howtos/references/cli_reference.md +253 -0
- package/skills/alloy-howtos/references/config_files.md +87 -0
- package/skills/alloy-howtos/references/custom_tags.md +147 -0
- package/skills/alloy-howtos/references/debugging_troubleshooting.md +101 -0
- package/skills/alloy-howtos/references/samples.md +167 -0
- package/skills/purgetss/SKILL.md +442 -0
- package/skills/purgetss/assets/purgetss.config.cjs +17 -0
- package/skills/purgetss/references/EXAMPLES.md +247 -0
- package/skills/purgetss/references/animation-system.md +1294 -0
- package/skills/purgetss/references/apply-directive.md +375 -0
- package/skills/purgetss/references/arbitrary-values.md +612 -0
- package/skills/purgetss/references/class-index.md +1350 -0
- package/skills/purgetss/references/cli-commands.md +948 -0
- package/skills/purgetss/references/configurable-properties.md +654 -0
- package/skills/purgetss/references/custom-rules.md +161 -0
- package/skills/purgetss/references/customization-deep-dive.md +722 -0
- package/skills/purgetss/references/dynamic-component-creation.md +489 -0
- package/skills/purgetss/references/grid-layout.md +455 -0
- package/skills/purgetss/references/icon-fonts.md +609 -0
- package/skills/purgetss/references/installation-setup.md +366 -0
- package/skills/purgetss/references/opacity-modifier.md +291 -0
- package/skills/purgetss/references/platform-modifiers.md +479 -0
- package/skills/purgetss/references/smart-mappings.md +42 -0
- package/skills/purgetss/references/titanium-resets.md +359 -0
- package/skills/purgetss/references/ui-ux-design.md +1526 -0
- package/skills/ti-guides/SKILL.md +94 -0
- package/skills/ti-guides/references/advanced-data-and-images.md +19 -0
- package/skills/ti-guides/references/alloy-cli-advanced.md +84 -0
- package/skills/ti-guides/references/alloy-data-mastery.md +29 -0
- package/skills/ti-guides/references/alloy-widgets-and-themes.md +19 -0
- package/skills/ti-guides/references/android-manifest.md +97 -0
- package/skills/ti-guides/references/app-distribution.md +258 -0
- package/skills/ti-guides/references/application-frameworks.md +377 -0
- package/skills/ti-guides/references/cli-reference.md +402 -0
- package/skills/ti-guides/references/coding-best-practices.md +102 -0
- package/skills/ti-guides/references/commonjs-advanced.md +134 -0
- package/skills/ti-guides/references/hello-world.md +100 -0
- package/skills/ti-guides/references/hyperloop-native-access.md +62 -0
- package/skills/ti-guides/references/javascript-primer.md +411 -0
- package/skills/ti-guides/references/reserved-words.md +36 -0
- package/skills/ti-guides/references/resources.md +183 -0
- package/skills/ti-guides/references/style-and-conventions.md +48 -0
- package/skills/ti-guides/references/tiapp-config.md +609 -0
- package/skills/ti-howtos/SKILL.md +174 -0
- package/skills/ti-howtos/references/android-platform-deep-dives.md +658 -0
- package/skills/ti-howtos/references/automation-fastlane-appium.md +95 -0
- package/skills/ti-howtos/references/buffer-codec-streams.md +140 -0
- package/skills/ti-howtos/references/cross-platform-development.md +348 -0
- package/skills/ti-howtos/references/debugging-profiling.md +543 -0
- package/skills/ti-howtos/references/extending-titanium.md +723 -0
- package/skills/ti-howtos/references/google-maps-v2.md +169 -0
- package/skills/ti-howtos/references/ios-map-kit.md +143 -0
- package/skills/ti-howtos/references/ios-platform-deep-dives.md +783 -0
- package/skills/ti-howtos/references/local-data-sources.md +301 -0
- package/skills/ti-howtos/references/location-and-maps.md +252 -0
- package/skills/ti-howtos/references/media-apis.md +210 -0
- package/skills/ti-howtos/references/notification-services.md +599 -0
- package/skills/ti-howtos/references/remote-data-sources.md +349 -0
- package/skills/ti-howtos/references/tutorials.md +502 -0
- package/skills/ti-howtos/references/using-modules.md +237 -0
- package/skills/ti-howtos/references/web-content-integration.md +307 -0
- package/skills/ti-howtos/references/webpack-build-pipeline.md +78 -0
- package/skills/ti-ui/SKILL.md +179 -0
- package/skills/ti-ui/references/accessibility-deep-dive.md +242 -0
- package/skills/ti-ui/references/animation-and-matrices.md +599 -0
- package/skills/ti-ui/references/application-structures.md +655 -0
- package/skills/ti-ui/references/custom-fonts-styling.md +579 -0
- package/skills/ti-ui/references/event-handling.md +393 -0
- package/skills/ti-ui/references/gestures.md +473 -0
- package/skills/ti-ui/references/icons-and-splash-screens.md +409 -0
- package/skills/ti-ui/references/layouts-and-positioning.md +462 -0
- package/skills/ti-ui/references/listviews-and-performance.md +619 -0
- package/skills/ti-ui/references/orientation.md +362 -0
- package/skills/ti-ui/references/platform-ui-android.md +635 -0
- package/skills/ti-ui/references/platform-ui-ios.md +469 -0
- package/skills/ti-ui/references/scrolling-views.md +252 -0
- package/skills/ti-ui/references/tableviews.md +568 -0
|
@@ -0,0 +1,1028 @@
|
|
|
1
|
+
# Alloy Models
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Overview](#overview)
|
|
6
|
+
2. [Alloy Collection and Model Objects](#alloy-collection-and-model-objects)
|
|
7
|
+
3. [Alloy Data Binding](#alloy-data-binding)
|
|
8
|
+
4. [Alloy Sync Adapters and Migrations](#alloy-sync-adapters-and-migrations)
|
|
9
|
+
5. [Backbone Objects without Alloy](#backbone-objects-without-alloy)
|
|
10
|
+
6. [Alloy Backbone Migration](#alloy-backbone-migration)
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
Alloy uses Backbone.js to provide support for its models and collections. Alloy also borrows the concepts of migrations and adapters from Rails for storage integration.
|
|
15
|
+
|
|
16
|
+
For models, collections and sync adapters, these guides only provides information on how Alloy utilizes the Backbone.js functionality and some simple examples of using it.
|
|
17
|
+
|
|
18
|
+
## Alloy Collection and Model Objects
|
|
19
|
+
|
|
20
|
+
### Models
|
|
21
|
+
|
|
22
|
+
In Alloy, models inherit from the [Backbone.Model](https://backbonejs.org/#Model-View-separation) class. They contain the interactive data and logic used to control and access it. Models are specified with JavaScript files, which provide a table schema, adapter configuration and logic to extend the Backbone.Model class. Models are automatically defined and available in the controller scope as the name of the JavaScript file.
|
|
23
|
+
|
|
24
|
+
The JavaScript file exports a definition object comprised of three different objects. The first object, called `config`, defines the table schema and adapter information. The next two objects `extendModel` and `extendCollection` define functions to extend, override or implement the Backbone.Model and Backbone.Collection classes, respectively.
|
|
25
|
+
|
|
26
|
+
**Example of the anatomy of a model file**
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
exports.definition = {
|
|
30
|
+
config : { // table schema and adapter information
|
|
31
|
+
},
|
|
32
|
+
extendModel(Model) {
|
|
33
|
+
_.extend(Model.prototype, { // Extend, override or implement Backbone.Model
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return Model;
|
|
37
|
+
},
|
|
38
|
+
extendCollection(Collection) {
|
|
39
|
+
_.extend(Collection.prototype, { // Extend, override or implement Backbone.Collection
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return Collection;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
To access a model locally in a controller, use the `Alloy.createModel` method. The first required parameter is the name of the JavaScript file minus the '.js' extension. The second optional parameter is the attributes for initializing the model object. For example:
|
|
48
|
+
|
|
49
|
+
**Basic model usage**
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
const book = Alloy.createModel('book', {title:'Green Eggs and Ham', author:'Dr. Seuss'});
|
|
53
|
+
const title = book.get('title');
|
|
54
|
+
const author = book.get('author');
|
|
55
|
+
|
|
56
|
+
// Label object in the view with id = 'label'
|
|
57
|
+
$.label.text = title + ' by ' + author;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The `book` model object is a Backbone object wrapped by Alloy, so it can be treated as a Backbone.Model object. You can use any Backbone Model or Events APIs with this object.
|
|
61
|
+
|
|
62
|
+
You can also create a global singleton instance of a model, either in markup or in the controller, which may be accessed in all controllers. Use the `Alloy.Models.instance` method with the name of the model file minus the extension as the only parameter to create or access the singleton. For example:
|
|
63
|
+
|
|
64
|
+
**Working with globally registered models**
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// This will create a singleton if it has not been previously created,
|
|
68
|
+
// or retrieves the singleton if it already exists.
|
|
69
|
+
const book = Alloy.Models.instance('book');
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Configuration Object
|
|
73
|
+
|
|
74
|
+
The `config` object is comprised of three different objects: `columns`, `defaults` and `adapter`.
|
|
75
|
+
|
|
76
|
+
The `columns` object defines the table schema information. The key is the record name and the value is the data type. The following data types are accepted and mapped to the appropriate SQLite type: `string`, `varchar`, `int`, `tinyint`, `smallint`, `bigint`, `double`, `float`, `decimal`, `number`, `date`, `datetime` and `boolean`. By default, any unknown data type maps to the SQLite type `TEXT`. Alternatively, the SQLite sync adapter accepts the SQLite keywords.
|
|
77
|
+
|
|
78
|
+
The optional `defaults` object defines the default values for a record if one or more record fields are left undefined upon creation. The key is the record name and the value is the default value.
|
|
79
|
+
|
|
80
|
+
The adapter object defines how to access persistent storage. It contains two key-value pairs: `type` and `collection_name`. The `type` key identifies the sync adapter and the `collection_name` key identifies the name of the table in the database or a namespace.
|
|
81
|
+
|
|
82
|
+
For example, suppose there is a model object called book (`book.js`) defined as:
|
|
83
|
+
|
|
84
|
+
**book.js**
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
exports.definition = {
|
|
88
|
+
config: {
|
|
89
|
+
"columns": {
|
|
90
|
+
"title": "String",
|
|
91
|
+
"author": "String"
|
|
92
|
+
},
|
|
93
|
+
"defaults": {
|
|
94
|
+
"title": "-",
|
|
95
|
+
"author": "-"
|
|
96
|
+
},
|
|
97
|
+
"adapter": {
|
|
98
|
+
"type": "sql",
|
|
99
|
+
"collection_name": "books"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The code above describes a book object, which has two `string` (or `TEXT`) fields: `title` and `author`. If either field is left undefined, it will be assigned with the default value, a dash ("-"). The `sql` type configures Backbone to use the SQL adapter to sync with the SQLite engine on Android and iOS devices to access a table in the database called "books".
|
|
106
|
+
|
|
107
|
+
You may add custom properties to the `config` object, which are available to the application as the model or collection's `config` property or can be processed by a custom sync adapter during application initialization.
|
|
108
|
+
|
|
109
|
+
#### Extending the Backbone.Model Class
|
|
110
|
+
|
|
111
|
+
The Backbone.Model class can be extended using the `extendModel` object, which implements the Backbone.Model `extend` method. This allows the Backbone.js code to be extended, overridden or implemented.
|
|
112
|
+
|
|
113
|
+
For example, the `validate` method is left unimplemented by Backbone.js. The model JS file can implement `validate(attrs)`, where the parameter `attrs` are changed attributes in the model. In Backbone.js, if `validate` is implemented, it is called by the `set` and `save(attributes)` methods before changing the attributes and is also called by the `isValid` method. For the `save` method, validate is called if the `attributes` parameter is defined.
|
|
114
|
+
|
|
115
|
+
In the example code `book.js` below, the JavaScript file implements the validate method, and adds a custom property and function.
|
|
116
|
+
|
|
117
|
+
**Extending a model**
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
exports.definition = {
|
|
121
|
+
config : { // table schema and adapter information
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
extendModel(Model) {
|
|
125
|
+
_.extend(Model.prototype, {
|
|
126
|
+
// Implement the validate method
|
|
127
|
+
validate(attrs) {
|
|
128
|
+
for (const key in attrs) {
|
|
129
|
+
const value = attrs[key];
|
|
130
|
+
if (key === "title") {
|
|
131
|
+
if (value.length <= 0) {
|
|
132
|
+
return "Error: No title!";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (key === "author") {
|
|
136
|
+
if (value.length <= 0) {
|
|
137
|
+
return "Error: No author!";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
// Extend Backbone.Model
|
|
143
|
+
customProperty: 'book',
|
|
144
|
+
customFunction() {
|
|
145
|
+
Ti.API.info('I am a book model.');
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return Model;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
In the controller, to access the model, do:
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
const book = Alloy.createModel('book', {title:'Green Eggs and Ham', author:'Dr. Seuss'});
|
|
158
|
+
// Since set or save(attribute) is not being called, we can call isValid to validate the model object
|
|
159
|
+
if (book.isValid() && book.customProperty == "book") { // Save data to persistent storage
|
|
160
|
+
book.save();
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
book.destroy();
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Collections
|
|
168
|
+
|
|
169
|
+
Collections are ordered sets of models and inherit from the Backbone.Collection class. Alloy Collections are automatically defined and available in the controller scope as the name of the model. To access a collection in the controller locally, use the `Alloy.createCollection` method with the name of the JavaScript file minus the '.js' extension as the required parameter. The second optional parameter can be an array of model objects for initialization. For example, the code below creates a collection using the previously defined model and reads data from persistent storage:
|
|
170
|
+
|
|
171
|
+
**Creating collections**
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
const library = Alloy.createCollection('book');
|
|
175
|
+
library.fetch(); // Grab data from persistent storage
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The `library` collection object is a Backbone object wrapped by Alloy, so it can be treated as a Backbone.Collection object. You can use any Backbone Collection or Events APIs with this object.
|
|
179
|
+
|
|
180
|
+
You can also create a global singleton instance, either in markup or in the controller, which may be accessed in all controllers. Use the `Alloy.Collections.instance` method with the name of the model file minus the extension as the only parameter to create or access the singleton. For example:
|
|
181
|
+
|
|
182
|
+
**Working with globally registered collections**
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
// This will create a singleton if it has not been previously created,
|
|
186
|
+
// or retrieves the singleton if it already exists.
|
|
187
|
+
const library = Alloy.Collections.instance('book');
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### Extending the Backbone.Collection Class
|
|
191
|
+
|
|
192
|
+
Like the Backbone.Model class, the Backbone.Collection class can be similarly extended in the model JavaScript file. For example, the `comparator` method is left unimplemented in Backbone.js. The code below sorts the library by book title:
|
|
193
|
+
|
|
194
|
+
**Extending a collection**
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
exports.definition = {
|
|
198
|
+
config : { // table schema and adapter information
|
|
199
|
+
},
|
|
200
|
+
extendModel(Model) {
|
|
201
|
+
_.extend(Model.prototype, { // Extend, override or implement Backbone.Model methods
|
|
202
|
+
});
|
|
203
|
+
return Model;
|
|
204
|
+
},
|
|
205
|
+
extendCollection(Collection) {
|
|
206
|
+
_.extend(Collection.prototype, { // Implement the comparator method.
|
|
207
|
+
comparator(book) {
|
|
208
|
+
return book.get('title');
|
|
209
|
+
}
|
|
210
|
+
}); // end extend
|
|
211
|
+
|
|
212
|
+
return Collection;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### Underscore.js Functionality
|
|
218
|
+
|
|
219
|
+
Additionally, the Backbone.Collection class inherits some functionality from [Underscore.js](https://underscorejs.org/), which can help simplify iterative functions. For example, to add the title of each book object in the library collection to a table, you could use the `map` function to set the table:
|
|
220
|
+
|
|
221
|
+
**Iterating over a collection with underscore**
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
const data = library.map(book => {
|
|
225
|
+
// The book argument is an individual model object in the collection
|
|
226
|
+
const title = book.get('title');
|
|
227
|
+
const row = Ti.UI.createTableViewRow({"title":title});
|
|
228
|
+
return row;
|
|
229
|
+
});
|
|
230
|
+
// TableView object in the view with id = 'table'
|
|
231
|
+
$.table.setData(data);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Event Handling
|
|
235
|
+
|
|
236
|
+
When working with Alloy Models and Collections, use the Backbone.Events `on`, `off` and `trigger` methods. For example:
|
|
237
|
+
|
|
238
|
+
**Using events with collections**
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
const library = Alloy.createCollection('book');
|
|
242
|
+
function event_callback (context) {
|
|
243
|
+
const output = context || 'change is bad.';
|
|
244
|
+
Ti.API.info(output);
|
|
245
|
+
};
|
|
246
|
+
// Bind the callback to the change event of the collection.
|
|
247
|
+
library.on('change', event_callback);
|
|
248
|
+
// Trigger the change event and pass context to the handler.
|
|
249
|
+
library.trigger('change', 'change is good.');
|
|
250
|
+
// Passing no parameters to the off method unbinds all event callbacks to the object.
|
|
251
|
+
library.off();
|
|
252
|
+
// This trigger does not have a response.
|
|
253
|
+
library.trigger('change');
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Alloy Model and Collection objects don't support the Titanium `addEventListener`, `removeEventListener` and `fireEvent` methods.
|
|
257
|
+
|
|
258
|
+
If you are using Alloy's Model-View binding mechanism, the Backbone add, change, destroy, fetch, remove, and reset events are automatically bound to an internal callback to update the model data in the view. Be careful not to override or unbind these events.
|
|
259
|
+
|
|
260
|
+
If you want to fire or listen to multiple events, Backbone.js uses spaces to delimit its events in the event string; therefore, do **NOT** name any custom events with spaces.
|
|
261
|
+
|
|
262
|
+
## Alloy Data Binding
|
|
263
|
+
|
|
264
|
+
### Collection vs Model Data Binding
|
|
265
|
+
|
|
266
|
+
You can bind both a collection of models or an individual model. To bind a model attribute the opening curly bracket is first followed by the model name and then the attribute. To bind a collection you add the `dataCollection` attribute to the container using the collection name as value.
|
|
267
|
+
|
|
268
|
+
```xml
|
|
269
|
+
<Alloy>
|
|
270
|
+
<Model src="currentCategory" />
|
|
271
|
+
<Collection src="book" />
|
|
272
|
+
<Window>
|
|
273
|
+
<!-- model data binding -->
|
|
274
|
+
<Label text="{currentCategory.name}" />
|
|
275
|
+
|
|
276
|
+
<!-- collection data binding -->
|
|
277
|
+
<ScrollView dataCollection="book">
|
|
278
|
+
<Label text="{title}" />
|
|
279
|
+
</ScrollView>
|
|
280
|
+
</Window>
|
|
281
|
+
</Alloy>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Global Singleton vs Local Instance
|
|
285
|
+
|
|
286
|
+
In the above code snippet, the model and collection are global singletons under `Alloy.Models.currentCategory` and `Alloy.Collections.book`. You can also use local instances for the current controller by adding `instance="true"` as attribute. You also need to assign them an ID in order to reference them in the XML and controller.
|
|
287
|
+
|
|
288
|
+
```xml
|
|
289
|
+
<Alloy>
|
|
290
|
+
<Model src="currentCategory" instance="true" id="c" />
|
|
291
|
+
<Collection src="book" instance="true" id="b" />
|
|
292
|
+
<Window>
|
|
293
|
+
<!-- model data binding -->
|
|
294
|
+
<Label text="{$.c.name}" />
|
|
295
|
+
|
|
296
|
+
<!-- collection data binding -->
|
|
297
|
+
<ScrollView dataCollection="$.b">
|
|
298
|
+
<Label text="{title}" />
|
|
299
|
+
</ScrollView>
|
|
300
|
+
</Window>
|
|
301
|
+
</Alloy>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Simple vs Complex Data Binding
|
|
305
|
+
|
|
306
|
+
It's important to understand the difference between simple and complex data binding as they were implemented in unique ways which results in different behaviour.
|
|
307
|
+
|
|
308
|
+
Simple data binding involves one model attribute where complex data binding involves a combination of strings (including white space) and model attributes or even multiple model attributes:
|
|
309
|
+
|
|
310
|
+
```xml
|
|
311
|
+
<Alloy>
|
|
312
|
+
<Model src="book">
|
|
313
|
+
<Window>
|
|
314
|
+
<!-- simple -->
|
|
315
|
+
<Label text="{book.title}" />
|
|
316
|
+
|
|
317
|
+
<!-- complex -->
|
|
318
|
+
<Label text="Title: {book.title}" />
|
|
319
|
+
<Label text="{book.author.name} {book.author.email}" />
|
|
320
|
+
</Window>
|
|
321
|
+
</Alloy>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Backbone Binding
|
|
325
|
+
|
|
326
|
+
The application can monitor Backbone events to trigger updates to the view.
|
|
327
|
+
|
|
328
|
+
For instance, the code below demonstrates how to update a table when a model object is added to a collection by monitoring the add event:
|
|
329
|
+
|
|
330
|
+
```javascript
|
|
331
|
+
library.on('add', e => {
|
|
332
|
+
// custom function to update the content on the view
|
|
333
|
+
updateFooView(library);
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Another method is to selectively monitor changes. For instance, the code below demonstrates how to update data if a title changes in the collection:
|
|
338
|
+
|
|
339
|
+
```javascript
|
|
340
|
+
library.on('change:title', e => {
|
|
341
|
+
// custom function to update the content on the view
|
|
342
|
+
updateFooView(library);
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
::: warning ⚠️ Warning
|
|
347
|
+
This only works if the Backbone method fires the change event and does not enable `{silent: true}` as an option.
|
|
348
|
+
:::
|
|
349
|
+
|
|
350
|
+
### Bind Deep Object Properties
|
|
351
|
+
|
|
352
|
+
You can bind deep object properties:
|
|
353
|
+
|
|
354
|
+
```xml
|
|
355
|
+
<Alloy>
|
|
356
|
+
<Model src="book" />
|
|
357
|
+
<Label text="{book.author.name}" />
|
|
358
|
+
</Alloy>
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Before, you needed to use a transformer to create a reference like `authorName`.
|
|
362
|
+
|
|
363
|
+
Prior to CLI 7.1.0, the only way to set object properties (e.g. `font.fontFamily` for a Label) was to use TSS. You can use dot notation in XML:
|
|
364
|
+
|
|
365
|
+
```xml
|
|
366
|
+
<Alloy>
|
|
367
|
+
<Model src="book" />
|
|
368
|
+
<Label font.fontFamily="Roboto">Hello</Label>
|
|
369
|
+
</Alloy>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Use Models and Properties with Special Characters
|
|
373
|
+
|
|
374
|
+
You can bind models and properties that use names with special characters like dashes and spaces. Simply wrap the names in square brackets and quotes like you'd do in JavaScript:
|
|
375
|
+
|
|
376
|
+
```xml
|
|
377
|
+
<Alloy>
|
|
378
|
+
<Model src="my-model">
|
|
379
|
+
<Label text="['my-model']['my-property']" />
|
|
380
|
+
</Alloy>
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Bind Multiple Models to the Same View
|
|
384
|
+
|
|
385
|
+
You have the ability to bind multiple models to the same view:
|
|
386
|
+
|
|
387
|
+
```xml
|
|
388
|
+
<Alloy>
|
|
389
|
+
<Model src="a" />
|
|
390
|
+
<Model src="b" />
|
|
391
|
+
<Label text="{a.hello} {b.world}" />
|
|
392
|
+
</Alloy>
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Define Transformations in the Model
|
|
396
|
+
|
|
397
|
+
Since Alloy 1.8.1, all types of data binding will generate the following logic to determine what object will be bound to the view:
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
let t;
|
|
401
|
+
if (_.isFunction(<dataTransform>)) { // only for collection binding
|
|
402
|
+
t = <dataTransform>(model);
|
|
403
|
+
} else if (_.isFunction(model.transform)) {
|
|
404
|
+
t = model.transform();
|
|
405
|
+
} else {
|
|
406
|
+
t = model.toJSON();
|
|
407
|
+
}
|
|
408
|
+
$.myLabel.text = t.author.name;
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
You'd extend a model with a `transform()` method as such:
|
|
412
|
+
|
|
413
|
+
```javascript
|
|
414
|
+
exports.definition = {
|
|
415
|
+
// config
|
|
416
|
+
extendModel(Model) {
|
|
417
|
+
_.extend(Model.prototype, {
|
|
418
|
+
transform() {
|
|
419
|
+
const t = this.toJSON();
|
|
420
|
+
t.titleCaps = t.title.toUpperCase();
|
|
421
|
+
return t;
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
return Model;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Lazy Transformation (Performance Tip)
|
|
430
|
+
|
|
431
|
+
The advantage of defining transformations in the model is that you don't need to repeat them in every controller. A possible disadvantage is that everywhere you bind the model all transformations are computed where you might only need some.
|
|
432
|
+
|
|
433
|
+
You can handle this using `Object.defineProperty()`. Its `get` callback will only be called when the transform key is actually requested:
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
const moment = require('alloy/moment');
|
|
437
|
+
|
|
438
|
+
exports.definition = {
|
|
439
|
+
extendModel(Model) {
|
|
440
|
+
_.extend(Model.prototype, {
|
|
441
|
+
transform() {
|
|
442
|
+
const model = this;
|
|
443
|
+
const t = this.toJSON();
|
|
444
|
+
|
|
445
|
+
Object.defineProperty(t, 'dateFormatted', {
|
|
446
|
+
get() {
|
|
447
|
+
return moment(t.date).format('LLLL');
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
return t;
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
return Model;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Populating a Model After Data Binding
|
|
460
|
+
|
|
461
|
+
When Alloy compiles your views and controllers, the generated view code precedes your controller code. Any models you define for data binding in the XML will also be created at that point. Just like you call `fetch()` to populate the collection, you do the exact same thing for the model.
|
|
462
|
+
|
|
463
|
+
**index.xml**
|
|
464
|
+
|
|
465
|
+
```xml
|
|
466
|
+
<Alloy>
|
|
467
|
+
<Model src="book" instance="true" id="current" />
|
|
468
|
+
<Window>
|
|
469
|
+
<Label text="{book.title}" />
|
|
470
|
+
</Window>
|
|
471
|
+
</Alloy>
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**index.js**
|
|
475
|
+
|
|
476
|
+
```javascript
|
|
477
|
+
$.current.fetch({
|
|
478
|
+
id: Ti.App.Properties.getString('currentBook')
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
$.index.open();
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Introduction
|
|
485
|
+
|
|
486
|
+
When data in the collection changes, you may want to update the view simultaneously to keep information synchronized. This concept is known as data binding. Both Alloy and Backbone provide some mechanisms to bind model data to a view.
|
|
487
|
+
|
|
488
|
+
### Alloy Binding
|
|
489
|
+
|
|
490
|
+
In Alloy, collection data can be synchronized to a view object, or a single model can be bound to a view component. Alloy monitors the Backbone add, change, destroy, fetch, remove, and reset events to update the data in the view.
|
|
491
|
+
|
|
492
|
+
#### Collection-View Binding
|
|
493
|
+
|
|
494
|
+
To enable collection-view binding, create a global singleton or controller-specific collection using the [Collection tag](https://titaniumsdk.com/guide/Alloy_Framework/Alloy_Guide/Alloy_Views/Alloy_XML_Markup.html#collection-element) in the XML markup of the main view, then add the view object you want to bind data to. The following Titanium view objects support binding to a Collection:
|
|
495
|
+
|
|
496
|
+
| View Object | Since Alloy version | Add data binding attributes to... | Repeater Object to map model attributes to view properties |
|
|
497
|
+
| -------------- | ------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
498
|
+
| ButtonBar | 1.1 | `<Labels>` | `<Label/>` |
|
|
499
|
+
| CoverFlowView | 1.1 | `<Images>` | `<Image/>` |
|
|
500
|
+
| ListView | 1.2 | `<ListSection>` | `<ListItem/>` |
|
|
501
|
+
| Map Module | 1.4 | `<Module module="ti.map" method="createView">` | None, model attributes will be used as params for createAnnotation() directly. |
|
|
502
|
+
| Picker | 1.5 | `<PickerColumn>` or `<Column>` | `<PickerRow/>` or `<Row/>` |
|
|
503
|
+
| ScrollableView | 1.1 | `<ScrollableView>` | `<View/>` May contain children view objects. |
|
|
504
|
+
| TableView | 1.0 | `<TableView>` | `<TableViewRow/>` May contain children view objects. |
|
|
505
|
+
| TabbedBar | 1.1 | `<Labels>` | `<Label/>` |
|
|
506
|
+
| Toolbar | 1.1 | `<Items>` | `<Item/>` |
|
|
507
|
+
| View | 1.0 | `<View>` | Any view object except a top-level container like a Window or TabGroup |
|
|
508
|
+
|
|
509
|
+
You need to specify additional attributes in the markup, which are only specific to collection data binding. The only mandatory attribute is `dataCollection`, which specifies the collection singleton or instance to render. Note that you can only add these attributes to specific XML elements (refer to the table above).
|
|
510
|
+
|
|
511
|
+
* `dataCollection`: specifies the collection singleton or instance to bind to the table. This is the name of the model file for singletons or the ID prefixed with the controller symbol ('$') for instances.
|
|
512
|
+
* `dataTransform`: specifies an optional callback to use to format model attributes. The passed argument is a model and the return value is a modified model as a JSON object.
|
|
513
|
+
* `dataFilter`: specifies an optional callback to use to filter data in the collection. The passed argument is a collection and the return value is an array of models.
|
|
514
|
+
* `dataFunction`: set to an arbitrary identifier (name) for a function call. Use this identifier to call a function in the controller to manually update the view.
|
|
515
|
+
|
|
516
|
+
Next, create a repeater object (refer to the table above) and place it inline with the view object with the `dataCollection` attribute, or place it in a separate view and use the `Require` tag to import it.
|
|
517
|
+
|
|
518
|
+
To map model attributes, enclose the attribute with curly brackets or braces ('{' and '}'). You can map more than one attribute to a repeater object's property. For example, to assign the Label.text property to the model's title and author attributes, use this notation: `<Label text="{title} by {author}" />.` For more complex transformations, use the `dataTransform` callback to create a custom attribute.
|
|
519
|
+
|
|
520
|
+
In the controller code of the repeater object, you can use the special variable `$model` to reference the current model being iterated over. This variable is present only in data bound controllers and is a reference to the currently bound model. For example, to get the title attribute of the current model, use `$model.title` to access it.
|
|
521
|
+
|
|
522
|
+
::: warning ⚠️ Warning
|
|
523
|
+
**IMPORTANT:** When using Alloy's data binding in a view-controller, you **MUST** call the `$.destroy()` function when closing a controller to prevent potential memory leaks. The `destroy` function unbinds the callbacks created by Alloy when the collection-view syntax is used. For example:
|
|
524
|
+
|
|
525
|
+
```
|
|
526
|
+
$.win.addEventListener("close", () => {
|
|
527
|
+
$.destroy();
|
|
528
|
+
});
|
|
529
|
+
```
|
|
530
|
+
:::
|
|
531
|
+
|
|
532
|
+
#### Collection-View Binding Example
|
|
533
|
+
|
|
534
|
+
The following example demonstrates how to add basic collection-view binding to an application. The example binds a collection of album models to a ScrollableView. In the ScrollableView, each model has its own view, which displays the album cover, title of the album and the artist. The `artist` and `title` attributes are bound to a Label object and the `cover` attribute is bound to an ImageView object.
|
|
535
|
+
|
|
536
|
+
1. Add the `<Collection>` tag as a child of the `<Alloy>` tag.
|
|
537
|
+
|
|
538
|
+
**app/views/index.xml**
|
|
539
|
+
|
|
540
|
+
```xml
|
|
541
|
+
<Alloy>
|
|
542
|
+
<Collection src="album" />
|
|
543
|
+
</Alloy>
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
2. Next, add the view object(s) you want to bind the data to.
|
|
547
|
+
|
|
548
|
+
**app/views/index.xml**
|
|
549
|
+
|
|
550
|
+
```xml
|
|
551
|
+
<Alloy>
|
|
552
|
+
<Collection src="album" />
|
|
553
|
+
<Window backgroundColor="white" onClose="cleanup">
|
|
554
|
+
<ScrollableView></ScrollableView>
|
|
555
|
+
</Window>
|
|
556
|
+
</Alloy>
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
3. Add the `dataCollection` attribute to the appropriate view object.
|
|
560
|
+
|
|
561
|
+
**app/views/index.xml**
|
|
562
|
+
|
|
563
|
+
```xml
|
|
564
|
+
<Alloy>
|
|
565
|
+
<Collection src="album" />
|
|
566
|
+
<Window backgroundColor="white" onClose="cleanup">
|
|
567
|
+
<ScrollableView dataCollection="album"></ScrollableView>
|
|
568
|
+
</Window>
|
|
569
|
+
</Alloy>
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
4. Next, create your repeater object and add model attributes.
|
|
573
|
+
|
|
574
|
+
**app/views/index.xml**
|
|
575
|
+
|
|
576
|
+
```xml
|
|
577
|
+
<Alloy>
|
|
578
|
+
<Collection src="album"/>
|
|
579
|
+
<Window backgroundColor="white" onClose="cleanup">
|
|
580
|
+
<ScrollableView dataCollection="album">
|
|
581
|
+
<View layout="vertical">
|
|
582
|
+
<ImageView image="{cover}" />
|
|
583
|
+
<Label text="{title} by {artist}" />
|
|
584
|
+
</View>
|
|
585
|
+
</ScrollableView>
|
|
586
|
+
</Window>
|
|
587
|
+
</Alloy>
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
5. In the controller, call the Collection's `fetch()` method to initialize the collection and sync any stored models to the view.
|
|
591
|
+
|
|
592
|
+
**app/controllers/index.js**
|
|
593
|
+
|
|
594
|
+
```javascript
|
|
595
|
+
$.index.open();
|
|
596
|
+
Alloy.Collections.album.fetch();
|
|
597
|
+
|
|
598
|
+
function cleanup() {
|
|
599
|
+
$.destroy();
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
#### Model-View Binding
|
|
604
|
+
|
|
605
|
+
To bind a single model to a component, create a global singleton or controller-specific model using the [Model tag](https://titaniumsdk.com/guide/Alloy_Framework/Alloy_Guide/Alloy_Views/Alloy_XML_Markup.html#model-element) in the XML markup of the main view and map the model attribute to the view component. To map the attribute to the view component, prefix the model name or id to the attribute, then enclose it with curly brackets or braces ('{' and '}').
|
|
606
|
+
|
|
607
|
+
To do complex transformations on the model attributes, extend the model prototype with a `transform()` function. It should return the modified model as a JSON object.
|
|
608
|
+
|
|
609
|
+
**app/models/album.js**
|
|
610
|
+
|
|
611
|
+
```javascript
|
|
612
|
+
exports.definition = {
|
|
613
|
+
config: {}, // model definition
|
|
614
|
+
extendModel(Model) {
|
|
615
|
+
_.extend(Model.prototype, {
|
|
616
|
+
transform() {
|
|
617
|
+
const transformed = this.toJSON();
|
|
618
|
+
transformed.artist = transformed.artist.toUpperCase();
|
|
619
|
+
return transformed;
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
return Model;
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
## Alloy Sync Adapters and Migrations
|
|
628
|
+
|
|
629
|
+
### Sync Adapters
|
|
630
|
+
|
|
631
|
+
In Alloy, a sync adapter allows you to store and load your models to a persistent storage device, such as an on-device database or remote server. Alloy relies on the Backbone API to sync model data to persistent storage.
|
|
632
|
+
|
|
633
|
+
#### Backbone Sync
|
|
634
|
+
|
|
635
|
+
Backbone syncs your models to persistent storage devices based on the implementation of the [Backbone.sync method](https://titaniumsdk.com/guide/Alloy_Framework/Alloy_Guide/Alloy_Models/Alloy_Sync_Adapters_and_Migrations.html). Since Backbone's primary use is for web applications, by default, the Backbone.sync method executes RESTful JSON requests to a URL specified by the Model.urlRoot or Collection.url attribute, when these classes are created.
|
|
636
|
+
|
|
637
|
+
The sync method depends on calls to other Backbone methods as described in the table below.
|
|
638
|
+
|
|
639
|
+
| **Backbone Method** | **Sync CRUD Method** | **Equivalent HTTP Method** | **Equivalent SQL Method** |
|
|
640
|
+
| ---------------------------------------------------------------- | -------------------- | -------------------------- | ------------------------- |
|
|
641
|
+
| Collection.fetch | read | GET | SELECT |
|
|
642
|
+
| Collection.create (id == null) or Collection.create (id != null) | create or update | POST or PUT | INSERT or UPDATE |
|
|
643
|
+
| Model.fetch | read | GET | SELECT |
|
|
644
|
+
| Model.save (id == null) or Model.save (id != null) | create or update | POST or PUT | INSERT or UPDATE |
|
|
645
|
+
| Model.destroy | delete | DELETE | DELETE |
|
|
646
|
+
|
|
647
|
+
#### Ready-Made Sync Adapters
|
|
648
|
+
|
|
649
|
+
Alloy provides a few ready-made sync adapters. In the 'adapter' object, set the 'type' to use one of the following:
|
|
650
|
+
|
|
651
|
+
* `sql` for the SQLite database on the Android and iOS platform.
|
|
652
|
+
* `properties` for storing data locally in the Titanium SDK context.
|
|
653
|
+
* `localStorage` for HTML5 localStorage on the Mobile Web platform. Deprecated since Alloy 1.5.0. Use the `properties` adapter instead.
|
|
654
|
+
|
|
655
|
+
These adapters are part of Alloy and are copied to the `Resources/alloy/sync` folder during compilation. These sync adapters assign the `id` attribute of the models, which means if you assign an ID when creating a model, it is overridden by any sync operations.
|
|
656
|
+
|
|
657
|
+
##### SQLite Sync Adapter Features
|
|
658
|
+
|
|
659
|
+
The `sql` sync adapter has a few extra features:
|
|
660
|
+
|
|
661
|
+
**Fetch method accepts SQL Query**
|
|
662
|
+
|
|
663
|
+
The Backbone.Collection.fetch method supports SQL queries as a parameter. Use `query` as the key in the dictionary object to create a simple query or query with a prepared statement.
|
|
664
|
+
|
|
665
|
+
```javascript
|
|
666
|
+
const library = Alloy.createCollection('book');
|
|
667
|
+
const table = library.config.adapter.collection_name;
|
|
668
|
+
// use a simple query
|
|
669
|
+
library.fetch({query:'SELECT * from ' + table + ' where author="' + searchAuthor + '"'});
|
|
670
|
+
// or a prepared statement
|
|
671
|
+
library.fetch({query: { statement: 'SELECT * from ' + table + ' where author = ?', params: [searchAuthor] }});
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
**Fetch method accepts ID attribute**
|
|
675
|
+
|
|
676
|
+
Since Alloy 1.3.0, to fetch a single model using its ID, pass a dictionary with one key-value pair, where `id` is the key and the model's ID as the value to retrieve that model, to the `fetch` method instead of using a SQL query. For example:
|
|
677
|
+
|
|
678
|
+
```
|
|
679
|
+
myModel.fetch({id: 123});
|
|
680
|
+
// is equivalent to
|
|
681
|
+
myModel.fetch({query: 'select * from ... where id = ' + 123 });
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
**Columns accept SQLite keywords**
|
|
685
|
+
|
|
686
|
+
The columns values accept SQLite keywords, such as AUTOINCREMENT and PRIMARY KEY. For example:
|
|
687
|
+
|
|
688
|
+
**app/models/book.js**
|
|
689
|
+
|
|
690
|
+
```javascript
|
|
691
|
+
exports.definition = {
|
|
692
|
+
config: {
|
|
693
|
+
"columns": {
|
|
694
|
+
"title": "TEXT",
|
|
695
|
+
"author": "TEXT",
|
|
696
|
+
"book_id": "INTEGER PRIMARY KEY AUTOINCREMENT"
|
|
697
|
+
},
|
|
698
|
+
"adapter": {
|
|
699
|
+
"type": "sql",
|
|
700
|
+
"collection_name": "books",
|
|
701
|
+
"idAttribute": "book_id"
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
**Specify columns property as primary ID**
|
|
708
|
+
|
|
709
|
+
Define the `idAttribute` key-value pair in the `config.adapter` object to use a `config.columns` key as the primary ID for the SQLite table.
|
|
710
|
+
|
|
711
|
+
**Specify a migration to use**
|
|
712
|
+
|
|
713
|
+
Define the `migration` key-value pair in the `config.adapter` object to specify the database version to use. The value of this key is the datatime code of the migration file. Alloy upgrades or rolls back the database based on this value.
|
|
714
|
+
|
|
715
|
+
**Specify a database to use**
|
|
716
|
+
|
|
717
|
+
Define the `db_name` key-value pair in the `config.adapter` object to specify the name of the database to use. If left undefined, Alloy uses the default database `_alloy_`.
|
|
718
|
+
|
|
719
|
+
**Specify a database file to preload**
|
|
720
|
+
|
|
721
|
+
Define the `db_file` key-value pair in the `config.adapter` object to specify the database file ('myfile.sqlite') to preload. Place this file in the `app/assets` directory of your Alloy project.
|
|
722
|
+
|
|
723
|
+
### Custom Sync Adapters
|
|
724
|
+
|
|
725
|
+
To create a custom sync adapter, create a JavaScript file in either `app/assets/alloy/sync` or `app/lib/alloy/sync`. During compilation, this file is copied to the `Resources/alloy/sync` folder. In the `config` object of the model file, set the `type` in the `adapter` object to the name of the JavaScript file minus the '.js' extension.
|
|
726
|
+
|
|
727
|
+
The sync adapter exports three functions:
|
|
728
|
+
|
|
729
|
+
* `module.exports.beforeModelCreate` (optional) - executes code before creating the Backbone.Model class. First passed parameter is the `config` object from the model file. Second passed parameter is the name of the Alloy Model file. Returns a `config` object.
|
|
730
|
+
|
|
731
|
+
* `module.exports.afterModelCreate` (optional) - execute code after creating the Backbone.Model class. First passed parameter is the newly created Backbone.Model class. Second passed parameter is the name of the Alloy Model file.
|
|
732
|
+
|
|
733
|
+
* `module.exports.sync` - implement the Backbone.sync method.
|
|
734
|
+
|
|
735
|
+
### Migrations
|
|
736
|
+
|
|
737
|
+
A migration is a description of incremental changes to a database, which takes your database from version 1 to version X, with a migration file for each step in the evolution of your database schema.
|
|
738
|
+
|
|
739
|
+
In Alloy, migrations are defined by JavaScript files located in the `app/migrations` folder of the project. The file should be named the same as the model JavaScript file prefixed with 'YYYYMMDDHHmmss_' (datetime code followed by an underscore), for example, `20120610049877_book.js`. Alloy applies the migrations from oldest to newest, according to the datetime code at the beginning of the file name.
|
|
740
|
+
|
|
741
|
+
The migration file contains two functions that need to be implemented: `migration.up(migrator)` and `migration.down(migrator)`, where `migrator` is a special migration object that provides references to the database and table as well as some convenient functions for table operations:
|
|
742
|
+
|
|
743
|
+
| Key | Description |
|
|
744
|
+
| ------------- | ----------------------------------------------------------------------------- |
|
|
745
|
+
| `db` | Handle to a `Ti.Database` instance. DO NOT CLOSE THIS HANDLE. |
|
|
746
|
+
| `dbname` | Name of the database. |
|
|
747
|
+
| `table` | Name of the table. Same as value of the `config.adapter.collection_name` key. |
|
|
748
|
+
| `idAttribute` | Name of the columns attribute to use as the primary key. |
|
|
749
|
+
| `createTable` | Function to create a table. Required parameter is the `columns` object. |
|
|
750
|
+
| `dropTable` | Function to drop the current table from the database. |
|
|
751
|
+
| `insertRow` | Function to insert data into the table. Useful for preloading data. |
|
|
752
|
+
| `deleteRow` | Function to delete data from the table. |
|
|
753
|
+
|
|
754
|
+
For example, the migration file below is the initial version of the database that preloads some data in the table.
|
|
755
|
+
|
|
756
|
+
**app/migrations/20120610049877_book.js**
|
|
757
|
+
|
|
758
|
+
```javascript
|
|
759
|
+
const preload_data = [
|
|
760
|
+
{title: 'To Kill a Mockingbird', author:'Harper Lee'},
|
|
761
|
+
{title: 'The Catcher in the Rye', author:'J. D. Salinger'},
|
|
762
|
+
{title: 'Of Mice and Men', author:'John Steinbeck'},
|
|
763
|
+
{title: 'Lord of the Flies', author:'William Golding'},
|
|
764
|
+
{title: 'The Great Gatsby', author:'F. Scott Fitzgerald'},
|
|
765
|
+
{title: 'Animal Farm', author:'George Orwell'}
|
|
766
|
+
];
|
|
767
|
+
|
|
768
|
+
migration.up = migrator => {
|
|
769
|
+
migrator.createTable({
|
|
770
|
+
"columns":
|
|
771
|
+
{
|
|
772
|
+
"book": "TEXT",
|
|
773
|
+
"author": "TEXT"
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
for (let i = 0; i < preload_data.length; i++) {
|
|
777
|
+
migrator.insertRow(preload_data[i]);
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
migration.down = migrator => {
|
|
782
|
+
migrator.dropTable();
|
|
783
|
+
};
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
#### Migration Rollback Example
|
|
787
|
+
|
|
788
|
+
Suppose later, you want to include some additional information for your books, such as an ISBN. The below migration file upgrades or rolls back the changes. Since SQLite does not support the DROP COLUMN operation, the migration needs to create a temporary table to hold the data, drop the new database, create the old database, then copy the data back.
|
|
789
|
+
|
|
790
|
+
**app/migrations/20130118069778_book.js**
|
|
791
|
+
|
|
792
|
+
```javascript
|
|
793
|
+
migration.up = migrator => {
|
|
794
|
+
migrator.db.execute('ALTER TABLE ' + migrator.table + ' ADD COLUMN isbn INT;');
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
migration.down = migrator => {
|
|
798
|
+
const db = migrator.db;
|
|
799
|
+
const table = migrator.table;
|
|
800
|
+
db.execute('CREATE TEMPORARY TABLE book_backup(title,author,alloy_id);')
|
|
801
|
+
db.execute('INSERT INTO book_backup SELECT title,author,alloy_id FROM ' + table + ';');
|
|
802
|
+
migrator.dropTable();
|
|
803
|
+
migrator.createTable({
|
|
804
|
+
columns: {
|
|
805
|
+
title:"TEXT",
|
|
806
|
+
author:"TEXT",
|
|
807
|
+
},
|
|
808
|
+
});
|
|
809
|
+
db.execute('INSERT INTO ' + table + ' SELECT title,author,alloy_id FROM book_backup;');
|
|
810
|
+
db.execute('DROP TABLE book_backup;');
|
|
811
|
+
};
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
## Backbone Objects without Alloy
|
|
815
|
+
|
|
816
|
+
You can use plain Backbone Collection and Model objects in place of the Alloy versions. This does not require any special Alloy or Titanium code. Use the Backbone API to create and control Backbone objects instead of using the `createCollection` and `createModel` methods. Backbone models also do not require a model configuration file.
|
|
817
|
+
|
|
818
|
+
**app/controllers/index.js**
|
|
819
|
+
|
|
820
|
+
```javascript
|
|
821
|
+
// Initialize a collection class and implement the comparator method for sorting
|
|
822
|
+
const collection = Backbone.Collection.extend({
|
|
823
|
+
comparator(model) {
|
|
824
|
+
return model.get('title');
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
// Create a new collection
|
|
829
|
+
const library = new collection([
|
|
830
|
+
{title: 'To Kill a Mockingbird', author:'Harper Lee'},
|
|
831
|
+
{title: 'The Catcher in the Rye', author:'J. D. Salinger'},
|
|
832
|
+
{title: 'Of Mice and Men', author:'John Steinbeck'},
|
|
833
|
+
{title: 'Lord of the Flies', author:'William Golding'},
|
|
834
|
+
{title: 'The Great Gatsby', author:'F. Scott Fitzgerald'},
|
|
835
|
+
{title: 'Tom Sawyer', author:'Mark Twain'},
|
|
836
|
+
{title: 'Animal Farm', author:'George Orwell'}
|
|
837
|
+
]);
|
|
838
|
+
|
|
839
|
+
// Initialize a model class
|
|
840
|
+
const modelClass = Backbone.Model.extend();
|
|
841
|
+
|
|
842
|
+
// Create a new model and add it to the collection
|
|
843
|
+
const book = new modelClass({title:'Bossypants', author:'Tina Fey'});
|
|
844
|
+
library.add(book);
|
|
845
|
+
|
|
846
|
+
// Remove the very first model from the collection
|
|
847
|
+
const model = library.at(0);
|
|
848
|
+
library.remove(model);
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
These Backbone objects cannot persist to external storage without implementing the Backbone.sync method, so if you make calls to Collection.fetch, Collection.create, Model.fetch, Model.save and Model.destroy, the application throws an error.
|
|
852
|
+
|
|
853
|
+
### Using Backbone Objects with Alloy Data Binding
|
|
854
|
+
|
|
855
|
+
You can use Alloy's Model-View binding mechanism to keep the local Backbone Models and Collections in sync with an Alloy view-controller. Follow the same directions for data binding except instead of using the `Model` or `Collections` XML tag, you need to first initialize your model or collection in the alloy.js initializer file and add it to the `Alloy.Models` or `Alloy.Collections` namespace.
|
|
856
|
+
|
|
857
|
+
**app/alloy.js**
|
|
858
|
+
|
|
859
|
+
```javascript
|
|
860
|
+
// Initialize a collection class and implement the comparator method for sorting
|
|
861
|
+
const collection = Backbone.Collection.extend({
|
|
862
|
+
comparator(model) {
|
|
863
|
+
return model.get('title');
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
// Create a new collection
|
|
868
|
+
const library = new collection([
|
|
869
|
+
{title: 'To Kill a Mockingbird', author:'Harper Lee'},
|
|
870
|
+
{title: 'The Catcher in the Rye', author:'J. D. Salinger'},
|
|
871
|
+
{title: 'Of Mice and Men', author:'John Steinbeck'},
|
|
872
|
+
{title: 'Lord of the Flies', author:'William Golding'},
|
|
873
|
+
{title: 'The Great Gatsby', author:'F. Scott Fitzgerald'},
|
|
874
|
+
{title: 'Tom Sawyer', author:'Mark Twain'},
|
|
875
|
+
{title: 'Animal Farm', author:'George Orwell'}
|
|
876
|
+
]);
|
|
877
|
+
|
|
878
|
+
// Add the collection to the global scope
|
|
879
|
+
Alloy.Collections.book = library;
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
**app/views/index.xml**
|
|
883
|
+
|
|
884
|
+
```xml
|
|
885
|
+
<!-- Markup the view the same except there is no Collection tag -->
|
|
886
|
+
<Alloy>
|
|
887
|
+
<Window class="container">
|
|
888
|
+
<TableView dataCollection="book" dataTransform="transformFunction" dataFilter="filterFunction">
|
|
889
|
+
<TableViewRow title="{title}" />
|
|
890
|
+
</TableView>
|
|
891
|
+
</Window>
|
|
892
|
+
</Alloy>
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
**app/controllers/index.js**
|
|
896
|
+
|
|
897
|
+
```javascript
|
|
898
|
+
$.index.open();
|
|
899
|
+
|
|
900
|
+
function transformFunction(model) {
|
|
901
|
+
const transform = model.toJSON();
|
|
902
|
+
transform.title = '[' + transform.title + ']';
|
|
903
|
+
transform.custom = transform.title + " by " + transform.author;
|
|
904
|
+
return transform;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function filterFunction(collection) {
|
|
908
|
+
return collection.where({author:'Mark Twain'});
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Get a reference to the library
|
|
912
|
+
const library = Alloy.Collections.book;
|
|
913
|
+
|
|
914
|
+
// Trigger the update using the 'change' event instead of the fetch method
|
|
915
|
+
library.trigger('change');
|
|
916
|
+
|
|
917
|
+
// Initialize a model class
|
|
918
|
+
const modelClass = Backbone.Model.extend();
|
|
919
|
+
|
|
920
|
+
// Create a new model and add it to the collection
|
|
921
|
+
const book = new modelClass({title:'Bossypants', author:'Tina Fey'});
|
|
922
|
+
library.add(book);
|
|
923
|
+
|
|
924
|
+
// Remove the very first model from the collection
|
|
925
|
+
const model = library.at(0);
|
|
926
|
+
library.remove(model);
|
|
927
|
+
|
|
928
|
+
// Do not forget to call destroy to unbind the event handlers created by Alloy
|
|
929
|
+
$.index.addEventListener('close', () => {
|
|
930
|
+
$.destroy();
|
|
931
|
+
});
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
## Alloy Backbone Migration
|
|
935
|
+
|
|
936
|
+
### Overview
|
|
937
|
+
|
|
938
|
+
Alloy 1.6.0 introduces support for Backbone 1.1.2. Currently, Alloy uses Backbone 0.9.2 to support its Model and Collection objects. This guide covers the changes from Backbone 0.9.2 to 1.1.2 and the modifications you may need to update your application. Note that only changes to the Backbone Collection, Event and Model APIs are discussed in this document.
|
|
939
|
+
|
|
940
|
+
Due to breaking changes from Backbone 0.9.2 to 1.1.2, Alloy still uses Backbone 0.9.2 as its default Model and Collection implementation. You will need to update the configuration file to use the newer Backbone library.
|
|
941
|
+
|
|
942
|
+
Alloy 1.10.12 adds support for Backbone 1.3.3. However, due to breaking changes in Backbone, 0.9.2 will remain the default version.
|
|
943
|
+
|
|
944
|
+
Supported versions of Backbone for Alloy 1.10.12 are 0.9.2, 1.1.2, 1.3.3.
|
|
945
|
+
|
|
946
|
+
### Setup
|
|
947
|
+
|
|
948
|
+
To use Backbone 1.1.2 to support Alloy Model and Collections objects, open the project's `./app/config.json` file and add the `backbone` key to the to the file with the value set to `1.1.2` (or `1.3.3`). You may also set this value to `0.9.2` to force support of Backbone 0.9.2.
|
|
949
|
+
|
|
950
|
+
**app/config.json**
|
|
951
|
+
|
|
952
|
+
```json
|
|
953
|
+
{
|
|
954
|
+
"global": {},
|
|
955
|
+
"env:development": {},
|
|
956
|
+
"env:test": {},
|
|
957
|
+
"env:production": {},
|
|
958
|
+
"os:android": {},
|
|
959
|
+
"os:blackberry": {},
|
|
960
|
+
"os:ios": {},
|
|
961
|
+
"os:mobileweb": {},
|
|
962
|
+
"dependencies": {},
|
|
963
|
+
"backbone": "1.1.2"
|
|
964
|
+
}
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
### Summary of Changes
|
|
968
|
+
|
|
969
|
+
#### Collection APIs
|
|
970
|
+
|
|
971
|
+
**Fetch Method Behavior Change**: Backbone Collection objects no longer emit the `reset` event after a `fetch()` call. To use old functionality, pass `{reset: true}` when calling `fetch()` or extend the Collection class.
|
|
972
|
+
|
|
973
|
+
**New Set Method**: To smartly update the contents of a Collection (adding new models, removing missing ones, and merging those already present), call `set()`.
|
|
974
|
+
|
|
975
|
+
**Return Value for Methods**: The return values of Collection's `add()`, `push()`, `remove()`, `reset()` and `shift()` methods return the changed model or list of models, instead of `this`.
|
|
976
|
+
|
|
977
|
+
**Add Method**: When invoking `add()` on a collection, passing `{merge: true}` will now cause duplicate models to have their attributes merged in to the existing models.
|
|
978
|
+
|
|
979
|
+
#### Event APIs
|
|
980
|
+
|
|
981
|
+
* All `invalid` events now pass consistent arguments. First the model in question, then the `error` object, then `options`.
|
|
982
|
+
* `Collection.sort()` now triggers a `sort` event, instead of a `reset` event.
|
|
983
|
+
* Both `sync` and `error` events within `Backbone.sync()` are now triggered regardless of the existence of success or error callbacks.
|
|
984
|
+
* While listening to a `reset` event, the list of previous models is now available in `options.previousModels`.
|
|
985
|
+
* The new Event methods `listenTo` and `stopListening` are meant for Backbone View objects. These APIs will not work with an Alloy application.
|
|
986
|
+
|
|
987
|
+
#### Model APIs
|
|
988
|
+
|
|
989
|
+
**Validation**: Model validation is now only enforced with the `save()` method. Previously, models were also validated with the `set()` method. To force validation when the `set()` method is called, pass `{validate: true}` to the method or extend the Model class.
|
|
990
|
+
|
|
991
|
+
**Other Changes**:
|
|
992
|
+
|
|
993
|
+
* Calling `destroy()` on a Model will now return `false` if the model's `isNew` is set to `true`.
|
|
994
|
+
* `Model.set()` no longer accepts another model as an argument.
|
|
995
|
+
* `url` and `urlRoot` properties may now be passed as options when instantiating a new Model.
|
|
996
|
+
* If you want to maintain current models in a collection when using `fetch` the property has changed from `{add:true}` to `{remove:false}`.
|
|
997
|
+
|
|
998
|
+
#### Silent Option
|
|
999
|
+
|
|
1000
|
+
Passing `{silent:true}` to methods now suppresses the `change:attr` events, thus a data-bound view will not be updated to reflect the changes. The sql sync adapter passed this option by default. It has been updated to no longer pass that option when Backbone 1.1.2 is used (still passed with 0.9.2).
|
|
1001
|
+
|
|
1002
|
+
### API Changes
|
|
1003
|
+
|
|
1004
|
+
#### New APIs
|
|
1005
|
+
|
|
1006
|
+
The following APIs have been added between Backbone 1.1.2 and 0.9.2.
|
|
1007
|
+
|
|
1008
|
+
| API | Type | Notes |
|
|
1009
|
+
| ----------------------------- | ------ | ------------------------------------------------------------------------ |
|
|
1010
|
+
| Backbone.request | event | Fired whenever a request begins to be made to the server. |
|
|
1011
|
+
| Backbone.Collection.findWhere | method | Same as `where()` but only returns the first result. |
|
|
1012
|
+
| Backbone.Collection.set | method | Performs a "smart" update of the collection. |
|
|
1013
|
+
| Backbone.Event.once | method | Same as `on()` except after the event is fired, the callback is removed. |
|
|
1014
|
+
| Backbone.Model.invert | method | Returns a copy of the object where keys and values are switched. |
|
|
1015
|
+
| Backbone.Model.keys | method | Returns an array of the object's keys. |
|
|
1016
|
+
| Backbone.Model.omit | method | Returns a copy of an object without the specified keys. |
|
|
1017
|
+
| Backbone.Model.pairs | method | Returns an array of `[key, value]` pairs. |
|
|
1018
|
+
| Backbone.Model.pick | method | Returns a copy of an object with the specified keys. |
|
|
1019
|
+
| Backbone.Model.values | method | Returns an array of the object's property values. |
|
|
1020
|
+
|
|
1021
|
+
#### Removed APIs
|
|
1022
|
+
|
|
1023
|
+
The following APIs have been removed between Backbone 1.1.2 and 0.9.2.
|
|
1024
|
+
|
|
1025
|
+
| API | Type | Notes |
|
|
1026
|
+
| ---------------------------- | ------ | ------------------------------------------- |
|
|
1027
|
+
| Backbone.Collection.getByCid | method | Pass the CID to the `get()` method instead. |
|
|
1028
|
+
| Backbone.Model.change | method | |
|