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.
@@ -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
@@ -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
- [Options](#options)
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: "options",
147
- key: :options,
256
+ title: "Account Type",
257
+ key: :account_type,
148
258
  type: :options,
149
- items: ['One', 'Two']
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/blob/master/LIST_OF_ROW_TYPES.md).
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.
@@ -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: :free,
79
+ key: :Free,
79
80
  type: :check,
80
81
  }, {
81
82
  title: "Basic",
82
83
  value: true,
83
- key: :basic,
84
+ key: :Basic,
84
85
  type: :check,
85
86
  }, {
86
87
  title: "Pro",
87
- key: :pro,
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,5 @@
1
+ .repl_history
2
+ build
3
+ resources/*.nib
4
+ resources/*.momd
5
+ resources/*.storyboardc
@@ -0,0 +1,3 @@
1
+ # Persistence example in Formotion
2
+
3
+ ![Persistence](http://i.imgur.com/fdCfV.png)
@@ -0,0 +1,9 @@
1
+ # -*- coding: utf-8 -*-
2
+ $:.unshift("/Library/RubyMotion/lib")
3
+ require 'motion/project'
4
+ require '../../lib/formotion'
5
+
6
+ Motion::Project::App.setup do |app|
7
+ # Use `rake config' to see complete project settings.
8
+ app.name = 'KitchenSink'
9
+ end
@@ -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
@@ -0,0 +1,9 @@
1
+ describe "Application 'KitchenSink'" do
2
+ before do
3
+ @app = UIApplication.sharedApplication
4
+ end
5
+
6
+ it "has one window" do
7
+ @app.windows.size.should == 1
8
+ end
9
+ end
@@ -114,4 +114,8 @@ hr.hidden-desktop {
114
114
 
115
115
  .install-instruct {
116
116
  margin-bottom: 20px;
117
+ }
118
+
119
+ #maintained {
120
+ margin-bottom: 20px;
117
121
  }
@@ -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
@@ -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
@@ -65,6 +65,8 @@ module Formotion
65
65
  row.make_cell
66
66
  end
67
67
 
68
+ row.update_cell(cell)
69
+
68
70
  cell
69
71
  end
70
72
 
@@ -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
- end
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
@@ -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
- picker.selectRow(picker_row, inComponent:0, animated:false)
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
@@ -1,3 +1,3 @@
1
1
  module Formotion
2
- VERSION = "1.0"
2
+ VERSION = "1.1"
3
3
  end
@@ -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
 
@@ -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 that was previously defined by when
2
+ # adds ability to trigger a defined callback
3
+ # that was previously defined by #when
3
4
  def trigger(events)
4
5
  @callback ||= {}
5
- @callback[events].call if @callback.has_key? events
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.0'
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-08-12 00:00:00.000000000 Z
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: 84446603769905079
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: 84446603769905079
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