formotion 0.5.1 → 1.0

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