marionette_rails_generators 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +30 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +71 -0
  6. data/Rakefile +2 -0
  7. data/lib/generators/marionette/attribute.rb +54 -0
  8. data/lib/generators/marionette/controller/controller_generator.rb +57 -0
  9. data/lib/generators/marionette/controller/templates/app/controllers/controller.js.coffee +12 -0
  10. data/lib/generators/marionette/controller/templates/app/controllers/controller_with_views.js.coffee +16 -0
  11. data/lib/generators/marionette/install/install_generator.rb +91 -0
  12. data/lib/generators/marionette/install/templates/after_backbone.js.coffee +8 -0
  13. data/lib/generators/marionette/install/templates/app.js.coffee +21 -0
  14. data/lib/generators/marionette/install/templates/app/templates/index.jst.eco +118 -0
  15. data/lib/generators/marionette/install/templates/base/controllers/_base_controller.js.coffee +42 -0
  16. data/lib/generators/marionette/install/templates/base/models/_base_collection.js.coffee +3 -0
  17. data/lib/generators/marionette/install/templates/base/models/_base_model.js.coffee +40 -0
  18. data/lib/generators/marionette/install/templates/base/views/_view.js.coffee +56 -0
  19. data/lib/generators/marionette/install/templates/base/views/collectionview.js.coffee +4 -0
  20. data/lib/generators/marionette/install/templates/base/views/compositeview.js.coffee +4 -0
  21. data/lib/generators/marionette/install/templates/base/views/itemview.js.coffee +3 -0
  22. data/lib/generators/marionette/install/templates/base/views/layout.js.coffee +3 -0
  23. data/lib/generators/marionette/install/templates/before_backbone.js.coffee +2 -0
  24. data/lib/generators/marionette/install/templates/config/config.js.coffee +72 -0
  25. data/lib/generators/marionette/install/templates/config/controllers/loading/loading_controller.js.coffee +55 -0
  26. data/lib/generators/marionette/install/templates/config/controllers/loading/loading_view.js.coffee +30 -0
  27. data/lib/generators/marionette/install/templates/config/models/fetch.js.coffee +11 -0
  28. data/lib/generators/marionette/install/templates/config/models/sync.js.coffee +19 -0
  29. data/lib/generators/marionette/install/templates/index.html.erb +9 -0
  30. data/lib/generators/marionette/install/templates/routes.js.coffee +13 -0
  31. data/lib/generators/marionette/model/model_generator.rb +42 -0
  32. data/lib/generators/marionette/model/templates/app/models/entity.js.coffee +14 -0
  33. data/lib/generators/marionette/resource_helpers.rb +104 -0
  34. data/lib/generators/marionette/scaffold/scaffold_generator.rb +194 -0
  35. data/lib/generators/marionette/scaffold/templates/templates/_empty.jst.eco +1 -0
  36. data/lib/generators/marionette/scaffold/templates/templates/_form.jst.eco +6 -0
  37. data/lib/generators/marionette/scaffold/templates/templates/_post.jst.eco +6 -0
  38. data/lib/generators/marionette/scaffold/templates/templates/edit.jst.eco +6 -0
  39. data/lib/generators/marionette/scaffold/templates/templates/index.jst.eco +19 -0
  40. data/lib/generators/marionette/scaffold/templates/templates/new.jst.eco +6 -0
  41. data/lib/generators/marionette/scaffold/templates/templates/show.jst.eco +10 -0
  42. data/lib/generators/marionette/view/templates/app/templates/collection_view.jst.eco +1 -0
  43. data/lib/generators/marionette/view/templates/app/templates/composite_view.jst.eco +1 -0
  44. data/lib/generators/marionette/view/templates/app/templates/item_view.jst.eco +10 -0
  45. data/lib/generators/marionette/view/templates/app/templates/layouts/application.jst.eco +2 -0
  46. data/lib/generators/marionette/view/templates/app/views/collection_view.js.coffee +4 -0
  47. data/lib/generators/marionette/view/templates/app/views/composite_view.js.coffee +4 -0
  48. data/lib/generators/marionette/view/templates/app/views/item_view.js.coffee +7 -0
  49. data/lib/generators/marionette/view/view_generator.rb +112 -0
  50. data/lib/marionette_rails_generators.rb +8 -0
  51. data/lib/marionette_rails_generators/version.rb +3 -0
  52. data/marionette_rails_generators.gemspec +33 -0
  53. data/spec/dummy/README.rdoc +28 -0
  54. data/spec/dummy/Rakefile +6 -0
  55. data/spec/dummy/app/assets/images/.keep +0 -0
  56. data/spec/dummy/app/assets/javascripts/application.js +32 -0
  57. data/spec/dummy/app/assets/javascripts/backbone/after_backbone.js.coffee +8 -0
  58. data/spec/dummy/app/assets/javascripts/backbone/app.js.coffee +21 -0
  59. data/spec/dummy/app/assets/javascripts/backbone/app/controllers/all/root_controller.js.coffee +14 -0
  60. data/spec/dummy/app/assets/javascripts/backbone/app/models/admin/books.js.coffee +11 -0
  61. data/spec/dummy/app/assets/javascripts/backbone/app/models/all/posts.js.coffee +13 -0
  62. data/spec/dummy/app/assets/javascripts/backbone/app/models/test_subsubmodule/test_submodule/test_module/stars.js.coffee +11 -0
  63. data/spec/dummy/app/assets/javascripts/backbone/app/templates/admin/test_collection_view2.jst.eco +1 -0
  64. data/spec/dummy/app/assets/javascripts/backbone/app/templates/all/test_collection_view1.jst.eco +1 -0
  65. data/spec/dummy/app/assets/javascripts/backbone/app/templates/all/test_composite_view1.jst.eco +1 -0
  66. data/spec/dummy/app/assets/javascripts/backbone/app/templates/all/test_item_view1.jst.eco +54 -0
  67. data/spec/dummy/app/assets/javascripts/backbone/app/templates/all/test_item_view2.jst.eco +1 -0
  68. data/spec/dummy/app/assets/javascripts/backbone/app/templates/layouts/application.jst.eco +2 -0
  69. data/spec/dummy/app/assets/javascripts/backbone/app/templates/layouts/test_layout1.jst.eco +2 -0
  70. data/spec/dummy/app/assets/javascripts/backbone/app/templates/layouts/test_layout2.jst.eco +2 -0
  71. data/spec/dummy/app/assets/javascripts/backbone/app/templates/layouts/test_layout3.jst.eco +2 -0
  72. data/spec/dummy/app/assets/javascripts/backbone/app/templates/root/index.jst.eco +118 -0
  73. data/spec/dummy/app/assets/javascripts/backbone/app/templates/test_module/test_composite_view2.jst.eco +1 -0
  74. data/spec/dummy/app/assets/javascripts/backbone/app/templates/test_module/test_item_view3.jst.eco +1 -0
  75. data/spec/dummy/app/assets/javascripts/backbone/app/templates/test_subsubmodule/test_submodule/test_module/test_collection_view3.jst.eco +1 -0
  76. data/spec/dummy/app/assets/javascripts/backbone/app/templates/test_subsubmodule/test_submodule/test_module/test_composite_view3.jst.eco +1 -0
  77. data/spec/dummy/app/assets/javascripts/backbone/app/templates/test_subsubmodule/test_submodule/test_module/test_item_view4.jst.eco +1 -0
  78. data/spec/dummy/app/assets/javascripts/backbone/app/views/admin/test_collection_view2.js.coffee +4 -0
  79. data/spec/dummy/app/assets/javascripts/backbone/app/views/all/test_collection_view1.js.coffee +4 -0
  80. data/spec/dummy/app/assets/javascripts/backbone/app/views/all/test_composite_view1.js.coffee +4 -0
  81. data/spec/dummy/app/assets/javascripts/backbone/app/views/all/test_item_view1.js.coffee +7 -0
  82. data/spec/dummy/app/assets/javascripts/backbone/app/views/all/test_item_view2.js.coffee +7 -0
  83. data/spec/dummy/app/assets/javascripts/backbone/app/views/layouts/layouts.js.coffee +21 -0
  84. data/spec/dummy/app/assets/javascripts/backbone/app/views/root/index.js.coffee +7 -0
  85. data/spec/dummy/app/assets/javascripts/backbone/app/views/test_module/test_composite_view2.js.coffee +4 -0
  86. data/spec/dummy/app/assets/javascripts/backbone/app/views/test_module/test_item_view3.js.coffee +7 -0
  87. data/spec/dummy/app/assets/javascripts/backbone/app/views/test_subsubmodule/test_submodule/test_module/test_collection_view3.js.coffee +4 -0
  88. data/spec/dummy/app/assets/javascripts/backbone/app/views/test_subsubmodule/test_submodule/test_module/test_composite_view3.js.coffee +4 -0
  89. data/spec/dummy/app/assets/javascripts/backbone/app/views/test_subsubmodule/test_submodule/test_module/test_item_view4.js.coffee +7 -0
  90. data/spec/dummy/app/assets/javascripts/backbone/base/controllers/_base_controller.js.coffee +42 -0
  91. data/spec/dummy/app/assets/javascripts/backbone/base/models/_base_collection.js.coffee +3 -0
  92. data/spec/dummy/app/assets/javascripts/backbone/base/models/_base_model.js.coffee +40 -0
  93. data/spec/dummy/app/assets/javascripts/backbone/base/views/_view.js.coffee +56 -0
  94. data/spec/dummy/app/assets/javascripts/backbone/base/views/collectionview.js.coffee +4 -0
  95. data/spec/dummy/app/assets/javascripts/backbone/base/views/compositeview.js.coffee +4 -0
  96. data/spec/dummy/app/assets/javascripts/backbone/base/views/itemview.js.coffee +3 -0
  97. data/spec/dummy/app/assets/javascripts/backbone/base/views/layout.js.coffee +3 -0
  98. data/spec/dummy/app/assets/javascripts/backbone/before_backbone.js.coffee +2 -0
  99. data/spec/dummy/app/assets/javascripts/backbone/config/config.js.coffee +72 -0
  100. data/spec/dummy/app/assets/javascripts/backbone/config/controllers/loading/loading_controller.js.coffee +55 -0
  101. data/spec/dummy/app/assets/javascripts/backbone/config/controllers/loading/loading_view.js.coffee +30 -0
  102. data/spec/dummy/app/assets/javascripts/backbone/config/models/fetch.js.coffee +11 -0
  103. data/spec/dummy/app/assets/javascripts/backbone/config/models/sync.js.coffee +19 -0
  104. data/spec/dummy/app/assets/javascripts/backbone/routes.js.coffee +14 -0
  105. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  106. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  107. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  108. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  109. data/spec/dummy/app/mailers/.keep +0 -0
  110. data/spec/dummy/app/models/.keep +0 -0
  111. data/spec/dummy/app/models/concerns/.keep +0 -0
  112. data/spec/dummy/app/views/application/index.html.erb +9 -0
  113. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  114. data/spec/dummy/bin/bundle +3 -0
  115. data/spec/dummy/bin/rails +4 -0
  116. data/spec/dummy/bin/rake +4 -0
  117. data/spec/dummy/bin/setup +29 -0
  118. data/spec/dummy/config.ru +4 -0
  119. data/spec/dummy/config/application.rb +30 -0
  120. data/spec/dummy/config/boot.rb +5 -0
  121. data/spec/dummy/config/database.yml +25 -0
  122. data/spec/dummy/config/environment.rb +5 -0
  123. data/spec/dummy/config/environments/development.rb +41 -0
  124. data/spec/dummy/config/environments/production.rb +79 -0
  125. data/spec/dummy/config/environments/test.rb +42 -0
  126. data/spec/dummy/config/initializers/assets.rb +11 -0
  127. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  128. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  129. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  130. data/spec/dummy/config/initializers/inflections.rb +16 -0
  131. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  132. data/spec/dummy/config/initializers/session_store.rb +3 -0
  133. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  134. data/spec/dummy/config/locales/en.yml +23 -0
  135. data/spec/dummy/config/routes.rb +58 -0
  136. data/spec/dummy/config/secrets.yml +22 -0
  137. data/spec/dummy/db/development.sqlite3 +0 -0
  138. data/spec/dummy/lib/assets/.keep +0 -0
  139. data/spec/dummy/public/404.html +67 -0
  140. data/spec/dummy/public/422.html +67 -0
  141. data/spec/dummy/public/500.html +66 -0
  142. data/spec/dummy/public/favicon.ico +0 -0
  143. data/spec/dummy/spec/javascripts/helpers/SpecHelper.coffee +6 -0
  144. data/spec/dummy/spec/javascripts/specs/backbone_install_spec.coffee +78 -0
  145. data/spec/dummy/spec/javascripts/specs/entity_generator_spec.coffee +13 -0
  146. data/spec/dummy/spec/javascripts/specs/view_generator_spec.coffee +44 -0
  147. data/spec/dummy/spec/javascripts/support/jasmine.yml +124 -0
  148. data/spec/dummy/spec/javascripts/support/jasmine_helper.rb +49 -0
  149. data/vendor/assets/javascripts/backbone-syphon.js +469 -0
  150. data/vendor/assets/javascripts/jquery.spin.js +435 -0
  151. metadata +445 -0
@@ -0,0 +1,49 @@
1
+ require 'coffee_script'
2
+
3
+ #Use this file to set/override Jasmine configuration options
4
+ #You can remove it if you don't need it.
5
+ #This file is loaded *after* jasmine.yml is interpreted.
6
+ #
7
+ #Example: using a different boot file.
8
+ #Jasmine.configure do |config|
9
+ # config.boot_dir = '/absolute/path/to/boot_dir'
10
+ # config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] }
11
+ #end
12
+ #
13
+ #Example: prevent PhantomJS auto install, uses PhantomJS already on your path.
14
+ #Jasmine.configure do |config|
15
+ # config.prevent_phantom_js_auto_install = true
16
+ #end
17
+ #
18
+
19
+ Jasmine.configure do |config|
20
+ config.show_console_log = true
21
+ end
22
+
23
+ puts 'To skip installation pass rake jasmine PASS_INSTALL=true'
24
+
25
+ unless ENV['PASS_INSTALL']
26
+ system('bundle exec rails d marionette:install')
27
+ system('bundle exec rails g marionette:install')
28
+
29
+ system('rails g marionette:view TestLayout1 Layout')
30
+ system('rails g marionette:view TestLayout2 Layout')
31
+ system('rails g marionette:view TestLayout3 Layout')
32
+
33
+ system('rails g marionette:view TestItemView1 ItemView title:string description:text url:text phone:string quantity:integer float_number:float decimal_number:decimal full:boolean email:string password:string')
34
+ system('rails g marionette:view TestItemView2 ItemView')
35
+ system('rails g marionette:view test_module/TestItemView3 ItemView')
36
+ system('rails g marionette:view TestSubsubmodule/test_submodule/TestModule/testItemView4 ItemView')
37
+
38
+ system('rails g marionette:view TestCollectionView1 CollectionView')
39
+ system('rails g marionette:view admin/TestCollectionView2 CollectionView')
40
+ system('rails g marionette:view TestSubsubmodule/test_submodule/TestModule/TestCollectionView3 CollectionView')
41
+
42
+ system('rails g marionette:view TestCompositeView1 CompositeView')
43
+ system('rails g marionette:view test_module/TestCompositeView2 CompositeView')
44
+ system('rails g marionette:view TestSubsubmodule/test_submodule/TestModule/testCompositeView3 CompositeView')
45
+
46
+ system('rails g marionette:model Posts title:string description:text')
47
+ system('rails g marionette:model admin/books')
48
+ system('rails g marionette:model TestSubsubmodule/test_submodule/TestModule/stars')
49
+ end
@@ -0,0 +1,469 @@
1
+ // Backbone.Syphon, v0.4.1
2
+ // Copyright (c)2012 Derick Bailey, Muted Solutions, LLC.
3
+ // Distributed under MIT license
4
+ // http://github.com/derickbailey/backbone.syphon
5
+ Backbone.Syphon = (function(Backbone, $, _){
6
+ var Syphon = {};
7
+
8
+ // Ignore Element Types
9
+ // --------------------
10
+
11
+ // Tell Syphon to ignore all elements of these types. You can
12
+ // push new types to ignore directly in to this array.
13
+ Syphon.ignoredTypes = ["button", "submit", "reset", "fieldset"];
14
+
15
+ // Syphon
16
+ // ------
17
+
18
+ // Get a JSON object that represents
19
+ // all of the form inputs, in this view.
20
+ // Alternately, pass a form element directly
21
+ // in place of the view.
22
+ Syphon.serialize = function(view, options){
23
+ var data = {};
24
+
25
+ // Build the configuration
26
+ var config = buildConfig(options);
27
+
28
+ // Get all of the elements to process
29
+ var elements = getInputElements(view, config);
30
+
31
+ // Process all of the elements
32
+ _.each(elements, function(el){
33
+ var $el = $(el);
34
+ var type = getElementType($el);
35
+
36
+ // Get the key for the input
37
+ var keyExtractor = config.keyExtractors.get(type);
38
+ var key = keyExtractor($el);
39
+
40
+ // Get the value for the input
41
+ var inputReader = config.inputReaders.get(type);
42
+ var value = inputReader($el);
43
+
44
+ // Get the key assignment validator and make sure
45
+ // it's valid before assigning the value to the key
46
+ var validKeyAssignment = config.keyAssignmentValidators.get(type);
47
+ if (validKeyAssignment($el, key, value)){
48
+ var keychain = config.keySplitter(key);
49
+ data = assignKeyValue(data, keychain, value);
50
+ }
51
+ });
52
+
53
+ // Done; send back the results.
54
+ return data;
55
+ };
56
+
57
+ // Use the given JSON object to populate
58
+ // all of the form inputs, in this view.
59
+ // Alternately, pass a form element directly
60
+ // in place of the view.
61
+ Syphon.deserialize = function(view, data, options){
62
+ // Build the configuration
63
+ var config = buildConfig(options);
64
+
65
+ // Get all of the elements to process
66
+ var elements = getInputElements(view, config);
67
+
68
+ // Flatten the data structure that we are deserializing
69
+ var flattenedData = flattenData(config, data);
70
+
71
+ // Process all of the elements
72
+ _.each(elements, function(el){
73
+ var $el = $(el);
74
+ var type = getElementType($el);
75
+
76
+ // Get the key for the input
77
+ var keyExtractor = config.keyExtractors.get(type);
78
+ var key = keyExtractor($el);
79
+
80
+ // Get the input writer and the value to write
81
+ var inputWriter = config.inputWriters.get(type);
82
+ var value = flattenedData[key];
83
+
84
+ // Write the value to the input
85
+ inputWriter($el, value);
86
+ });
87
+ };
88
+
89
+ // Helpers
90
+ // -------
91
+
92
+ // Retrieve all of the form inputs
93
+ // from the form
94
+ var getInputElements = function(view, config){
95
+ var form = getForm(view);
96
+ var elements = form.elements;
97
+
98
+ elements = _.reject(elements, function(el){
99
+ var reject;
100
+ var type = getElementType(el);
101
+ var extractor = config.keyExtractors.get(type);
102
+ var identifier = extractor($(el));
103
+
104
+ var foundInIgnored = _.include(config.ignoredTypes, type);
105
+ var foundInInclude = _.include(config.include, identifier);
106
+ var foundInExclude = _.include(config.exclude, identifier);
107
+
108
+ if (foundInInclude){
109
+ reject = false;
110
+ } else {
111
+ if (config.include){
112
+ reject = true;
113
+ } else {
114
+ reject = (foundInExclude || foundInIgnored);
115
+ }
116
+ }
117
+
118
+ return reject;
119
+ });
120
+
121
+ return elements;
122
+ };
123
+
124
+ // Determine what type of element this is. It
125
+ // will either return the `type` attribute of
126
+ // an `<input>` element, or the `tagName` of
127
+ // the element when the element is not an `<input>`.
128
+ var getElementType = function(el){
129
+ var typeAttr;
130
+ var $el = $(el);
131
+ var tagName = $el[0].tagName;
132
+ var type = tagName;
133
+
134
+ if (tagName.toLowerCase() === "input"){
135
+ typeAttr = $el.attr("type");
136
+ if (typeAttr){
137
+ type = typeAttr;
138
+ } else {
139
+ type = "text";
140
+ }
141
+ }
142
+
143
+ // Always return the type as lowercase
144
+ // so it can be matched to lowercase
145
+ // type registrations.
146
+ return type.toLowerCase();
147
+ };
148
+
149
+ // If a form element is given, just return it.
150
+ // Otherwise, get the form element from the view.
151
+ var getForm = function(viewOrForm){
152
+ if (_.isUndefined(viewOrForm.$el) && viewOrForm.tagName.toLowerCase() === 'form'){
153
+ return viewOrForm;
154
+ } else {
155
+ return viewOrForm.$el.is("form") ? viewOrForm.el : viewOrForm.$("form")[0];
156
+ }
157
+ };
158
+
159
+ // Build a configuration object and initialize
160
+ // default values.
161
+ var buildConfig = function(options){
162
+ var config = _.clone(options) || {};
163
+
164
+ config.ignoredTypes = _.clone(Syphon.ignoredTypes);
165
+ config.inputReaders = config.inputReaders || Syphon.InputReaders;
166
+ config.inputWriters = config.inputWriters || Syphon.InputWriters;
167
+ config.keyExtractors = config.keyExtractors || Syphon.KeyExtractors;
168
+ config.keySplitter = config.keySplitter || Syphon.KeySplitter;
169
+ config.keyJoiner = config.keyJoiner || Syphon.KeyJoiner;
170
+ config.keyAssignmentValidators = config.keyAssignmentValidators || Syphon.KeyAssignmentValidators;
171
+
172
+ return config;
173
+ };
174
+
175
+ // Assigns `value` to a parsed JSON key.
176
+ //
177
+ // The first parameter is the object which will be
178
+ // modified to store the key/value pair.
179
+ //
180
+ // The second parameter accepts an array of keys as a
181
+ // string with an option array containing a
182
+ // single string as the last option.
183
+ //
184
+ // The third parameter is the value to be assigned.
185
+ //
186
+ // Examples:
187
+ //
188
+ // `["foo", "bar", "baz"] => {foo: {bar: {baz: "value"}}}`
189
+ //
190
+ // `["foo", "bar", ["baz"]] => {foo: {bar: {baz: ["value"]}}}`
191
+ //
192
+ // When the final value is an array with a string, the key
193
+ // becomes an array, and values are pushed in to the array,
194
+ // allowing multiple fields with the same name to be
195
+ // assigned to the array.
196
+ var assignKeyValue = function(obj, keychain, value) {
197
+ if (!keychain){ return obj; }
198
+
199
+ var key = keychain.shift();
200
+
201
+ // build the current object we need to store data
202
+ if (!obj[key]){
203
+ obj[key] = _.isArray(key) ? [] : {};
204
+ }
205
+
206
+ // if it's the last key in the chain, assign the value directly
207
+ if (keychain.length === 0){
208
+ if (_.isArray(obj[key])){
209
+ obj[key].push(value);
210
+ } else {
211
+ obj[key] = value;
212
+ }
213
+ }
214
+
215
+ // recursive parsing of the array, depth-first
216
+ if (keychain.length > 0){
217
+ assignKeyValue(obj[key], keychain, value);
218
+ }
219
+
220
+ return obj;
221
+ };
222
+
223
+ // Flatten the data structure in to nested strings, using the
224
+ // provided `KeyJoiner` function.
225
+ //
226
+ // Example:
227
+ //
228
+ // This input:
229
+ //
230
+ // ```js
231
+ // {
232
+ // widget: "wombat",
233
+ // foo: {
234
+ // bar: "baz",
235
+ // baz: {
236
+ // quux: "qux"
237
+ // },
238
+ // quux: ["foo", "bar"]
239
+ // }
240
+ // }
241
+ // ```
242
+ //
243
+ // With a KeyJoiner that uses [ ] square brackets,
244
+ // should produce this output:
245
+ //
246
+ // ```js
247
+ // {
248
+ // "widget": "wombat",
249
+ // "foo[bar]": "baz",
250
+ // "foo[baz][quux]": "qux",
251
+ // "foo[quux]": ["foo", "bar"]
252
+ // }
253
+ // ```
254
+ var flattenData = function(config, data, parentKey){
255
+ var flatData = {};
256
+
257
+ _.each(data, function(value, keyName){
258
+ var hash = {};
259
+
260
+ // If there is a parent key, join it with
261
+ // the current, child key.
262
+ if (parentKey){
263
+ keyName = config.keyJoiner(parentKey, keyName);
264
+ }
265
+
266
+ if (_.isArray(value)){
267
+ keyName += "[]";
268
+ hash[keyName] = value;
269
+ } else if (_.isObject(value)){
270
+ hash = flattenData(config, value, keyName);
271
+ } else {
272
+ hash[keyName] = value;
273
+ }
274
+
275
+ // Store the resulting key/value pairs in the
276
+ // final flattened data object
277
+ _.extend(flatData, hash);
278
+ });
279
+
280
+ return flatData;
281
+ };
282
+
283
+ return Syphon;
284
+ })(Backbone, jQuery, _);
285
+
286
+ // Type Registry
287
+ // -------------
288
+
289
+ // Type Registries allow you to register something to
290
+ // an input type, and retrieve either the item registered
291
+ // for a specific type or the default registration
292
+ Backbone.Syphon.TypeRegistry = function(){
293
+ this.registeredTypes = {};
294
+ };
295
+
296
+ // Borrow Backbone's `extend` keyword for our TypeRegistry
297
+ Backbone.Syphon.TypeRegistry.extend = Backbone.Model.extend;
298
+
299
+ _.extend(Backbone.Syphon.TypeRegistry.prototype, {
300
+
301
+ // Get the registered item by type. If nothing is
302
+ // found for the specified type, the default is
303
+ // returned.
304
+ get: function(type){
305
+ var item = this.registeredTypes[type];
306
+
307
+ if (!item){
308
+ item = this.registeredTypes["default"];
309
+ }
310
+
311
+ return item;
312
+ },
313
+
314
+ // Register a new item for a specified type
315
+ register: function(type, item){
316
+ this.registeredTypes[type] = item;
317
+ },
318
+
319
+ // Register a default item to be used when no
320
+ // item for a specified type is found
321
+ registerDefault: function(item){
322
+ this.registeredTypes["default"] = item;
323
+ },
324
+
325
+ // Remove an item from a given type registration
326
+ unregister: function(type){
327
+ if (this.registeredTypes[type]){
328
+ delete this.registeredTypes[type];
329
+ }
330
+ }
331
+ });
332
+
333
+
334
+
335
+
336
+ // Key Extractors
337
+ // --------------
338
+
339
+ // Key extractors produce the "key" in `{key: "value"}`
340
+ // pairs, when serializing.
341
+ Backbone.Syphon.KeyExtractorSet = Backbone.Syphon.TypeRegistry.extend();
342
+
343
+ // Built-in Key Extractors
344
+ Backbone.Syphon.KeyExtractors = new Backbone.Syphon.KeyExtractorSet();
345
+
346
+ // The default key extractor, which uses the
347
+ // input element's "id" attribute
348
+ Backbone.Syphon.KeyExtractors.registerDefault(function($el){
349
+ return $el.prop("name");
350
+ });
351
+
352
+
353
+ // Input Readers
354
+ // -------------
355
+
356
+ // Input Readers are used to extract the value from
357
+ // an input element, for the serialized object result
358
+ Backbone.Syphon.InputReaderSet = Backbone.Syphon.TypeRegistry.extend();
359
+
360
+ // Built-in Input Readers
361
+ Backbone.Syphon.InputReaders = new Backbone.Syphon.InputReaderSet();
362
+
363
+ // The default input reader, which uses an input
364
+ // element's "value"
365
+ Backbone.Syphon.InputReaders.registerDefault(function($el){
366
+ return $el.val();
367
+ });
368
+
369
+ // Checkbox reader, returning a boolean value for
370
+ // whether or not the checkbox is checked.
371
+ Backbone.Syphon.InputReaders.register("checkbox", function($el){
372
+ var checked = $el.prop("checked");
373
+ return checked;
374
+ });
375
+
376
+
377
+ // Input Writers
378
+ // -------------
379
+
380
+ // Input Writers are used to insert a value from an
381
+ // object into an input element.
382
+ Backbone.Syphon.InputWriterSet = Backbone.Syphon.TypeRegistry.extend();
383
+
384
+ // Built-in Input Writers
385
+ Backbone.Syphon.InputWriters = new Backbone.Syphon.InputWriterSet();
386
+
387
+ // The default input writer, which sets an input
388
+ // element's "value"
389
+ Backbone.Syphon.InputWriters.registerDefault(function($el, value){
390
+ $el.val(value);
391
+ });
392
+
393
+ // Checkbox writer, set whether or not the checkbox is checked
394
+ // depending on the boolean value.
395
+ Backbone.Syphon.InputWriters.register("checkbox", function($el, value){
396
+ $el.prop("checked", value);
397
+ });
398
+
399
+ // Radio button writer, set whether or not the radio button is
400
+ // checked. The button should only be checked if it's value
401
+ // equals the given value.
402
+ Backbone.Syphon.InputWriters.register("radio", function($el, value){
403
+ $el.prop("checked", $el.val() === value);
404
+ });
405
+
406
+ // Key Assignment Validators
407
+ // -------------------------
408
+
409
+ // Key Assignment Validators are used to determine whether or not a
410
+ // key should be assigned to a value, after the key and value have been
411
+ // extracted from the element. This is the last opportunity to prevent
412
+ // bad data from getting serialized to your object.
413
+
414
+ Backbone.Syphon.KeyAssignmentValidatorSet = Backbone.Syphon.TypeRegistry.extend();
415
+
416
+ // Build-in Key Assignment Validators
417
+ Backbone.Syphon.KeyAssignmentValidators = new Backbone.Syphon.KeyAssignmentValidatorSet();
418
+
419
+ // Everything is valid by default
420
+ Backbone.Syphon.KeyAssignmentValidators.registerDefault(function(){ return true; });
421
+
422
+ // But only the "checked" radio button for a given
423
+ // radio button group is valid
424
+ Backbone.Syphon.KeyAssignmentValidators.register("radio", function($el, key, value){
425
+ return $el.prop("checked");
426
+ });
427
+
428
+
429
+ // Backbone.Syphon.KeySplitter
430
+ // ---------------------------
431
+
432
+ // This function is used to split DOM element keys in to an array
433
+ // of parts, which are then used to create a nested result structure.
434
+ // returning `["foo", "bar"]` results in `{foo: { bar: "value" }}`.
435
+ //
436
+ // Override this method to use a custom key splitter, such as:
437
+ // `<input name="foo.bar.baz">`, `return key.split(".")`
438
+ Backbone.Syphon.KeySplitter = function(key){
439
+ var matches = key.match(/[^\[\]]+/g);
440
+
441
+ if (key.indexOf("[]") === key.length - 2){
442
+ lastKey = matches.pop();
443
+ matches.push([lastKey]);
444
+ }
445
+
446
+ return matches;
447
+ }
448
+
449
+
450
+ // Backbone.Syphon.KeyJoiner
451
+ // -------------------------
452
+
453
+ // Take two segments of a key and join them together, to create the
454
+ // de-normalized key name, when deserializing a data structure back
455
+ // in to a form.
456
+ //
457
+ // Example:
458
+ //
459
+ // With this data strucutre `{foo: { bar: {baz: "value", quux: "another"} } }`,
460
+ // the key joiner will be called with these parameters, and assuming the
461
+ // join happens with "[ ]" square brackets, the specified output:
462
+ //
463
+ // `KeyJoiner("foo", "bar")` //=> "foo[bar]"
464
+ // `KeyJoiner("foo[bar]", "baz")` //=> "foo[bar][baz]"
465
+ // `KeyJoiner("foo[bar]", "quux")` //=> "foo[bar][quux]"
466
+
467
+ Backbone.Syphon.KeyJoiner = function(parentKey, childKey){
468
+ return parentKey + "[" + childKey + "]";
469
+ }