slices 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+