slices 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. data/CHANGELOG.md +3 -0
  2. data/README.md +51 -0
  3. data/Rakefile +9 -0
  4. data/app/assets/images/slices/ajax-loader.gif +0 -0
  5. data/app/assets/images/slices/asset-background.png +0 -0
  6. data/app/assets/images/slices/asset-spinner.gif +0 -0
  7. data/app/assets/images/slices/bg_header.gif +0 -0
  8. data/app/assets/images/slices/black-Linen.png +0 -0
  9. data/app/assets/images/slices/calendar.svg +68 -0
  10. data/app/assets/images/slices/chosen-sprite.png +0 -0
  11. data/app/assets/images/slices/drag-handle.svg +9 -0
  12. data/app/assets/images/slices/icon_admins.png +0 -0
  13. data/app/assets/images/slices/icon_app.png +0 -0
  14. data/app/assets/images/slices/icon_assets.png +0 -0
  15. data/app/assets/images/slices/icon_collapse.png +0 -0
  16. data/app/assets/images/slices/icon_drag.png +0 -0
  17. data/app/assets/images/slices/icon_files.png +0 -0
  18. data/app/assets/images/slices/icon_generic_file.png +0 -0
  19. data/app/assets/images/slices/icon_images.png +0 -0
  20. data/app/assets/images/slices/icon_padlock.png +0 -0
  21. data/app/assets/images/slices/icon_page.png +0 -0
  22. data/app/assets/images/slices/icon_search.png +0 -0
  23. data/app/assets/images/slices/icon_set-link.png +0 -0
  24. data/app/assets/images/slices/icon_set.png +0 -0
  25. data/app/assets/images/slices/icon_sitemap.png +0 -0
  26. data/app/assets/images/slices/icon_snippets.png +0 -0
  27. data/app/assets/images/slices/icon_template.jpg +0 -0
  28. data/app/assets/images/slices/icon_upload_happy.png +0 -0
  29. data/app/assets/images/slices/icon_upload_sad.png +0 -0
  30. data/app/assets/images/slices/icon_upload_thinking.png +0 -0
  31. data/app/assets/images/slices/noise.png +0 -0
  32. data/app/assets/images/slices/sitemap_icon_ghost.png +0 -0
  33. data/app/assets/images/slices/sitemap_icon_home.png +0 -0
  34. data/app/assets/images/slices/sitemap_icon_page.png +0 -0
  35. data/app/assets/images/slices/sitemap_icon_set_page.png +0 -0
  36. data/app/assets/images/slices/sitemap_icon_virtual_page.png +0 -0
  37. data/app/assets/images/slices/sitemap_overlay.png +0 -0
  38. data/app/assets/images/slices/spinner.gif +0 -0
  39. data/app/assets/images/slices/trash.png +0 -0
  40. data/app/assets/javascripts/admin.js.erb +18 -0
  41. data/app/assets/javascripts/slices/app/backbones/admins.js +114 -0
  42. data/app/assets/javascripts/slices/app/backbones/entries.js +172 -0
  43. data/app/assets/javascripts/slices/app/backbones/generic.js +101 -0
  44. data/app/assets/javascripts/slices/app/backbones/snippets.js +113 -0
  45. data/app/assets/javascripts/slices/app/helpers/assets.js +61 -0
  46. data/app/assets/javascripts/slices/app/helpers/breadcrumbs.js +30 -0
  47. data/app/assets/javascripts/slices/app/helpers/composer.js +26 -0
  48. data/app/assets/javascripts/slices/app/helpers/date_field.js +16 -0
  49. data/app/assets/javascripts/slices/app/helpers/get_value.js +31 -0
  50. data/app/assets/javascripts/slices/app/helpers/icon_upload_names.js.erb +5 -0
  51. data/app/assets/javascripts/slices/app/helpers/layout.js +20 -0
  52. data/app/assets/javascripts/slices/app/helpers/sitemap.js +150 -0
  53. data/app/assets/javascripts/slices/app/helpers/slice_preview.js +48 -0
  54. data/app/assets/javascripts/slices/app/helpers/tagging.js +73 -0
  55. data/app/assets/javascripts/slices/app/helpers/token_field.js +17 -0
  56. data/app/assets/javascripts/slices/app/helpers/upload_icons.js.erb +5 -0
  57. data/app/assets/javascripts/slices/app/helpers/uploader.js +127 -0
  58. data/app/assets/javascripts/slices/app/models/asset.js +29 -0
  59. data/app/assets/javascripts/slices/app/models/asset_collection.js +41 -0
  60. data/app/assets/javascripts/slices/app/models/attachment.js +29 -0
  61. data/app/assets/javascripts/slices/app/models/attachment_collection.js +7 -0
  62. data/app/assets/javascripts/slices/app/models/composer_item.js +1 -0
  63. data/app/assets/javascripts/slices/app/models/composer_item_collection.js +3 -0
  64. data/app/assets/javascripts/slices/app/models/file.js +103 -0
  65. data/app/assets/javascripts/slices/app/models/page.js +186 -0
  66. data/app/assets/javascripts/slices/app/models/s3_file.js +64 -0
  67. data/app/assets/javascripts/slices/app/slices.js +661 -0
  68. data/app/assets/javascripts/slices/app/views/asset_editor_view.js.erb +209 -0
  69. data/app/assets/javascripts/slices/app/views/asset_library_view.js +720 -0
  70. data/app/assets/javascripts/slices/app/views/asset_thumb_view.js.erb +191 -0
  71. data/app/assets/javascripts/slices/app/views/attachment_composer_view.js +350 -0
  72. data/app/assets/javascripts/slices/app/views/attachment_view.js +101 -0
  73. data/app/assets/javascripts/slices/app/views/calendar_view.js +198 -0
  74. data/app/assets/javascripts/slices/app/views/composer_item_view.js +54 -0
  75. data/app/assets/javascripts/slices/app/views/composer_view.js +130 -0
  76. data/app/assets/javascripts/slices/app/views/date_field_view.js +177 -0
  77. data/app/assets/javascripts/slices/app/views/file_view.js +142 -0
  78. data/app/assets/javascripts/slices/app/views/token_field_view.js +253 -0
  79. data/app/assets/javascripts/slices/lib/freeze.js +14 -0
  80. data/app/assets/javascripts/slices/lib/human_file_size.js +16 -0
  81. data/app/assets/javascripts/slices/lib/json_patch.js +9 -0
  82. data/app/assets/javascripts/slices/lib/moment.js +47 -0
  83. data/app/assets/javascripts/slices/lib/plugins.js +101 -0
  84. data/app/assets/javascripts/slices/lib/sortable.js +14 -0
  85. data/app/assets/javascripts/slices/slices.js +27 -0
  86. data/app/assets/javascripts/slices/vendor/autoscroll.js +188 -0
  87. data/app/assets/javascripts/slices/vendor/backbone.js +38 -0
  88. data/app/assets/javascripts/slices/vendor/handlebars.js +1920 -0
  89. data/app/assets/javascripts/slices/vendor/jqmodal.js +69 -0
  90. data/app/assets/javascripts/slices/vendor/jquery-ui.js +274 -0
  91. data/app/assets/javascripts/slices/vendor/jquery-ui_nested-sortable.js +357 -0
  92. data/app/assets/javascripts/slices/vendor/jquery.ajaxprogress.js +76 -0
  93. data/app/assets/javascripts/slices/vendor/jquery.js +2 -0
  94. data/app/assets/javascripts/slices/vendor/livefield.js +459 -0
  95. data/app/assets/javascripts/slices/vendor/moment.js +6 -0
  96. data/app/assets/javascripts/slices/vendor/rails.js +315 -0
  97. data/app/assets/javascripts/slices/vendor/underscore-string.js +1 -0
  98. data/app/assets/javascripts/slices/vendor/underscore.js +5 -0
  99. data/app/assets/stylesheets/admin.css +1 -0
  100. data/app/assets/stylesheets/slices/admin.css.erb +2237 -0
  101. data/app/assets/stylesheets/slices/reset_html5.css +106 -0
  102. data/app/assets/stylesheets/slices/slices.css +7 -0
  103. data/app/controllers/admin/admin_controller.rb +10 -0
  104. data/app/controllers/admin/admins_controller.rb +76 -0
  105. data/app/controllers/admin/assets_controller.rb +53 -0
  106. data/app/controllers/admin/auth/omniauth_callbacks_controller.rb +15 -0
  107. data/app/controllers/admin/auth/passwords_controller.rb +4 -0
  108. data/app/controllers/admin/auth/sessions_controller.rb +4 -0
  109. data/app/controllers/admin/entries_controller.rb +88 -0
  110. data/app/controllers/admin/page_search_controller.rb +12 -0
  111. data/app/controllers/admin/pages_controller.rb +103 -0
  112. data/app/controllers/admin/site_maps_controller.rb +15 -0
  113. data/app/controllers/admin/snippets_controller.rb +33 -0
  114. data/app/controllers/application_controller.rb +4 -0
  115. data/app/controllers/pages_controller.rb +45 -0
  116. data/app/controllers/slices_controller.rb +63 -0
  117. data/app/controllers/static_assets_controller.rb +52 -0
  118. data/app/helpers/admin/admin_helper.rb +63 -0
  119. data/app/helpers/admin/assets_helper.rb +36 -0
  120. data/app/helpers/admin/entries_helper.rb +13 -0
  121. data/app/helpers/admin/site_maps_helper.rb +104 -0
  122. data/app/helpers/assets_helper.rb +64 -0
  123. data/app/helpers/navigation_helper.rb +195 -0
  124. data/app/helpers/pages_helper.rb +119 -0
  125. data/app/models/admin.rb +34 -0
  126. data/app/models/asset.rb +211 -0
  127. data/app/models/attachment.rb +11 -0
  128. data/app/models/layout.rb +44 -0
  129. data/app/models/page.rb +214 -0
  130. data/app/models/placeholder_slice.rb +8 -0
  131. data/app/models/set_page.rb +12 -0
  132. data/app/models/set_slice.rb +57 -0
  133. data/app/models/site_map.rb +24 -0
  134. data/app/models/slice.rb +80 -0
  135. data/app/models/snippet.rb +21 -0
  136. data/app/observers/asset_observer.rb +6 -0
  137. data/app/observers/page_observer.rb +37 -0
  138. data/app/presenters/entry_presenter.rb +17 -0
  139. data/app/presenters/page_presenter.rb +67 -0
  140. data/app/presenters/presenter.rb +9 -0
  141. data/app/presenters/set_page_presenter.rb +2 -0
  142. data/app/views/admin/admins/index.html.erb +26 -0
  143. data/app/views/admin/admins/show.html.erb +27 -0
  144. data/app/views/admin/assets/index.html.erb +1 -0
  145. data/app/views/admin/auth/passwords/edit.html.erb +20 -0
  146. data/app/views/admin/auth/passwords/new.html.erb +14 -0
  147. data/app/views/admin/auth/sessions/_form.html.erb +35 -0
  148. data/app/views/admin/auth/sessions/new.html.erb +14 -0
  149. data/app/views/admin/entries/index.html.erb +32 -0
  150. data/app/views/admin/pages/_breadcrumbs.html.erb +32 -0
  151. data/app/views/admin/pages/_slices.html.erb +27 -0
  152. data/app/views/admin/pages/new.html.erb +14 -0
  153. data/app/views/admin/pages/show.html.erb +50 -0
  154. data/app/views/admin/shared/_asset_storage.html.erb +17 -0
  155. data/app/views/admin/shared/_custom_links.html.erb +1 -0
  156. data/app/views/admin/shared/_custom_navigation.html.erb +1 -0
  157. data/app/views/admin/shared/_navigation.html.erb +5 -0
  158. data/app/views/admin/site_maps/_page_li.html.erb +20 -0
  159. data/app/views/admin/site_maps/_set_page_li.html.erb +23 -0
  160. data/app/views/admin/site_maps/index.html.erb +29 -0
  161. data/app/views/admin/snippets/form.html.erb +12 -0
  162. data/app/views/admin/snippets/index.html.erb +20 -0
  163. data/app/views/admin/snippets/update.html.erb +0 -0
  164. data/app/views/layouts/admin.html.erb +72 -0
  165. data/lib/ext/file_store_cache.rb +18 -0
  166. data/lib/generators/humans/USAGE +8 -0
  167. data/lib/generators/humans/humans_generator.rb +10 -0
  168. data/lib/generators/humans/templates/humans.txt +6 -0
  169. data/lib/generators/slice/USAGE +28 -0
  170. data/lib/generators/slice/slice_generator.rb +123 -0
  171. data/lib/generators/slice/templates/main_fields.hbs +11 -0
  172. data/lib/generators/slice/templates/meta_fields.hbs +11 -0
  173. data/lib/generators/slice/templates/page.rb +19 -0
  174. data/lib/generators/slice/templates/presenter.rb +53 -0
  175. data/lib/generators/slice/templates/set.html.erb +8 -0
  176. data/lib/generators/slice/templates/set_slice.rb +14 -0
  177. data/lib/generators/slice/templates/set_slice_fields.hbs +5 -0
  178. data/lib/generators/slice/templates/show.html.erb +48 -0
  179. data/lib/generators/slice/templates/show_slice.rb +20 -0
  180. data/lib/generators/slice/templates/slice.rb +58 -0
  181. data/lib/generators/slice/templates/slice_fields.hbs +74 -0
  182. data/lib/generators/templates/slices.rb +211 -0
  183. data/lib/mongo_search.rb +84 -0
  184. data/lib/paperclip_validator.rb +5 -0
  185. data/lib/rack_utf8_fix.rb +10 -0
  186. data/lib/sRGB.icc +0 -0
  187. data/lib/set_link_renderer.rb +31 -0
  188. data/lib/slices.rb +68 -0
  189. data/lib/slices/asset/maker.rb +55 -0
  190. data/lib/slices/asset/rename.rb +67 -0
  191. data/lib/slices/available_slices.rb +43 -0
  192. data/lib/slices/cms_form_builder.rb +42 -0
  193. data/lib/slices/config.rb +93 -0
  194. data/lib/slices/container_parser.rb +70 -0
  195. data/lib/slices/generator_macros.rb +36 -0
  196. data/lib/slices/has_attachments.rb +111 -0
  197. data/lib/slices/has_slices.rb +88 -0
  198. data/lib/slices/i18n.rb +6 -0
  199. data/lib/slices/i18n/backend.rb +32 -0
  200. data/lib/slices/i18n_backend.rb +24 -0
  201. data/lib/slices/paperclip.rb +13 -0
  202. data/lib/slices/position_helper.rb +98 -0
  203. data/lib/slices/renderer.rb +52 -0
  204. data/lib/slices/slices_engine.rb +51 -0
  205. data/lib/slices/split_date_time_field.rb +14 -0
  206. data/lib/slices/tasks/assets.rake +35 -0
  207. data/lib/slices/tasks/db.rake +50 -0
  208. data/lib/slices/tasks/seeds.rake +93 -0
  209. data/lib/slices/tasks/validate.rake +62 -0
  210. data/lib/slices/tree.rb +306 -0
  211. data/lib/slices/version.rb +4 -0
  212. data/lib/slices/will_paginate.rb +12 -0
  213. data/lib/slices/will_paginate_mongoid.rb +45 -0
  214. data/lib/standard_tree.rb +193 -0
  215. metadata +483 -0
@@ -0,0 +1,186 @@
1
+ slices.model.Page = (
2
+ function () {
3
+
4
+ var settings,
5
+ pageId,
6
+ pageData,
7
+ newSliceIdCounter = 0,
8
+ NEW_SLICE_ID_NAME = '__new__',
9
+ slicesInContainers = {};
10
+
11
+ function load(callback) {
12
+ $.ajax({
13
+ url: settings.loadPagePath.split('{{id}}').join(pageId),
14
+ contentType: 'application/json',
15
+ dataType: 'json',
16
+ error: function (XMLHttpRequest, textStatus, errorThrown) {
17
+ // FIXME
18
+ throw new Error(errorThrown);
19
+ },
20
+ success: function (data, textStatus, XMLHttpRequest) {
21
+ parseLoadedData(data);
22
+ callback();
23
+ }
24
+ });
25
+ }
26
+
27
+ function save() {
28
+ var dfd = $.Deferred();
29
+
30
+ var params = $.extend({}, pageData);
31
+
32
+ _.each(params.slices, function (slice) {
33
+ // Don't pass back the temp ID assigned to new slices so that the backend knows to
34
+ // generate from scratch...
35
+ if (slice.id.indexOf(NEW_SLICE_ID_NAME) === 0) {
36
+ slice.client_id = slice.id;
37
+ slice._new = 1;
38
+ delete slice.id;
39
+ delete slice.template;
40
+ delete slice.name;
41
+ }
42
+ });
43
+
44
+ /* Strip these out of the returned JSON object, only values
45
+ * represented in the page form should be passed back HACKHACK */
46
+ var do_not_pass_back = [
47
+ 'id',
48
+ 'position',
49
+ 'created_at',
50
+ 'updated_at',
51
+ 'has_content',
52
+ 'path',
53
+ 'page_id'];
54
+
55
+ _.each(do_not_pass_back, function(item) {
56
+ delete params[item]
57
+ });
58
+
59
+ $.ajax({
60
+ url: settings.savePagePath.split('{{id}}').join(pageId),
61
+ type: 'PUT',
62
+ data: JSON.stringify({ page: params }),
63
+ contentType: 'application/json',
64
+ dataType: 'json',
65
+ error: function (xhr, textStatus, errorThrown) {
66
+ if (xhr.status == 422) {
67
+ var errors = $.parseJSON(xhr.responseText);
68
+
69
+ // If the slice is new re-assign the ids we stripped off when saving
70
+ _.each(pageData.slices, function (slice) {
71
+ if (slice._new) slice.id = slice.client_id;
72
+ });
73
+
74
+ dfd.reject(errors);
75
+ } else {
76
+ throw new Error(errorThrown);
77
+ }
78
+ },
79
+ success: function (data, textStatus, XMLHttpRequest) {
80
+ parseLoadedData(data);
81
+ dfd.resolve();
82
+ }
83
+ });
84
+
85
+ return dfd.promise();
86
+ }
87
+
88
+ function parseLoadedData(data) {
89
+ pageData = data;
90
+
91
+ _.each(slices.availableContainers, function (container, machineName) {
92
+ slicesInContainers[machineName] = [];
93
+ });
94
+
95
+ pageData.slices = _.sortBy(pageData.slices, function (slice) {
96
+ return slice.position;
97
+ });
98
+
99
+ _.each(pageData.slices, function (slice) {
100
+ // Remove the client id which the backend returns even after a successfully created new slice
101
+ delete slice.client_id;
102
+ delete slice._new;
103
+
104
+ try {
105
+ slicesInContainers[slice.container].push(slice);
106
+ } catch(error) {
107
+ if (console != undefined) {
108
+ console.log("Error, missing container on slice?", error, slice);
109
+ }
110
+ }
111
+ });
112
+ }
113
+
114
+ function addSlice(slice) {
115
+ slice.id = NEW_SLICE_ID_NAME + newSliceIdCounter++;
116
+ pageData.slices.push(slice);
117
+ slicesInContainers[slice.container].push(slice);
118
+ }
119
+
120
+ function positionSlice(sliceId, pos) {
121
+ _.detect(pageData.slices, function (slice) {
122
+ return slice.id == sliceId;
123
+ }).position = pos;
124
+ }
125
+
126
+ return {
127
+ init: function (s) {
128
+ settings = s;
129
+ },
130
+ id: function (id) {
131
+ if (arguments.length === 0) {
132
+ return pageId;
133
+ }
134
+ pageId = id;
135
+ },
136
+ load: function (callback) {
137
+ load(callback);
138
+ },
139
+ save: function () {
140
+ return save();
141
+ },
142
+ field: function (varName) {
143
+ return pageData[varName];
144
+ },
145
+ getSlices: function (container) {
146
+ return slicesInContainers[container];
147
+ },
148
+ setSlices: function (container, slices) {
149
+ slicesInContainers[container] = [];
150
+ _.each(slices, function(slice) {
151
+ pageData.slices = _.reject(pageData.slices, function(pdslice) {
152
+ return pdslice.id === slice.id
153
+ });
154
+ pageData.slices.push(slice);
155
+ slicesInContainers[container].push(slice);
156
+ });
157
+ },
158
+ addSlice: function (slice) {
159
+ addSlice(slice);
160
+ },
161
+ positionSlice: function (sliceId, pos) {
162
+ positionSlice(sliceId, pos);
163
+ changed = true;
164
+ },
165
+ getMeta: function(key) {
166
+ return pageData[key];
167
+ },
168
+ setMeta: function(key, value) {
169
+ pageData[key] = value;
170
+ },
171
+ // This returns true if the page was created in the last minute.
172
+ seemsNew: function() {
173
+ var created = moment(this.field('created_at')),
174
+ recently = moment().subtract('minutes', 1);
175
+
176
+ return created >= recently && this.field('active') == false;
177
+ },
178
+ data: function() {
179
+ return pageData;
180
+ },
181
+ slices: function() {
182
+ return pageData.slices;
183
+ }
184
+ };
185
+ }
186
+ )();
@@ -0,0 +1,64 @@
1
+ slices.S3File = slices.File.extend({
2
+
3
+ setS3Key: function(name) {
4
+ this.set('key', slices.S3_TEMPFILE_KEY_PREFIX + '/' + Date.now() + '/' + name);
5
+ },
6
+
7
+ fileS3URL: function() {
8
+ return slices.S3_URL + this.get('key');
9
+ },
10
+
11
+ formDataToS3: function() {
12
+ var formData = this.formDataWithOptions(slices.S3_UPLOADER_DEFAULTS);
13
+ var browserFile = this.get('browserFile');
14
+
15
+ this.setS3Key(browserFile.name);
16
+ formData.append('key', this.get('key'));
17
+ formData.append('Content-Type', browserFile.type);
18
+ formData.append('file', browserFile);
19
+ return formData;
20
+ },
21
+
22
+ formDataFromS3: function() {
23
+ var formData = this.formDataWithOptions(this.params);
24
+ formData.append('file', this.fileS3URL());
25
+ return formData;
26
+ },
27
+
28
+ upload: function() {
29
+ var self = this,
30
+ url = slices.S3_URL,
31
+ data = this.formDataToS3();
32
+
33
+ this.uploadWithOptions(url, data, {
34
+ progress: function(xhr, progress) { self.onS3Progress(progress) },
35
+ success: function(response) { self.onS3Success(response) },
36
+ error: function(response) { self.onS3Error(response) }
37
+ });
38
+ },
39
+
40
+ onS3Progress: function(progress) {
41
+ this.onProgress(progress);
42
+ },
43
+
44
+ onS3Success: function(response) {
45
+ this.uploadToSlicesFromS3(response);
46
+ },
47
+
48
+ onS3Error: function(response) {
49
+ this.onError(response);
50
+ },
51
+
52
+ uploadToSlicesFromS3: function() {
53
+ var self = this,
54
+ url = this.url,
55
+ data = this.formDataFromS3();
56
+
57
+ this.uploadWithOptions(url, data, {
58
+ progress: function() {},
59
+ success: function(response) { self.onSuccess(response) },
60
+ error: function(error) { self.onError(error) }
61
+ });
62
+ },
63
+
64
+ });
@@ -0,0 +1,661 @@
1
+ // Slices.js
2
+ // (c) 2011 With Associates
3
+ // http://slices.withassociates.com/
4
+
5
+ var slices = {
6
+
7
+ defaultSettings: {
8
+ mainTemplate: '_page_main',
9
+ metaTemplate: '_page_meta',
10
+ templatesPath: '/slices/templates/',
11
+ loadPagePath: '/admin/pages/{{id}}.json',
12
+ savePagePath: '/admin/pages/{{id}}.json'
13
+ },
14
+
15
+ templates: {},
16
+ availableSlices: {},
17
+ availableContainers: {},
18
+ model: {},
19
+
20
+ fieldId: function(context, field) {
21
+ // If `this` is the current page, then we’ll use a `meta-{{field}}` ID.
22
+ if (context === slices.model.Page.data()) {
23
+ return ['meta', field].join('-');
24
+
25
+ // If this has an id, we’re dealing with a slice, so we’ll use a `slices-{{id}}-{{field}}` ID.
26
+ } else if (context.hasOwnProperty('id')) {
27
+ return ['slices', context.id, field].join('-');
28
+
29
+ // Otherwise, we just need a unique id.
30
+ } else {
31
+ slices.__uid__ = slices.__uid__ || 0;
32
+ return 'field-' + ++slices.__uid__;
33
+ }
34
+ },
35
+
36
+ controller: function SlicesController() {
37
+
38
+ // private vars
39
+ var settings,
40
+ busy = true;
41
+
42
+ // private methods
43
+
44
+ function init(pageId, passed_settings) {
45
+ settings = $.extend({}, slices.defaultSettings, passed_settings);
46
+
47
+ observeFormEvents();
48
+
49
+ slices.model.Page.init(settings);
50
+ slices.model.Page.id(pageId);
51
+
52
+ slices.availableContainers = settings.availableContainers;
53
+ slices.availableSlices = settings.availableSlices;
54
+
55
+
56
+ addSliceOptions(slices.availableSlices);
57
+
58
+ templates = [
59
+ 'slice',
60
+ settings.mainTemplate,
61
+ settings.metaTemplate,
62
+ settings.mainExtraTemplate,
63
+ settings.metaExtraTemplate
64
+ ];
65
+
66
+ loadSliceTemplates(pageId);
67
+ loadTemplates(templates, pageId, loadPageModel);
68
+ };
69
+
70
+ function addSliceOptions(slices) {
71
+ var select = $('#add-slice-fields select');
72
+
73
+ select.find('option:not(:disabled)').remove();
74
+
75
+ _.each(slices, function (slice, machineName) {
76
+ if (!restrictedAndNotSuper(slice)) {
77
+ select.append(
78
+ '<option value="' + machineName + '">' + slice.name + '</option>'
79
+ );
80
+ }
81
+ });
82
+ };
83
+
84
+ function templateUrl(name, pageId) {
85
+ name = name.split('{{id}}').join(pageId);
86
+
87
+ if (name.indexOf('/') === 0) {
88
+ return name + '.hbs';
89
+ } else {
90
+ return settings.templatesPath + name + '.hbs';
91
+ }
92
+ };
93
+
94
+ function loadSliceTemplates(pageId) {
95
+ _.each(slices.availableSlices, function(slice, name) {
96
+ $.ajax({
97
+ url: templateUrl(slice.template, pageId),
98
+ success: function(raw_template, textStatus, XMLHttpRequest) {
99
+ slices.templates[slice.template] = Handlebars.compile(raw_template);
100
+ }
101
+ });
102
+ })
103
+ };
104
+
105
+ function loadTemplates(templates, pageId, callback) {
106
+ templates = _.compact(templates);
107
+ var numTemplates = templates.length;
108
+ _.each(templates, function (templateName) {
109
+ $.ajax({
110
+ url: templateUrl(templateName, pageId),
111
+ success: function (raw_template, textStatus, XMLHttpRequest) {
112
+ slices.templates[templateName] = Handlebars.compile(raw_template);
113
+ if (--numTemplates == 0) callback();
114
+ }
115
+ });
116
+ });
117
+ };
118
+
119
+ function loadPageModel() {
120
+ slices.model.Page.load(onPageLoaded);
121
+ };
122
+
123
+ function addSliceTemplate(slice) {
124
+ var templateData = {},
125
+ sliceBlock,
126
+ sliceContent,
127
+ sliceContentTemplate = slices.availableSlices[slice.type].template,
128
+ isOpen = false;
129
+
130
+ var sliceBlock = $(
131
+ slices.templates['slice']({
132
+ id: slice.id,
133
+ name: slice.name,
134
+ css_class: slice.type + '-slice',
135
+ content: function () {
136
+ return slices.templates[sliceContentTemplate](slice)
137
+ }
138
+ })
139
+ );
140
+
141
+ var sliceContent = sliceBlock.find('.slice-content'),
142
+ controlBar = sliceBlock.find('.control-bar'),
143
+ preview = sliceBlock.find('.slice-preview');
144
+
145
+ if (_.isFunction(window.customSlicePreview)) {
146
+ var slicePreviewHelper = window.customSlicePreview;
147
+ delete window.customSlicePreview;
148
+ } else {
149
+ var slicePreviewHelper = slices.defaultSlicePreview;
150
+ }
151
+
152
+ var updateSlicePreview = function() {
153
+ preview.html(slicePreviewHelper.call(sliceContent));
154
+ }
155
+
156
+ updateSlicePreview();
157
+
158
+ controlBar.on('click', function () {
159
+ var closed = sliceBlock.is('.closed');
160
+
161
+ if (closed) {
162
+ sliceContent.slideDown('fast');
163
+ sliceBlock.removeClass('closed');
164
+ } else {
165
+ updateSlicePreview();
166
+ sliceContent.slideUp('fast');
167
+ sliceBlock.addClass('closed');
168
+ }
169
+
170
+ updateMinimiseAllSlices();
171
+
172
+ return false;
173
+ });
174
+
175
+ sliceBlock.find('a.sort').on('click', false);
176
+
177
+ if (restrictedAndNotSuper(slice)) {
178
+ sliceBlock.find('a.delete').remove();
179
+ } else {
180
+ sliceBlock.find('a.delete').on('click', function () {
181
+ slice._destroy = 1;
182
+ sliceBlock.slideUp('fast');
183
+ sliceBlock.trigger('change');
184
+ enableSaveButton();
185
+ return false;
186
+ });
187
+ }
188
+
189
+ $('#container-' + slice.container + '>ul').append(sliceBlock);
190
+
191
+ sliceContent.applyDataValues();
192
+ sliceContent.initDataPlugins();
193
+
194
+ enableContainerSelect(slice, sliceBlock);
195
+
196
+ }
197
+
198
+ function onPageLoaded() {
199
+ var tabControls = $('#container-tab-controls'),
200
+ containersHolder = $('#containers-holder').empty(),
201
+ addSliceFields = $('#add-slice-fields');
202
+
203
+ initMeta();
204
+
205
+ _.each(slices.availableContainers, function (container, machineName) {
206
+ // Create ourselves a tab button.
207
+ var tabButton = $(
208
+ '<a href="#container-' + machineName + '">' +
209
+ container.name +
210
+ '</a>'
211
+ );
212
+
213
+ if (container.primary) tabButton.addClass('primary');
214
+
215
+ container.availableSlices = _.clone(slices.availableSlices);
216
+
217
+ // Filter available slices to those specified in the layout
218
+
219
+ if (container.only) {
220
+ _.each(slices.availableSlices, function(slice, machineName) {
221
+ if (!_.contains(container.only, machineName)) {
222
+ delete container.availableSlices[machineName];
223
+ }
224
+ });
225
+ }
226
+
227
+ if (container.except) {
228
+ _.each(slices.availableSlices, function(slice, machineName) {
229
+ if (_.contains(container.except, machineName)) {
230
+ delete container.availableSlices[machineName];
231
+ }
232
+ });
233
+ }
234
+
235
+ tabButton.data('container', container);
236
+
237
+ // Bind the tab button click event.
238
+ tabButton.on('click', function() {
239
+ var tab = $(this);
240
+ var container = tab.data('container');
241
+
242
+ tabControls.find('a').removeClass('active');
243
+ containersHolder.find('>div').hide();
244
+ $('#container-' + machineName).show();
245
+ $(this).addClass('active');
246
+
247
+ updateMinimiseAllSlices();
248
+ addSliceOptions(container.availableSlices);
249
+
250
+ return false;
251
+ });
252
+
253
+ // Append our tab button (in an li) to the tab controls container.
254
+ tabControls.append($('<li />').append(tabButton));
255
+
256
+ // Create ourselves a container, holder and adder.
257
+ var container = $('<div id="container-' + machineName + '" class="container" />');
258
+ var holder = $('<ul class="slices-holder" />');
259
+ var adder = addSliceFields.clone();
260
+
261
+ // Remove redundant id attribute on our cloned adder.
262
+ // adder.attr({ id: '' });
263
+
264
+ // Bind the behaviour of our slice selector.
265
+ adder.find('select').bind('change', function() {
266
+ var sliceType = $(this).val();
267
+
268
+ if (sliceType === '') return false;
269
+
270
+ addNewSlice(
271
+ machineName,
272
+ $(this).val(),
273
+ $('#container-' + machineName + ' ul li').length
274
+ );
275
+
276
+ $('#container-' + machineName + ' ul').trigger('sortstop');
277
+
278
+ $(this).find('option:selected').removeAttr('selected');
279
+ $(this).find('option:disabled').attr('selected', 'selected');
280
+
281
+ return false;
282
+ });
283
+
284
+ // Glue everything together.
285
+ container.append(holder, adder);
286
+ containersHolder.append(container);
287
+ });
288
+
289
+ addSliceFields.remove();
290
+ tabControls.find('a:first, a.primary').trigger('click');
291
+ addSliceContainers();
292
+ addMinimiseAllSlices();
293
+
294
+ if ($('#page-meta').children().length > 0) {
295
+ $('<div id="show-meta"><a href="#">advanced options&hellip;</a></div>').
296
+ insertAfter('#page-meta').
297
+ toggle(function() {
298
+ $('#page-meta').slideDown('fast');
299
+ $(this).html('<a href="#">hide advanced options</a>');
300
+ $(this).addClass('open');
301
+ }, function() {
302
+ $('#page-meta').slideUp('fast');
303
+ $(this).html('<a href="#">advanced options&hellip;</a>');
304
+ $(this).removeClass('open');
305
+ });
306
+
307
+ if (slices.model.Page.seemsNew()) $('#show-meta').trigger('click');
308
+ }
309
+
310
+ busy = false;
311
+ }
312
+
313
+ function initMeta() {
314
+ $('#page-main').html(renderMetaFields(settings.mainTemplate));
315
+
316
+ $('#page-meta').html(renderMetaFields(settings.metaTemplate));
317
+
318
+ if (settings.metaExtraTemplate) {
319
+ $('#page-meta').append(renderMetaFields(settings.metaExtraTemplate));
320
+ }
321
+
322
+ if (settings.mainExtraTemplate) {
323
+ $('#page-extra-main').html(renderMetaFields(settings.mainExtraTemplate));
324
+ }
325
+
326
+ $('#page-meta-fields').applyDataValues().initDataPlugins();
327
+
328
+ Tagging.detect();
329
+ }
330
+
331
+ function renderMetaFields(name) {
332
+ var template = slices.templates[name],
333
+ page = slices.model.Page,
334
+ data = page.data();
335
+
336
+ return template(data);
337
+ }
338
+
339
+ function addNewSlice(container, type, pos) {
340
+ var slice = _.extend({}, slices.availableSlices[type], {
341
+ type: type,
342
+ container: container,
343
+ position: pos
344
+ });
345
+ slices.model.Page.addSlice(slice);
346
+ addSliceTemplate(slice);
347
+ }
348
+
349
+ function addSliceContainers() {
350
+ _.each(slices.availableContainers, function (container, machineName) {
351
+ $('#container-' + machineName + ' ul').empty();
352
+ });
353
+
354
+ _.each(slices.availableContainers, function (container, machineName) {
355
+ var container_slices = slices.model.Page.getSlices(machineName);
356
+
357
+ container_slices = _.map(container_slices, function(slice) {
358
+ return $.extend(true, {},
359
+ slices.availableSlices[slice.type],
360
+ slice
361
+ );
362
+ });
363
+
364
+ slices.model.Page.setSlices(machineName, container_slices);
365
+
366
+ _.each(container_slices, addSliceTemplate);
367
+
368
+ var ul = $('#container-' + machineName + '>ul');
369
+
370
+ ul.sortable({
371
+ handle: '.control-bar',
372
+ scroll: false,
373
+ beforeStart: function() {
374
+ ul.freezeHeight();
375
+ window.autoscroll.start();
376
+ },
377
+ stop: function() {
378
+ window.autoscroll.stop();
379
+ ul.thawHeight();
380
+ }
381
+ });
382
+
383
+ ul.on('sortstop', function (event, ui) {
384
+ ul.find('>li').each(function (i) {
385
+ slices.model.Page.positionSlice($(this).attr('rel'), i);
386
+ });
387
+
388
+ onReorder();
389
+ });
390
+
391
+ ul.trigger('sortstop');
392
+ });
393
+ }
394
+
395
+ function addMinimiseAllSlices(){
396
+ var minimise = $('#minimise');
397
+ minimise.text('Minimise all slices');
398
+
399
+ minimise.on('click', function () {
400
+ if (minimise.text().indexOf('Minimise') != -1){
401
+ $('.container:visible .slice:not(.closed) .control-bar').click();
402
+ } else {
403
+ $('.container:visible .slice.closed .control-bar').click();
404
+ }
405
+ return false;
406
+ })
407
+ }
408
+
409
+ function updateMinimiseAllSlices() {
410
+ var slices = $('.container:visible .slice');
411
+
412
+ if (slices.length === 0) return;
413
+
414
+ var closed = slices.filter('.closed'),
415
+ allClosed = closed.length === slices.length,
416
+ allOpen = closed.length === 0;
417
+
418
+ if (allClosed) {
419
+ $('#minimise').text('Expand all slices');
420
+ } else if (allOpen) {
421
+ $('#minimise').text('Minimise all slices');
422
+ }
423
+ }
424
+
425
+ function getArrayInputValuesFor(key) {
426
+ var result = [];
427
+
428
+ $('input[name="meta-' + key + '"]').each(function() {
429
+ var input = $(this);
430
+
431
+ switch (input.attr('type')) {
432
+ case 'checkbox':
433
+ if (input.is(':checked')) result.push(input.val());
434
+ break;
435
+ default:
436
+ result.push(input.val());
437
+ break;
438
+ }
439
+ });
440
+
441
+ return result;
442
+ }
443
+
444
+ function updateMeta() {
445
+ var model = slices.model.Page,
446
+ inputs = $('[id^="meta-"]');
447
+
448
+ inputs.each(function() {
449
+ var input = $(this),
450
+ id = input.attr('id'),
451
+ key = id.match(/meta-(.+)/)[1],
452
+ oldValue = model.getMeta(key),
453
+ newValue = slices.getValueForId(id);
454
+
455
+ if (newValue === undefined || newValue == oldValue) return;
456
+
457
+ model.setMeta(key, newValue);
458
+ model.changed = true;
459
+ });
460
+ }
461
+
462
+ function updateSlices() {
463
+ var model = slices.model.Page;
464
+
465
+ _.each(model.slices(), function(slice) {
466
+ var prefix = 'slices-' + slice.id + '-',
467
+ inputs = $('[id^="' + prefix + '"]');
468
+
469
+ inputs.each(function() {
470
+ var input = $(this),
471
+ id = input.attr('id'),
472
+ key = id.substr(prefix.length),
473
+ oldValue = slice[key],
474
+ newValue = slices.getValueForId(id);
475
+
476
+ if (newValue === undefined || newValue == oldValue) return;
477
+
478
+ slice[key] = newValue;
479
+ model.changed = true;
480
+ });
481
+ });
482
+ }
483
+
484
+ function updateModel() {
485
+ var model = slices.model.Page;
486
+
487
+ model.changed = false;
488
+
489
+ updateMeta()
490
+ updateSlices();
491
+
492
+ if (model.changed) enableSaveButton();
493
+ }
494
+
495
+ function observeFormEvents() {
496
+ $('#slices-form')
497
+ .on('submit', onFormSubmitted)
498
+ .on('change', onChange)
499
+ .on('keyup', 'input, textarea', _.debounce(function() {
500
+ $(this).trigger('change');
501
+ }, 100));
502
+ }
503
+
504
+ function onChange() {
505
+ if (!busy) updateModel();
506
+ }
507
+
508
+ function onReorder() {
509
+ if (!busy) enableSaveButton();
510
+ }
511
+
512
+ function onFormSubmitted(event) {
513
+ event.preventDefault();
514
+
515
+ busy = true;
516
+
517
+ $(document).trigger('slices:willSubmit');
518
+
519
+ updateModel();
520
+
521
+ disableSaveButton();
522
+
523
+ $('.error-message').remove();
524
+ $('.field-with-errors').removeClass('field-with-errors');
525
+ $('#container').freezeHeight();
526
+
527
+ // Save it...
528
+ slices.model.Page.save()
529
+ .done(function() {
530
+ initMeta();
531
+ addSliceContainers();
532
+ updateViewLink();
533
+ updateBreadcrumbs();
534
+ })
535
+ .fail(function(errors) {
536
+ _.each(errors, function(value, key) {
537
+ if (key == 'slices') {
538
+ value = _.flatten([value])[0];
539
+ _.each(value, function(fields, sliceId) {
540
+ _.each(fields, function(errorMsgs, fieldName) {
541
+
542
+ var inpId = ['slices', sliceId, fieldName].join('-'),
543
+ input = $('#' + inpId);
544
+
545
+ input.closest('li').addClass('field-with-errors');
546
+
547
+ var errorMsg = _.flatten([errorMsgs]).join(', ');
548
+ input.after(
549
+ $('<div class="error-message" />').text(
550
+ $('label[for=' + inpId + ']').text() + ' ' + errorMsg
551
+ )
552
+ );
553
+
554
+ var containerId = input.closest('.container').attr('id');
555
+
556
+ $('#container-tab-controls a[href=#' + containerId + ']')
557
+ .addClass('field-with-errors');
558
+ });
559
+ });
560
+ } else {
561
+ var li = $('#meta-' + key).closest('li');
562
+
563
+ li
564
+ .addClass('field-with-errors')
565
+ .append(
566
+ $('<div class="error-message" />').text(
567
+ li.find('.label').text() + ' ' + value
568
+ )
569
+ );
570
+ }
571
+ });
572
+ }).
573
+ always(function() {
574
+ disableSaveButton();
575
+ updateMinimiseAllSlices();
576
+ busy = false;
577
+ _.defer(function() { $('#container').thawHeight() });
578
+ });
579
+ }
580
+
581
+ function restrictedAndNotSuper(slice) {
582
+ return (slice.restricted && !settings.super_user);
583
+ }
584
+
585
+ // Rig-up the little container select widget in the control bar.
586
+ // This is not the prettiest, but you don't need me to tell you that,
587
+ // you can just look below.
588
+ function enableContainerSelect(slice, sliceBlock) {
589
+
590
+ var potentialContainers = {};
591
+
592
+ _.each(slices.availableContainers, function(container, machineName) {
593
+ if (container.availableSlices.hasOwnProperty(slice.type)) {
594
+ potentialContainers[machineName] = container;
595
+ }
596
+ });
597
+
598
+ if (Object.keys(potentialContainers).length <= 1) {
599
+ sliceBlock.find('.container-select').remove();
600
+ return;
601
+ }
602
+
603
+ sliceBlock.find('.container-select').each(function() {
604
+ var select = $(this);
605
+
606
+ // Add the options
607
+ select.append('<option value="default" selected disabled>Move to another container</option>');
608
+
609
+ _.each(potentialContainers, function(container, machineName) {
610
+ select.append('<option value="' + machineName + '">' + container.name + '</option>');
611
+ });
612
+
613
+ // On change, we'll move our slice to the selected container
614
+ // and notify all the appropriate objects that things have changed.
615
+ select.bind('change', function() {
616
+ // Attach to the new container, both in data and dom.
617
+ var newMachineName = select.val();
618
+ var newContainer = $('#container-' + newMachineName + ' > ul');
619
+ slice.container = newMachineName;
620
+ sliceBlock.detach().appendTo(newContainer);
621
+
622
+ // Reset the widget.
623
+ select.val('default');
624
+
625
+ // Make sure slice positions are accurate.
626
+ newContainer.find('> li').each(function(i) {
627
+ slices.model.Page.positionSlice($(this).attr('rel'), i);
628
+ });
629
+
630
+ // Fire re-order hooks.
631
+ onReorder();
632
+ });
633
+ });
634
+ }
635
+
636
+ // Enables/disables the 'Save changes' button.
637
+ function enableSaveButton() {
638
+ $('#save-changes').attr('disabled', false);
639
+ }
640
+ function disableSaveButton() {
641
+ $('#save-changes').attr('disabled', true);
642
+ }
643
+
644
+ function updateViewLink() {
645
+ $('#page-view-on-site').attr('href', slices.model.Page.data().path);
646
+ }
647
+
648
+ function updateBreadcrumbs() {
649
+ $('#breadcrumbs .current a').html(slices.model.Page.data().name);
650
+ }
651
+
652
+ // public API
653
+ return {
654
+ init: function (pageId, settings) {
655
+ return init(pageId, settings);
656
+ }
657
+ }
658
+
659
+ }()
660
+ };
661
+