camaleon_cms 2.1.2.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of camaleon_cms might be problematic. Click here for more details.

Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -4
  3. data/app/apps/plugins/attack/attack_helper.rb +3 -0
  4. data/app/apps/plugins/attack/config/custom_models.rb +4 -2
  5. data/app/apps/plugins/attack/config/locales/translation.yml +19 -0
  6. data/app/apps/plugins/attack/models/attack.rb +1 -1
  7. data/app/apps/plugins/front_cache/config/initializer.rb +4 -2
  8. data/app/apps/plugins/front_cache/config/locales/translation.yml +19 -0
  9. data/app/apps/themes/camaleon_first/main_helper.rb +1 -3
  10. data/app/apps/themes/default/views/admin/settings.html.erb +0 -1
  11. data/app/apps/themes/default/views/layouts/index.html.erb +5 -5
  12. data/app/apps/themes/new/assets/css/main.css +1 -1
  13. data/app/apps/themes/new/assets/js/main.js +1 -1
  14. data/app/apps/themes/new/views/admin/settings.html.erb +1 -1
  15. data/app/assets/images/camaleon_cms/language/{pt_br.png → pt-BR.png} +0 -0
  16. data/app/assets/javascripts/camaleon_cms/admin/_custom_fields.js +105 -51
  17. data/app/assets/javascripts/camaleon_cms/admin/_libraries.js +4 -2
  18. data/app/assets/javascripts/camaleon_cms/admin/_translator.js +2 -2
  19. data/app/assets/javascripts/camaleon_cms/admin/custom_fields_form.js +9 -7
  20. data/app/assets/javascripts/camaleon_cms/admin/jquery_validate/{pt_br.js → pt-BR.js} +0 -0
  21. data/app/assets/javascripts/camaleon_cms/admin/momentjs/{pt_br.js → pt-BR.js} +1 -1
  22. data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js.coffee +15 -1
  23. data/app/assets/javascripts/camaleon_cms/admin/tinymce/langs/{pt_br.js → pt-BR.js} +1 -1
  24. data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js.coffee +24 -12
  25. data/app/assets/stylesheets/camaleon_cms/admin/_custom_admin.css.scss +9 -0
  26. data/app/assets/stylesheets/camaleon_cms/admin/lte/_admin.css.scss +1 -1
  27. data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +13 -6
  28. data/app/controllers/camaleon_cms/admin/appearances/themes_controller.rb +1 -1
  29. data/app/controllers/camaleon_cms/admin/appearances/widgets/assign_controller.rb +1 -1
  30. data/app/controllers/camaleon_cms/admin/appearances/widgets/main_controller.rb +1 -1
  31. data/app/controllers/camaleon_cms/admin/appearances/widgets/sidebar_controller.rb +1 -1
  32. data/app/controllers/camaleon_cms/admin/comments_controller.rb +1 -1
  33. data/app/controllers/camaleon_cms/admin/media_controller.rb +15 -5
  34. data/app/controllers/camaleon_cms/admin/plugins_controller.rb +1 -1
  35. data/app/controllers/camaleon_cms/admin/settings/custom_fields_controller.rb +14 -8
  36. data/app/controllers/camaleon_cms/admin/settings_controller.rb +22 -7
  37. data/app/controllers/camaleon_cms/admin/user_roles_controller.rb +1 -1
  38. data/app/controllers/camaleon_cms/admin/users_controller.rb +1 -1
  39. data/app/controllers/camaleon_cms/apps/plugins_admin_controller.rb +4 -1
  40. data/app/controllers/camaleon_cms/apps/plugins_front_controller.rb +5 -10
  41. data/app/controllers/camaleon_cms/apps/themes_front_controller.rb +1 -4
  42. data/app/controllers/camaleon_cms/camaleon_controller.rb +4 -13
  43. data/app/controllers/camaleon_cms/frontend_controller.rb +15 -9
  44. data/app/controllers/concerns/camaleon_cms/frontend_concern.rb +17 -8
  45. data/app/decorators/camaleon_cms/application_decorator.rb +3 -3
  46. data/app/decorators/camaleon_cms/custom_fields_concern.rb +21 -6
  47. data/app/decorators/camaleon_cms/post_comment_decorator.rb +21 -0
  48. data/app/decorators/camaleon_cms/site_decorator.rb +5 -5
  49. data/app/decorators/camaleon_cms/theme_decorator.rb +10 -0
  50. data/app/decorators/camaleon_cms/user_decorator.rb +2 -2
  51. data/app/helpers/camaleon_cms/admin/custom_fields_helper.rb +24 -2
  52. data/app/helpers/camaleon_cms/admin/menus_helper.rb +13 -12
  53. data/app/helpers/camaleon_cms/frontend/application_helper.rb +1 -1
  54. data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +7 -6
  55. data/app/helpers/camaleon_cms/plugins_helper.rb +20 -18
  56. data/app/helpers/camaleon_cms/site_helper.rb +1 -20
  57. data/app/helpers/camaleon_cms/theme_helper.rb +1 -1
  58. data/app/helpers/camaleon_cms/uploader_helper.rb +25 -20
  59. data/app/helpers/camaleon_cms/user_roles_helper.rb +6 -1
  60. data/app/mailers/camaleon_cms/html_mailer.rb +2 -1
  61. data/app/models/camaleon_cms/ability.rb +3 -26
  62. data/app/models/camaleon_cms/custom_field.rb +2 -1
  63. data/app/models/camaleon_cms/custom_field_group.rb +23 -22
  64. data/app/models/camaleon_cms/custom_fields_relationship.rb +1 -1
  65. data/app/models/camaleon_cms/nav_menu.rb +1 -1
  66. data/app/models/camaleon_cms/nav_menu_item.rb +12 -19
  67. data/app/models/camaleon_cms/post.rb +1 -1
  68. data/app/models/camaleon_cms/post_comment.rb +3 -1
  69. data/app/models/camaleon_cms/post_default.rb +1 -1
  70. data/app/models/camaleon_cms/post_type.rb +5 -4
  71. data/app/models/camaleon_cms/site.rb +12 -0
  72. data/app/models/camaleon_cms/term_taxonomy.rb +1 -1
  73. data/app/models/camaleon_cms/user_role.rb +5 -1
  74. data/app/models/concerns/camaleon_cms/custom_fields_read.rb +99 -49
  75. data/app/uploaders/camaleon_cms_aws_uploader.rb +1 -1
  76. data/app/uploaders/camaleon_cms_local_uploader.rb +19 -3
  77. data/app/uploaders/camaleon_cms_uploader.rb +13 -6
  78. data/app/views/camaleon_cms/admin/appearances/nav_menus/_custom_menus.html.erb +9 -2
  79. data/app/views/camaleon_cms/admin/appearances/nav_menus/_form.html.erb +1 -1
  80. data/app/views/camaleon_cms/admin/appearances/nav_menus/_menu_items.html.erb +2 -2
  81. data/app/views/camaleon_cms/admin/appearances/themes/index.html.erb +1 -3
  82. data/app/views/camaleon_cms/admin/media/_render_file_item.html.erb +1 -0
  83. data/app/views/camaleon_cms/admin/media/index.html.erb +3 -3
  84. data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +1 -1
  85. data/app/views/camaleon_cms/admin/posts/form.html.erb +1 -1
  86. data/app/views/camaleon_cms/admin/settings/_configuration_settings.html.erb +4 -0
  87. data/app/views/camaleon_cms/admin/settings/custom_fields/_get_items.html.erb +6 -6
  88. data/app/views/camaleon_cms/admin/settings/custom_fields/_meta_data.html.erb +0 -9
  89. data/app/views/camaleon_cms/admin/settings/custom_fields/_render.html.erb +39 -55
  90. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_audio.html.erb +1 -1
  91. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_checkbox.html.erb +2 -2
  92. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_checkboxes.html.erb +3 -3
  93. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_colorpicker.html.erb +3 -3
  94. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_date.html.erb +3 -3
  95. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_editor.html.erb +2 -2
  96. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_email.html.erb +1 -1
  97. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_field_attrs.html.erb +3 -3
  98. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_file.html.erb +1 -1
  99. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_image.html.erb +2 -2
  100. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_numeric.html.erb +1 -1
  101. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_phone.html.erb +1 -1
  102. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_posts.html.erb +3 -3
  103. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_private_file.html.erb +4 -0
  104. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_radio.html.erb +3 -3
  105. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_select.html.erb +3 -3
  106. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_select_eval.html.erb +1 -1
  107. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_text_area.html.erb +2 -2
  108. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_text_box.html.erb +2 -2
  109. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_url.html.erb +1 -1
  110. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_users.html.erb +1 -1
  111. data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_video.html.erb +1 -1
  112. data/app/views/camaleon_cms/admin/settings/custom_fields/form.html.erb +8 -0
  113. data/app/views/camaleon_cms/admin/settings/site.html.erb +1 -20
  114. data/app/views/camaleon_cms/admin/settings/theme.html.erb +20 -0
  115. data/app/views/camaleon_cms/admin/user_roles/form.html.erb +2 -2
  116. data/app/views/camaleon_cms/default_theme/admin/settings.html.erb +0 -1
  117. data/app/views/camaleon_cms/default_theme/partials/_comments.html.erb +0 -2
  118. data/app/views/camaleon_cms/default_theme/partials/_search_form.html.erb +1 -1
  119. data/app/views/camaleon_cms/default_theme/partials/_sidebar.html.erb +3 -3
  120. data/app/views/layouts/camaleon_cms/admin/_footer.html.erb +1 -1
  121. data/config/initializers/action_view.rb +12 -7
  122. data/config/initializers/custom_initializers.rb +6 -12
  123. data/config/locales/camaleon_cms/admin/en.yml +1 -0
  124. data/config/locales/camaleon_cms/admin/es.yml +4 -0
  125. data/config/locales/camaleon_cms/admin/it.yml +1 -24
  126. data/config/locales/camaleon_cms/admin/js.yml +41 -0
  127. data/config/locales/camaleon_cms/admin/{pt_br.yml → pt-BR.yml} +48 -18
  128. data/config/locales/camaleon_cms/common.yml +66 -0
  129. data/config/locales/camaleon_cms/languages.yml +13 -0
  130. data/config/locales/camaleon_cms/routes.yml +2 -2
  131. data/config/routes/admin.rb +3 -0
  132. data/config/system.json +3 -3
  133. data/db/migrate/20160606135421_improve_menus_structure.rb +7 -0
  134. data/db/migrate/20160609121449_add_group_to_custom_field_values.rb +5 -0
  135. data/lib/camaleon_cms/engine.rb +4 -3
  136. data/lib/camaleon_cms/version.rb +1 -1
  137. data/lib/ext/string.rb +20 -0
  138. data/lib/ext/translator.rb +2 -2
  139. data/lib/generators/camaleon_cms/gem_plugin_generator.rb +1 -1
  140. data/lib/generators/camaleon_cms/gem_plugin_template/config/camaleon_plugin.json +1 -1
  141. data/lib/generators/camaleon_cms/install_generator.rb +1 -0
  142. data/lib/generators/camaleon_cms/theme_template/app/apps/themes/my_theme/main_helper.rb +1 -4
  143. data/lib/generators/camaleon_cms/theme_template/app/apps/themes/my_theme/views/index.html.erb +1 -25
  144. data/lib/generators/camaleon_cms/theme_template/app/apps/themes/my_theme/views/layouts/index.html.erb +17 -18
  145. data/lib/plugin_routes.rb +4 -4
  146. metadata +25 -48
  147. data/app/apps/plugins/contact_form/admin_forms_controller.rb +0 -85
  148. data/app/apps/plugins/contact_form/assets/css/admin/form-builder/formbuilder.css +0 -70
  149. data/app/apps/plugins/contact_form/assets/css/contact-form.css +0 -8
  150. data/app/apps/plugins/contact_form/assets/css/front/railsform.scss +0 -94
  151. data/app/apps/plugins/contact_form/assets/css/readme.txt +0 -1
  152. data/app/apps/plugins/contact_form/assets/js/contact_form.js +0 -2
  153. data/app/apps/plugins/contact_form/assets/js/form-builder/formbuilder.js +0 -1271
  154. data/app/apps/plugins/contact_form/assets/js/form-builder/vendor.js +0 -3072
  155. data/app/apps/plugins/contact_form/assets/js/readme.txt +0 -1
  156. data/app/apps/plugins/contact_form/config/config.json +0 -35
  157. data/app/apps/plugins/contact_form/config/custom_models.rb +0 -3
  158. data/app/apps/plugins/contact_form/config/locales/readme.txt +0 -1
  159. data/app/apps/plugins/contact_form/config/locales/translation.yml +0 -315
  160. data/app/apps/plugins/contact_form/config/routes_admin.txt +0 -4
  161. data/app/apps/plugins/contact_form/config/routes_front.txt +0 -2
  162. data/app/apps/plugins/contact_form/contact_form_helper.rb +0 -154
  163. data/app/apps/plugins/contact_form/contact_form_html_helper.rb +0 -140
  164. data/app/apps/plugins/contact_form/front_controller.rb +0 -50
  165. data/app/apps/plugins/contact_form/models/contact_form.rb +0 -26
  166. data/app/apps/plugins/contact_form/views/admin_forms/_form.html.erb +0 -33
  167. data/app/apps/plugins/contact_form/views/admin_forms/edit.html.erb +0 -338
  168. data/app/apps/plugins/contact_form/views/admin_forms/index.html.erb +0 -65
  169. data/app/apps/plugins/contact_form/views/admin_forms/responses.html.erb +0 -60
  170. data/app/apps/plugins/contact_form/views/contact_form/_email_content.html.erb +0 -26
  171. data/app/apps/plugins/contact_form/views/forms_shorcode.html.erb +0 -28
  172. data/app/apps/plugins/contact_form/views/front/index.html.erb +0 -3
  173. data/app/apps/themes/camaleon_first/views/admin/settings.html.erb +0 -4
  174. data/app/apps/themes/new/assets/css/bootstrap/bootstrap.min.css +0 -6735
  175. data/app/apps/themes/new/assets/js/plugins/bootstrap/bootstrap.min.js +0 -6
  176. data/app/apps/themes/new/assets/js/plugins/jquery/jquery.min.js +0 -4
  177. data/lib/generators/camaleon_cms/gem_theme_generator.rb +0 -97
  178. data/lib/generators/camaleon_cms/gem_theme_template/app/assets/images/themes/my_plugin/image.png +0 -0
  179. data/lib/generators/camaleon_cms/gem_theme_template/app/assets/javascripts/themes/my_plugin/main.js +0 -14
  180. data/lib/generators/camaleon_cms/gem_theme_template/app/assets/stylesheets/themes/my_plugin/main.css +0 -13
  181. data/lib/generators/camaleon_cms/gem_theme_template/app/helpers/themes/my_plugin/main_helper.rb +0 -26
  182. data/lib/generators/camaleon_cms/gem_theme_template/app/views/themes/my_plugin/admin/settings.html.erb +0 -4
  183. data/lib/generators/camaleon_cms/gem_theme_template/app/views/themes/my_plugin/index.html.erb +0 -25
  184. data/lib/generators/camaleon_cms/gem_theme_template/app/views/themes/my_plugin/layouts/index.html.erb +0 -70
  185. data/lib/generators/camaleon_cms/gem_theme_template/app/views/themes/my_plugin/partials/readme.txt +0 -1
  186. data/lib/generators/camaleon_cms/gem_theme_template/config/camaleon_theme.json +0 -14
@@ -1,3072 +0,0 @@
1
- var Node = Node || {
2
- ELEMENT_NODE: 1,
3
- ATTRIBUTE_NODE: 2,
4
- TEXT_NODE: 3
5
- };
6
-
7
-
8
- $.scrollWindowTo = function(pos, duration, cb) {
9
- if (duration == null) {
10
- duration = 0;
11
- }
12
- if (pos === $(window).scrollTop()) {
13
- $(window).trigger('scroll');
14
- if (typeof cb === "function") {
15
- cb();
16
- }
17
- return;
18
- }
19
- return $('html, body').animate({
20
- scrollTop: pos
21
- }, duration, function() {
22
- return typeof cb === "function" ? cb() : void 0;
23
- });
24
- };
25
-
26
- (function() {
27
- var arrays, basicObjects, deepClone, deepExtend, deepExtendCouple, isBasicObject,
28
- __slice = [].slice;
29
-
30
- deepClone = function(obj) {
31
- var func, isArr;
32
- if (!_.isObject(obj || _.isFunction(obj))) {
33
- return obj;
34
- }
35
- if (_.isDate(obj)) {
36
- return new Date(obj.getTime());
37
- }
38
- if (_.isRegExp(obj)) {
39
- return new RegExp(obj.source, obj.toString().replace(/.*\//, ""));
40
- }
41
- isArr = _.isArray(obj || _.isArguments(obj));
42
- func = function(memo, value, key) {
43
- if (isArr) {
44
- memo.push(deepClone(value));
45
- } else {
46
- memo[key] = deepClone(value);
47
- }
48
- return memo;
49
- };
50
- return _.reduce(obj, func, isArr ? [] : {});
51
- };
52
-
53
- isBasicObject = function(object) {
54
- if(object === null) return false;
55
- return (object.prototype === {}.prototype || object.prototype === Object.prototype) && _.isObject(object) && !_.isArray(object) && !_.isFunction(object) && !_.isDate(object) && !_.isRegExp(object) && !_.isArguments(object);
56
- };
57
-
58
- basicObjects = function(object) {
59
- return _.filter(_.keys(object), function(key) {
60
- return isBasicObject(object[key]);
61
- });
62
- };
63
-
64
- arrays = function(object) {
65
- return _.filter(_.keys(object), function(key) {
66
- return _.isArray(object[key]);
67
- });
68
- };
69
-
70
- deepExtendCouple = function(destination, source, maxDepth) {
71
- var combine, recurse, sharedArrayKey, sharedArrayKeys, sharedObjectKey, sharedObjectKeys, _i, _j, _len, _len1;
72
- if (maxDepth == null) {
73
- maxDepth = 20;
74
- }
75
- if (maxDepth <= 0) {
76
- console.warn('_.deepExtend(): Maximum depth of recursion hit.');
77
- return _.extend(destination, source);
78
- }
79
- sharedObjectKeys = _.intersection(basicObjects(destination), basicObjects(source));
80
- recurse = function(key) {
81
- return source[key] = deepExtendCouple(destination[key], source[key], maxDepth - 1);
82
- };
83
- for (_i = 0, _len = sharedObjectKeys.length; _i < _len; _i++) {
84
- sharedObjectKey = sharedObjectKeys[_i];
85
- recurse(sharedObjectKey);
86
- }
87
- sharedArrayKeys = _.intersection(arrays(destination), arrays(source));
88
- combine = function(key) {
89
- return source[key] = _.union(destination[key], source[key]);
90
- };
91
- for (_j = 0, _len1 = sharedArrayKeys.length; _j < _len1; _j++) {
92
- sharedArrayKey = sharedArrayKeys[_j];
93
- combine(sharedArrayKey);
94
- }
95
- return _.extend(destination, source);
96
- };
97
-
98
- deepExtend = function() {
99
- var finalObj, maxDepth, objects, _i;
100
- objects = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), maxDepth = arguments[_i++];
101
- if (!_.isNumber(maxDepth)) {
102
- objects.push(maxDepth);
103
- maxDepth = 20;
104
- }
105
- if (objects.length <= 1) {
106
- return objects[0];
107
- }
108
- if (maxDepth <= 0) {
109
- return _.extend.apply(this, objects);
110
- }
111
- finalObj = objects.shift();
112
- while (objects.length > 0) {
113
- finalObj = deepExtendCouple(finalObj, deepClone(objects.shift()), maxDepth);
114
- }
115
- return finalObj;
116
- };
117
-
118
- _.mixin({
119
- deepClone: deepClone,
120
- isBasicObject: isBasicObject,
121
- basicObjects: basicObjects,
122
- arrays: arrays,
123
- deepExtend: deepExtend
124
- });
125
-
126
- }).call(this);
127
- // Rivets.js
128
- // version: 0.5.13
129
- // author: Michael Richards
130
- // license: MIT
131
- (function() {
132
- var Rivets, jQuery,
133
- __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
134
- __slice = [].slice,
135
- __hasProp = {}.hasOwnProperty,
136
- __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
137
- __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
138
-
139
- Rivets = {};
140
-
141
- jQuery = window.jQuery || window.Zepto;
142
-
143
- if (!String.prototype.trim) {
144
- String.prototype.trim = function() {
145
- return this.replace(/^\s+|\s+$/g, '');
146
- };
147
- }
148
-
149
- Rivets.Binding = (function() {
150
- function Binding(view, el, type, key, keypath, options) {
151
- var identifier, regexp, value, _ref;
152
- this.view = view;
153
- this.el = el;
154
- this.type = type;
155
- this.key = key;
156
- this.keypath = keypath;
157
- this.options = options != null ? options : {};
158
- this.update = __bind(this.update, this);
159
- this.unbind = __bind(this.unbind, this);
160
- this.bind = __bind(this.bind, this);
161
- this.publish = __bind(this.publish, this);
162
- this.sync = __bind(this.sync, this);
163
- this.set = __bind(this.set, this);
164
- this.eventHandler = __bind(this.eventHandler, this);
165
- this.formattedValue = __bind(this.formattedValue, this);
166
- if (!(this.binder = this.view.binders[type])) {
167
- _ref = this.view.binders;
168
- for (identifier in _ref) {
169
- value = _ref[identifier];
170
- if (identifier !== '*' && identifier.indexOf('*') !== -1) {
171
- regexp = new RegExp("^" + (identifier.replace('*', '.+')) + "$");
172
- if (regexp.test(type)) {
173
- this.binder = value;
174
- this.args = new RegExp("^" + (identifier.replace('*', '(.+)')) + "$").exec(type);
175
- this.args.shift();
176
- }
177
- }
178
- }
179
- }
180
- this.binder || (this.binder = this.view.binders['*']);
181
- if (this.binder instanceof Function) {
182
- this.binder = {
183
- routine: this.binder
184
- };
185
- }
186
- this.formatters = this.options.formatters || [];
187
- this.model = this.key ? this.view.models[this.key] : this.view.models;
188
- }
189
-
190
- Binding.prototype.formattedValue = function(value) {
191
- var args, formatter, id, _i, _len, _ref;
192
- _ref = this.formatters;
193
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
194
- formatter = _ref[_i];
195
- args = formatter.split(/\s+/);
196
- id = args.shift();
197
- formatter = this.model[id] instanceof Function ? this.model[id] : this.view.formatters[id];
198
- if ((formatter != null ? formatter.read : void 0) instanceof Function) {
199
- value = formatter.read.apply(formatter, [value].concat(__slice.call(args)));
200
- } else if (formatter instanceof Function) {
201
- value = formatter.apply(null, [value].concat(__slice.call(args)));
202
- }
203
- }
204
- return value;
205
- };
206
-
207
- Binding.prototype.eventHandler = function(fn) {
208
- var binding, handler;
209
- handler = (binding = this).view.config.handler;
210
- return function(ev) {
211
- return handler.call(fn, this, ev, binding);
212
- };
213
- };
214
-
215
- Binding.prototype.set = function(value) {
216
- var _ref;
217
- value = value instanceof Function && !this.binder["function"] ? this.formattedValue(value.call(this.model)) : this.formattedValue(value);
218
- return (_ref = this.binder.routine) != null ? _ref.call(this, this.el, value) : void 0;
219
- };
220
-
221
- Binding.prototype.sync = function() {
222
- return this.set(this.options.bypass ? this.model[this.keypath] : this.view.config.adapter.read(this.model, this.keypath));
223
- };
224
-
225
- Binding.prototype.publish = function() {
226
- var args, formatter, id, value, _i, _len, _ref, _ref1, _ref2;
227
- value = Rivets.Util.getInputValue(this.el);
228
- _ref = this.formatters.slice(0).reverse();
229
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
230
- formatter = _ref[_i];
231
- args = formatter.split(/\s+/);
232
- id = args.shift();
233
- if ((_ref1 = this.view.formatters[id]) != null ? _ref1.publish : void 0) {
234
- value = (_ref2 = this.view.formatters[id]).publish.apply(_ref2, [value].concat(__slice.call(args)));
235
- }
236
- }
237
- return this.view.config.adapter.publish(this.model, this.keypath, value);
238
- };
239
-
240
- Binding.prototype.bind = function() {
241
- var dependency, keypath, model, _i, _len, _ref, _ref1, _ref2, _results;
242
- if ((_ref = this.binder.bind) != null) {
243
- _ref.call(this, this.el);
244
- }
245
- if (this.options.bypass) {
246
- this.sync();
247
- } else {
248
- this.view.config.adapter.subscribe(this.model, this.keypath, this.sync);
249
- if (this.view.config.preloadData) {
250
- this.sync();
251
- }
252
- }
253
- if ((_ref1 = this.options.dependencies) != null ? _ref1.length : void 0) {
254
- _ref2 = this.options.dependencies;
255
- _results = [];
256
- for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
257
- dependency = _ref2[_i];
258
- if (/^\./.test(dependency)) {
259
- model = this.model;
260
- keypath = dependency.substr(1);
261
- } else {
262
- dependency = dependency.split('.');
263
- model = this.view.models[dependency.shift()];
264
- keypath = dependency.join('.');
265
- }
266
- _results.push(this.view.config.adapter.subscribe(model, keypath, this.sync));
267
- }
268
- return _results;
269
- }
270
- };
271
-
272
- Binding.prototype.unbind = function() {
273
- var dependency, keypath, model, _i, _len, _ref, _ref1, _ref2, _results;
274
- if ((_ref = this.binder.unbind) != null) {
275
- _ref.call(this, this.el);
276
- }
277
- if (!this.options.bypass) {
278
- this.view.config.adapter.unsubscribe(this.model, this.keypath, this.sync);
279
- }
280
- if ((_ref1 = this.options.dependencies) != null ? _ref1.length : void 0) {
281
- _ref2 = this.options.dependencies;
282
- _results = [];
283
- for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
284
- dependency = _ref2[_i];
285
- if (/^\./.test(dependency)) {
286
- model = this.model;
287
- keypath = dependency.substr(1);
288
- } else {
289
- dependency = dependency.split('.');
290
- model = this.view.models[dependency.shift()];
291
- keypath = dependency.join('.');
292
- }
293
- _results.push(this.view.config.adapter.unsubscribe(model, keypath, this.sync));
294
- }
295
- return _results;
296
- }
297
- };
298
-
299
- Binding.prototype.update = function(models) {
300
- var _ref;
301
- if (models == null) {
302
- models = {};
303
- }
304
- if (this.key) {
305
- if (models[this.key]) {
306
- if (!this.options.bypass) {
307
- this.view.config.adapter.unsubscribe(this.model, this.keypath, this.sync);
308
- }
309
- this.model = models[this.key];
310
- if (this.options.bypass) {
311
- this.sync();
312
- } else {
313
- this.view.config.adapter.subscribe(this.model, this.keypath, this.sync);
314
- if (this.view.config.preloadData) {
315
- this.sync();
316
- }
317
- }
318
- }
319
- } else {
320
- this.sync();
321
- }
322
- return (_ref = this.binder.update) != null ? _ref.call(this, models) : void 0;
323
- };
324
-
325
- return Binding;
326
-
327
- })();
328
-
329
- Rivets.ComponentBinding = (function(_super) {
330
- __extends(ComponentBinding, _super);
331
-
332
- function ComponentBinding(view, el, type) {
333
- var attribute, _i, _len, _ref, _ref1;
334
- this.view = view;
335
- this.el = el;
336
- this.type = type;
337
- this.unbind = __bind(this.unbind, this);
338
- this.bind = __bind(this.bind, this);
339
- this.update = __bind(this.update, this);
340
- this.locals = __bind(this.locals, this);
341
- this.component = Rivets.components[this.type];
342
- this.attributes = {};
343
- this.inflections = {};
344
- _ref = this.el.attributes || [];
345
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
346
- attribute = _ref[_i];
347
- if (_ref1 = attribute.name, __indexOf.call(this.component.attributes, _ref1) >= 0) {
348
- this.attributes[attribute.name] = attribute.value;
349
- } else {
350
- this.inflections[attribute.name] = attribute.value;
351
- }
352
- }
353
- }
354
-
355
- ComponentBinding.prototype.sync = function() {};
356
-
357
- ComponentBinding.prototype.locals = function(models) {
358
- var inverse, key, model, path, result, _i, _len, _ref, _ref1;
359
- if (models == null) {
360
- models = this.view.models;
361
- }
362
- result = {};
363
- _ref = this.inflections;
364
- for (key in _ref) {
365
- inverse = _ref[key];
366
- _ref1 = inverse.split('.');
367
- for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
368
- path = _ref1[_i];
369
- result[key] = (result[key] || models)[path];
370
- }
371
- }
372
- for (key in models) {
373
- model = models[key];
374
- if (result[key] == null) {
375
- result[key] = model;
376
- }
377
- }
378
- return result;
379
- };
380
-
381
- ComponentBinding.prototype.update = function(models) {
382
- var _ref;
383
- return (_ref = this.componentView) != null ? _ref.update(this.locals(models)) : void 0;
384
- };
385
-
386
- ComponentBinding.prototype.bind = function() {
387
- var el, _ref;
388
- if (this.componentView != null) {
389
- return (_ref = this.componentView) != null ? _ref.bind() : void 0;
390
- } else {
391
- el = this.component.build.call(this.attributes);
392
- (this.componentView = new Rivets.View(el, this.locals(), this.view.options)).bind();
393
- return this.el.parentNode.replaceChild(el, this.el);
394
- }
395
- };
396
-
397
- ComponentBinding.prototype.unbind = function() {
398
- var _ref;
399
- return (_ref = this.componentView) != null ? _ref.unbind() : void 0;
400
- };
401
-
402
- return ComponentBinding;
403
-
404
- })(Rivets.Binding);
405
-
406
- Rivets.TextBinding = (function(_super) {
407
- __extends(TextBinding, _super);
408
-
409
- function TextBinding(view, el, type, key, keypath, options) {
410
- this.view = view;
411
- this.el = el;
412
- this.type = type;
413
- this.key = key;
414
- this.keypath = keypath;
415
- this.options = options != null ? options : {};
416
- this.sync = __bind(this.sync, this);
417
- this.formatters = this.options.formatters || [];
418
- this.model = this.key ? this.view.models[this.key] : this.view.models;
419
- }
420
-
421
- TextBinding.prototype.binder = {
422
- routine: function(node, value) {
423
- return node.data = value != null ? value : '';
424
- }
425
- };
426
-
427
- TextBinding.prototype.sync = function() {
428
- return TextBinding.__super__.sync.apply(this, arguments);
429
- };
430
-
431
- return TextBinding;
432
-
433
- })(Rivets.Binding);
434
-
435
- Rivets.View = (function() {
436
- function View(els, models, options) {
437
- var k, option, v, _base, _i, _len, _ref, _ref1, _ref2;
438
- this.els = els;
439
- this.models = models;
440
- this.options = options != null ? options : {};
441
- this.update = __bind(this.update, this);
442
- this.publish = __bind(this.publish, this);
443
- this.sync = __bind(this.sync, this);
444
- this.unbind = __bind(this.unbind, this);
445
- this.bind = __bind(this.bind, this);
446
- this.select = __bind(this.select, this);
447
- this.build = __bind(this.build, this);
448
- this.componentRegExp = __bind(this.componentRegExp, this);
449
- this.bindingRegExp = __bind(this.bindingRegExp, this);
450
- if (typeof this.els.length === 'undefined') {
451
- this.els = [this.els];
452
- }
453
- _ref = ['config', 'binders', 'formatters'];
454
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
455
- option = _ref[_i];
456
- this[option] = {};
457
- if (this.options[option]) {
458
- _ref1 = this.options[option];
459
- for (k in _ref1) {
460
- v = _ref1[k];
461
- this[option][k] = v;
462
- }
463
- }
464
- _ref2 = Rivets[option];
465
- for (k in _ref2) {
466
- v = _ref2[k];
467
- if ((_base = this[option])[k] == null) {
468
- _base[k] = v;
469
- }
470
- }
471
- }
472
- this.build();
473
- }
474
-
475
- View.prototype.bindingRegExp = function() {
476
- var prefix;
477
- prefix = this.config.prefix;
478
- if (prefix) {
479
- return new RegExp("^data-" + prefix + "-");
480
- } else {
481
- return /^data-/;
482
- }
483
- };
484
-
485
- View.prototype.componentRegExp = function() {
486
- var _ref, _ref1;
487
- return new RegExp("^" + ((_ref = (_ref1 = this.config.prefix) != null ? _ref1.toUpperCase() : void 0) != null ? _ref : 'RV') + "-");
488
- };
489
-
490
- View.prototype.build = function() {
491
- var bindingRegExp, buildBinding, componentRegExp, el, parse, skipNodes, _i, _len, _ref,
492
- _this = this;
493
- this.bindings = [];
494
- skipNodes = [];
495
- bindingRegExp = this.bindingRegExp();
496
- componentRegExp = this.componentRegExp();
497
- buildBinding = function(binding, node, type, declaration) {
498
- var context, ctx, dependencies, key, keypath, options, path, pipe, pipes, splitPath;
499
- options = {};
500
- pipes = (function() {
501
- var _i, _len, _ref, _results;
502
- _ref = declaration.split('|');
503
- _results = [];
504
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
505
- pipe = _ref[_i];
506
- _results.push(pipe.trim());
507
- }
508
- return _results;
509
- })();
510
- context = (function() {
511
- var _i, _len, _ref, _results;
512
- _ref = pipes.shift().split('<');
513
- _results = [];
514
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
515
- ctx = _ref[_i];
516
- _results.push(ctx.trim());
517
- }
518
- return _results;
519
- })();
520
- path = context.shift();
521
- splitPath = path.split(/\.|:/);
522
- options.formatters = pipes;
523
- options.bypass = path.indexOf(':') !== -1;
524
- if (splitPath[0]) {
525
- key = splitPath.shift();
526
- } else {
527
- key = null;
528
- splitPath.shift();
529
- }
530
- keypath = splitPath.join('.');
531
- if (dependencies = context.shift()) {
532
- options.dependencies = dependencies.split(/\s+/);
533
- }
534
- return _this.bindings.push(new Rivets[binding](_this, node, type, key, keypath, options));
535
- };
536
- parse = function(node) {
537
- var attribute, attributes, binder, childNode, delimiters, identifier, n, parser, regexp, restTokens, startToken, text, token, tokens, type, value, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m, _ref, _ref1, _ref2, _ref3, _ref4, _results;
538
- if (__indexOf.call(skipNodes, node) < 0) {
539
- if (node.nodeType === Node.TEXT_NODE) {
540
- parser = Rivets.TextTemplateParser;
541
- if (delimiters = _this.config.templateDelimiters) {
542
- if ((tokens = parser.parse(node.data, delimiters)).length) {
543
- if (!(tokens.length === 1 && tokens[0].type === parser.types.text)) {
544
- startToken = tokens[0], restTokens = 2 <= tokens.length ? __slice.call(tokens, 1) : [];
545
- node.data = startToken.value;
546
- if (startToken.type === 0) {
547
- node.data = startToken.value;
548
- } else {
549
- buildBinding('TextBinding', node, null, startToken.value);
550
- }
551
- for (_i = 0, _len = restTokens.length; _i < _len; _i++) {
552
- token = restTokens[_i];
553
- text = document.createTextNode(token.value);
554
- node.parentNode.appendChild(text);
555
- if (token.type === 1) {
556
- buildBinding('TextBinding', text, null, token.value);
557
- }
558
- }
559
- }
560
- }
561
- }
562
- } else if (componentRegExp.test(node.tagName)) {
563
- type = node.tagName.replace(componentRegExp, '').toLowerCase();
564
- _this.bindings.push(new Rivets.ComponentBinding(_this, node, type));
565
- } else if (node.attributes != null) {
566
- _ref = node.attributes;
567
- for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
568
- attribute = _ref[_j];
569
- if (bindingRegExp.test(attribute.name)) {
570
- type = attribute.name.replace(bindingRegExp, '');
571
- if (!(binder = _this.binders[type])) {
572
- _ref1 = _this.binders;
573
- for (identifier in _ref1) {
574
- value = _ref1[identifier];
575
- if (identifier !== '*' && identifier.indexOf('*') !== -1) {
576
- regexp = new RegExp("^" + (identifier.replace('*', '.+')) + "$");
577
- if (regexp.test(type)) {
578
- binder = value;
579
- }
580
- }
581
- }
582
- }
583
- binder || (binder = _this.binders['*']);
584
- if (binder.block) {
585
- _ref2 = node.childNodes;
586
- for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
587
- n = _ref2[_k];
588
- skipNodes.push(n);
589
- }
590
- attributes = [attribute];
591
- }
592
- }
593
- }
594
- _ref3 = attributes || node.attributes;
595
- for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
596
- attribute = _ref3[_l];
597
- if (bindingRegExp.test(attribute.name)) {
598
- type = attribute.name.replace(bindingRegExp, '');
599
- buildBinding('Binding', node, type, attribute.value);
600
- }
601
- }
602
- }
603
- _ref4 = node.childNodes;
604
- _results = [];
605
- for (_m = 0, _len4 = _ref4.length; _m < _len4; _m++) {
606
- childNode = _ref4[_m];
607
- _results.push(parse(childNode));
608
- }
609
- return _results;
610
- }
611
- };
612
- _ref = this.els;
613
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
614
- el = _ref[_i];
615
- parse(el);
616
- }
617
- };
618
-
619
- View.prototype.select = function(fn) {
620
- var binding, _i, _len, _ref, _results;
621
- _ref = this.bindings;
622
- _results = [];
623
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
624
- binding = _ref[_i];
625
- if (fn(binding)) {
626
- _results.push(binding);
627
- }
628
- }
629
- return _results;
630
- };
631
-
632
- View.prototype.bind = function() {
633
- var binding, _i, _len, _ref, _results;
634
- _ref = this.bindings;
635
- _results = [];
636
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
637
- binding = _ref[_i];
638
- _results.push(binding.bind());
639
- }
640
- return _results;
641
- };
642
-
643
- View.prototype.unbind = function() {
644
- var binding, _i, _len, _ref, _results;
645
- _ref = this.bindings;
646
- _results = [];
647
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
648
- binding = _ref[_i];
649
- _results.push(binding.unbind());
650
- }
651
- return _results;
652
- };
653
-
654
- View.prototype.sync = function() {
655
- var binding, _i, _len, _ref, _results;
656
- _ref = this.bindings;
657
- _results = [];
658
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
659
- binding = _ref[_i];
660
- _results.push(binding.sync());
661
- }
662
- return _results;
663
- };
664
-
665
- View.prototype.publish = function() {
666
- var binding, _i, _len, _ref, _results;
667
- _ref = this.select(function(b) {
668
- return b.binder.publishes;
669
- });
670
- _results = [];
671
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
672
- binding = _ref[_i];
673
- _results.push(binding.publish());
674
- }
675
- return _results;
676
- };
677
-
678
- View.prototype.update = function(models) {
679
- var binding, key, model, _i, _len, _ref, _results;
680
- if (models == null) {
681
- models = {};
682
- }
683
- for (key in models) {
684
- model = models[key];
685
- this.models[key] = model;
686
- }
687
- _ref = this.bindings;
688
- _results = [];
689
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
690
- binding = _ref[_i];
691
- _results.push(binding.update(models));
692
- }
693
- return _results;
694
- };
695
-
696
- return View;
697
-
698
- })();
699
-
700
- Rivets.TextTemplateParser = (function() {
701
- function TextTemplateParser() {}
702
-
703
- TextTemplateParser.types = {
704
- text: 0,
705
- binding: 1
706
- };
707
-
708
- TextTemplateParser.parse = function(template, delimiters) {
709
- var index, lastIndex, lastToken, length, substring, tokens, value;
710
- tokens = [];
711
- length = template.length;
712
- index = 0;
713
- lastIndex = 0;
714
- while (lastIndex < length) {
715
- index = template.indexOf(delimiters[0], lastIndex);
716
- if (index < 0) {
717
- tokens.push({
718
- type: this.types.text,
719
- value: template.slice(lastIndex)
720
- });
721
- break;
722
- } else {
723
- if (index > 0 && lastIndex < index) {
724
- tokens.push({
725
- type: this.types.text,
726
- value: template.slice(lastIndex, index)
727
- });
728
- }
729
- lastIndex = index + 2;
730
- index = template.indexOf(delimiters[1], lastIndex);
731
- if (index < 0) {
732
- substring = template.slice(lastIndex - 2);
733
- lastToken = tokens[tokens.length - 1];
734
- if ((lastToken != null ? lastToken.type : void 0) === this.types.text) {
735
- lastToken.value += substring;
736
- } else {
737
- tokens.push({
738
- type: this.types.text,
739
- value: substring
740
- });
741
- }
742
- break;
743
- }
744
- value = template.slice(lastIndex, index).trim();
745
- tokens.push({
746
- type: this.types.binding,
747
- value: value
748
- });
749
- lastIndex = index + 2;
750
- }
751
- }
752
- return tokens;
753
- };
754
-
755
- return TextTemplateParser;
756
-
757
- })();
758
-
759
- Rivets.Util = {
760
- bindEvent: function(el, event, handler) {
761
- if (window.jQuery != null) {
762
- el = jQuery(el);
763
- if (el.on != null) {
764
- return el.on(event, handler);
765
- } else {
766
- return el.bind(event, handler);
767
- }
768
- } else if (window.addEventListener != null) {
769
- return el.addEventListener(event, handler, false);
770
- } else {
771
- event = 'on' + event;
772
- return el.attachEvent(event, handler);
773
- }
774
- },
775
- unbindEvent: function(el, event, handler) {
776
- if (window.jQuery != null) {
777
- el = jQuery(el);
778
- if (el.off != null) {
779
- return el.off(event, handler);
780
- } else {
781
- return el.unbind(event, handler);
782
- }
783
- } else if (window.removeEventListener != null) {
784
- return el.removeEventListener(event, handler, false);
785
- } else {
786
- event = 'on' + event;
787
- return el.detachEvent(event, handler);
788
- }
789
- },
790
- getInputValue: function(el) {
791
- var o, _i, _len, _results;
792
- if (window.jQuery != null) {
793
- el = jQuery(el);
794
- switch (el[0].type) {
795
- case 'checkbox':
796
- return el.is(':checked');
797
- default:
798
- return el.val();
799
- }
800
- } else {
801
- switch (el.type) {
802
- case 'checkbox':
803
- return el.checked;
804
- case 'select-multiple':
805
- _results = [];
806
- for (_i = 0, _len = el.length; _i < _len; _i++) {
807
- o = el[_i];
808
- if (o.selected) {
809
- _results.push(o.value);
810
- }
811
- }
812
- return _results;
813
- break;
814
- default:
815
- return el.value;
816
- }
817
- }
818
- }
819
- };
820
-
821
- Rivets.binders = {
822
- enabled: function(el, value) {
823
- return el.disabled = !value;
824
- },
825
- disabled: function(el, value) {
826
- return el.disabled = !!value;
827
- },
828
- checked: {
829
- publishes: true,
830
- bind: function(el) {
831
- return Rivets.Util.bindEvent(el, 'change', this.publish);
832
- },
833
- unbind: function(el) {
834
- return Rivets.Util.unbindEvent(el, 'change', this.publish);
835
- },
836
- routine: function(el, value) {
837
- var _ref;
838
- if (el.type === 'radio') {
839
- return el.checked = ((_ref = el.value) != null ? _ref.toString() : void 0) === (value != null ? value.toString() : void 0);
840
- } else {
841
- return el.checked = !!value;
842
- }
843
- }
844
- },
845
- unchecked: {
846
- publishes: true,
847
- bind: function(el) {
848
- return Rivets.Util.bindEvent(el, 'change', this.publish);
849
- },
850
- unbind: function(el) {
851
- return Rivets.Util.unbindEvent(el, 'change', this.publish);
852
- },
853
- routine: function(el, value) {
854
- var _ref;
855
- if (el.type === 'radio') {
856
- return el.checked = ((_ref = el.value) != null ? _ref.toString() : void 0) !== (value != null ? value.toString() : void 0);
857
- } else {
858
- return el.checked = !value;
859
- }
860
- }
861
- },
862
- show: function(el, value) {
863
- return el.style.display = value ? '' : 'none';
864
- },
865
- hide: function(el, value) {
866
- return el.style.display = value ? 'none' : '';
867
- },
868
- html: function(el, value) {
869
- return el.innerHTML = value != null ? value : '';
870
- },
871
- value: {
872
- publishes: true,
873
- bind: function(el) {
874
- return Rivets.Util.bindEvent(el, 'change', this.publish);
875
- },
876
- unbind: function(el) {
877
- return Rivets.Util.unbindEvent(el, 'change', this.publish);
878
- },
879
- routine: function(el, value) {
880
- var o, _i, _len, _ref, _ref1, _ref2, _results;
881
- if (window.jQuery != null) {
882
- el = jQuery(el);
883
- if ((value != null ? value.toString() : void 0) !== ((_ref = el.val()) != null ? _ref.toString() : void 0)) {
884
- return el.val(value != null ? value : '');
885
- }
886
- } else {
887
- if (el.type === 'select-multiple') {
888
- if (value != null) {
889
- _results = [];
890
- for (_i = 0, _len = el.length; _i < _len; _i++) {
891
- o = el[_i];
892
- _results.push(o.selected = (_ref1 = o.value, __indexOf.call(value, _ref1) >= 0));
893
- }
894
- return _results;
895
- }
896
- } else if ((value != null ? value.toString() : void 0) !== ((_ref2 = el.value) != null ? _ref2.toString() : void 0)) {
897
- return el.value = value != null ? value : '';
898
- }
899
- }
900
- }
901
- },
902
- text: function(el, value) {
903
- if (el.innerText != null) {
904
- return el.innerText = value != null ? value : '';
905
- } else {
906
- return el.textContent = value != null ? value : '';
907
- }
908
- },
909
- "if": {
910
- block: true,
911
- bind: function(el) {
912
- var attr, declaration;
913
- if (this.marker == null) {
914
- attr = ['data', this.view.config.prefix, this.type].join('-').replace('--', '-');
915
- declaration = el.getAttribute(attr);
916
- this.marker = document.createComment(" rivets: " + this.type + " " + declaration + " ");
917
- el.removeAttribute(attr);
918
- el.parentNode.insertBefore(this.marker, el);
919
- return el.parentNode.removeChild(el);
920
- }
921
- },
922
- unbind: function() {
923
- var _ref;
924
- return (_ref = this.nested) != null ? _ref.unbind() : void 0;
925
- },
926
- routine: function(el, value) {
927
- var key, model, models, options, _ref;
928
- if (!!value === (this.nested == null)) {
929
- if (value) {
930
- models = {};
931
- _ref = this.view.models;
932
- for (key in _ref) {
933
- model = _ref[key];
934
- models[key] = model;
935
- }
936
- options = {
937
- binders: this.view.options.binders,
938
- formatters: this.view.options.formatters,
939
- config: this.view.options.config
940
- };
941
- (this.nested = new Rivets.View(el, models, options)).bind();
942
- return this.marker.parentNode.insertBefore(el, this.marker.nextSibling);
943
- } else {
944
- el.parentNode.removeChild(el);
945
- this.nested.unbind();
946
- return delete this.nested;
947
- }
948
- }
949
- },
950
- update: function(models) {
951
- var _ref;
952
- return (_ref = this.nested) != null ? _ref.update(models) : void 0;
953
- }
954
- },
955
- unless: {
956
- block: true,
957
- bind: function(el) {
958
- return Rivets.binders["if"].bind.call(this, el);
959
- },
960
- unbind: function() {
961
- return Rivets.binders["if"].unbind.call(this);
962
- },
963
- routine: function(el, value) {
964
- return Rivets.binders["if"].routine.call(this, el, !value);
965
- },
966
- update: function(models) {
967
- return Rivets.binders["if"].update.call(this, models);
968
- }
969
- },
970
- "on-*": {
971
- "function": true,
972
- unbind: function(el) {
973
- if (this.handler) {
974
- return Rivets.Util.unbindEvent(el, this.args[0], this.handler);
975
- }
976
- },
977
- routine: function(el, value) {
978
- if (this.handler) {
979
- Rivets.Util.unbindEvent(el, this.args[0], this.handler);
980
- }
981
- return Rivets.Util.bindEvent(el, this.args[0], this.handler = this.eventHandler(value));
982
- }
983
- },
984
- "each-*": {
985
- block: true,
986
- bind: function(el) {
987
- var attr;
988
- if (this.marker == null) {
989
- attr = ['data', this.view.config.prefix, this.type].join('-').replace('--', '-');
990
- this.marker = document.createComment(" rivets: " + this.type + " ");
991
- this.iterated = [];
992
- el.removeAttribute(attr);
993
- el.parentNode.insertBefore(this.marker, el);
994
- return el.parentNode.removeChild(el);
995
- }
996
- },
997
- unbind: function(el) {
998
- var view, _i, _len, _ref, _results;
999
- if (this.iterated != null) {
1000
- _ref = this.iterated;
1001
- _results = [];
1002
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1003
- view = _ref[_i];
1004
- _results.push(view.unbind());
1005
- }
1006
- return _results;
1007
- }
1008
- },
1009
- routine: function(el, collection) {
1010
- var data, i, index, k, key, model, modelName, options, previous, template, v, view, _i, _j, _len, _len1, _ref, _ref1, _ref2, _results;
1011
- modelName = this.args[0];
1012
- collection = collection || [];
1013
- if (this.iterated.length > collection.length) {
1014
- _ref = Array(this.iterated.length - collection.length);
1015
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1016
- i = _ref[_i];
1017
- view = this.iterated.pop();
1018
- view.unbind();
1019
- this.marker.parentNode.removeChild(view.els[0]);
1020
- }
1021
- }
1022
- _results = [];
1023
- for (index = _j = 0, _len1 = collection.length; _j < _len1; index = ++_j) {
1024
- model = collection[index];
1025
- data = {};
1026
- data[modelName] = model;
1027
- if (this.iterated[index] == null) {
1028
- _ref1 = this.view.models;
1029
- for (key in _ref1) {
1030
- model = _ref1[key];
1031
- if (data[key] == null) {
1032
- data[key] = model;
1033
- }
1034
- }
1035
- previous = this.iterated.length ? this.iterated[this.iterated.length - 1].els[0] : this.marker;
1036
- options = {
1037
- binders: this.view.options.binders,
1038
- formatters: this.view.options.formatters,
1039
- config: {}
1040
- };
1041
- _ref2 = this.view.options.config;
1042
- for (k in _ref2) {
1043
- v = _ref2[k];
1044
- options.config[k] = v;
1045
- }
1046
- options.config.preloadData = true;
1047
- template = el.cloneNode(true);
1048
- view = new Rivets.View(template, data, options);
1049
- view.bind();
1050
- this.iterated.push(view);
1051
- _results.push(this.marker.parentNode.insertBefore(template, previous.nextSibling));
1052
- } else if (this.iterated[index].models[modelName] !== model) {
1053
- _results.push(this.iterated[index].update(data));
1054
- } else {
1055
- _results.push(void 0);
1056
- }
1057
- }
1058
- return _results;
1059
- },
1060
- update: function(models) {
1061
- var data, key, model, view, _i, _len, _ref, _results;
1062
- data = {};
1063
- for (key in models) {
1064
- model = models[key];
1065
- if (key !== this.args[0]) {
1066
- data[key] = model;
1067
- }
1068
- }
1069
- _ref = this.iterated;
1070
- _results = [];
1071
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1072
- view = _ref[_i];
1073
- _results.push(view.update(data));
1074
- }
1075
- return _results;
1076
- }
1077
- },
1078
- "class-*": function(el, value) {
1079
- var elClass;
1080
- elClass = " " + el.className + " ";
1081
- if (!value === (elClass.indexOf(" " + this.args[0] + " ") !== -1)) {
1082
- return el.className = value ? "" + el.className + " " + this.args[0] : elClass.replace(" " + this.args[0] + " ", ' ').trim();
1083
- }
1084
- },
1085
- "*": function(el, value) {
1086
- if (value) {
1087
- return el.setAttribute(this.type, value);
1088
- } else {
1089
- return el.removeAttribute(this.type);
1090
- }
1091
- }
1092
- };
1093
-
1094
- Rivets.components = {};
1095
-
1096
- Rivets.config = {
1097
- preloadData: true,
1098
- handler: function(context, ev, binding) {
1099
- return this.call(context, ev, binding.view.models);
1100
- }
1101
- };
1102
-
1103
- Rivets.formatters = {};
1104
-
1105
- Rivets.factory = function(exports) {
1106
- exports._ = Rivets;
1107
- exports.binders = Rivets.binders;
1108
- exports.components = Rivets.components;
1109
- exports.formatters = Rivets.formatters;
1110
- exports.config = Rivets.config;
1111
- exports.configure = function(options) {
1112
- var property, value;
1113
- if (options == null) {
1114
- options = {};
1115
- }
1116
- for (property in options) {
1117
- value = options[property];
1118
- Rivets.config[property] = value;
1119
- }
1120
- };
1121
- return exports.bind = function(el, models, options) {
1122
- var view;
1123
- if (models == null) {
1124
- models = {};
1125
- }
1126
- if (options == null) {
1127
- options = {};
1128
- }
1129
- view = new Rivets.View(el, models, options);
1130
- view.bind();
1131
- return view;
1132
- };
1133
- };
1134
-
1135
- if (typeof exports === 'object') {
1136
- Rivets.factory(exports);
1137
- } else if (typeof define === 'function' && define.amd) {
1138
- define(['exports'], function(exports) {
1139
- Rivets.factory(this.rivets = exports);
1140
- return exports;
1141
- });
1142
- } else {
1143
- Rivets.factory(this.rivets = {});
1144
- }
1145
-
1146
- }).call(this);
1147
-
1148
- // Backbone.js 1.1.2
1149
-
1150
- // (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
1151
- // Backbone may be freely distributed under the MIT license.
1152
- // For all details and documentation:
1153
- // http://backbonejs.org
1154
-
1155
- (function(root, factory) {
1156
-
1157
- // Set up Backbone appropriately for the environment. Start with AMD.
1158
- if (typeof define === 'function' && define.amd) {
1159
- define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
1160
- // Export global even in AMD case in case this script is loaded with
1161
- // others that may still expect a global Backbone.
1162
- root.Backbone = factory(root, exports, _, $);
1163
- });
1164
-
1165
- // Next for Node.js or CommonJS. jQuery may not be needed as a module.
1166
- } else if (typeof exports !== 'undefined') {
1167
- var _ = require('underscore');
1168
- factory(root, exports, _);
1169
-
1170
- // Finally, as a browser global.
1171
- } else {
1172
- root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
1173
- }
1174
-
1175
- }(this, function(root, Backbone, _, $) {
1176
-
1177
- // Initial Setup
1178
- // -------------
1179
-
1180
- // Save the previous value of the `Backbone` variable, so that it can be
1181
- // restored later on, if `noConflict` is used.
1182
- var previousBackbone = root.Backbone;
1183
-
1184
- // Create local references to array methods we'll want to use later.
1185
- var array = [];
1186
- var push = array.push;
1187
- var slice = array.slice;
1188
- var splice = array.splice;
1189
-
1190
- // Current version of the library. Keep in sync with `package.json`.
1191
- Backbone.VERSION = '1.1.2';
1192
-
1193
- // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
1194
- // the `$` variable.
1195
- Backbone.$ = $;
1196
-
1197
- // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
1198
- // to its previous owner. Returns a reference to this Backbone object.
1199
- Backbone.noConflict = function() {
1200
- root.Backbone = previousBackbone;
1201
- return this;
1202
- };
1203
-
1204
- // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
1205
- // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
1206
- // set a `X-Http-Method-Override` header.
1207
- Backbone.emulateHTTP = false;
1208
-
1209
- // Turn on `emulateJSON` to support legacy servers that can't deal with direct
1210
- // `application/json` requests ... will encode the body as
1211
- // `application/x-www-form-urlencoded` instead and will send the model in a
1212
- // form param named `model`.
1213
- Backbone.emulateJSON = false;
1214
-
1215
- // Backbone.Events
1216
- // ---------------
1217
-
1218
- // A module that can be mixed in to *any object* in order to provide it with
1219
- // custom events. You may bind with `on` or remove with `off` callback
1220
- // functions to an event; `trigger`-ing an event fires all callbacks in
1221
- // succession.
1222
- //
1223
- // var object = {};
1224
- // _.extend(object, Backbone.Events);
1225
- // object.on('expand', function(){ alert('expanded'); });
1226
- // object.trigger('expand');
1227
- //
1228
- var Events = Backbone.Events = {
1229
-
1230
- // Bind an event to a `callback` function. Passing `"all"` will bind
1231
- // the callback to all events fired.
1232
- on: function(name, callback, context) {
1233
- if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
1234
- this._events || (this._events = {});
1235
- var events = this._events[name] || (this._events[name] = []);
1236
- events.push({callback: callback, context: context, ctx: context || this});
1237
- return this;
1238
- },
1239
-
1240
- // Bind an event to only be triggered a single time. After the first time
1241
- // the callback is invoked, it will be removed.
1242
- once: function(name, callback, context) {
1243
- if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
1244
- var self = this;
1245
- var once = _.once(function() {
1246
- self.off(name, once);
1247
- callback.apply(this, arguments);
1248
- });
1249
- once._callback = callback;
1250
- return this.on(name, once, context);
1251
- },
1252
-
1253
- // Remove one or many callbacks. If `context` is null, removes all
1254
- // callbacks with that function. If `callback` is null, removes all
1255
- // callbacks for the event. If `name` is null, removes all bound
1256
- // callbacks for all events.
1257
- off: function(name, callback, context) {
1258
- var retain, ev, events, names, i, l, j, k;
1259
- if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
1260
- if (!name && !callback && !context) {
1261
- this._events = void 0;
1262
- return this;
1263
- }
1264
- names = name ? [name] : _.keys(this._events);
1265
- for (i = 0, l = names.length; i < l; i++) {
1266
- name = names[i];
1267
- if (events = this._events[name]) {
1268
- this._events[name] = retain = [];
1269
- if (callback || context) {
1270
- for (j = 0, k = events.length; j < k; j++) {
1271
- ev = events[j];
1272
- if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
1273
- (context && context !== ev.context)) {
1274
- retain.push(ev);
1275
- }
1276
- }
1277
- }
1278
- if (!retain.length) delete this._events[name];
1279
- }
1280
- }
1281
-
1282
- return this;
1283
- },
1284
-
1285
- // Trigger one or many events, firing all bound callbacks. Callbacks are
1286
- // passed the same arguments as `trigger` is, apart from the event name
1287
- // (unless you're listening on `"all"`, which will cause your callback to
1288
- // receive the true name of the event as the first argument).
1289
- trigger: function(name) {
1290
- if (!this._events) return this;
1291
- var args = slice.call(arguments, 1);
1292
- if (!eventsApi(this, 'trigger', name, args)) return this;
1293
- var events = this._events[name];
1294
- var allEvents = this._events.all;
1295
- if (events) triggerEvents(events, args);
1296
- if (allEvents) triggerEvents(allEvents, arguments);
1297
- return this;
1298
- },
1299
-
1300
- // Tell this object to stop listening to either specific events ... or
1301
- // to every object it's currently listening to.
1302
- stopListening: function(obj, name, callback) {
1303
- var listeningTo = this._listeningTo;
1304
- if (!listeningTo) return this;
1305
- var remove = !name && !callback;
1306
- if (!callback && typeof name === 'object') callback = this;
1307
- if (obj) (listeningTo = {})[obj._listenId] = obj;
1308
- for (var id in listeningTo) {
1309
- obj = listeningTo[id];
1310
- obj.off(name, callback, this);
1311
- if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
1312
- }
1313
- return this;
1314
- }
1315
-
1316
- };
1317
-
1318
- // Regular expression used to split event strings.
1319
- var eventSplitter = /\s+/;
1320
-
1321
- // Implement fancy features of the Events API such as multiple event
1322
- // names `"change blur"` and jQuery-style event maps `{change: action}`
1323
- // in terms of the existing API.
1324
- var eventsApi = function(obj, action, name, rest) {
1325
- if (!name) return true;
1326
-
1327
- // Handle event maps.
1328
- if (typeof name === 'object') {
1329
- for (var key in name) {
1330
- obj[action].apply(obj, [key, name[key]].concat(rest));
1331
- }
1332
- return false;
1333
- }
1334
-
1335
- // Handle space separated event names.
1336
- if (eventSplitter.test(name)) {
1337
- var names = name.split(eventSplitter);
1338
- for (var i = 0, l = names.length; i < l; i++) {
1339
- obj[action].apply(obj, [names[i]].concat(rest));
1340
- }
1341
- return false;
1342
- }
1343
-
1344
- return true;
1345
- };
1346
-
1347
- // A difficult-to-believe, but optimized internal dispatch function for
1348
- // triggering events. Tries to keep the usual cases speedy (most internal
1349
- // Backbone events have 3 arguments).
1350
- var triggerEvents = function(events, args) {
1351
- var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
1352
- switch (args.length) {
1353
- case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
1354
- case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
1355
- case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
1356
- case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
1357
- default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
1358
- }
1359
- };
1360
-
1361
- var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
1362
-
1363
- // Inversion-of-control versions of `on` and `once`. Tell *this* object to
1364
- // listen to an event in another object ... keeping track of what it's
1365
- // listening to.
1366
- _.each(listenMethods, function(implementation, method) {
1367
- Events[method] = function(obj, name, callback) {
1368
- var listeningTo = this._listeningTo || (this._listeningTo = {});
1369
- var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
1370
- listeningTo[id] = obj;
1371
- if (!callback && typeof name === 'object') callback = this;
1372
- obj[implementation](name, callback, this);
1373
- return this;
1374
- };
1375
- });
1376
-
1377
- // Aliases for backwards compatibility.
1378
- Events.bind = Events.on;
1379
- Events.unbind = Events.off;
1380
-
1381
- // Allow the `Backbone` object to serve as a global event bus, for folks who
1382
- // want global "pubsub" in a convenient place.
1383
- _.extend(Backbone, Events);
1384
-
1385
- // Backbone.Model
1386
- // --------------
1387
-
1388
- // Backbone **Models** are the basic data object in the framework --
1389
- // frequently representing a row in a table in a database on your server.
1390
- // A discrete chunk of data and a bunch of useful, related methods for
1391
- // performing computations and transformations on that data.
1392
-
1393
- // Create a new model with the specified attributes. A client id (`cid`)
1394
- // is automatically generated and assigned for you.
1395
- var Model = Backbone.Model = function(attributes, options) {
1396
- var attrs = attributes || {};
1397
- options || (options = {});
1398
- this.cid = _.uniqueId('c');
1399
- this.attributes = {};
1400
- if (options.collection) this.collection = options.collection;
1401
- if (options.parse) attrs = this.parse(attrs, options) || {};
1402
- attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
1403
- this.set(attrs, options);
1404
- this.changed = {};
1405
- this.initialize.apply(this, arguments);
1406
- };
1407
-
1408
- // Attach all inheritable methods to the Model prototype.
1409
- _.extend(Model.prototype, Events, {
1410
-
1411
- // A hash of attributes whose current and previous value differ.
1412
- changed: null,
1413
-
1414
- // The value returned during the last failed validation.
1415
- validationError: null,
1416
-
1417
- // The default name for the JSON `id` attribute is `"id"`. MongoDB and
1418
- // CouchDB users may want to set this to `"_id"`.
1419
- idAttribute: 'id',
1420
-
1421
- // Initialize is an empty function by default. Override it with your own
1422
- // initialization logic.
1423
- initialize: function(){},
1424
-
1425
- // Return a copy of the model's `attributes` object.
1426
- toJSON: function(options) {
1427
- return _.clone(this.attributes);
1428
- },
1429
-
1430
- // Proxy `Backbone.sync` by default -- but override this if you need
1431
- // custom syncing semantics for *this* particular model.
1432
- sync: function() {
1433
- return Backbone.sync.apply(this, arguments);
1434
- },
1435
-
1436
- // Get the value of an attribute.
1437
- get: function(attr) {
1438
- return this.attributes[attr];
1439
- },
1440
-
1441
- // Get the HTML-escaped value of an attribute.
1442
- escape: function(attr) {
1443
- return _.escape(this.get(attr));
1444
- },
1445
-
1446
- // Returns `true` if the attribute contains a value that is not null
1447
- // or undefined.
1448
- has: function(attr) {
1449
- return this.get(attr) != null;
1450
- },
1451
-
1452
- // Set a hash of model attributes on the object, firing `"change"`. This is
1453
- // the core primitive operation of a model, updating the data and notifying
1454
- // anyone who needs to know about the change in state. The heart of the beast.
1455
- set: function(key, val, options) {
1456
- var attr, attrs, unset, changes, silent, changing, prev, current;
1457
- if (key == null) return this;
1458
-
1459
- // Handle both `"key", value` and `{key: value}` -style arguments.
1460
- if (typeof key === 'object') {
1461
- attrs = key;
1462
- options = val;
1463
- } else {
1464
- (attrs = {})[key] = val;
1465
- }
1466
-
1467
- options || (options = {});
1468
-
1469
- // Run validation.
1470
- if (!this._validate(attrs, options)) return false;
1471
-
1472
- // Extract attributes and options.
1473
- unset = options.unset;
1474
- silent = options.silent;
1475
- changes = [];
1476
- changing = this._changing;
1477
- this._changing = true;
1478
-
1479
- if (!changing) {
1480
- this._previousAttributes = _.clone(this.attributes);
1481
- this.changed = {};
1482
- }
1483
- current = this.attributes, prev = this._previousAttributes;
1484
-
1485
- // Check for changes of `id`.
1486
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
1487
-
1488
- // For each `set` attribute, update or delete the current value.
1489
- for (attr in attrs) {
1490
- val = attrs[attr];
1491
- if (!_.isEqual(current[attr], val)) changes.push(attr);
1492
- if (!_.isEqual(prev[attr], val)) {
1493
- this.changed[attr] = val;
1494
- } else {
1495
- delete this.changed[attr];
1496
- }
1497
- unset ? delete current[attr] : current[attr] = val;
1498
- }
1499
-
1500
- // Trigger all relevant attribute changes.
1501
- if (!silent) {
1502
- if (changes.length) this._pending = options;
1503
- for (var i = 0, l = changes.length; i < l; i++) {
1504
- this.trigger('change:' + changes[i], this, current[changes[i]], options);
1505
- }
1506
- }
1507
-
1508
- // You might be wondering why there's a `while` loop here. Changes can
1509
- // be recursively nested within `"change"` events.
1510
- if (changing) return this;
1511
- if (!silent) {
1512
- while (this._pending) {
1513
- options = this._pending;
1514
- this._pending = false;
1515
- this.trigger('change', this, options);
1516
- }
1517
- }
1518
- this._pending = false;
1519
- this._changing = false;
1520
- return this;
1521
- },
1522
-
1523
- // Remove an attribute from the model, firing `"change"`. `unset` is a noop
1524
- // if the attribute doesn't exist.
1525
- unset: function(attr, options) {
1526
- return this.set(attr, void 0, _.extend({}, options, {unset: true}));
1527
- },
1528
-
1529
- // Clear all attributes on the model, firing `"change"`.
1530
- clear: function(options) {
1531
- var attrs = {};
1532
- for (var key in this.attributes) attrs[key] = void 0;
1533
- return this.set(attrs, _.extend({}, options, {unset: true}));
1534
- },
1535
-
1536
- // Determine if the model has changed since the last `"change"` event.
1537
- // If you specify an attribute name, determine if that attribute has changed.
1538
- hasChanged: function(attr) {
1539
- if (attr == null) return !_.isEmpty(this.changed);
1540
- return _.has(this.changed, attr);
1541
- },
1542
-
1543
- // Return an object containing all the attributes that have changed, or
1544
- // false if there are no changed attributes. Useful for determining what
1545
- // parts of a view need to be updated and/or what attributes need to be
1546
- // persisted to the server. Unset attributes will be set to undefined.
1547
- // You can also pass an attributes object to diff against the model,
1548
- // determining if there *would be* a change.
1549
- changedAttributes: function(diff) {
1550
- if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
1551
- var val, changed = false;
1552
- var old = this._changing ? this._previousAttributes : this.attributes;
1553
- for (var attr in diff) {
1554
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
1555
- (changed || (changed = {}))[attr] = val;
1556
- }
1557
- return changed;
1558
- },
1559
-
1560
- // Get the previous value of an attribute, recorded at the time the last
1561
- // `"change"` event was fired.
1562
- previous: function(attr) {
1563
- if (attr == null || !this._previousAttributes) return null;
1564
- return this._previousAttributes[attr];
1565
- },
1566
-
1567
- // Get all of the attributes of the model at the time of the previous
1568
- // `"change"` event.
1569
- previousAttributes: function() {
1570
- return _.clone(this._previousAttributes);
1571
- },
1572
-
1573
- // Fetch the model from the server. If the server's representation of the
1574
- // model differs from its current attributes, they will be overridden,
1575
- // triggering a `"change"` event.
1576
- fetch: function(options) {
1577
- options = options ? _.clone(options) : {};
1578
- if (options.parse === void 0) options.parse = true;
1579
- var model = this;
1580
- var success = options.success;
1581
- options.success = function(resp) {
1582
- if (!model.set(model.parse(resp, options), options)) return false;
1583
- if (success) success(model, resp, options);
1584
- model.trigger('sync', model, resp, options);
1585
- };
1586
- wrapError(this, options);
1587
- return this.sync('read', this, options);
1588
- },
1589
-
1590
- // Set a hash of model attributes, and sync the model to the server.
1591
- // If the server returns an attributes hash that differs, the model's
1592
- // state will be `set` again.
1593
- save: function(key, val, options) {
1594
- var attrs, method, xhr, attributes = this.attributes;
1595
-
1596
- // Handle both `"key", value` and `{key: value}` -style arguments.
1597
- if (key == null || typeof key === 'object') {
1598
- attrs = key;
1599
- options = val;
1600
- } else {
1601
- (attrs = {})[key] = val;
1602
- }
1603
-
1604
- options = _.extend({validate: true}, options);
1605
-
1606
- // If we're not waiting and attributes exist, save acts as
1607
- // `set(attr).save(null, opts)` with validation. Otherwise, check if
1608
- // the model will be valid when the attributes, if any, are set.
1609
- if (attrs && !options.wait) {
1610
- if (!this.set(attrs, options)) return false;
1611
- } else {
1612
- if (!this._validate(attrs, options)) return false;
1613
- }
1614
-
1615
- // Set temporary attributes if `{wait: true}`.
1616
- if (attrs && options.wait) {
1617
- this.attributes = _.extend({}, attributes, attrs);
1618
- }
1619
-
1620
- // After a successful server-side save, the client is (optionally)
1621
- // updated with the server-side state.
1622
- if (options.parse === void 0) options.parse = true;
1623
- var model = this;
1624
- var success = options.success;
1625
- options.success = function(resp) {
1626
- // Ensure attributes are restored during synchronous saves.
1627
- model.attributes = attributes;
1628
- var serverAttrs = model.parse(resp, options);
1629
- if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
1630
- if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
1631
- return false;
1632
- }
1633
- if (success) success(model, resp, options);
1634
- model.trigger('sync', model, resp, options);
1635
- };
1636
- wrapError(this, options);
1637
-
1638
- method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
1639
- if (method === 'patch') options.attrs = attrs;
1640
- xhr = this.sync(method, this, options);
1641
-
1642
- // Restore attributes.
1643
- if (attrs && options.wait) this.attributes = attributes;
1644
-
1645
- return xhr;
1646
- },
1647
-
1648
- // Destroy this model on the server if it was already persisted.
1649
- // Optimistically removes the model from its collection, if it has one.
1650
- // If `wait: true` is passed, waits for the server to respond before removal.
1651
- destroy: function(options) {
1652
- options = options ? _.clone(options) : {};
1653
- var model = this;
1654
- var success = options.success;
1655
-
1656
- var destroy = function() {
1657
- model.trigger('destroy', model, model.collection, options);
1658
- };
1659
-
1660
- options.success = function(resp) {
1661
- if (options.wait || model.isNew()) destroy();
1662
- if (success) success(model, resp, options);
1663
- if (!model.isNew()) model.trigger('sync', model, resp, options);
1664
- };
1665
-
1666
- if (this.isNew()) {
1667
- options.success();
1668
- return false;
1669
- }
1670
- wrapError(this, options);
1671
-
1672
- var xhr = this.sync('delete', this, options);
1673
- if (!options.wait) destroy();
1674
- return xhr;
1675
- },
1676
-
1677
- // Default URL for the model's representation on the server -- if you're
1678
- // using Backbone's restful methods, override this to change the endpoint
1679
- // that will be called.
1680
- url: function() {
1681
- var base =
1682
- _.result(this, 'urlRoot') ||
1683
- _.result(this.collection, 'url') ||
1684
- urlError();
1685
- if (this.isNew()) return base;
1686
- return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
1687
- },
1688
-
1689
- // **parse** converts a response into the hash of attributes to be `set` on
1690
- // the model. The default implementation is just to pass the response along.
1691
- parse: function(resp, options) {
1692
- return resp;
1693
- },
1694
-
1695
- // Create a new model with identical attributes to this one.
1696
- clone: function() {
1697
- return new this.constructor(this.attributes);
1698
- },
1699
-
1700
- // A model is new if it has never been saved to the server, and lacks an id.
1701
- isNew: function() {
1702
- return !this.has(this.idAttribute);
1703
- },
1704
-
1705
- // Check if the model is currently in a valid state.
1706
- isValid: function(options) {
1707
- return this._validate({}, _.extend(options || {}, { validate: true }));
1708
- },
1709
-
1710
- // Run validation against the next complete set of model attributes,
1711
- // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
1712
- _validate: function(attrs, options) {
1713
- if (!options.validate || !this.validate) return true;
1714
- attrs = _.extend({}, this.attributes, attrs);
1715
- var error = this.validationError = this.validate(attrs, options) || null;
1716
- if (!error) return true;
1717
- this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
1718
- return false;
1719
- }
1720
-
1721
- });
1722
-
1723
- // Underscore methods that we want to implement on the Model.
1724
- var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
1725
-
1726
- // Mix in each Underscore method as a proxy to `Model#attributes`.
1727
- _.each(modelMethods, function(method) {
1728
- Model.prototype[method] = function() {
1729
- var args = slice.call(arguments);
1730
- args.unshift(this.attributes);
1731
- return _[method].apply(_, args);
1732
- };
1733
- });
1734
-
1735
- // Backbone.Collection
1736
- // -------------------
1737
-
1738
- // If models tend to represent a single row of data, a Backbone Collection is
1739
- // more analagous to a table full of data ... or a small slice or page of that
1740
- // table, or a collection of rows that belong together for a particular reason
1741
- // -- all of the messages in this particular folder, all of the documents
1742
- // belonging to this particular author, and so on. Collections maintain
1743
- // indexes of their models, both in order, and for lookup by `id`.
1744
-
1745
- // Create a new **Collection**, perhaps to contain a specific type of `model`.
1746
- // If a `comparator` is specified, the Collection will maintain
1747
- // its models in sort order, as they're added and removed.
1748
- var Collection = Backbone.Collection = function(models, options) {
1749
- options || (options = {});
1750
- if (options.model) this.model = options.model;
1751
- if (options.comparator !== void 0) this.comparator = options.comparator;
1752
- this._reset();
1753
- this.initialize.apply(this, arguments);
1754
- if (models) this.reset(models, _.extend({silent: true}, options));
1755
- };
1756
-
1757
- // Default options for `Collection#set`.
1758
- var setOptions = {add: true, remove: true, merge: true};
1759
- var addOptions = {add: true, remove: false};
1760
-
1761
- // Define the Collection's inheritable methods.
1762
- _.extend(Collection.prototype, Events, {
1763
-
1764
- // The default model for a collection is just a **Backbone.Model**.
1765
- // This should be overridden in most cases.
1766
- model: Model,
1767
-
1768
- // Initialize is an empty function by default. Override it with your own
1769
- // initialization logic.
1770
- initialize: function(){},
1771
-
1772
- // The JSON representation of a Collection is an array of the
1773
- // models' attributes.
1774
- toJSON: function(options) {
1775
- return this.map(function(model){ return model.toJSON(options); });
1776
- },
1777
-
1778
- // Proxy `Backbone.sync` by default.
1779
- sync: function() {
1780
- return Backbone.sync.apply(this, arguments);
1781
- },
1782
-
1783
- // Add a model, or list of models to the set.
1784
- add: function(models, options) {
1785
- return this.set(models, _.extend({merge: false}, options, addOptions));
1786
- },
1787
-
1788
- // Remove a model, or a list of models from the set.
1789
- remove: function(models, options) {
1790
- var singular = !_.isArray(models);
1791
- models = singular ? [models] : _.clone(models);
1792
- options || (options = {});
1793
- var i, l, index, model;
1794
- for (i = 0, l = models.length; i < l; i++) {
1795
- model = models[i] = this.get(models[i]);
1796
- if (!model) continue;
1797
- delete this._byId[model.id];
1798
- delete this._byId[model.cid];
1799
- index = this.indexOf(model);
1800
- this.models.splice(index, 1);
1801
- this.length--;
1802
- if (!options.silent) {
1803
- options.index = index;
1804
- model.trigger('remove', model, this, options);
1805
- }
1806
- this._removeReference(model, options);
1807
- }
1808
- return singular ? models[0] : models;
1809
- },
1810
-
1811
- // Update a collection by `set`-ing a new list of models, adding new ones,
1812
- // removing models that are no longer present, and merging models that
1813
- // already exist in the collection, as necessary. Similar to **Model#set**,
1814
- // the core operation for updating the data contained by the collection.
1815
- set: function(models, options) {
1816
- options = _.defaults({}, options, setOptions);
1817
- if (options.parse) models = this.parse(models, options);
1818
- var singular = !_.isArray(models);
1819
- models = singular ? (models ? [models] : []) : _.clone(models);
1820
- var i, l, id, model, attrs, existing, sort;
1821
- var at = options.at;
1822
- var targetModel = this.model;
1823
- var sortable = this.comparator && (at == null) && options.sort !== false;
1824
- var sortAttr = _.isString(this.comparator) ? this.comparator : null;
1825
- var toAdd = [], toRemove = [], modelMap = {};
1826
- var add = options.add, merge = options.merge, remove = options.remove;
1827
- var order = !sortable && add && remove ? [] : false;
1828
-
1829
- // Turn bare objects into model references, and prevent invalid models
1830
- // from being added.
1831
- for (i = 0, l = models.length; i < l; i++) {
1832
- attrs = models[i] || {};
1833
- if (attrs instanceof Model) {
1834
- id = model = attrs;
1835
- } else {
1836
- id = attrs[targetModel.prototype.idAttribute || 'id'];
1837
- }
1838
-
1839
- // If a duplicate is found, prevent it from being added and
1840
- // optionally merge it into the existing model.
1841
- if (existing = this.get(id)) {
1842
- if (remove) modelMap[existing.cid] = true;
1843
- if (merge) {
1844
- attrs = attrs === model ? model.attributes : attrs;
1845
- if (options.parse) attrs = existing.parse(attrs, options);
1846
- existing.set(attrs, options);
1847
- if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
1848
- }
1849
- models[i] = existing;
1850
-
1851
- // If this is a new, valid model, push it to the `toAdd` list.
1852
- } else if (add) {
1853
- model = models[i] = this._prepareModel(attrs, options);
1854
- if (!model) continue;
1855
- toAdd.push(model);
1856
- this._addReference(model, options);
1857
- }
1858
-
1859
- // Do not add multiple models with the same `id`.
1860
- model = existing || model;
1861
- if (order && (model.isNew() || !modelMap[model.id])) order.push(model);
1862
- modelMap[model.id] = true;
1863
- }
1864
-
1865
- // Remove nonexistent models if appropriate.
1866
- if (remove) {
1867
- for (i = 0, l = this.length; i < l; ++i) {
1868
- if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
1869
- }
1870
- if (toRemove.length) this.remove(toRemove, options);
1871
- }
1872
-
1873
- // See if sorting is needed, update `length` and splice in new models.
1874
- if (toAdd.length || (order && order.length)) {
1875
- if (sortable) sort = true;
1876
- this.length += toAdd.length;
1877
- if (at != null) {
1878
- for (i = 0, l = toAdd.length; i < l; i++) {
1879
- this.models.splice(at + i, 0, toAdd[i]);
1880
- }
1881
- } else {
1882
- if (order) this.models.length = 0;
1883
- var orderedModels = order || toAdd;
1884
- for (i = 0, l = orderedModels.length; i < l; i++) {
1885
- this.models.push(orderedModels[i]);
1886
- }
1887
- }
1888
- }
1889
-
1890
- // Silently sort the collection if appropriate.
1891
- if (sort) this.sort({silent: true});
1892
-
1893
- // Unless silenced, it's time to fire all appropriate add/sort events.
1894
- if (!options.silent) {
1895
- for (i = 0, l = toAdd.length; i < l; i++) {
1896
- (model = toAdd[i]).trigger('add', model, this, options);
1897
- }
1898
- if (sort || (order && order.length)) this.trigger('sort', this, options);
1899
- }
1900
-
1901
- // Return the added (or merged) model (or models).
1902
- return singular ? models[0] : models;
1903
- },
1904
-
1905
- // When you have more items than you want to add or remove individually,
1906
- // you can reset the entire set with a new list of models, without firing
1907
- // any granular `add` or `remove` events. Fires `reset` when finished.
1908
- // Useful for bulk operations and optimizations.
1909
- reset: function(models, options) {
1910
- options || (options = {});
1911
- for (var i = 0, l = this.models.length; i < l; i++) {
1912
- this._removeReference(this.models[i], options);
1913
- }
1914
- options.previousModels = this.models;
1915
- this._reset();
1916
- models = this.add(models, _.extend({silent: true}, options));
1917
- if (!options.silent) this.trigger('reset', this, options);
1918
- return models;
1919
- },
1920
-
1921
- // Add a model to the end of the collection.
1922
- push: function(model, options) {
1923
- return this.add(model, _.extend({at: this.length}, options));
1924
- },
1925
-
1926
- // Remove a model from the end of the collection.
1927
- pop: function(options) {
1928
- var model = this.at(this.length - 1);
1929
- this.remove(model, options);
1930
- return model;
1931
- },
1932
-
1933
- // Add a model to the beginning of the collection.
1934
- unshift: function(model, options) {
1935
- return this.add(model, _.extend({at: 0}, options));
1936
- },
1937
-
1938
- // Remove a model from the beginning of the collection.
1939
- shift: function(options) {
1940
- var model = this.at(0);
1941
- this.remove(model, options);
1942
- return model;
1943
- },
1944
-
1945
- // Slice out a sub-array of models from the collection.
1946
- slice: function() {
1947
- return slice.apply(this.models, arguments);
1948
- },
1949
-
1950
- // Get a model from the set by id.
1951
- get: function(obj) {
1952
- if (obj == null) return void 0;
1953
- return this._byId[obj] || this._byId[obj.id] || this._byId[obj.cid];
1954
- },
1955
-
1956
- // Get the model at the given index.
1957
- at: function(index) {
1958
- return this.models[index];
1959
- },
1960
-
1961
- // Return models with matching attributes. Useful for simple cases of
1962
- // `filter`.
1963
- where: function(attrs, first) {
1964
- if (_.isEmpty(attrs)) return first ? void 0 : [];
1965
- return this[first ? 'find' : 'filter'](function(model) {
1966
- for (var key in attrs) {
1967
- if (attrs[key] !== model.get(key)) return false;
1968
- }
1969
- return true;
1970
- });
1971
- },
1972
-
1973
- // Return the first model with matching attributes. Useful for simple cases
1974
- // of `find`.
1975
- findWhere: function(attrs) {
1976
- return this.where(attrs, true);
1977
- },
1978
-
1979
- // Force the collection to re-sort itself. You don't need to call this under
1980
- // normal circumstances, as the set will maintain sort order as each item
1981
- // is added.
1982
- sort: function(options) {
1983
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
1984
- options || (options = {});
1985
-
1986
- // Run sort based on type of `comparator`.
1987
- if (_.isString(this.comparator) || this.comparator.length === 1) {
1988
- this.models = this.sortBy(this.comparator, this);
1989
- } else {
1990
- this.models.sort(_.bind(this.comparator, this));
1991
- }
1992
-
1993
- if (!options.silent) this.trigger('sort', this, options);
1994
- return this;
1995
- },
1996
-
1997
- // Pluck an attribute from each model in the collection.
1998
- pluck: function(attr) {
1999
- return _.invoke(this.models, 'get', attr);
2000
- },
2001
-
2002
- // Fetch the default set of models for this collection, resetting the
2003
- // collection when they arrive. If `reset: true` is passed, the response
2004
- // data will be passed through the `reset` method instead of `set`.
2005
- fetch: function(options) {
2006
- options = options ? _.clone(options) : {};
2007
- if (options.parse === void 0) options.parse = true;
2008
- var success = options.success;
2009
- var collection = this;
2010
- options.success = function(resp) {
2011
- var method = options.reset ? 'reset' : 'set';
2012
- collection[method](resp, options);
2013
- if (success) success(collection, resp, options);
2014
- collection.trigger('sync', collection, resp, options);
2015
- };
2016
- wrapError(this, options);
2017
- return this.sync('read', this, options);
2018
- },
2019
-
2020
- // Create a new instance of a model in this collection. Add the model to the
2021
- // collection immediately, unless `wait: true` is passed, in which case we
2022
- // wait for the server to agree.
2023
- create: function(model, options) {
2024
- options = options ? _.clone(options) : {};
2025
- if (!(model = this._prepareModel(model, options))) return false;
2026
- if (!options.wait) this.add(model, options);
2027
- var collection = this;
2028
- var success = options.success;
2029
- options.success = function(model, resp) {
2030
- if (options.wait) collection.add(model, options);
2031
- if (success) success(model, resp, options);
2032
- };
2033
- model.save(null, options);
2034
- return model;
2035
- },
2036
-
2037
- // **parse** converts a response into a list of models to be added to the
2038
- // collection. The default implementation is just to pass it through.
2039
- parse: function(resp, options) {
2040
- return resp;
2041
- },
2042
-
2043
- // Create a new collection with an identical list of models as this one.
2044
- clone: function() {
2045
- return new this.constructor(this.models);
2046
- },
2047
-
2048
- // Private method to reset all internal state. Called when the collection
2049
- // is first initialized or reset.
2050
- _reset: function() {
2051
- this.length = 0;
2052
- this.models = [];
2053
- this._byId = {};
2054
- },
2055
-
2056
- // Prepare a hash of attributes (or other model) to be added to this
2057
- // collection.
2058
- _prepareModel: function(attrs, options) {
2059
- if (attrs instanceof Model) return attrs;
2060
- options = options ? _.clone(options) : {};
2061
- options.collection = this;
2062
- var model = new this.model(attrs, options);
2063
- if (!model.validationError) return model;
2064
- this.trigger('invalid', this, model.validationError, options);
2065
- return false;
2066
- },
2067
-
2068
- // Internal method to create a model's ties to a collection.
2069
- _addReference: function(model, options) {
2070
- this._byId[model.cid] = model;
2071
- if (model.id != null) this._byId[model.id] = model;
2072
- if (!model.collection) model.collection = this;
2073
- model.on('all', this._onModelEvent, this);
2074
- },
2075
-
2076
- // Internal method to sever a model's ties to a collection.
2077
- _removeReference: function(model, options) {
2078
- if (this === model.collection) delete model.collection;
2079
- model.off('all', this._onModelEvent, this);
2080
- },
2081
-
2082
- // Internal method called every time a model in the set fires an event.
2083
- // Sets need to update their indexes when models change ids. All other
2084
- // events simply proxy through. "add" and "remove" events that originate
2085
- // in other collections are ignored.
2086
- _onModelEvent: function(event, model, collection, options) {
2087
- if ((event === 'add' || event === 'remove') && collection !== this) return;
2088
- if (event === 'destroy') this.remove(model, options);
2089
- if (model && event === 'change:' + model.idAttribute) {
2090
- delete this._byId[model.previous(model.idAttribute)];
2091
- if (model.id != null) this._byId[model.id] = model;
2092
- }
2093
- this.trigger.apply(this, arguments);
2094
- }
2095
-
2096
- });
2097
-
2098
- // Underscore methods that we want to implement on the Collection.
2099
- // 90% of the core usefulness of Backbone Collections is actually implemented
2100
- // right here:
2101
- var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
2102
- 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
2103
- 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
2104
- 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
2105
- 'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
2106
- 'lastIndexOf', 'isEmpty', 'chain', 'sample'];
2107
-
2108
- // Mix in each Underscore method as a proxy to `Collection#models`.
2109
- _.each(methods, function(method) {
2110
- Collection.prototype[method] = function() {
2111
- var args = slice.call(arguments);
2112
- args.unshift(this.models);
2113
- return _[method].apply(_, args);
2114
- };
2115
- });
2116
-
2117
- // Underscore methods that take a property name as an argument.
2118
- var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];
2119
-
2120
- // Use attributes instead of properties.
2121
- _.each(attributeMethods, function(method) {
2122
- Collection.prototype[method] = function(value, context) {
2123
- var iterator = _.isFunction(value) ? value : function(model) {
2124
- return model.get(value);
2125
- };
2126
- return _[method](this.models, iterator, context);
2127
- };
2128
- });
2129
-
2130
- // Backbone.View
2131
- // -------------
2132
-
2133
- // Backbone Views are almost more convention than they are actual code. A View
2134
- // is simply a JavaScript object that represents a logical chunk of UI in the
2135
- // DOM. This might be a single item, an entire list, a sidebar or panel, or
2136
- // even the surrounding frame which wraps your whole app. Defining a chunk of
2137
- // UI as a **View** allows you to define your DOM events declaratively, without
2138
- // having to worry about render order ... and makes it easy for the view to
2139
- // react to specific changes in the state of your models.
2140
-
2141
- // Creating a Backbone.View creates its initial element outside of the DOM,
2142
- // if an existing element is not provided...
2143
- var View = Backbone.View = function(options) {
2144
- this.cid = _.uniqueId('view');
2145
- options || (options = {});
2146
- _.extend(this, _.pick(options, viewOptions));
2147
- this._ensureElement();
2148
- this.initialize.apply(this, arguments);
2149
- this.delegateEvents();
2150
- };
2151
-
2152
- // Cached regex to split keys for `delegate`.
2153
- var delegateEventSplitter = /^(\S+)\s*(.*)$/;
2154
-
2155
- // List of view options to be merged as properties.
2156
- var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
2157
-
2158
- // Set up all inheritable **Backbone.View** properties and methods.
2159
- _.extend(View.prototype, Events, {
2160
-
2161
- // The default `tagName` of a View's element is `"div"`.
2162
- tagName: 'div',
2163
-
2164
- // jQuery delegate for element lookup, scoped to DOM elements within the
2165
- // current view. This should be preferred to global lookups where possible.
2166
- $: function(selector) {
2167
- return this.$el.find(selector);
2168
- },
2169
-
2170
- // Initialize is an empty function by default. Override it with your own
2171
- // initialization logic.
2172
- initialize: function(){},
2173
-
2174
- // **render** is the core function that your view should override, in order
2175
- // to populate its element (`this.el`), with the appropriate HTML. The
2176
- // convention is for **render** to always return `this`.
2177
- render: function() {
2178
- return this;
2179
- },
2180
-
2181
- // Remove this view by taking the element out of the DOM, and removing any
2182
- // applicable Backbone.Events listeners.
2183
- remove: function() {
2184
- this.$el.remove();
2185
- this.stopListening();
2186
- return this;
2187
- },
2188
-
2189
- // Change the view's element (`this.el` property), including event
2190
- // re-delegation.
2191
- setElement: function(element, delegate) {
2192
- if (this.$el) this.undelegateEvents();
2193
- this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
2194
- this.el = this.$el[0];
2195
- if (delegate !== false) this.delegateEvents();
2196
- return this;
2197
- },
2198
-
2199
- // Set callbacks, where `this.events` is a hash of
2200
- //
2201
- // *{"event selector": "callback"}*
2202
- //
2203
- // {
2204
- // 'mousedown .title': 'edit',
2205
- // 'click .button': 'save',
2206
- // 'click .open': function(e) { ... }
2207
- // }
2208
- //
2209
- // pairs. Callbacks will be bound to the view, with `this` set properly.
2210
- // Uses event delegation for efficiency.
2211
- // Omitting the selector binds the event to `this.el`.
2212
- // This only works for delegate-able events: not `focus`, `blur`, and
2213
- // not `change`, `submit`, and `reset` in Internet Explorer.
2214
- delegateEvents: function(events) {
2215
- if (!(events || (events = _.result(this, 'events')))) return this;
2216
- this.undelegateEvents();
2217
- for (var key in events) {
2218
- var method = events[key];
2219
- if (!_.isFunction(method)) method = this[events[key]];
2220
- if (!method) continue;
2221
-
2222
- var match = key.match(delegateEventSplitter);
2223
- var eventName = match[1], selector = match[2];
2224
- method = _.bind(method, this);
2225
- eventName += '.delegateEvents' + this.cid;
2226
- if (selector === '') {
2227
- this.$el.on(eventName, method);
2228
- } else {
2229
- this.$el.on(eventName, selector, method);
2230
- }
2231
- }
2232
- return this;
2233
- },
2234
-
2235
- // Clears all callbacks previously bound to the view with `delegateEvents`.
2236
- // You usually don't need to use this, but may wish to if you have multiple
2237
- // Backbone views attached to the same DOM element.
2238
- undelegateEvents: function() {
2239
- this.$el.off('.delegateEvents' + this.cid);
2240
- return this;
2241
- },
2242
-
2243
- // Ensure that the View has a DOM element to render into.
2244
- // If `this.el` is a string, pass it through `$()`, take the first
2245
- // matching element, and re-assign it to `el`. Otherwise, create
2246
- // an element from the `id`, `className` and `tagName` properties.
2247
- _ensureElement: function() {
2248
- if (!this.el) {
2249
- var attrs = _.extend({}, _.result(this, 'attributes'));
2250
- if (this.id) attrs.id = _.result(this, 'id');
2251
- if (this.className) attrs['class'] = _.result(this, 'className');
2252
- var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
2253
- this.setElement($el, false);
2254
- } else {
2255
- this.setElement(_.result(this, 'el'), false);
2256
- }
2257
- }
2258
-
2259
- });
2260
-
2261
- // Backbone.sync
2262
- // -------------
2263
-
2264
- // Override this function to change the manner in which Backbone persists
2265
- // models to the server. You will be passed the type of request, and the
2266
- // model in question. By default, makes a RESTful Ajax request
2267
- // to the model's `url()`. Some possible customizations could be:
2268
- //
2269
- // * Use `setTimeout` to batch rapid-fire updates into a single request.
2270
- // * Send up the models as XML instead of JSON.
2271
- // * Persist models via WebSockets instead of Ajax.
2272
- //
2273
- // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
2274
- // as `POST`, with a `_method` parameter containing the true HTTP method,
2275
- // as well as all requests with the body as `application/x-www-form-urlencoded`
2276
- // instead of `application/json` with the model in a param named `model`.
2277
- // Useful when interfacing with server-side languages like **PHP** that make
2278
- // it difficult to read the body of `PUT` requests.
2279
- Backbone.sync = function(method, model, options) {
2280
- var type = methodMap[method];
2281
-
2282
- // Default options, unless specified.
2283
- _.defaults(options || (options = {}), {
2284
- emulateHTTP: Backbone.emulateHTTP,
2285
- emulateJSON: Backbone.emulateJSON
2286
- });
2287
-
2288
- // Default JSON-request options.
2289
- var params = {type: type, dataType: 'json'};
2290
-
2291
- // Ensure that we have a URL.
2292
- if (!options.url) {
2293
- params.url = _.result(model, 'url') || urlError();
2294
- }
2295
-
2296
- // Ensure that we have the appropriate request data.
2297
- if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
2298
- params.contentType = 'application/json';
2299
- params.data = JSON.stringify(options.attrs || model.toJSON(options));
2300
- }
2301
-
2302
- // For older servers, emulate JSON by encoding the request into an HTML-form.
2303
- if (options.emulateJSON) {
2304
- params.contentType = 'application/x-www-form-urlencoded';
2305
- params.data = params.data ? {model: params.data} : {};
2306
- }
2307
-
2308
- // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
2309
- // And an `X-HTTP-Method-Override` header.
2310
- if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
2311
- params.type = 'POST';
2312
- if (options.emulateJSON) params.data._method = type;
2313
- var beforeSend = options.beforeSend;
2314
- options.beforeSend = function(xhr) {
2315
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
2316
- if (beforeSend) return beforeSend.apply(this, arguments);
2317
- };
2318
- }
2319
-
2320
- // Don't process data on a non-GET request.
2321
- if (params.type !== 'GET' && !options.emulateJSON) {
2322
- params.processData = false;
2323
- }
2324
-
2325
- // If we're sending a `PATCH` request, and we're in an old Internet Explorer
2326
- // that still has ActiveX enabled by default, override jQuery to use that
2327
- // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
2328
- if (params.type === 'PATCH' && noXhrPatch) {
2329
- params.xhr = function() {
2330
- return new ActiveXObject("Microsoft.XMLHTTP");
2331
- };
2332
- }
2333
-
2334
- // Make the request, allowing the user to override any Ajax options.
2335
- var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
2336
- model.trigger('request', model, xhr, options);
2337
- return xhr;
2338
- };
2339
-
2340
- var noXhrPatch =
2341
- typeof window !== 'undefined' && !!window.ActiveXObject &&
2342
- !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
2343
-
2344
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
2345
- var methodMap = {
2346
- 'create': 'POST',
2347
- 'update': 'PUT',
2348
- 'patch': 'PATCH',
2349
- 'delete': 'DELETE',
2350
- 'read': 'GET'
2351
- };
2352
-
2353
- // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
2354
- // Override this if you'd like to use a different library.
2355
- Backbone.ajax = function() {
2356
- return Backbone.$.ajax.apply(Backbone.$, arguments);
2357
- };
2358
-
2359
- // Backbone.Router
2360
- // ---------------
2361
-
2362
- // Routers map faux-URLs to actions, and fire events when routes are
2363
- // matched. Creating a new one sets its `routes` hash, if not set statically.
2364
- var Router = Backbone.Router = function(options) {
2365
- options || (options = {});
2366
- if (options.routes) this.routes = options.routes;
2367
- this._bindRoutes();
2368
- this.initialize.apply(this, arguments);
2369
- };
2370
-
2371
- // Cached regular expressions for matching named param parts and splatted
2372
- // parts of route strings.
2373
- var optionalParam = /\((.*?)\)/g;
2374
- var namedParam = /(\(\?)?:\w+/g;
2375
- var splatParam = /\*\w+/g;
2376
- var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
2377
-
2378
- // Set up all inheritable **Backbone.Router** properties and methods.
2379
- _.extend(Router.prototype, Events, {
2380
-
2381
- // Initialize is an empty function by default. Override it with your own
2382
- // initialization logic.
2383
- initialize: function(){},
2384
-
2385
- // Manually bind a single named route to a callback. For example:
2386
- //
2387
- // this.route('search/:query/p:num', 'search', function(query, num) {
2388
- // ...
2389
- // });
2390
- //
2391
- route: function(route, name, callback) {
2392
- if (!_.isRegExp(route)) route = this._routeToRegExp(route);
2393
- if (_.isFunction(name)) {
2394
- callback = name;
2395
- name = '';
2396
- }
2397
- if (!callback) callback = this[name];
2398
- var router = this;
2399
- Backbone.history.route(route, function(fragment) {
2400
- var args = router._extractParameters(route, fragment);
2401
- router.execute(callback, args);
2402
- router.trigger.apply(router, ['route:' + name].concat(args));
2403
- router.trigger('route', name, args);
2404
- Backbone.history.trigger('route', router, name, args);
2405
- });
2406
- return this;
2407
- },
2408
-
2409
- // Execute a route handler with the provided parameters. This is an
2410
- // excellent place to do pre-route setup or post-route cleanup.
2411
- execute: function(callback, args) {
2412
- if (callback) callback.apply(this, args);
2413
- },
2414
-
2415
- // Simple proxy to `Backbone.history` to save a fragment into the history.
2416
- navigate: function(fragment, options) {
2417
- Backbone.history.navigate(fragment, options);
2418
- return this;
2419
- },
2420
-
2421
- // Bind all defined routes to `Backbone.history`. We have to reverse the
2422
- // order of the routes here to support behavior where the most general
2423
- // routes can be defined at the bottom of the route map.
2424
- _bindRoutes: function() {
2425
- if (!this.routes) return;
2426
- this.routes = _.result(this, 'routes');
2427
- var route, routes = _.keys(this.routes);
2428
- while ((route = routes.pop()) != null) {
2429
- this.route(route, this.routes[route]);
2430
- }
2431
- },
2432
-
2433
- // Convert a route string into a regular expression, suitable for matching
2434
- // against the current location hash.
2435
- _routeToRegExp: function(route) {
2436
- route = route.replace(escapeRegExp, '\\$&')
2437
- .replace(optionalParam, '(?:$1)?')
2438
- .replace(namedParam, function(match, optional) {
2439
- return optional ? match : '([^/?]+)';
2440
- })
2441
- .replace(splatParam, '([^?]*?)');
2442
- return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
2443
- },
2444
-
2445
- // Given a route, and a URL fragment that it matches, return the array of
2446
- // extracted decoded parameters. Empty or unmatched parameters will be
2447
- // treated as `null` to normalize cross-browser behavior.
2448
- _extractParameters: function(route, fragment) {
2449
- var params = route.exec(fragment).slice(1);
2450
- return _.map(params, function(param, i) {
2451
- // Don't decode the search params.
2452
- if (i === params.length - 1) return param || null;
2453
- return param ? decodeURIComponent(param) : null;
2454
- });
2455
- }
2456
-
2457
- });
2458
-
2459
- // Backbone.History
2460
- // ----------------
2461
-
2462
- // Handles cross-browser history management, based on either
2463
- // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
2464
- // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
2465
- // and URL fragments. If the browser supports neither (old IE, natch),
2466
- // falls back to polling.
2467
- var History = Backbone.History = function() {
2468
- this.handlers = [];
2469
- _.bindAll(this, 'checkUrl');
2470
-
2471
- // Ensure that `History` can be used outside of the browser.
2472
- if (typeof window !== 'undefined') {
2473
- this.location = window.location;
2474
- this.history = window.history;
2475
- }
2476
- };
2477
-
2478
- // Cached regex for stripping a leading hash/slash and trailing space.
2479
- var routeStripper = /^[#\/]|\s+$/g;
2480
-
2481
- // Cached regex for stripping leading and trailing slashes.
2482
- var rootStripper = /^\/+|\/+$/g;
2483
-
2484
- // Cached regex for detecting MSIE.
2485
- var isExplorer = /msie [\w.]+/;
2486
-
2487
- // Cached regex for removing a trailing slash.
2488
- var trailingSlash = /\/$/;
2489
-
2490
- // Cached regex for stripping urls of hash.
2491
- var pathStripper = /#.*$/;
2492
-
2493
- // Has the history handling already been started?
2494
- History.started = false;
2495
-
2496
- // Set up all inheritable **Backbone.History** properties and methods.
2497
- _.extend(History.prototype, Events, {
2498
-
2499
- // The default interval to poll for hash changes, if necessary, is
2500
- // twenty times a second.
2501
- interval: 50,
2502
-
2503
- // Are we at the app root?
2504
- atRoot: function() {
2505
- return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root;
2506
- },
2507
-
2508
- // Gets the true hash value. Cannot use location.hash directly due to bug
2509
- // in Firefox where location.hash will always be decoded.
2510
- getHash: function(window) {
2511
- var match = (window || this).location.href.match(/#(.*)$/);
2512
- return match ? match[1] : '';
2513
- },
2514
-
2515
- // Get the cross-browser normalized URL fragment, either from the URL,
2516
- // the hash, or the override.
2517
- getFragment: function(fragment, forcePushState) {
2518
- if (fragment == null) {
2519
- if (this._hasPushState || !this._wantsHashChange || forcePushState) {
2520
- fragment = decodeURI(this.location.pathname + this.location.search);
2521
- var root = this.root.replace(trailingSlash, '');
2522
- if (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
2523
- } else {
2524
- fragment = this.getHash();
2525
- }
2526
- }
2527
- return fragment.replace(routeStripper, '');
2528
- },
2529
-
2530
- // Start the hash change handling, returning `true` if the current URL matches
2531
- // an existing route, and `false` otherwise.
2532
- start: function(options) {
2533
- if (History.started) throw new Error("Backbone.history has already been started");
2534
- History.started = true;
2535
-
2536
- // Figure out the initial configuration. Do we need an iframe?
2537
- // Is pushState desired ... is it available?
2538
- this.options = _.extend({root: '/'}, this.options, options);
2539
- this.root = this.options.root;
2540
- this._wantsHashChange = this.options.hashChange !== false;
2541
- this._wantsPushState = !!this.options.pushState;
2542
- this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
2543
- var fragment = this.getFragment();
2544
- var docMode = document.documentMode;
2545
- var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
2546
-
2547
- // Normalize root to always include a leading and trailing slash.
2548
- this.root = ('/' + this.root + '/').replace(rootStripper, '/');
2549
-
2550
- if (oldIE && this._wantsHashChange) {
2551
- var frame = Backbone.$('<iframe src="javascript:0" tabindex="-1">');
2552
- this.iframe = frame.hide().appendTo('body')[0].contentWindow;
2553
- this.navigate(fragment);
2554
- }
2555
-
2556
- // Depending on whether we're using pushState or hashes, and whether
2557
- // 'onhashchange' is supported, determine how we check the URL state.
2558
- if (this._hasPushState) {
2559
- Backbone.$(window).on('popstate', this.checkUrl);
2560
- } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
2561
- Backbone.$(window).on('hashchange', this.checkUrl);
2562
- } else if (this._wantsHashChange) {
2563
- this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
2564
- }
2565
-
2566
- // Determine if we need to change the base url, for a pushState link
2567
- // opened by a non-pushState browser.
2568
- this.fragment = fragment;
2569
- var loc = this.location;
2570
-
2571
- // Transition from hashChange to pushState or vice versa if both are
2572
- // requested.
2573
- if (this._wantsHashChange && this._wantsPushState) {
2574
-
2575
- // If we've started off with a route from a `pushState`-enabled
2576
- // browser, but we're currently in a browser that doesn't support it...
2577
- if (!this._hasPushState && !this.atRoot()) {
2578
- this.fragment = this.getFragment(null, true);
2579
- this.location.replace(this.root + '#' + this.fragment);
2580
- // Return immediately as browser will do redirect to new url
2581
- return true;
2582
-
2583
- // Or if we've started out with a hash-based route, but we're currently
2584
- // in a browser where it could be `pushState`-based instead...
2585
- } else if (this._hasPushState && this.atRoot() && loc.hash) {
2586
- this.fragment = this.getHash().replace(routeStripper, '');
2587
- this.history.replaceState({}, document.title, this.root + this.fragment);
2588
- }
2589
-
2590
- }
2591
-
2592
- if (!this.options.silent) return this.loadUrl();
2593
- },
2594
-
2595
- // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
2596
- // but possibly useful for unit testing Routers.
2597
- stop: function() {
2598
- Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
2599
- if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
2600
- History.started = false;
2601
- },
2602
-
2603
- // Add a route to be tested when the fragment changes. Routes added later
2604
- // may override previous routes.
2605
- route: function(route, callback) {
2606
- this.handlers.unshift({route: route, callback: callback});
2607
- },
2608
-
2609
- // Checks the current URL to see if it has changed, and if it has,
2610
- // calls `loadUrl`, normalizing across the hidden iframe.
2611
- checkUrl: function(e) {
2612
- var current = this.getFragment();
2613
- if (current === this.fragment && this.iframe) {
2614
- current = this.getFragment(this.getHash(this.iframe));
2615
- }
2616
- if (current === this.fragment) return false;
2617
- if (this.iframe) this.navigate(current);
2618
- this.loadUrl();
2619
- },
2620
-
2621
- // Attempt to load the current URL fragment. If a route succeeds with a
2622
- // match, returns `true`. If no defined routes matches the fragment,
2623
- // returns `false`.
2624
- loadUrl: function(fragment) {
2625
- fragment = this.fragment = this.getFragment(fragment);
2626
- return _.any(this.handlers, function(handler) {
2627
- if (handler.route.test(fragment)) {
2628
- handler.callback(fragment);
2629
- return true;
2630
- }
2631
- });
2632
- },
2633
-
2634
- // Save a fragment into the hash history, or replace the URL state if the
2635
- // 'replace' option is passed. You are responsible for properly URL-encoding
2636
- // the fragment in advance.
2637
- //
2638
- // The options object can contain `trigger: true` if you wish to have the
2639
- // route callback be fired (not usually desirable), or `replace: true`, if
2640
- // you wish to modify the current URL without adding an entry to the history.
2641
- navigate: function(fragment, options) {
2642
- if (!History.started) return false;
2643
- if (!options || options === true) options = {trigger: !!options};
2644
-
2645
- var url = this.root + (fragment = this.getFragment(fragment || ''));
2646
-
2647
- // Strip the hash for matching.
2648
- fragment = fragment.replace(pathStripper, '');
2649
-
2650
- if (this.fragment === fragment) return;
2651
- this.fragment = fragment;
2652
-
2653
- // Don't include a trailing slash on the root.
2654
- if (fragment === '' && url !== '/') url = url.slice(0, -1);
2655
-
2656
- // If pushState is available, we use it to set the fragment as a real URL.
2657
- if (this._hasPushState) {
2658
- this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
2659
-
2660
- // If hash changes haven't been explicitly disabled, update the hash
2661
- // fragment to store history.
2662
- } else if (this._wantsHashChange) {
2663
- this._updateHash(this.location, fragment, options.replace);
2664
- if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
2665
- // Opening and closing the iframe tricks IE7 and earlier to push a
2666
- // history entry on hash-tag change. When replace is true, we don't
2667
- // want this.
2668
- if(!options.replace) this.iframe.document.open().close();
2669
- this._updateHash(this.iframe.location, fragment, options.replace);
2670
- }
2671
-
2672
- // If you've told us that you explicitly don't want fallback hashchange-
2673
- // based history, then `navigate` becomes a page refresh.
2674
- } else {
2675
- return this.location.assign(url);
2676
- }
2677
- if (options.trigger) return this.loadUrl(fragment);
2678
- },
2679
-
2680
- // Update the hash location, either replacing the current entry, or adding
2681
- // a new one to the browser history.
2682
- _updateHash: function(location, fragment, replace) {
2683
- if (replace) {
2684
- var href = location.href.replace(/(javascript:|#).*$/, '');
2685
- location.replace(href + '#' + fragment);
2686
- } else {
2687
- // Some browsers require that `hash` contains a leading #.
2688
- location.hash = '#' + fragment;
2689
- }
2690
- }
2691
-
2692
- });
2693
-
2694
- // Create the default Backbone.history.
2695
- Backbone.history = new History;
2696
-
2697
- // Helpers
2698
- // -------
2699
-
2700
- // Helper function to correctly set up the prototype chain, for subclasses.
2701
- // Similar to `goog.inherits`, but uses a hash of prototype properties and
2702
- // class properties to be extended.
2703
- var extend = function(protoProps, staticProps) {
2704
- var parent = this;
2705
- var child;
2706
-
2707
- // The constructor function for the new subclass is either defined by you
2708
- // (the "constructor" property in your `extend` definition), or defaulted
2709
- // by us to simply call the parent's constructor.
2710
- if (protoProps && _.has(protoProps, 'constructor')) {
2711
- child = protoProps.constructor;
2712
- } else {
2713
- child = function(){ return parent.apply(this, arguments); };
2714
- }
2715
-
2716
- // Add static properties to the constructor function, if supplied.
2717
- _.extend(child, parent, staticProps);
2718
-
2719
- // Set the prototype chain to inherit from `parent`, without calling
2720
- // `parent`'s constructor function.
2721
- var Surrogate = function(){ this.constructor = child; };
2722
- Surrogate.prototype = parent.prototype;
2723
- child.prototype = new Surrogate;
2724
-
2725
- // Add prototype properties (instance properties) to the subclass,
2726
- // if supplied.
2727
- if (protoProps) _.extend(child.prototype, protoProps);
2728
-
2729
- // Set a convenience property in case the parent's prototype is needed
2730
- // later.
2731
- child.__super__ = parent.prototype;
2732
-
2733
- return child;
2734
- };
2735
-
2736
- // Set up inheritance for the model, collection, router, view and history.
2737
- Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
2738
-
2739
- // Throw an error when a URL is needed, and none is supplied.
2740
- var urlError = function() {
2741
- throw new Error('A "url" property or function must be specified');
2742
- };
2743
-
2744
- // Wrap an optional error callback with a fallback error event.
2745
- var wrapError = function(model, options) {
2746
- var error = options.error;
2747
- options.error = function(resp) {
2748
- if (error) error(model, resp, options);
2749
- model.trigger('error', model, resp, options);
2750
- };
2751
- };
2752
-
2753
- return Backbone;
2754
-
2755
- }));
2756
-
2757
- /**
2758
- * Main source
2759
- */
2760
-
2761
- ;(function(factory) {
2762
- if (typeof define === 'function' && define.amd) {
2763
- // AMD
2764
- define(['underscore', 'backbone'], factory);
2765
- } else {
2766
- // globals
2767
- factory(_, Backbone);
2768
- }
2769
- }(function(_, Backbone) {
2770
-
2771
- /**
2772
- * Takes a nested object and returns a shallow object keyed with the path names
2773
- * e.g. { "level1.level2": "value" }
2774
- *
2775
- * @param {Object} Nested object e.g. { level1: { level2: 'value' } }
2776
- * @return {Object} Shallow object with path names e.g. { 'level1.level2': 'value' }
2777
- */
2778
- function objToPaths(obj) {
2779
- var ret = {},
2780
- separator = DeepModel.keyPathSeparator;
2781
-
2782
- for (var key in obj) {
2783
- var val = obj[key];
2784
-
2785
- if (val && val.constructor === Object && !_.isEmpty(val)) {
2786
- //Recursion for embedded objects
2787
- var obj2 = objToPaths(val);
2788
-
2789
- for (var key2 in obj2) {
2790
- var val2 = obj2[key2];
2791
-
2792
- ret[key + separator + key2] = val2;
2793
- }
2794
- } else {
2795
- ret[key] = val;
2796
- }
2797
- }
2798
-
2799
- return ret;
2800
- }
2801
-
2802
- /**
2803
- * @param {Object} Object to fetch attribute from
2804
- * @param {String} Object path e.g. 'user.name'
2805
- * @return {Mixed}
2806
- */
2807
- function getNested(obj, path, return_exists) {
2808
- var separator = DeepModel.keyPathSeparator;
2809
-
2810
- var fields = path.split(separator);
2811
- var result = obj;
2812
- return_exists || (return_exists === false);
2813
- for (var i = 0, n = fields.length; i < n; i++) {
2814
- if (return_exists && !_.has(result, fields[i])) {
2815
- return false;
2816
- }
2817
- result = result[fields[i]];
2818
-
2819
- if (result == null && i < n - 1) {
2820
- result = {};
2821
- }
2822
-
2823
- if (typeof result === 'undefined') {
2824
- if (return_exists)
2825
- {
2826
- return true;
2827
- }
2828
- return result;
2829
- }
2830
- }
2831
- if (return_exists)
2832
- {
2833
- return true;
2834
- }
2835
- return result;
2836
- }
2837
-
2838
- /**
2839
- * @param {Object} obj Object to fetch attribute from
2840
- * @param {String} path Object path e.g. 'user.name'
2841
- * @param {Object} [options] Options
2842
- * @param {Boolean} [options.unset] Whether to delete the value
2843
- * @param {Mixed} Value to set
2844
- */
2845
- function setNested(obj, path, val, options) {
2846
- options = options || {};
2847
-
2848
- var separator = DeepModel.keyPathSeparator;
2849
-
2850
- var fields = path.split(separator);
2851
- var result = obj;
2852
- for (var i = 0, n = fields.length; i < n && result !== undefined ; i++) {
2853
- var field = fields[i];
2854
-
2855
- //If the last in the path, set the value
2856
- if (i === n - 1) {
2857
- options.unset ? delete result[field] : result[field] = val;
2858
- } else {
2859
- //Create the child object if it doesn't exist, or isn't an object
2860
- if (typeof result[field] === 'undefined' || ! _.isObject(result[field])) {
2861
- result[field] = {};
2862
- }
2863
-
2864
- //Move onto the next part of the path
2865
- result = result[field];
2866
- }
2867
- }
2868
- }
2869
-
2870
- function deleteNested(obj, path) {
2871
- setNested(obj, path, null, { unset: true });
2872
- }
2873
-
2874
- var DeepModel = Backbone.Model.extend({
2875
-
2876
- // Override constructor
2877
- // Support having nested defaults by using _.deepExtend instead of _.extend
2878
- constructor: function(attributes, options) {
2879
- var defaults;
2880
- var attrs = attributes || {};
2881
- this.cid = _.uniqueId('c');
2882
- this.attributes = {};
2883
- if (options && options.collection) this.collection = options.collection;
2884
- if (options && options.parse) attrs = this.parse(attrs, options) || {};
2885
- if (defaults = _.result(this, 'defaults')) {
2886
- //<custom code>
2887
- // Replaced the call to _.defaults with _.deepExtend.
2888
- attrs = _.deepExtend({}, defaults, attrs);
2889
- //</custom code>
2890
- }
2891
- this.set(attrs, options);
2892
- this.changed = {};
2893
- this.initialize.apply(this, arguments);
2894
- },
2895
-
2896
- // Return a copy of the model's `attributes` object.
2897
- toJSON: function(options) {
2898
- return _.deepClone(this.attributes);
2899
- },
2900
-
2901
- // Override get
2902
- // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
2903
- get: function(attr) {
2904
- return getNested(this.attributes, attr);
2905
- },
2906
-
2907
- // Override set
2908
- // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
2909
- set: function(key, val, options) {
2910
- var attr, attrs, unset, changes, silent, changing, prev, current;
2911
- if (key == null) return this;
2912
-
2913
- // Handle both `"key", value` and `{key: value}` -style arguments.
2914
- if (typeof key === 'object') {
2915
- attrs = key;
2916
- options = val || {};
2917
- } else {
2918
- (attrs = {})[key] = val;
2919
- }
2920
-
2921
- options || (options = {});
2922
-
2923
- // Run validation.
2924
- if (!this._validate(attrs, options)) return false;
2925
-
2926
- // Extract attributes and options.
2927
- unset = options.unset;
2928
- silent = options.silent;
2929
- changes = [];
2930
- changing = this._changing;
2931
- this._changing = true;
2932
-
2933
- if (!changing) {
2934
- this._previousAttributes = _.deepClone(this.attributes); //<custom>: Replaced _.clone with _.deepClone
2935
- this.changed = {};
2936
- }
2937
- current = this.attributes, prev = this._previousAttributes;
2938
-
2939
- // Check for changes of `id`.
2940
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
2941
-
2942
- //<custom code>
2943
- attrs = objToPaths(attrs);
2944
- //</custom code>
2945
-
2946
- // For each `set` attribute, update or delete the current value.
2947
- for (attr in attrs) {
2948
- val = attrs[attr];
2949
-
2950
- //<custom code>: Using getNested, setNested and deleteNested
2951
- if (!_.isEqual(getNested(current, attr), val)) changes.push(attr);
2952
- if (!_.isEqual(getNested(prev, attr), val)) {
2953
- setNested(this.changed, attr, val);
2954
- } else {
2955
- deleteNested(this.changed, attr);
2956
- }
2957
- unset ? deleteNested(current, attr) : setNested(current, attr, val);
2958
- //</custom code>
2959
- }
2960
-
2961
- // Trigger all relevant attribute changes.
2962
- if (!silent) {
2963
- if (changes.length) this._pending = true;
2964
-
2965
- //<custom code>
2966
- var separator = DeepModel.keyPathSeparator;
2967
-
2968
- for (var i = 0, l = changes.length; i < l; i++) {
2969
- var key = changes[i];
2970
-
2971
- this.trigger('change:' + key, this, getNested(current, key), options);
2972
-
2973
- var fields = key.split(separator);
2974
-
2975
- //Trigger change events for parent keys with wildcard (*) notation
2976
- for(var n = fields.length - 1; n > 0; n--) {
2977
- var parentKey = _.first(fields, n).join(separator),
2978
- wildcardKey = parentKey + separator + '*';
2979
-
2980
- this.trigger('change:' + wildcardKey, this, getNested(current, parentKey), options);
2981
- }
2982
- //</custom code>
2983
- }
2984
- }
2985
-
2986
- if (changing) return this;
2987
- if (!silent) {
2988
- while (this._pending) {
2989
- this._pending = false;
2990
- this.trigger('change', this, options);
2991
- }
2992
- }
2993
- this._pending = false;
2994
- this._changing = false;
2995
- return this;
2996
- },
2997
-
2998
- // Clear all attributes on the model, firing `"change"` unless you choose
2999
- // to silence it.
3000
- clear: function(options) {
3001
- var attrs = {};
3002
- var shallowAttributes = objToPaths(this.attributes);
3003
- for (var key in shallowAttributes) attrs[key] = void 0;
3004
- return this.set(attrs, _.extend({}, options, {unset: true}));
3005
- },
3006
-
3007
- // Determine if the model has changed since the last `"change"` event.
3008
- // If you specify an attribute name, determine if that attribute has changed.
3009
- hasChanged: function(attr) {
3010
- if (attr == null) return !_.isEmpty(this.changed);
3011
- return getNested(this.changed, attr) !== undefined;
3012
- },
3013
-
3014
- // Return an object containing all the attributes that have changed, or
3015
- // false if there are no changed attributes. Useful for determining what
3016
- // parts of a view need to be updated and/or what attributes need to be
3017
- // persisted to the server. Unset attributes will be set to undefined.
3018
- // You can also pass an attributes object to diff against the model,
3019
- // determining if there *would be* a change.
3020
- changedAttributes: function(diff) {
3021
- //<custom code>: objToPaths
3022
- if (!diff) return this.hasChanged() ? objToPaths(this.changed) : false;
3023
- //</custom code>
3024
-
3025
- var old = this._changing ? this._previousAttributes : this.attributes;
3026
-
3027
- //<custom code>
3028
- diff = objToPaths(diff);
3029
- old = objToPaths(old);
3030
- //</custom code>
3031
-
3032
- var val, changed = false;
3033
- for (var attr in diff) {
3034
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
3035
- (changed || (changed = {}))[attr] = val;
3036
- }
3037
- return changed;
3038
- },
3039
-
3040
- // Get the previous value of an attribute, recorded at the time the last
3041
- // `"change"` event was fired.
3042
- previous: function(attr) {
3043
- if (attr == null || !this._previousAttributes) return null;
3044
-
3045
- //<custom code>
3046
- return getNested(this._previousAttributes, attr);
3047
- //</custom code>
3048
- },
3049
-
3050
- // Get all of the attributes of the model at the time of the previous
3051
- // `"change"` event.
3052
- previousAttributes: function() {
3053
- //<custom code>
3054
- return _.deepClone(this._previousAttributes);
3055
- //</custom code>
3056
- }
3057
- });
3058
-
3059
-
3060
- //Config; override in your app to customise
3061
- DeepModel.keyPathSeparator = '.';
3062
-
3063
-
3064
- //Exports
3065
- Backbone.DeepModel = DeepModel;
3066
-
3067
- //For use in NodeJS
3068
- if (typeof module != 'undefined') module.exports = DeepModel;
3069
-
3070
- return Backbone;
3071
-
3072
- }));