formotion 0.5.1 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/CHANGELOG.md +24 -0
  2. data/LIST_OF_ROW_TYPES.md +1 -1
  3. data/Rakefile +20 -5
  4. data/app/app_delegate.rb +89 -17
  5. data/examples/FormModel/.gitignore +5 -0
  6. data/examples/FormModel/Rakefile +9 -0
  7. data/examples/FormModel/app/app_delegate.rb +11 -0
  8. data/examples/FormModel/app/user.rb +18 -0
  9. data/examples/FormModel/app/users_controller.rb +52 -0
  10. data/examples/FormModel/spec/main_spec.rb +9 -0
  11. data/examples/KitchenSink/app/app_delegate.rb +2 -2
  12. data/gh-pages/assets/css/bootstrap-responsive.css +815 -0
  13. data/gh-pages/assets/css/bootstrap-responsive.min.css +9 -0
  14. data/gh-pages/assets/css/bootstrap.css +4983 -0
  15. data/gh-pages/assets/css/bootstrap.min.css +9 -0
  16. data/gh-pages/assets/css/formotion.css +117 -0
  17. data/gh-pages/assets/css/pygments.css +62 -0
  18. data/gh-pages/assets/img/glyphicons-halflings-white.png +0 -0
  19. data/gh-pages/assets/img/glyphicons-halflings.png +0 -0
  20. data/gh-pages/assets/js/bootstrap-alert.js +90 -0
  21. data/gh-pages/assets/js/bootstrap-button.js +96 -0
  22. data/gh-pages/assets/js/bootstrap-carousel.js +169 -0
  23. data/gh-pages/assets/js/bootstrap-collapse.js +157 -0
  24. data/gh-pages/assets/js/bootstrap-dropdown.js +100 -0
  25. data/gh-pages/assets/js/bootstrap-modal.js +218 -0
  26. data/gh-pages/assets/js/bootstrap-popover.js +98 -0
  27. data/gh-pages/assets/js/bootstrap-scrollspy.js +151 -0
  28. data/gh-pages/assets/js/bootstrap-tab.js +135 -0
  29. data/gh-pages/assets/js/bootstrap-tooltip.js +275 -0
  30. data/gh-pages/assets/js/bootstrap-transition.js +61 -0
  31. data/gh-pages/assets/js/bootstrap-typeahead.js +285 -0
  32. data/gh-pages/assets/js/bootstrap.js +1825 -0
  33. data/gh-pages/assets/js/bootstrap.min.js +6 -0
  34. data/gh-pages/index.html +205 -0
  35. data/lib/formotion.rb +15 -2
  36. data/lib/formotion/base.rb +5 -5
  37. data/lib/formotion/{form_controller.rb → controller/form_controller.rb} +22 -3
  38. data/lib/formotion/controller/formable_controller.rb +21 -0
  39. data/lib/formotion/form/form.rb +31 -8
  40. data/lib/formotion/form/form_delegate.rb +26 -3
  41. data/lib/formotion/model/formable.rb +78 -0
  42. data/lib/formotion/row/row.rb +54 -21
  43. data/lib/formotion/row/row_cell_builder.rb +1 -1
  44. data/lib/formotion/row_type/back_row.rb +11 -0
  45. data/lib/formotion/row_type/base.rb +42 -1
  46. data/lib/formotion/row_type/button.rb +30 -0
  47. data/lib/formotion/row_type/check_row.rb +9 -5
  48. data/lib/formotion/row_type/date_row.rb +5 -0
  49. data/lib/formotion/row_type/edit_row.rb +14 -0
  50. data/lib/formotion/row_type/image_row.rb +7 -7
  51. data/lib/formotion/row_type/options_row.rb +18 -8
  52. data/lib/formotion/row_type/slider_row.rb +14 -5
  53. data/lib/formotion/row_type/string_row.rb +17 -5
  54. data/lib/formotion/row_type/subform_row.rb +16 -0
  55. data/lib/formotion/row_type/submit_row.rb +1 -24
  56. data/lib/formotion/row_type/switch_row.rb +10 -2
  57. data/lib/formotion/row_type/template_row.rb +77 -0
  58. data/lib/formotion/row_type/text_row.rb +12 -4
  59. data/lib/formotion/section/section.rb +9 -1
  60. data/lib/formotion/version.rb +1 -1
  61. data/spec/form_spec.rb +24 -0
  62. data/spec/formable_spec.rb +100 -0
  63. data/spec/functional/formable_controller_spec.rb +47 -0
  64. data/spec/functional/image_row_spec.rb +2 -2
  65. data/spec/functional/subform_row.rb +33 -0
  66. data/spec/functional/template_row_spec.rb +57 -0
  67. data/spec/helpers/row_test_helpers.rb +26 -0
  68. data/spec/row_type/back_spec.rb +31 -0
  69. data/spec/row_type/check_spec.rb +9 -9
  70. data/spec/row_type/date_spec.rb +3 -10
  71. data/spec/row_type/email_spec.rb +1 -9
  72. data/spec/row_type/image_spec.rb +3 -12
  73. data/spec/row_type/number_spec.rb +1 -9
  74. data/spec/row_type/options_spec.rb +18 -10
  75. data/spec/row_type/phone_spec.rb +1 -9
  76. data/spec/row_type/picker_spec.rb +2 -11
  77. data/spec/row_type/slider_spec.rb +11 -10
  78. data/spec/row_type/static_spec.rb +1 -9
  79. data/spec/row_type/string_spec.rb +13 -9
  80. data/spec/row_type/subform_spec.rb +47 -0
  81. data/spec/row_type/submit_spec.rb +1 -9
  82. data/spec/row_type/switch_spec.rb +9 -9
  83. data/spec/row_type/template_spec.rb +14 -0
  84. data/spec/row_type/text_spec.rb +9 -9
  85. metadata +58 -6
@@ -1,13 +1,14 @@
1
1
  module Formotion
2
2
  module RowType
3
3
  class SliderRow < Base
4
+ include BW::KVO
4
5
 
5
6
  SLIDER_VIEW_TAG = 1200
6
7
 
7
8
  def build_cell(cell)
8
9
  cell.selectionStyle = UITableViewCellSelectionStyleNone
9
10
  slideView = UISlider.alloc.initWithFrame(CGRectZero)
10
- cell.accessoryView = slideView
11
+ cell.accessoryView = cell.editingAccessoryView = slideView
11
12
  row.range ||= (1..10)
12
13
  slideView.minimumValue = row.range.first
13
14
  slideView.maximumValue = row.range.last
@@ -16,9 +17,17 @@ module Formotion
16
17
  slideView.accessibilityLabel = row.title + " Slider"
17
18
 
18
19
  slideView.when(UIControlEventValueChanged) do
19
- row.value = slideView.value
20
+ break_with_semaphore do
21
+ row.value = slideView.value
22
+ end
23
+ end
24
+ observe(self.row, "value") do |old_value, new_value|
25
+ break_with_semaphore do
26
+ slideView.setValue(row.value, animated:false)
27
+ end
20
28
  end
21
29
 
30
+
22
31
  cell.swizzle(:layoutSubviews) do
23
32
  def layoutSubviews
24
33
  old_layoutSubviews
@@ -29,9 +38,9 @@ module Formotion
29
38
 
30
39
  field_frame = formotion_field.frame
31
40
  field_frame.origin.y = 10
32
- field_frame.origin.x = self.textLabel.frame.origin.x + self.textLabel.frame.size.width + 20
33
- field_frame.size.width = self.frame.size.width - field_frame.origin.x - 20
34
- field_frame.size.height = self.frame.size.height - 20
41
+ field_frame.origin.x = self.textLabel.frame.origin.x + self.textLabel.frame.size.width + FIELD_BUFFER
42
+ field_frame.size.width = self.frame.size.width - field_frame.origin.x - FIELD_BUFFER
43
+ field_frame.size.height = self.frame.size.height - FIELD_BUFFER
35
44
  formotion_field.frame = field_frame
36
45
  end
37
46
  end
@@ -1,6 +1,7 @@
1
1
  module Formotion
2
2
  module RowType
3
3
  class StringRow < Base
4
+ include BW::KVO
4
5
 
5
6
  # The new UITextField in a UITableViewCell
6
7
  # will be assigned this tag, if applicable.
@@ -21,8 +22,11 @@ module Formotion
21
22
  field = UITextField.alloc.initWithFrame(CGRectZero)
22
23
  field.tag = TEXT_FIELD_TAG
23
24
 
24
- field.placeholder = row.placeholder
25
- field.text = row.value.to_s
25
+ observe(self.row, "value") do |old_value, new_value|
26
+ break_with_semaphore do
27
+ update_text_field(new_value)
28
+ end
29
+ end
26
30
 
27
31
  field.clearButtonMode = UITextFieldViewModeWhileEditing
28
32
  field.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter
@@ -47,13 +51,15 @@ module Formotion
47
51
  formotion_field.sizeToFit
48
52
 
49
53
  field_frame = formotion_field.frame
50
- field_frame.origin.x = self.textLabel.frame.origin.x + self.textLabel.frame.size.width + 20
54
+ field_frame.origin.x = self.textLabel.frame.origin.x + self.textLabel.frame.size.width + FIELD_BUFFER
51
55
  field_frame.origin.y = ((self.frame.size.height - field_frame.size.height) / 2.0).round
52
- field_frame.size.width = self.frame.size.width - field_frame.origin.x - 20
56
+ field_frame.size.width = self.frame.size.width - field_frame.origin.x - FIELD_BUFFER
53
57
  formotion_field.frame = field_frame
54
58
  end
55
59
  end
56
60
 
61
+ field.placeholder = row.placeholder
62
+ field.text = row.value.to_s
57
63
  cell.addSubview(field)
58
64
  field
59
65
 
@@ -100,13 +106,19 @@ module Formotion
100
106
  end
101
107
 
102
108
  def on_change(text_field)
103
- row.value = text_field.text
109
+ break_with_semaphore do
110
+ row.value = text_field.text
111
+ end
104
112
  end
105
113
 
106
114
  def on_select(tableView, tableViewDelegate)
107
115
  row.text_field.becomeFirstResponder
108
116
  end
109
117
 
118
+ # Used when row.value changes
119
+ def update_text_field(new_value)
120
+ self.row.text_field.text = new_value
121
+ end
110
122
  end
111
123
  end
112
124
  end
@@ -0,0 +1,16 @@
1
+ module Formotion
2
+ module RowType
3
+ class SubformRow < Base
4
+
5
+ def build_cell(cell)
6
+ cell.accessoryType = cell.editingAccessoryType = UITableViewCellAccessoryDisclosureIndicator
7
+ end
8
+
9
+ def on_select(tableView, tableViewDelegate)
10
+ subform = row.subform.to_form
11
+ row.form.controller.push_subform(subform)
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -1,29 +1,6 @@
1
1
  module Formotion
2
2
  module RowType
3
- class SubmitRow < Base
4
-
5
- def submit_button?
6
- true
7
- end
8
-
9
- # Does a clever little trick to override #layoutSubviews
10
- # for just this one UITableViewCell object, in order to
11
- # center it's labels horizontally.
12
- def build_cell(cell)
13
- cell.swizzle(:layoutSubviews) do
14
- def layoutSubviews
15
- old_layoutSubviews
16
-
17
- center = lambda {|frame, dimen|
18
- ((self.frame.size.send(dimen) - frame.size.send(dimen)) / 2.0)
19
- }
20
-
21
- self.textLabel.center = CGPointMake(self.frame.size.width / 2 - 10, self.textLabel.center.y)
22
- self.detailTextLabel.center = CGPointMake(self.frame.size.width / 2 - 10, self.detailTextLabel.center.y)
23
- end
24
- end
25
- nil
26
- end
3
+ class SubmitRow < Button
27
4
 
28
5
  def on_select(tableView, tableViewDelegate)
29
6
  tableViewDelegate.submit
@@ -1,15 +1,23 @@
1
1
  module Formotion
2
2
  module RowType
3
3
  class SwitchRow < Base
4
+ include BW::KVO
4
5
 
5
6
  def build_cell(cell)
6
7
  cell.selectionStyle = UITableViewCellSelectionStyleNone
7
8
  switchView = UISwitch.alloc.initWithFrame(CGRectZero)
8
9
  switchView.accessibilityLabel = row.title + " Switch"
9
- cell.accessoryView = switchView
10
+ cell.accessoryView = cell.editingAccessoryView = switchView
10
11
  switchView.setOn(row.value || false, animated:false)
11
12
  switchView.when(UIControlEventValueChanged) do
12
- row.value = switchView.isOn
13
+ break_with_semaphore do
14
+ row.value = switchView.isOn
15
+ end
16
+ end
17
+ observe(self.row, "value") do |old_value, new_value|
18
+ break_with_semaphore do
19
+ switchView.setOn(row.value || false, animated: false)
20
+ end
13
21
  end
14
22
  nil
15
23
  end
@@ -0,0 +1,77 @@
1
+ # Define template row:
2
+ # {
3
+ # title: "Add nickname",
4
+ # key: :nicknames,
5
+ # type: :template,
6
+ # template: {
7
+ # title: 'Nickname %{index}',
8
+ # type: :string
9
+ # }
10
+ # value: ['Samy', 'Pamy']
11
+ # }
12
+
13
+ # row.value = ['Samy', 'Pamy']
14
+ module Formotion
15
+ module RowType
16
+ class TemplateRow < Base
17
+
18
+ def cellEditingStyle
19
+ UITableViewCellEditingStyleInsert
20
+ end
21
+
22
+ def indentWhileEditing?
23
+ true
24
+ end
25
+
26
+ def build_cell(cell)
27
+ cell.selectionStyle = UITableViewCellSelectionStyleBlue
28
+ @add_button ||= begin
29
+ button = UIButton.buttonWithType(UIButtonTypeContactAdd)
30
+ button.when(UIControlEventTouchUpInside) do
31
+ self.on_select(nil, nil)
32
+ end
33
+ button
34
+ end
35
+ cell.accessoryView = @add_button
36
+
37
+ nil
38
+ end
39
+
40
+ def on_select(tableView, tableViewDelegate)
41
+ on_insert(tableView, tableViewDelegate)
42
+ end
43
+
44
+ def on_insert(tableView, tableViewDelegate)
45
+ @template_index = row.section.rows.count
46
+ new_row = build_new_row
47
+ move_row_in_list(new_row)
48
+ insert_row(new_row)
49
+ end
50
+
51
+ def build_new_row(options = {})
52
+ # build row
53
+ new_row = row.section.create_row(row.template.merge(options))
54
+ new_row.remove_on_delete = true
55
+ new_row.template_parent = self.row
56
+ new_row
57
+ end
58
+
59
+ def move_row_in_list(new_row)
60
+ # move to top
61
+ row.section.rows.pop
62
+ row.section.rows.insert(@template_index - 1, new_row)
63
+
64
+ # reset indexes
65
+ row.section.refresh_row_indexes
66
+ end
67
+
68
+ def insert_row(new_row)
69
+ index_path = NSIndexPath.indexPathForRow(new_row.index, inSection:row.section.index)
70
+ tableView.beginUpdates
71
+ tableView.insertRowsAtIndexPaths [index_path], withRowAnimation:UITableViewRowAnimationBottom
72
+ tableView.endUpdates
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -1,6 +1,7 @@
1
1
  module Formotion
2
2
  module RowType
3
3
  class TextRow < Base
4
+ include BW::KVO
4
5
 
5
6
  TEXT_VIEW_TAG=1000
6
7
 
@@ -30,8 +31,15 @@ module Formotion
30
31
  true
31
32
  end
32
33
 
34
+ observe(self.row, "value") do |old_value, new_value|
35
+ break_with_semaphore do
36
+ field.text = row.value
37
+ end
38
+ end
33
39
  field.on_change do |text_field|
34
- row.value = text_field.text
40
+ break_with_semaphore do
41
+ row.value = text_field.text
42
+ end
35
43
  end
36
44
 
37
45
  field.on_end do |text_field|
@@ -52,9 +60,9 @@ module Formotion
52
60
 
53
61
  field_frame = formotion_field.frame
54
62
  field_frame.origin.y = 10
55
- field_frame.origin.x = self.textLabel.frame.origin.x + self.textLabel.frame.size.width + 20
56
- field_frame.size.width = self.frame.size.width - field_frame.origin.x - 20
57
- field_frame.size.height = self.frame.size.height - 20
63
+ field_frame.origin.x = self.textLabel.frame.origin.x + self.textLabel.frame.size.width + FIELD_BUFFER
64
+ field_frame.size.width = self.frame.size.width - field_frame.origin.x - FIELD_BUFFER
65
+ field_frame.size.height = self.frame.size.height - FIELD_BUFFER
58
66
  formotion_field.frame = field_frame
59
67
  end
60
68
  end
@@ -51,6 +51,8 @@ module Formotion
51
51
  end
52
52
  row.section = self
53
53
  row.index = self.rows.count
54
+ # dont move to after the appending.
55
+ row.after_create
54
56
  self.rows << row
55
57
  row
56
58
  end
@@ -100,8 +102,14 @@ module Formotion
100
102
  nil
101
103
  end
102
104
 
105
+ def refresh_row_indexes
106
+ rows.each_with_index do |row, index|
107
+ row.index = index
108
+ end
109
+ end
110
+
103
111
  #########################
104
- # Retreiving data
112
+ # Retreiving data
105
113
  def to_hash
106
114
  h = super
107
115
  h[:rows] = self.rows.collect {|row| row.to_hash}
@@ -1,3 +1,3 @@
1
1
  module Formotion
2
- VERSION = "0.5.1"
2
+ VERSION = "1.0"
3
3
  end
data/spec/form_spec.rb CHANGED
@@ -68,6 +68,30 @@ describe "Forms" do
68
68
  @form.render[:email].should == 'something@email.com'
69
69
  end
70
70
 
71
+ it "render with subforms works correctly" do
72
+ @form = Formotion::Form.new(sections: [{
73
+ rows: [{
74
+ type: :subform,
75
+ key: :subform,
76
+ subform: {
77
+ sections: [{
78
+ rows: [{
79
+ key: :email,
80
+ type: :email,
81
+ editable: true,
82
+ title: 'Email'
83
+ }]
84
+ }]
85
+ }
86
+ }]}])
87
+
88
+ subform = @form.sections[0].rows[0].subform.to_form
89
+ row = subform.sections[0].rows[0]
90
+ row.value = 'something@email.com'
91
+
92
+ @form.render[:subform][:email].should == 'something@email.com'
93
+ end
94
+
71
95
  it "hashifying should be same as input" do
72
96
  h = {
73
97
  sections: [{
@@ -0,0 +1,100 @@
1
+ class FormableTestModel
2
+ def self.the_transform
3
+ @transform ||= lambda { |value| value.to_i + 10 }
4
+ end
5
+
6
+ include Formotion::Formable
7
+
8
+ attr_accessor :my_name, :my_number
9
+
10
+ form_property :my_name, :string, title: "My Name"
11
+ form_property :my_number, :number, title: "My Number", :transform => the_transform
12
+
13
+ form_title "Custom Title"
14
+
15
+ def on_submit
16
+ @submitted = true
17
+ end
18
+ end
19
+
20
+ describe "Formotion::Formable" do
21
+ it "form_properties should have correct structure" do
22
+ FormableTestModel.form_properties.should == [{
23
+ property: :my_name, row_type: :string, title: "My Name"
24
+ }, {
25
+ property: :my_number, row_type: :number, title: "My Number", :transform => FormableTestModel.the_transform
26
+ }]
27
+ end
28
+
29
+ it "should set form_title" do
30
+ FormableTestModel.form_title.should == "Custom Title"
31
+ end
32
+
33
+ describe ".to_form" do
34
+ it "should have correct values" do
35
+ comparison_form = Formotion::Form.new({
36
+ title: "Custom Title",
37
+ sections: [{
38
+ rows: [{
39
+ title: "My Name",
40
+ key: :my_name,
41
+ type: :string,
42
+ value: nil
43
+ }, {
44
+ title: "My Number",
45
+ key: :my_number,
46
+ type: :number,
47
+ value: nil
48
+ }]
49
+ }]
50
+ })
51
+
52
+ model = FormableTestModel.new
53
+ model.to_form.to_hash.should == comparison_form.to_hash
54
+
55
+ model.my_name = "Clay"
56
+ model.my_number = 123
57
+
58
+ comparison_form = Formotion::Form.new({
59
+ title: "Custom Title",
60
+ sections: [{
61
+ rows: [{
62
+ title: "My Name",
63
+ key: :my_name,
64
+ type: :string,
65
+ value: "Clay"
66
+ }, {
67
+ title: "My Number",
68
+ key: :my_number,
69
+ type: :number,
70
+ value: 123
71
+ }]
72
+ }]
73
+ })
74
+
75
+ model.to_form.to_hash.should == comparison_form.to_hash
76
+ end
77
+
78
+ it "should have transformed render values" do
79
+ comparison_render = {
80
+ my_name: "Clay",
81
+ my_number: 133
82
+ }
83
+
84
+ model = FormableTestModel.new
85
+ model.my_name = "Clay"
86
+ model.my_number = 113
87
+
88
+ form = model.to_form
89
+ form.sections[0].rows[1].value = 123
90
+
91
+ form.render.should == comparison_render
92
+ end
93
+
94
+ it "should have correct submit" do
95
+ model = FormableTestModel.new
96
+ model.to_form.submit
97
+ model.instance_variable_get("@submitted").should == true
98
+ end
99
+ end
100
+ end