formotion 1.0 → 1.1
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.
- 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
|

|
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
|

|
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
|

|
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
|
+

|
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
|

|
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
|

|
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
|

|
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
|

|
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
|

|
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
|

|
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
|
+

|
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
|
+

|
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
|
+

|
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
|
+

|
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
|
+

|
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
|
+

|
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
|