formotion 0.0.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/.gitignore +10 -0
- data/Formotion.gemspec +19 -0
- data/README.md +168 -0
- data/Rakefile +11 -0
- data/app/app_delegate.rb +68 -0
- data/examples/KitchenSink/.gitignore +5 -0
- data/examples/KitchenSink/README.md +5 -0
- data/examples/KitchenSink/Rakefile +9 -0
- data/examples/KitchenSink/app/app_delegate.rb +101 -0
- data/examples/KitchenSink/spec/main_spec.rb +9 -0
- data/lib/formotion.rb +6 -0
- data/lib/formotion/base.rb +44 -0
- data/lib/formotion/exceptions.rb +20 -0
- data/lib/formotion/form.rb +173 -0
- data/lib/formotion/form_controller.rb +102 -0
- data/lib/formotion/form_delegate.rb +87 -0
- data/lib/formotion/patch/ui_text_field.rb +158 -0
- data/lib/formotion/row.rb +211 -0
- data/lib/formotion/row_cell_builder.rb +168 -0
- data/lib/formotion/row_type.rb +33 -0
- data/lib/formotion/section.rb +108 -0
- data/lib/formotion/version.rb +3 -0
- data/spec/form_spec.rb +106 -0
- data/spec/row_spec.rb +25 -0
- data/spec/section_spec.rb +20 -0
- metadata +105 -0
data/.gitignore
ADDED
data/Formotion.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/formotion/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "formotion"
|
6
|
+
s.version = Formotion::VERSION
|
7
|
+
s.authors = ["Clay Allsopp"]
|
8
|
+
s.email = ["clay.allsopp@gmail.com"]
|
9
|
+
s.homepage = "https://github.com/clayallsopp/Formotion"
|
10
|
+
s.summary = "Making iOS Forms insanely great with RubyMotion"
|
11
|
+
s.description = "Making iOS Forms insanely great with RubyMotion"
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split($\)
|
14
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
|
17
|
+
s.add_dependency "bubble-wrap"
|
18
|
+
s.add_development_dependency 'rake'
|
19
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
# Formotion
|
2
|
+
|
3
|
+
Make this:
|
4
|
+
|
5
|
+

|
6
|
+
|
7
|
+
using just this:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
@form = Formotion::Form.new({
|
11
|
+
sections: [{
|
12
|
+
title: "Register",
|
13
|
+
rows: [{
|
14
|
+
title: "Email",
|
15
|
+
key: :email,
|
16
|
+
placeholder: "me@mail.com",
|
17
|
+
type: :email,
|
18
|
+
auto_correction: :no,
|
19
|
+
auto_capitalization: :none
|
20
|
+
}, {
|
21
|
+
title: "Password",
|
22
|
+
key: :password,
|
23
|
+
placeholder: "required",
|
24
|
+
type: :string,
|
25
|
+
secure: true
|
26
|
+
}, {
|
27
|
+
title: "Password",
|
28
|
+
subtitle: "Confirmation"
|
29
|
+
key: :confirm,
|
30
|
+
placeholder: "required",
|
31
|
+
type: :string,
|
32
|
+
secure: true
|
33
|
+
}, {
|
34
|
+
title: "Remember?",
|
35
|
+
key: :remember,
|
36
|
+
type: :switch,
|
37
|
+
}]
|
38
|
+
}, {
|
39
|
+
title: "Account Type",
|
40
|
+
key: :account_type,
|
41
|
+
select_one: true,
|
42
|
+
rows: [{
|
43
|
+
title: "Free",
|
44
|
+
key: :free,
|
45
|
+
type: :check,
|
46
|
+
}, {
|
47
|
+
title: "Basic",
|
48
|
+
value: true,
|
49
|
+
key: :basic,
|
50
|
+
type: :check,
|
51
|
+
}, {
|
52
|
+
title: "Pro",
|
53
|
+
key: :pro,
|
54
|
+
type: :check,
|
55
|
+
}]
|
56
|
+
}, {
|
57
|
+
rows: [{
|
58
|
+
title: "Sign Up",
|
59
|
+
type: :submit,
|
60
|
+
}]
|
61
|
+
}]
|
62
|
+
})
|
63
|
+
|
64
|
+
@form_controller = FormController.alloc.initWithForm(@form)
|
65
|
+
@window.rootViewController = @form_controller
|
66
|
+
```
|
67
|
+
|
68
|
+
And after the user enters some data, do this:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
@form.render
|
72
|
+
=> {:email=>"me@email.com", :password=>"password",
|
73
|
+
:confirm=>"password", :remember=>true, :account_type=>:pro}
|
74
|
+
```
|
75
|
+
|
76
|
+
## Installation
|
77
|
+
|
78
|
+
`gem install formotion`
|
79
|
+
|
80
|
+
In your `Rakefile`:
|
81
|
+
|
82
|
+
`require 'formotion'`
|
83
|
+
|
84
|
+
## Usage
|
85
|
+
|
86
|
+
### Initialize
|
87
|
+
|
88
|
+
You can initialize a `Formotion::Form` using either a hash (as above) or the DSL:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
form = Formotion::Form.new
|
92
|
+
|
93
|
+
form.build_section do |section|
|
94
|
+
section.title = "Title"
|
95
|
+
|
96
|
+
section.build_row do |row|
|
97
|
+
row.title = "Label"
|
98
|
+
row.subtitle = "Placeholder"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
Then attach it to a `Formotion::Controller` and you're ready to rock and roll:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
@controller = Formotion::Controller.alloc.initWithForm(form)
|
107
|
+
self.navigationController.pushViewController(@controller, animated: true)
|
108
|
+
```
|
109
|
+
|
110
|
+
### Retreive
|
111
|
+
|
112
|
+
You have `form#submit`, `form#on_submit`, and `form#render` at your disposal. Here's an example:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
class PeopleController < Formotion::Controller
|
116
|
+
def viewDidLoad
|
117
|
+
self.navigationItem.rightBarButtonItem = UIBarButtonItem.alloc.initWithBarButtonSystemItem(UIBarButtonSystemItemSave, target:self, action:'submit')
|
118
|
+
end
|
119
|
+
|
120
|
+
def submit
|
121
|
+
data = self.form.render
|
122
|
+
|
123
|
+
person.name = data[:name]
|
124
|
+
person.address = data[:address]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
Why would you use `form#on_submit`? In case you want to use `Formotion::RowType::SUBMIT`. Ex:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
@form = Formotion::Form.new({
|
133
|
+
sections: [{
|
134
|
+
...
|
135
|
+
}, {
|
136
|
+
rows: [{
|
137
|
+
title: "Save",
|
138
|
+
type: Formotion::RowType::SUBMIT
|
139
|
+
}]
|
140
|
+
}]
|
141
|
+
})
|
142
|
+
|
143
|
+
@form.on_submit do |form|
|
144
|
+
# do something with form.render
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
`form#submit` just triggers `form#on_submit`.
|
149
|
+
|
150
|
+
### Data Types
|
151
|
+
|
152
|
+
Formotion current supports static and editable text, switches, and checkboxes.
|
153
|
+
|
154
|
+
`Formotion::Form`, `Formotion::Section`, and `Formotion::Row` all respond to a `::PROPERTIES` attribute. These are settable as an attribute (ie `section.title = 'title'`) or in the initialization hash (ie `{sections: [{title: 'title', ...}]}`). Check the comments in the 3 main files (`form.rb`, `section.rb`, and `row.rb` for details on what these do).
|
155
|
+
|
156
|
+
See the [KitchenSink example](https://github.com/clayallsopp/formotion/tree/master/examples/KitchenSink) for a bunch of options in action.
|
157
|
+
|
158
|
+
## Forking
|
159
|
+
|
160
|
+
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.
|
161
|
+
|
162
|
+
## Todo
|
163
|
+
|
164
|
+
- Not very efficient right now (creates a unique reuse idenitifer for each cell)
|
165
|
+
- More data entry types (dates, etc)
|
166
|
+
- More tests
|
167
|
+
- Styling/overriding the form for custom UITableViewDelegate/Data Source behaviors.
|
168
|
+
- Custom cell text field alignments
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
$:.unshift("/Library/RubyMotion/lib")
|
2
|
+
require 'motion/project'
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
|
5
|
+
$:.unshift("./lib/")
|
6
|
+
require './lib/formotion'
|
7
|
+
|
8
|
+
Motion::Project::App.setup do |app|
|
9
|
+
# Use `rake config' to see complete project settings.
|
10
|
+
app.name = 'Formotion'
|
11
|
+
end
|
data/app/app_delegate.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
class AppDelegate
|
2
|
+
def application(application, didFinishLaunchingWithOptions:launchOptions)
|
3
|
+
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
|
4
|
+
|
5
|
+
form = Formotion::Form.new({
|
6
|
+
sections: [{
|
7
|
+
title: "Register",
|
8
|
+
rows: [{
|
9
|
+
title: "Email",
|
10
|
+
key: :email,
|
11
|
+
placeholder: "me@mail.com",
|
12
|
+
type: :email,
|
13
|
+
auto_correction: :no,
|
14
|
+
auto_capitalization: :none
|
15
|
+
}, {
|
16
|
+
title: "Password",
|
17
|
+
key: :password,
|
18
|
+
placeholder: "required",
|
19
|
+
type: :string,
|
20
|
+
secure: true
|
21
|
+
}, {
|
22
|
+
title: "Password",
|
23
|
+
subtitle: "Confirmation",
|
24
|
+
key: :confirm,
|
25
|
+
placeholder: "required",
|
26
|
+
type: :string,
|
27
|
+
secure: true
|
28
|
+
}, {
|
29
|
+
title: "Switch",
|
30
|
+
key: :switch,
|
31
|
+
type: :switch,
|
32
|
+
}]
|
33
|
+
}, {
|
34
|
+
title: "Account Type",
|
35
|
+
key: :account_type,
|
36
|
+
select_one: true,
|
37
|
+
rows: [{
|
38
|
+
title: "Free",
|
39
|
+
key: :free,
|
40
|
+
type: :check,
|
41
|
+
}, {
|
42
|
+
title: "Basic",
|
43
|
+
value: true,
|
44
|
+
key: :basic,
|
45
|
+
type: :check,
|
46
|
+
}, {
|
47
|
+
title: "Pro",
|
48
|
+
key: :pro,
|
49
|
+
type: :check,
|
50
|
+
}]
|
51
|
+
}, {
|
52
|
+
rows: [{
|
53
|
+
title: "Sign Up",
|
54
|
+
type: :submit,
|
55
|
+
}]
|
56
|
+
}]
|
57
|
+
})
|
58
|
+
|
59
|
+
@view_controller = Formotion::FormController.alloc.initWithForm(form)
|
60
|
+
@view_controller.form.on_submit do |form|
|
61
|
+
p @view_controller.form.render
|
62
|
+
end
|
63
|
+
|
64
|
+
@window.rootViewController = @view_controller
|
65
|
+
@window.makeKeyAndVisible
|
66
|
+
true
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
class AppDelegate
|
2
|
+
def application(application, didFinishLaunchingWithOptions:launchOptions)
|
3
|
+
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
|
4
|
+
|
5
|
+
@form = Formotion::Form.new({
|
6
|
+
title: "Kitchen Sink",
|
7
|
+
sections: [{
|
8
|
+
title: "Section Title",
|
9
|
+
rows: [{
|
10
|
+
title: "Static",
|
11
|
+
type: :static,
|
12
|
+
}, {
|
13
|
+
title: "Email",
|
14
|
+
key: :email,
|
15
|
+
placeholder: "me@mail.com",
|
16
|
+
type: :email,
|
17
|
+
auto_correction: :no,
|
18
|
+
auto_capitalization: :none
|
19
|
+
}, {
|
20
|
+
title: "Password",
|
21
|
+
key: :password,
|
22
|
+
placeholder: "required",
|
23
|
+
type: :string,
|
24
|
+
secure: true
|
25
|
+
}, {
|
26
|
+
title: "Phone",
|
27
|
+
key: :phone,
|
28
|
+
placeholder: "555-555-5555",
|
29
|
+
type: :phone,
|
30
|
+
auto_correction: :no,
|
31
|
+
auto_capitalization: :none
|
32
|
+
}, {
|
33
|
+
title: "Number",
|
34
|
+
key: :number,
|
35
|
+
placeholder: "12345",
|
36
|
+
type: :number,
|
37
|
+
auto_correction: :no,
|
38
|
+
auto_capitalization: :none
|
39
|
+
}, {
|
40
|
+
title: "Subtitle",
|
41
|
+
subtitle: "Confirmation",
|
42
|
+
key: :confirm,
|
43
|
+
placeholder: "required",
|
44
|
+
type: :string,
|
45
|
+
secure: true
|
46
|
+
}, {
|
47
|
+
title: "Remember?",
|
48
|
+
key: :remember,
|
49
|
+
type: :switch,
|
50
|
+
}]
|
51
|
+
}, {
|
52
|
+
title: "Select One",
|
53
|
+
key: :account_type,
|
54
|
+
select_one: true,
|
55
|
+
rows: [{
|
56
|
+
title: "A",
|
57
|
+
key: :a,
|
58
|
+
type: :check,
|
59
|
+
}, {
|
60
|
+
title: "B (value: true)",
|
61
|
+
value: true,
|
62
|
+
key: :b,
|
63
|
+
type: :check,
|
64
|
+
}, {
|
65
|
+
title: "C",
|
66
|
+
key: :c,
|
67
|
+
type: :check,
|
68
|
+
}]
|
69
|
+
}, {
|
70
|
+
rows: [{
|
71
|
+
title: "Submit",
|
72
|
+
type: :submit,
|
73
|
+
}]
|
74
|
+
}]
|
75
|
+
})
|
76
|
+
|
77
|
+
@navigation_controller = UINavigationController.alloc.init
|
78
|
+
|
79
|
+
@view_controller = Formotion::FormController.alloc.initWithForm(@form)
|
80
|
+
@view_controller.form.on_submit do |form|
|
81
|
+
alert = UIAlertView.alloc.init
|
82
|
+
alert.title = "@form.render"
|
83
|
+
alert.message = @form.render.to_s
|
84
|
+
alert.addButtonWithTitle("OK")
|
85
|
+
alert.show
|
86
|
+
end
|
87
|
+
|
88
|
+
@view_controller.navigationItem.rightBarButtonItem = UIBarButtonItem.alloc.initWithBarButtonSystemItem(UIBarButtonSystemItemSave, target:self, action:'submit')
|
89
|
+
|
90
|
+
@navigation_controller = UINavigationController.alloc.initWithRootViewController(@view_controller)
|
91
|
+
|
92
|
+
@window.rootViewController = @navigation_controller
|
93
|
+
@window.makeKeyAndVisible
|
94
|
+
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
def submit
|
99
|
+
@form.submit
|
100
|
+
end
|
101
|
+
end
|
data/lib/formotion.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Formotion
|
2
|
+
class Base
|
3
|
+
def initialize(params = {})
|
4
|
+
params.each { |key, value|
|
5
|
+
if self.class.const_get(:PROPERTIES).member? key.to_sym
|
6
|
+
self.send((key.to_s + "=:").to_sym, value)
|
7
|
+
end
|
8
|
+
}
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_hash
|
12
|
+
h = {}
|
13
|
+
self.class.const_get(:PROPERTIES).each { |prop|
|
14
|
+
val = self.send(prop)
|
15
|
+
h[prop] = val if val
|
16
|
+
}
|
17
|
+
h
|
18
|
+
end
|
19
|
+
|
20
|
+
# NSCoding + NSCopying
|
21
|
+
def encodeWithCoder(encoder)
|
22
|
+
self.class.const_get(:SERIALIZE_PROPERTIES).each {|prop|
|
23
|
+
encoder.encodeObject(self.send(prop), forKey: prop.to_s)
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def initWithCoder(decoder)
|
28
|
+
self.init
|
29
|
+
self.class.const_get(:SERIALIZE_PROPERTIES).each {|prop|
|
30
|
+
value = decoder.decodeObjectForKey(prop.to_s)
|
31
|
+
self.send((prop.to_s + "=:").to_sym, value) if not value.nil?
|
32
|
+
}
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def copyWithZone(zone)
|
37
|
+
copy = self.class.allocWithZone(zone).init
|
38
|
+
self.class.const_get(:SERIALIZE_PROPERTIES).each {|prop|
|
39
|
+
copy.send((prop.to_s + "=:").to_sym, self.send(prop))
|
40
|
+
}
|
41
|
+
copy
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|