formotion 1.0 → 1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +9 -1
- data/LIST_OF_ROW_TYPES.md +226 -6
- data/README.md +21 -2
- data/app/app_delegate.rb +4 -3
- data/examples/KitchenSink/app/app_delegate.rb +67 -0
- data/examples/Persistence/.gitignore +5 -0
- data/examples/Persistence/README.md +3 -0
- data/examples/Persistence/Rakefile +9 -0
- data/examples/Persistence/app/app_delegate.rb +199 -0
- data/examples/Persistence/spec/main_spec.rb +9 -0
- data/gh-pages/assets/css/formotion.css +4 -0
- data/gh-pages/index.html +16 -0
- data/lib/formotion/controller/form_controller.rb +9 -1
- data/lib/formotion/form/form.rb +101 -3
- data/lib/formotion/form/form_delegate.rb +2 -0
- data/lib/formotion/model/formable.rb +15 -4
- data/lib/formotion/patch/object.rb +11 -1
- data/lib/formotion/patch/ui_text_view_placeholder.rb +8 -2
- data/lib/formotion/row/row.rb +48 -1
- data/lib/formotion/row_type/base.rb +5 -0
- data/lib/formotion/row_type/picker_row.rb +5 -1
- data/lib/formotion/row_type/subform_row.rb +37 -0
- data/lib/formotion/version.rb +1 -1
- data/spec/form/persist_spec.rb +58 -0
- data/spec/functional/formable_controller_spec.rb +9 -0
- data/spec/row_spec.rb +9 -0
- data/spec/support/ui_control_wrap_extension.rb +4 -2
- metadata +11 -4
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,15 @@
|
|
1
|
-
## 1.1 -
|
1
|
+
## 1.1 - September 20, 2012
|
2
|
+
|
3
|
+
### Features
|
4
|
+
|
5
|
+
-:display_key for subform row; when given, the value for that key in the subform's `#render` will be displayed alongside the chevron.
|
6
|
+
|
7
|
+
-`Formotion::Form.persist` and `:persist_as` to automatically synchronize your form to the disk.
|
2
8
|
|
3
9
|
### Bug Fixes
|
4
10
|
|
11
|
+
- Fix problems with inheriting Formable models.
|
12
|
+
|
5
13
|
## 1.0 - August 11, 2012
|
6
14
|
|
7
15
|
### Features
|
data/LIST_OF_ROW_TYPES.md
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
[String](#string)<br/>
|
3
3
|
[Text](#text)<br/>
|
4
4
|
[Email](#email)<br/>
|
5
|
+
[Phone](#phone)<br/>
|
5
6
|
[Number](#number)<br/>
|
6
7
|
[Date](#date)
|
7
8
|
|
@@ -11,14 +12,49 @@
|
|
11
12
|
[Check](#check)<br/>
|
12
13
|
[Slider](#slider)<br/>
|
13
14
|
[Image](#image)<br/>
|
14
|
-
[
|
15
|
+
[Option](#option)<br/>
|
16
|
+
[Picker](#picker)<br/>
|
17
|
+
[Subform](#subform)<br/>
|
18
|
+
[Template](#template)
|
15
19
|
|
16
20
|
**Buttons**<br/>
|
17
|
-
[Submit](#submit)
|
21
|
+
[Submit](#submit)<br/>
|
22
|
+
[Back](#back)<br/>
|
23
|
+
[Edit](#edit)<br/>
|
24
|
+
|
25
|
+
## General
|
26
|
+
|
27
|
+
All row types accept following properties:
|
28
|
+
```ruby
|
29
|
+
{
|
30
|
+
key: :user_name, # Unique identifier for the row
|
31
|
+
value: 'Some Value', # The initial value passed to the row
|
32
|
+
title: 'Title', # Title of the row
|
33
|
+
subtitle: 'Subtitle', # Subtitle of the row
|
34
|
+
type: :string, # The type of row (string, phone, switch, etc)
|
35
|
+
row_height: 100 # Height of the row
|
36
|
+
}
|
37
|
+
```
|
38
|
+
|
39
|
+
The `type` property will define what kind of row type will get built. It looks for any Class within the `Formotion::RowType` namespace that has the constantized name (e.g. Formotion::RowType::StringRow).
|
40
|
+
|
18
41
|
|
19
42
|
## Character based
|
20
43
|
|
44
|
+
All character based row types accept following properties:
|
45
|
+
```ruby
|
46
|
+
{
|
47
|
+
placeholder: 'James Bond', # Placeholder before a value exists
|
48
|
+
auto_correction: :no, # Disable auto correction
|
49
|
+
auto_capitalization: :none, # Disable auto capitalization
|
50
|
+
secure: true, # Enable secure input (Password)
|
51
|
+
clear_button: :while_editing, # Enable clear button
|
52
|
+
return_key: :google, # Define return key
|
53
|
+
}
|
54
|
+
```
|
55
|
+
|
21
56
|
### <a name="string"></a> String row
|
57
|
+
|
22
58
|
![String row](https://github.com/clayallsopp/formotion/wiki/row-types/string.png)
|
23
59
|
```ruby
|
24
60
|
{
|
@@ -31,6 +67,9 @@
|
|
31
67
|
}
|
32
68
|
```
|
33
69
|
|
70
|
+
The `StringRow` is a simple text input row and opens a `UIKeyboardTypeDefault` keyboard when editing.
|
71
|
+
|
72
|
+
|
34
73
|
### <a name="text"></a> Text row
|
35
74
|
![Text Row](https://github.com/clayallsopp/formotion/wiki/row-types/text.png)
|
36
75
|
|
@@ -44,6 +83,10 @@
|
|
44
83
|
}
|
45
84
|
```
|
46
85
|
|
86
|
+
The `TextRow` is a multiline text input row and opens the default keyboard when editing.
|
87
|
+
To define the height of the row set the property `row_height`.
|
88
|
+
|
89
|
+
|
47
90
|
### <a name="email"></a> Email row
|
48
91
|
![Email row](https://github.com/clayallsopp/formotion/wiki/row-types/email.png)
|
49
92
|
```ruby
|
@@ -55,6 +98,23 @@
|
|
55
98
|
}
|
56
99
|
```
|
57
100
|
|
101
|
+
The `EmailRow` is a string input row for an email address and opens a `UIKeyboardTypeEmailAddress` keyboard when editing.
|
102
|
+
|
103
|
+
|
104
|
+
### <a name="phone"></a> Phone row
|
105
|
+
![Email row](https://github.com/clayallsopp/formotion/wiki/row-types/phone.png)
|
106
|
+
```ruby
|
107
|
+
{
|
108
|
+
title: "Phone",
|
109
|
+
key: :phone,
|
110
|
+
type: :phone,
|
111
|
+
placeholder: '+01 877 412 7753'
|
112
|
+
}
|
113
|
+
```
|
114
|
+
|
115
|
+
The `PhoneRow` is a string input row for a phone number and opens a `UIKeyboardTypePhonePad` keyboard when editing.
|
116
|
+
|
117
|
+
|
58
118
|
### <a name="number"></a> Number row
|
59
119
|
![Number row](https://github.com/clayallsopp/formotion/wiki/row-types/number.png)
|
60
120
|
|
@@ -67,6 +127,9 @@
|
|
67
127
|
}
|
68
128
|
```
|
69
129
|
|
130
|
+
The `NumberRow` is a string input row for a number and opens a `UIKeyboardTypeDecimalPad` keyboard when editing.
|
131
|
+
|
132
|
+
|
70
133
|
### <a name="date"></a> Date row
|
71
134
|
![Date row](https://github.com/clayallsopp/formotion/wiki/row-types/date.png)
|
72
135
|
|
@@ -80,6 +143,19 @@
|
|
80
143
|
}
|
81
144
|
```
|
82
145
|
|
146
|
+
The `DateRow` is a string input row for a date and opens a `UIDatePicker` when editing.
|
147
|
+
You can pass one of following formats as property:
|
148
|
+
```ruby
|
149
|
+
:no # NSDateFormatterNoStyle - Specifies no style.
|
150
|
+
:short # NSDateFormatterShortStyle - Specifies a short style, typically numeric only, such as “11/23/37” or “3:30pm”.
|
151
|
+
:medium # NSDateFormatterMediumStyle - Specifies a medium style, typically with abbreviated text, such as “Nov 23, 1937”.
|
152
|
+
:long # NSDateFormatterLongStyle - Specifies a long style, typically with full text, such as “November 23, 1937” or “3:30:32pm”.
|
153
|
+
:full # NSDateFormatterFullStyle -Specifies a full style with complete details, such as “Tuesday, April 12, 1952 AD” or “3:30:42pm PST”.
|
154
|
+
```
|
155
|
+
|
156
|
+
The `value` is a `NSDate.timeIntervalSince1970` as an integer.
|
157
|
+
|
158
|
+
|
83
159
|
## Other
|
84
160
|
|
85
161
|
### <a name="static"></a> Static row
|
@@ -91,6 +167,9 @@
|
|
91
167
|
}
|
92
168
|
```
|
93
169
|
|
170
|
+
The `StaticRow` has no input funcitonality and therefore needs no `key` property. It is used for static text display like in Settings > General > About on your iOS device.
|
171
|
+
|
172
|
+
|
94
173
|
### <a name="switch"></a> Switch row
|
95
174
|
![Switch row](https://github.com/clayallsopp/formotion/wiki/row-types/switch.png)
|
96
175
|
|
@@ -102,6 +181,9 @@
|
|
102
181
|
}
|
103
182
|
```
|
104
183
|
|
184
|
+
The `SwitchRow` is a on/off switch that has the `value` true or false.
|
185
|
+
|
186
|
+
|
105
187
|
### <a name="check"></a> Check row
|
106
188
|
![Check row](https://github.com/clayallsopp/formotion/wiki/row-types/check.png)
|
107
189
|
|
@@ -114,6 +196,32 @@
|
|
114
196
|
}
|
115
197
|
```
|
116
198
|
|
199
|
+
The `CheckRow` is a row you can check and uncheck by tapping on it. It has either the `value` true or false.
|
200
|
+
You can create a radio button style group by defining a section with `select_one: true` like this:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
{
|
204
|
+
title: "Account Type",
|
205
|
+
key: :account_type,
|
206
|
+
select_one: true,
|
207
|
+
rows: [{
|
208
|
+
title: "Free",
|
209
|
+
key: :free,
|
210
|
+
type: :check,
|
211
|
+
}, {
|
212
|
+
title: "Basic",
|
213
|
+
value: true,
|
214
|
+
key: :basic,
|
215
|
+
type: :check,
|
216
|
+
}, {
|
217
|
+
title: "Pro",
|
218
|
+
key: :pro,
|
219
|
+
type: :check,
|
220
|
+
}]
|
221
|
+
}
|
222
|
+
```
|
223
|
+
|
224
|
+
|
117
225
|
### <a name="slider"></a> Slider row
|
118
226
|
![Slider row](https://github.com/clayallsopp/formotion/wiki/row-types/slider.png)
|
119
227
|
|
@@ -127,6 +235,8 @@
|
|
127
235
|
}
|
128
236
|
```
|
129
237
|
|
238
|
+
The `SliderRow` takes a ruby range as `range` property that defines the min and max value of the slider.
|
239
|
+
|
130
240
|
### <a name="image"></a> Image row
|
131
241
|
![Image row](https://github.com/clayallsopp/formotion/wiki/row-types/image.png)
|
132
242
|
|
@@ -143,10 +253,91 @@
|
|
143
253
|
|
144
254
|
```ruby
|
145
255
|
{
|
146
|
-
title: "
|
147
|
-
key: :
|
256
|
+
title: "Account Type",
|
257
|
+
key: :account_type,
|
148
258
|
type: :options,
|
149
|
-
items: [
|
259
|
+
items: ["Free", "Basic", "Pro"]
|
260
|
+
}
|
261
|
+
```
|
262
|
+
|
263
|
+
|
264
|
+
### <a name="picker"></a> Picker row
|
265
|
+
![Picker row](https://github.com/clayallsopp/formotion/wiki/row-types/picker.png)
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
{
|
269
|
+
title: "Picker",
|
270
|
+
key: :pick,
|
271
|
+
type: :picker,
|
272
|
+
items: ["Ruby", "Motion", "Rocks"],
|
273
|
+
value: "Motion"
|
274
|
+
}
|
275
|
+
```
|
276
|
+
|
277
|
+
|
278
|
+
### <a name="subform"></a> Subform row
|
279
|
+
![Subform row](https://github.com/clayallsopp/formotion/wiki/row-types/subform.gif)
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
{
|
283
|
+
title: "Advanced",
|
284
|
+
type: :subform,
|
285
|
+
key: :advanced,
|
286
|
+
subform: {
|
287
|
+
title: "Advanced",
|
288
|
+
sections: [{
|
289
|
+
title: "Advanced Settings",
|
290
|
+
rows: [{
|
291
|
+
title: "Server URL",
|
292
|
+
key: :server,
|
293
|
+
placeholder: "example.com",
|
294
|
+
type: :string,
|
295
|
+
}]
|
296
|
+
}, {
|
297
|
+
rows: [{
|
298
|
+
title: 'Back',
|
299
|
+
type: :back
|
300
|
+
}]
|
301
|
+
}]
|
302
|
+
}
|
303
|
+
}
|
304
|
+
```
|
305
|
+
|
306
|
+
Use a `:display_key` to show the value of the subform in the row:
|
307
|
+
|
308
|
+
![Subform row with display](http://i.imgur.com/FoDo1.png)
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
{
|
312
|
+
title: "Subform",
|
313
|
+
subtitle: "With display_key",
|
314
|
+
type: :subform,
|
315
|
+
display_key: :type,
|
316
|
+
subform: {
|
317
|
+
...
|
318
|
+
}
|
319
|
+
}
|
320
|
+
```
|
321
|
+
|
322
|
+
### <a name="template"></a> Template row
|
323
|
+
![Template row](https://github.com/clayallsopp/formotion/wiki/row-types/template.gif)
|
324
|
+
|
325
|
+
```ruby
|
326
|
+
{
|
327
|
+
title: 'Your nicknames',
|
328
|
+
rows: [{
|
329
|
+
title: "Add nickname",
|
330
|
+
key: :nicknames,
|
331
|
+
type: :template,
|
332
|
+
value: ['Nici', 'Sam'],
|
333
|
+
template: {
|
334
|
+
title: 'Nickname',
|
335
|
+
type: :string,
|
336
|
+
placeholder: 'Enter here',
|
337
|
+
indented: true,
|
338
|
+
deletable: true
|
339
|
+
}
|
340
|
+
}]
|
150
341
|
}
|
151
342
|
```
|
152
343
|
|
@@ -160,4 +351,33 @@
|
|
160
351
|
title: "Submit",
|
161
352
|
type: :submit,
|
162
353
|
}
|
163
|
-
```
|
354
|
+
```
|
355
|
+
|
356
|
+
The `SubmitRow` triggers the `form.submit` which triggers the defined `on_submit` callback.
|
357
|
+
|
358
|
+
|
359
|
+
### <a name="back"></a> Back row
|
360
|
+
![Submit row](https://github.com/clayallsopp/formotion/wiki/row-types/back.png)
|
361
|
+
|
362
|
+
```ruby
|
363
|
+
{
|
364
|
+
title: "Back",
|
365
|
+
type: :back,
|
366
|
+
}
|
367
|
+
```
|
368
|
+
|
369
|
+
The `BackRow` is used in subforms to go back to the parent form.
|
370
|
+
|
371
|
+
|
372
|
+
### <a name="edit"></a> Edit row
|
373
|
+
![Submit row](https://github.com/clayallsopp/formotion/wiki/row-types/edit.png)
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
{
|
377
|
+
title: "Edit",
|
378
|
+
alt_title: "View",
|
379
|
+
type: :edit,
|
380
|
+
}
|
381
|
+
```
|
382
|
+
|
383
|
+
The `EditRow` is used to switch between edit and view mode. It's mainly used for the `TemplateRow`.
|
data/README.md
CHANGED
@@ -109,7 +109,7 @@ self.navigationController.pushViewController(@controller, animated: true)
|
|
109
109
|
|
110
110
|
### Data Types
|
111
111
|
|
112
|
-
See [the visual list of support row types](https://github.com/clayallsopp/formotion/
|
112
|
+
See [the visual list of support row types](https://github.com/clayallsopp/formotion/wiki/List-of-all-the-row-types).
|
113
113
|
|
114
114
|
To add your own, check [the guide to adding new row types](https://github.com/clayallsopp/formotion/blob/master/NEW_ROW_TYPES.md).
|
115
115
|
|
@@ -155,6 +155,25 @@ end
|
|
155
155
|
|
156
156
|
`form#submit` just triggers `form#on_submit`.
|
157
157
|
|
158
|
+
### Persistence
|
159
|
+
|
160
|
+
You can easily synchronize a `Form`'s state to disk using the `persist_as` key in conjunction with the `persist` method. When your user edits the form, any changes will be immediately saved. For example:
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
@form = Formotion::Form.persist({
|
164
|
+
persist_as: :settings,
|
165
|
+
sections: ...
|
166
|
+
})
|
167
|
+
```
|
168
|
+
|
169
|
+
This will load the form if it exists, or create it if necessary. If you use `Formotion::Form.new`, then the form will not be loaded and any changes you make will override existing saved forms.
|
170
|
+
|
171
|
+
Your form values and state are automatically persisted across application loads, no save buttons needed.
|
172
|
+
|
173
|
+
To reset the form, `persist` it and call `reset`, which restores it to the original state.
|
174
|
+
|
175
|
+
View the [Persistence Example](./formotion/tree/master/examples/Persistence) to see it in action.
|
176
|
+
|
158
177
|
## Forking
|
159
178
|
|
160
179
|
Feel free to fork and submit pull requests! And if you end up using Formotion in your app, I'd love to hear about your experience.
|
@@ -162,4 +181,4 @@ Feel free to fork and submit pull requests! And if you end up using Formotion in
|
|
162
181
|
## Todo
|
163
182
|
|
164
183
|
- Not very efficient right now (creates a unique reuse idenitifer for each cell)
|
165
|
-
- Styling/overriding the form for custom UITableViewDelegate/Data Source behaviors.
|
184
|
+
- Styling/overriding the form for custom UITableViewDelegate/Data Source behaviors.
|
data/app/app_delegate.rb
CHANGED
@@ -68,6 +68,7 @@ class AppDelegate
|
|
68
68
|
title: "Account type",
|
69
69
|
type: :subform,
|
70
70
|
key: :account,
|
71
|
+
display_key: :type,
|
71
72
|
subform: {
|
72
73
|
title: "Account Type",
|
73
74
|
sections: [{
|
@@ -75,16 +76,16 @@ class AppDelegate
|
|
75
76
|
select_one: true,
|
76
77
|
rows: [{
|
77
78
|
title: "Free",
|
78
|
-
key: :
|
79
|
+
key: :Free,
|
79
80
|
type: :check,
|
80
81
|
}, {
|
81
82
|
title: "Basic",
|
82
83
|
value: true,
|
83
|
-
key: :
|
84
|
+
key: :Basic,
|
84
85
|
type: :check,
|
85
86
|
}, {
|
86
87
|
title: "Pro",
|
87
|
-
key: :
|
88
|
+
key: :Pro,
|
88
89
|
type: :check,
|
89
90
|
}]
|
90
91
|
}, {
|
@@ -116,6 +116,73 @@ class AppDelegate
|
|
116
116
|
key: :c,
|
117
117
|
type: :check,
|
118
118
|
}]
|
119
|
+
}, {
|
120
|
+
title: "Subforms",
|
121
|
+
rows: [{
|
122
|
+
title: "Subform",
|
123
|
+
subtitle: "With display_key",
|
124
|
+
type: :subform,
|
125
|
+
key: :subform,
|
126
|
+
display_key: :type,
|
127
|
+
subform: {
|
128
|
+
title: "Account Type",
|
129
|
+
sections: [{
|
130
|
+
key: :type,
|
131
|
+
select_one: true,
|
132
|
+
rows: [{
|
133
|
+
title: "Free",
|
134
|
+
key: :Free,
|
135
|
+
type: :check,
|
136
|
+
}, {
|
137
|
+
title: "Basic",
|
138
|
+
value: true,
|
139
|
+
key: :Basic,
|
140
|
+
type: :check,
|
141
|
+
}, {
|
142
|
+
title: "Pro",
|
143
|
+
key: :Pro,
|
144
|
+
type: :check,
|
145
|
+
}]
|
146
|
+
}, {
|
147
|
+
rows: [{
|
148
|
+
title: "Advanced",
|
149
|
+
type: :subform,
|
150
|
+
key: :advanced,
|
151
|
+
subform: {
|
152
|
+
title: "Advanced",
|
153
|
+
sections: [{
|
154
|
+
key: :currency,
|
155
|
+
select_one: true,
|
156
|
+
rows: [{
|
157
|
+
title: "USD",
|
158
|
+
value: true,
|
159
|
+
key: :usd,
|
160
|
+
type: :check,
|
161
|
+
}, {
|
162
|
+
title: "EUR",
|
163
|
+
key: :eur,
|
164
|
+
type: :check,
|
165
|
+
}, {
|
166
|
+
title: "CHF",
|
167
|
+
key: :chf,
|
168
|
+
type: :check,
|
169
|
+
}]
|
170
|
+
}, {
|
171
|
+
rows: [{
|
172
|
+
title: 'Back',
|
173
|
+
type: :back
|
174
|
+
}]
|
175
|
+
}]
|
176
|
+
}
|
177
|
+
}]
|
178
|
+
}, {
|
179
|
+
rows: [{
|
180
|
+
title: 'Back',
|
181
|
+
type: :back
|
182
|
+
}]
|
183
|
+
}]
|
184
|
+
}
|
185
|
+
}]
|
119
186
|
}, {
|
120
187
|
rows: [{
|
121
188
|
title: "Submit",
|
@@ -0,0 +1,199 @@
|
|
1
|
+
class AppDelegate
|
2
|
+
def application(application, didFinishLaunchingWithOptions:launchOptions)
|
3
|
+
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
|
4
|
+
|
5
|
+
@form = Formotion::Form.persist({
|
6
|
+
title: "Persist Example",
|
7
|
+
persist_as: "example",
|
8
|
+
sections: [{
|
9
|
+
title: "Section Title",
|
10
|
+
footer: "This is the footer for the section. It's good for displaying detailed data about the section.",
|
11
|
+
rows: [{
|
12
|
+
title: "Static",
|
13
|
+
type: :static,
|
14
|
+
}, {
|
15
|
+
title: "Email",
|
16
|
+
key: :email,
|
17
|
+
placeholder: "me@mail.com",
|
18
|
+
type: :email,
|
19
|
+
auto_correction: :no,
|
20
|
+
auto_capitalization: :none
|
21
|
+
}, {
|
22
|
+
title: "Gender",
|
23
|
+
key: :gender,
|
24
|
+
type: :options,
|
25
|
+
items: ['Female', 'Male']
|
26
|
+
}, {
|
27
|
+
title: "Password",
|
28
|
+
key: :password,
|
29
|
+
placeholder: "required",
|
30
|
+
type: :string,
|
31
|
+
secure: true
|
32
|
+
}, {
|
33
|
+
title: "Phone",
|
34
|
+
key: :phone,
|
35
|
+
placeholder: "555-555-5555",
|
36
|
+
type: :phone,
|
37
|
+
auto_correction: :no,
|
38
|
+
auto_capitalization: :none
|
39
|
+
}, {
|
40
|
+
title: "Number",
|
41
|
+
key: :number,
|
42
|
+
placeholder: "12345",
|
43
|
+
type: :number,
|
44
|
+
auto_correction: :no,
|
45
|
+
auto_capitalization: :none
|
46
|
+
}, {
|
47
|
+
title: "Subtitle",
|
48
|
+
subtitle: "Confirmation",
|
49
|
+
key: :confirm,
|
50
|
+
placeholder: "required",
|
51
|
+
type: :string,
|
52
|
+
secure: true
|
53
|
+
}, {
|
54
|
+
title: "Row Height",
|
55
|
+
key: :row_height,
|
56
|
+
placeholder: "60px",
|
57
|
+
type: :string,
|
58
|
+
row_height: 60
|
59
|
+
}, {
|
60
|
+
title: "Text",
|
61
|
+
key: :text,
|
62
|
+
type: :text,
|
63
|
+
placeholder: "Enter your text here",
|
64
|
+
row_height: 100
|
65
|
+
}, {
|
66
|
+
title: "Check",
|
67
|
+
key: :check,
|
68
|
+
type: :check,
|
69
|
+
value: true
|
70
|
+
}, {
|
71
|
+
title: "Remember?",
|
72
|
+
key: :remember,
|
73
|
+
type: :switch,
|
74
|
+
}, {
|
75
|
+
title: "Date Full",
|
76
|
+
subtitle: "w/ :value",
|
77
|
+
value: 326937600,
|
78
|
+
key: :date_long,
|
79
|
+
type: :date,
|
80
|
+
format: :full
|
81
|
+
}, {
|
82
|
+
title: "Date Medium",
|
83
|
+
subtitle: "w/ :value",
|
84
|
+
value: 1341273600,
|
85
|
+
key: :date_medium,
|
86
|
+
type: :date,
|
87
|
+
format: :medium
|
88
|
+
}, {
|
89
|
+
title: "Date Short",
|
90
|
+
subtitle: "w/ :placeholder",
|
91
|
+
placeholder: "DOB",
|
92
|
+
key: :date_short,
|
93
|
+
type: :date,
|
94
|
+
format: :short
|
95
|
+
}, {
|
96
|
+
title: "Slider",
|
97
|
+
key: :slider,
|
98
|
+
type: :slider,
|
99
|
+
range: (1..100),
|
100
|
+
value: 25
|
101
|
+
}]
|
102
|
+
}, {
|
103
|
+
title: "Subforms",
|
104
|
+
rows: [{
|
105
|
+
title: "Subform",
|
106
|
+
subtitle: "With display_key",
|
107
|
+
type: :subform,
|
108
|
+
key: :subform,
|
109
|
+
display_key: :type,
|
110
|
+
subform: {
|
111
|
+
title: "Account Type",
|
112
|
+
sections: [{
|
113
|
+
key: :type,
|
114
|
+
select_one: true,
|
115
|
+
rows: [{
|
116
|
+
title: "Free",
|
117
|
+
key: :Free,
|
118
|
+
type: :check,
|
119
|
+
}, {
|
120
|
+
title: "Basic",
|
121
|
+
value: true,
|
122
|
+
key: :Basic,
|
123
|
+
type: :check,
|
124
|
+
}, {
|
125
|
+
title: "Pro",
|
126
|
+
key: :Pro,
|
127
|
+
type: :check,
|
128
|
+
}]
|
129
|
+
}, {
|
130
|
+
rows: [{
|
131
|
+
title: "Advanced",
|
132
|
+
type: :subform,
|
133
|
+
key: :advanced,
|
134
|
+
subform: {
|
135
|
+
title: "Advanced",
|
136
|
+
sections: [{
|
137
|
+
key: :currency,
|
138
|
+
select_one: true,
|
139
|
+
rows: [{
|
140
|
+
title: "USD",
|
141
|
+
value: true,
|
142
|
+
key: :usd,
|
143
|
+
type: :check,
|
144
|
+
}, {
|
145
|
+
title: "EUR",
|
146
|
+
key: :eur,
|
147
|
+
type: :check,
|
148
|
+
}, {
|
149
|
+
title: "CHF",
|
150
|
+
key: :chf,
|
151
|
+
type: :check,
|
152
|
+
}]
|
153
|
+
}, {
|
154
|
+
rows: [{
|
155
|
+
title: 'Back',
|
156
|
+
type: :back
|
157
|
+
}]
|
158
|
+
}]
|
159
|
+
}
|
160
|
+
}]
|
161
|
+
}, {
|
162
|
+
rows: [{
|
163
|
+
title: 'Back',
|
164
|
+
type: :back
|
165
|
+
}]
|
166
|
+
}]
|
167
|
+
}
|
168
|
+
}]
|
169
|
+
}]
|
170
|
+
})
|
171
|
+
|
172
|
+
@view_controller = Formotion::FormController.alloc.initWithForm(@form)
|
173
|
+
@view_controller.form.on_submit do |form|
|
174
|
+
form.active_row && form.active_row.text_field.resignFirstResponder
|
175
|
+
alert = UIAlertView.alloc.init
|
176
|
+
alert.title = "@form.render"
|
177
|
+
alert.message = @form.render.to_s
|
178
|
+
alert.addButtonWithTitle("OK")
|
179
|
+
alert.show
|
180
|
+
end
|
181
|
+
|
182
|
+
@view_controller.navigationItem.leftBarButtonItem = UIBarButtonItem.alloc.initWithTitle("Reset", style: UIBarButtonItemStyleBordered, target:self, action:'reset_form')
|
183
|
+
|
184
|
+
@navigation_controller = UINavigationController.alloc.initWithRootViewController(@view_controller)
|
185
|
+
|
186
|
+
@window.rootViewController = @navigation_controller
|
187
|
+
@window.makeKeyAndVisible
|
188
|
+
|
189
|
+
true
|
190
|
+
end
|
191
|
+
|
192
|
+
def submit
|
193
|
+
@form.submit
|
194
|
+
end
|
195
|
+
|
196
|
+
def reset_form
|
197
|
+
@form.reset
|
198
|
+
end
|
199
|
+
end
|
data/gh-pages/index.html
CHANGED
@@ -199,6 +199,22 @@
|
|
199
199
|
|
200
200
|
<hr />
|
201
201
|
|
202
|
+
<div class="row">
|
203
|
+
<div class="span12" id="maintained">
|
204
|
+
<h1>Maintained By</h1>
|
205
|
+
</div>
|
206
|
+
<div class="span3">
|
207
|
+
<a href="https://twitter.com/clayallsopp" class="twitter-follow-button" data-show-count="false" data-size="large">Follow @clayallsopp</a>
|
208
|
+
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
|
209
|
+
</div>
|
210
|
+
<div class="span3">
|
211
|
+
<a href="https://twitter.com/mordaroso" class="twitter-follow-button" data-show-count="false" data-size="large">Follow @mordaroso</a>
|
212
|
+
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
|
213
|
+
</div>
|
214
|
+
</div>
|
215
|
+
|
216
|
+
<hr />
|
217
|
+
|
202
218
|
|
203
219
|
</div> <!-- /container -->
|
204
220
|
</body>
|
@@ -17,6 +17,7 @@ module Formotion
|
|
17
17
|
self.form = form
|
18
18
|
#self.view.setEditing true, animated: true
|
19
19
|
self.tableView.allowsSelectionDuringEditing = true
|
20
|
+
|
20
21
|
self
|
21
22
|
end
|
22
23
|
|
@@ -49,6 +50,13 @@ module Formotion
|
|
49
50
|
@form.controller = self
|
50
51
|
end
|
51
52
|
|
53
|
+
|
54
|
+
def viewWillAppear(animated)
|
55
|
+
super
|
56
|
+
|
57
|
+
self.tableView.reloadData
|
58
|
+
end
|
59
|
+
|
52
60
|
# Subview Methods
|
53
61
|
def push_subform(form)
|
54
62
|
@subform_controller = self.class.alloc.initWithForm(form)
|
@@ -68,4 +76,4 @@ module Formotion
|
|
68
76
|
end
|
69
77
|
end
|
70
78
|
end
|
71
|
-
end
|
79
|
+
end
|
data/lib/formotion/form/form.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
module Formotion
|
2
2
|
class Form < Formotion::Base
|
3
|
+
extend BW::KVO
|
4
|
+
include BW::KVO
|
5
|
+
|
3
6
|
PROPERTIES = [
|
4
7
|
# By default, Formotion::Controller will set it's title to this
|
5
8
|
# (so navigation bars will reflect it).
|
6
9
|
:title,
|
7
10
|
# If you want to have some internal id to track the form.
|
8
|
-
:id
|
11
|
+
:id,
|
12
|
+
# [STRING/SYMBOL] used to store your form's state on disk
|
13
|
+
:persist_as
|
9
14
|
]
|
10
15
|
PROPERTIES.each {|prop|
|
11
16
|
attr_accessor prop
|
@@ -15,6 +20,12 @@ module Formotion
|
|
15
20
|
# them to be pased in the hash
|
16
21
|
SERIALIZE_PROPERTIES = PROPERTIES + [:sections]
|
17
22
|
|
23
|
+
def self.persist(params = {})
|
24
|
+
form = new(params)
|
25
|
+
form.open && form.save
|
26
|
+
form
|
27
|
+
end
|
28
|
+
|
18
29
|
def initialize(params = {})
|
19
30
|
# super takes care of initializing the ::PROPERTIES in params
|
20
31
|
super
|
@@ -104,7 +115,6 @@ module Formotion
|
|
104
115
|
# as shown above.
|
105
116
|
def submit
|
106
117
|
if @on_submit_callback.nil?
|
107
|
-
p "`Form#submit` invoked without a callback, doing nothing."
|
108
118
|
return
|
109
119
|
end
|
110
120
|
|
@@ -184,7 +194,95 @@ module Formotion
|
|
184
194
|
kv
|
185
195
|
end
|
186
196
|
|
197
|
+
#########################
|
198
|
+
# Persisting Forms
|
199
|
+
|
200
|
+
# loads the given settings into the the form, and
|
201
|
+
# places observers to save on changes
|
202
|
+
def open
|
203
|
+
@form_observer ||= lambda { |form, saved_form|
|
204
|
+
form.send(:each_row) do |row, index|
|
205
|
+
s_index = row.section.index
|
206
|
+
temp_row = saved_form.sections[s_index].rows[index]
|
207
|
+
|
208
|
+
if row.subform?
|
209
|
+
saved_subform = temp_row.subform.to_form
|
210
|
+
@form_observer.call(row.subform.to_form, saved_subform)
|
211
|
+
else
|
212
|
+
row.value = temp_row.value
|
213
|
+
|
214
|
+
observe(row, "value") do |old_value, new_value|
|
215
|
+
self.save
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
}
|
220
|
+
|
221
|
+
saved_hash = load_state
|
222
|
+
@form_observer.call(self, Formotion::Form.new(saved_hash))
|
223
|
+
end
|
224
|
+
|
225
|
+
# places hash of values into application persistance
|
226
|
+
def save
|
227
|
+
App::Persistence[persist_key] = to_hash.to_archived_data
|
228
|
+
App::Persistence[original_persist_key] ||= self.to_hash.to_archived_data
|
229
|
+
end
|
230
|
+
|
231
|
+
def reset
|
232
|
+
App::Persistence[persist_key] = nil
|
233
|
+
|
234
|
+
@form_resetter ||= lambda { |form, original_form|
|
235
|
+
form.send(:each_row) do |row, index|
|
236
|
+
s_index = row.section.index
|
237
|
+
temp_row = original_form.sections[s_index].rows[index]
|
238
|
+
|
239
|
+
if row.subform?
|
240
|
+
original_subform = temp_row.subform.to_form
|
241
|
+
@form_resetter.call(row.subform.to_form, original_subform)
|
242
|
+
else
|
243
|
+
row.value = temp_row.value
|
244
|
+
end
|
245
|
+
end
|
246
|
+
}
|
247
|
+
|
248
|
+
temp_form = Formotion::Form.new(App::Persistence[original_persist_key].unarchive)
|
249
|
+
@form_resetter.call(self, temp_form)
|
250
|
+
|
251
|
+
self.save
|
252
|
+
end
|
253
|
+
|
187
254
|
private
|
255
|
+
def persist_key
|
256
|
+
"FORMOTION_#{self.persist_as}"
|
257
|
+
end
|
258
|
+
|
259
|
+
def original_persist_key
|
260
|
+
"#{persist_key}_ORIGINAL"
|
261
|
+
end
|
262
|
+
|
263
|
+
def load_state
|
264
|
+
begin
|
265
|
+
state = App::Persistence[persist_key] && App::Persistence[persist_key].unarchive
|
266
|
+
rescue
|
267
|
+
self.save
|
268
|
+
end
|
269
|
+
|
270
|
+
state ||= self.to_hash
|
271
|
+
end
|
272
|
+
|
273
|
+
def each_row(&block)
|
274
|
+
self.sections.each_with_index do |section, s_index|
|
275
|
+
section.rows.each_with_index do |row, index|
|
276
|
+
case block.arity
|
277
|
+
when 1
|
278
|
+
block.call(row)
|
279
|
+
when 2
|
280
|
+
block.call(row, index)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
188
286
|
def recursive_delete_nil(h)
|
189
287
|
delete_empty = Proc.new { |k, v|
|
190
288
|
v.delete_if(&delete_empty) if v.kind_of?(Hash)
|
@@ -193,4 +291,4 @@ module Formotion
|
|
193
291
|
h.delete_if &delete_empty
|
194
292
|
end
|
195
293
|
end
|
196
|
-
end
|
294
|
+
end
|
@@ -5,6 +5,21 @@ module Formotion
|
|
5
5
|
end
|
6
6
|
|
7
7
|
module ClassMethods
|
8
|
+
INHERITABLE_ATTRIBUTES = [:form_properties, :form_title].each do |prop|
|
9
|
+
attr_accessor prop
|
10
|
+
end
|
11
|
+
|
12
|
+
def inherited(subclass)
|
13
|
+
INHERITABLE_ATTRIBUTES.each do |inheritable_attribute|
|
14
|
+
instance_var = "@#{inheritable_attribute}"
|
15
|
+
subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def form_properties
|
20
|
+
@form_properties ||= []
|
21
|
+
end
|
22
|
+
|
8
23
|
# Relates a property to a RowType.
|
9
24
|
# @param property is the name of the attribute to KVO
|
10
25
|
# @param row_type is the Formotion::RowType to use for that attribute
|
@@ -19,10 +34,6 @@ module Formotion
|
|
19
34
|
self.form_properties << { property: property, row_type: row_type}.merge(options)
|
20
35
|
end
|
21
36
|
|
22
|
-
def form_properties
|
23
|
-
@form_properties ||= []
|
24
|
-
end
|
25
|
-
|
26
37
|
# Sets the top bar title for this model
|
27
38
|
# EX
|
28
39
|
# form_title "Some Settings"
|
@@ -6,4 +6,14 @@ class Object
|
|
6
6
|
self.class.send(:alias_method, "old_#{method.to_s}".to_sym, method)
|
7
7
|
self.instance_eval &block
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
|
+
def to_archived_data
|
11
|
+
NSKeyedArchiver.archivedDataWithRootObject(self)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class NSData
|
16
|
+
def unarchive
|
17
|
+
NSKeyedUnarchiver.unarchiveObjectWithData(self)
|
18
|
+
end
|
19
|
+
end
|
@@ -19,7 +19,13 @@ class UITextView
|
|
19
19
|
def setup
|
20
20
|
@foreground_observer = NSNotificationCenter.defaultCenter.observe UITextViewTextDidChangeNotification do |notification|
|
21
21
|
updateShouldDrawPlaceholder
|
22
|
-
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :old_dealloc, :dealloc
|
26
|
+
def dealloc
|
27
|
+
NSNotificationCenter.defaultCenter.unobserve @foreground_observer
|
28
|
+
old_dealloc
|
23
29
|
end
|
24
30
|
|
25
31
|
alias_method :old_drawRect, :drawRect
|
@@ -62,4 +68,4 @@ class UITextView
|
|
62
68
|
|
63
69
|
self.setNeedsDisplay if (prev != @shouldDrawPlaceholder)
|
64
70
|
end
|
65
|
-
end
|
71
|
+
end
|
data/lib/formotion/row/row.rb
CHANGED
@@ -53,6 +53,11 @@ module Formotion
|
|
53
53
|
# A hash for a Form used for subforms
|
54
54
|
# DEFAULT is nil
|
55
55
|
:subform,
|
56
|
+
# Used in a subform row; when given,
|
57
|
+
# will display the value of the matching key
|
58
|
+
# of the subform's rendering.
|
59
|
+
# DEFAULT is nil
|
60
|
+
:display_key,
|
56
61
|
# A hash for a Row used for templates
|
57
62
|
# DEFAULT is nil
|
58
63
|
:template,
|
@@ -160,6 +165,19 @@ module Formotion
|
|
160
165
|
object.button?
|
161
166
|
end
|
162
167
|
|
168
|
+
def subform?
|
169
|
+
self.type == :subform
|
170
|
+
end
|
171
|
+
|
172
|
+
#########################
|
173
|
+
# getter overrides
|
174
|
+
def items
|
175
|
+
if @items.respond_to?(:call)
|
176
|
+
@items = @items.call
|
177
|
+
end
|
178
|
+
@items
|
179
|
+
end
|
180
|
+
|
163
181
|
#########################
|
164
182
|
# setter overrides
|
165
183
|
def type=(type)
|
@@ -167,6 +185,20 @@ module Formotion
|
|
167
185
|
@type = type
|
168
186
|
end
|
169
187
|
|
188
|
+
def range=(range)
|
189
|
+
if range
|
190
|
+
case range
|
191
|
+
when Range
|
192
|
+
# all good
|
193
|
+
when Array
|
194
|
+
range = Range.new(range[0], range[1])
|
195
|
+
else
|
196
|
+
raise Formotion::InvalidClassError, "Attempted Row.range = #{range.inspect} should be of type Range or Array"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
@range = range
|
200
|
+
end
|
201
|
+
|
170
202
|
def return_key=(value)
|
171
203
|
@return_key = const_int_get("UIReturnKey", value)
|
172
204
|
end
|
@@ -205,10 +237,24 @@ module Formotion
|
|
205
237
|
cell
|
206
238
|
end
|
207
239
|
|
240
|
+
# Called on every tableView:cellForRowAtIndexPath:
|
241
|
+
# so keep implementation details minimal
|
242
|
+
def update_cell(cell)
|
243
|
+
self.object.update_cell(cell)
|
244
|
+
cell
|
245
|
+
end
|
246
|
+
|
208
247
|
#########################
|
209
248
|
# Retreiving data
|
210
249
|
def to_hash
|
211
|
-
super
|
250
|
+
h = super
|
251
|
+
if h[:range] && h[:range].is_a?(Range)
|
252
|
+
h[:range] = [self.range.begin, self.range.end]
|
253
|
+
end
|
254
|
+
if subform?
|
255
|
+
h[:subform] = self.subform.to_form.to_hash
|
256
|
+
end
|
257
|
+
h
|
212
258
|
end
|
213
259
|
|
214
260
|
def subform=(subform)
|
@@ -225,6 +271,7 @@ module Formotion
|
|
225
271
|
@hash_subform ||= self
|
226
272
|
end
|
227
273
|
end
|
274
|
+
@subform
|
228
275
|
end
|
229
276
|
|
230
277
|
private
|
@@ -43,6 +43,11 @@ module Formotion
|
|
43
43
|
def after_build(cell)
|
44
44
|
end
|
45
45
|
|
46
|
+
# Called on every tableView:cellForRowAtIndexPath:
|
47
|
+
# so keep implementation details minimal
|
48
|
+
def update_cell(cell)
|
49
|
+
end
|
50
|
+
|
46
51
|
# method gets triggered when tableView(tableView, didSelectRowAtIndexPath:indexPath)
|
47
52
|
# in UITableViewDelegate is executed
|
48
53
|
def on_select(tableView, tableViewDelegate)
|
@@ -18,7 +18,11 @@ module Formotion
|
|
18
18
|
|
19
19
|
if self.row.value
|
20
20
|
picker_row = self.row.items.index(row.value)
|
21
|
-
|
21
|
+
if picker_row
|
22
|
+
picker.selectRow(picker_row, inComponent:0, animated:false)
|
23
|
+
else
|
24
|
+
warn "Picker item '#{row.value}' not found in #{row.items.inspect} for '#{row.key}'"
|
25
|
+
end
|
22
26
|
end
|
23
27
|
|
24
28
|
picker
|
@@ -2,8 +2,36 @@ module Formotion
|
|
2
2
|
module RowType
|
3
3
|
class SubformRow < Base
|
4
4
|
|
5
|
+
LABEL_TAG=1001
|
6
|
+
|
5
7
|
def build_cell(cell)
|
6
8
|
cell.accessoryType = cell.editingAccessoryType = UITableViewCellAccessoryDisclosureIndicator
|
9
|
+
|
10
|
+
cell.contentView.addSubview(self.display_key_label)
|
11
|
+
|
12
|
+
cell.swizzle(:layoutSubviews) do
|
13
|
+
def layoutSubviews
|
14
|
+
old_layoutSubviews
|
15
|
+
|
16
|
+
formotion_label = self.viewWithTag(LABEL_TAG)
|
17
|
+
formotion_label.sizeToFit
|
18
|
+
|
19
|
+
field_frame = formotion_label.frame
|
20
|
+
# HARDCODED CONSTANT
|
21
|
+
field_frame.origin.x = self.contentView.frame.size.width - field_frame.size.width - 10
|
22
|
+
field_frame.origin.y = ((self.contentView.frame.size.height - field_frame.size.height) / 2.0).round
|
23
|
+
formotion_label.frame = field_frame
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
display_key_label.highlightedTextColor = cell.textLabel.highlightedTextColor
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_cell(cell)
|
31
|
+
subform = row.subform.to_form
|
32
|
+
if row.display_key and subform.render[row.display_key]
|
33
|
+
self.display_key_label.text = subform.render[row.display_key]
|
34
|
+
end
|
7
35
|
end
|
8
36
|
|
9
37
|
def on_select(tableView, tableViewDelegate)
|
@@ -11,6 +39,15 @@ module Formotion
|
|
11
39
|
row.form.controller.push_subform(subform)
|
12
40
|
end
|
13
41
|
|
42
|
+
def display_key_label
|
43
|
+
@display_key_label ||= begin
|
44
|
+
label = UILabel.alloc.initWithFrame(CGRectZero)
|
45
|
+
label.textColor = "#385387".to_color
|
46
|
+
label.tag = LABEL_TAG
|
47
|
+
label.backgroundColor = UIColor.clearColor
|
48
|
+
label
|
49
|
+
end
|
50
|
+
end
|
14
51
|
end
|
15
52
|
end
|
16
53
|
end
|
data/lib/formotion/version.rb
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
describe "Form Persisting" do
|
2
|
+
|
3
|
+
it "works" do
|
4
|
+
f = Formotion::Form.persist({
|
5
|
+
persist_as: "test_#{rand(255)}",
|
6
|
+
sections: [
|
7
|
+
rows: [ {
|
8
|
+
key: "first",
|
9
|
+
type: "string",
|
10
|
+
value: "initial value"
|
11
|
+
}
|
12
|
+
]
|
13
|
+
]
|
14
|
+
})
|
15
|
+
|
16
|
+
r = f.sections[0].rows[0]
|
17
|
+
r.value = "new value"
|
18
|
+
|
19
|
+
saved = Formotion::Form.new(f.send(:load_state))
|
20
|
+
saved.sections[0].rows[0].value.should == r.value
|
21
|
+
|
22
|
+
f.reset
|
23
|
+
r.value.should == "initial value"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "works with subforms" do
|
27
|
+
f = Formotion::Form.persist({
|
28
|
+
persist_as: "test_#{rand(255)}",
|
29
|
+
sections: [
|
30
|
+
rows: [ {
|
31
|
+
key: :subform,
|
32
|
+
type: :subform,
|
33
|
+
title: "Subform",
|
34
|
+
subform: {
|
35
|
+
title: "New Page",
|
36
|
+
sections: [
|
37
|
+
rows: [{
|
38
|
+
key: "second",
|
39
|
+
type: "string",
|
40
|
+
value: "initial value"
|
41
|
+
}]
|
42
|
+
]
|
43
|
+
}
|
44
|
+
}
|
45
|
+
]
|
46
|
+
]
|
47
|
+
})
|
48
|
+
|
49
|
+
r = f.sections[0].rows[0].subform.to_form.sections[0].rows[0]
|
50
|
+
r.value = "new value"
|
51
|
+
|
52
|
+
saved = Formotion::Form.new(f.send(:load_state))
|
53
|
+
saved.sections[0].rows[0].subform.to_form.sections[0].rows[0].value.should == r.value
|
54
|
+
|
55
|
+
f.reset
|
56
|
+
r.value.should == "initial value"
|
57
|
+
end
|
58
|
+
end
|
@@ -7,6 +7,15 @@ class TestModel
|
|
7
7
|
form_property :my_number, :number, title: "My Number", :transform => lambda { |value| value.to_i + 10 }
|
8
8
|
end
|
9
9
|
|
10
|
+
class AdditionalTestModel
|
11
|
+
include Formotion::Formable
|
12
|
+
|
13
|
+
attr_accessor :my_other_name, :my_other_number
|
14
|
+
|
15
|
+
form_property :my_other_name, :string
|
16
|
+
form_property :my_other_number, :number
|
17
|
+
end
|
18
|
+
|
10
19
|
describe "FormableController" do
|
11
20
|
tests Formotion::FormableController
|
12
21
|
|
data/spec/row_spec.rb
CHANGED
@@ -22,4 +22,13 @@ describe "Rows" do
|
|
22
22
|
r.title?.should == true
|
23
23
|
}
|
24
24
|
end
|
25
|
+
|
26
|
+
it "should allow arrays and blocks for items" do
|
27
|
+
r = Formotion::Row.new()
|
28
|
+
r.items = [1, 2, 3]
|
29
|
+
r.items.should == [1, 2, 3]
|
30
|
+
|
31
|
+
r.items = lambda { [1, 2, 3].reverse }
|
32
|
+
r.items.should == [3, 2, 1]
|
33
|
+
end
|
25
34
|
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
module UIControlWrap
|
2
|
-
# adds ability to trigger a defined callback
|
2
|
+
# adds ability to trigger a defined callback
|
3
|
+
# that was previously defined by #when
|
3
4
|
def trigger(events)
|
4
5
|
@callback ||= {}
|
5
|
-
|
6
|
+
p "Callback #{@callback}"
|
7
|
+
@callback[events].map(&:call) if @callback.has_key? events
|
6
8
|
end
|
7
9
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: formotion
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '1.
|
4
|
+
version: '1.1'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bubble-wrap
|
@@ -77,6 +77,11 @@ files:
|
|
77
77
|
- examples/Login/app/login_controller.rb
|
78
78
|
- examples/Login/app/user_controller.rb
|
79
79
|
- examples/Login/spec/main_spec.rb
|
80
|
+
- examples/Persistence/.gitignore
|
81
|
+
- examples/Persistence/README.md
|
82
|
+
- examples/Persistence/Rakefile
|
83
|
+
- examples/Persistence/app/app_delegate.rb
|
84
|
+
- examples/Persistence/spec/main_spec.rb
|
80
85
|
- gh-pages/assets/css/bootstrap-responsive.css
|
81
86
|
- gh-pages/assets/css/bootstrap-responsive.min.css
|
82
87
|
- gh-pages/assets/css/bootstrap.css
|
@@ -138,6 +143,7 @@ files:
|
|
138
143
|
- lib/formotion/row_type/text_row.rb
|
139
144
|
- lib/formotion/section/section.rb
|
140
145
|
- lib/formotion/version.rb
|
146
|
+
- spec/form/persist_spec.rb
|
141
147
|
- spec/form_spec.rb
|
142
148
|
- spec/formable_spec.rb
|
143
149
|
- spec/functional/character_spec.rb
|
@@ -188,7 +194,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
188
194
|
version: '0'
|
189
195
|
segments:
|
190
196
|
- 0
|
191
|
-
hash:
|
197
|
+
hash: 4038020492425824062
|
192
198
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
193
199
|
none: false
|
194
200
|
requirements:
|
@@ -197,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
197
203
|
version: '0'
|
198
204
|
segments:
|
199
205
|
- 0
|
200
|
-
hash:
|
206
|
+
hash: 4038020492425824062
|
201
207
|
requirements: []
|
202
208
|
rubyforge_project:
|
203
209
|
rubygems_version: 1.8.23
|
@@ -205,6 +211,7 @@ signing_key:
|
|
205
211
|
specification_version: 3
|
206
212
|
summary: Making iOS Forms insanely great with RubyMotion
|
207
213
|
test_files:
|
214
|
+
- spec/form/persist_spec.rb
|
208
215
|
- spec/form_spec.rb
|
209
216
|
- spec/formable_spec.rb
|
210
217
|
- spec/functional/character_spec.rb
|