asset_host_core 2.0.0.beta

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 (199) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.markdown +138 -0
  3. data/Rakefile +11 -0
  4. data/app/assets/images/asset_host_core/alert-overlay.png +0 -0
  5. data/app/assets/images/asset_host_core/arrow-left.gif +0 -0
  6. data/app/assets/images/asset_host_core/arrow-right.gif +0 -0
  7. data/app/assets/images/asset_host_core/fallback-img-rect.png +0 -0
  8. data/app/assets/images/asset_host_core/videoplayer-play.png +0 -0
  9. data/app/assets/images/asset_host_core/x.png +0 -0
  10. data/app/assets/javascripts/asset_host_core/admin/assets.js.coffee +221 -0
  11. data/app/assets/javascripts/asset_host_core/application.js +20 -0
  12. data/app/assets/javascripts/asset_host_core/assetadmin.js.coffee +56 -0
  13. data/app/assets/javascripts/asset_host_core/assethost.js.coffee.erb +17 -0
  14. data/app/assets/javascripts/asset_host_core/browserui.js.coffee +139 -0
  15. data/app/assets/javascripts/asset_host_core/chooserui.js.coffee +381 -0
  16. data/app/assets/javascripts/asset_host_core/client.js.coffee +29 -0
  17. data/app/assets/javascripts/asset_host_core/clients/BrightcoveVideo.js.coffee +64 -0
  18. data/app/assets/javascripts/asset_host_core/clients/templates/brightcove_embed.jst.eco +18 -0
  19. data/app/assets/javascripts/asset_host_core/clients/templates/vimeo_embed.jst.eco +1 -0
  20. data/app/assets/javascripts/asset_host_core/clients/templates/youtube_embed.jst.eco +1 -0
  21. data/app/assets/javascripts/asset_host_core/clients/vimeo_video.js.coffee +21 -0
  22. data/app/assets/javascripts/asset_host_core/clients/youtube_video.js.coffee +21 -0
  23. data/app/assets/javascripts/asset_host_core/cmsplugin.js.coffee +235 -0
  24. data/app/assets/javascripts/asset_host_core/models.js.coffee +586 -0
  25. data/app/assets/javascripts/asset_host_core/railsCMS.js.coffee +141 -0
  26. data/app/assets/javascripts/asset_host_core/slideshow.js.coffee +428 -0
  27. data/app/assets/javascripts/asset_host_core/templates/after_upload_button.jst.eco +3 -0
  28. data/app/assets/javascripts/asset_host_core/templates/asset_drop_asset.jst.eco +4 -0
  29. data/app/assets/javascripts/asset_host_core/templates/asset_modal.jst.eco +13 -0
  30. data/app/assets/javascripts/asset_host_core/templates/asset_preview.jst.eco +35 -0
  31. data/app/assets/javascripts/asset_host_core/templates/asset_search.jst.eco +2 -0
  32. data/app/assets/javascripts/asset_host_core/templates/browser_asset.jst.eco +1 -0
  33. data/app/assets/javascripts/asset_host_core/templates/browser_asset_tip.jst.eco +3 -0
  34. data/app/assets/javascripts/asset_host_core/templates/edit_modal.jst.eco +40 -0
  35. data/app/assets/javascripts/asset_host_core/templates/import_help.jst.eco +59 -0
  36. data/app/assets/javascripts/asset_host_core/templates/pagination_link.jst.eco +1 -0
  37. data/app/assets/javascripts/asset_host_core/templates/pagination_links.jst.eco +13 -0
  38. data/app/assets/javascripts/asset_host_core/templates/queued_file.jst.eco +11 -0
  39. data/app/assets/javascripts/asset_host_core/templates/save_and_close_view.jst.eco +4 -0
  40. data/app/assets/javascripts/asset_host_core/templates/upload_all_button.jst.eco +4 -0
  41. data/app/assets/javascripts/asset_host_core/templates/url_input.jst.eco +8 -0
  42. data/app/assets/stylesheets/asset_host_core/application.css.scss +384 -0
  43. data/app/assets/stylesheets/asset_host_core/jquery-ui.css +105 -0
  44. data/app/assets/stylesheets/asset_host_core/public.css.scss +204 -0
  45. data/app/assets/stylesheets/asset_host_core/slidetest.css.scss +93 -0
  46. data/app/controllers/asset_host_core/admin/api_users_controller.rb +72 -0
  47. data/app/controllers/asset_host_core/admin/assets_controller.rb +140 -0
  48. data/app/controllers/asset_host_core/admin/base_controller.rb +36 -0
  49. data/app/controllers/asset_host_core/admin/home_controller.rb +13 -0
  50. data/app/controllers/asset_host_core/admin/outputs_controller.rb +55 -0
  51. data/app/controllers/asset_host_core/api/assets_controller.rb +110 -0
  52. data/app/controllers/asset_host_core/api/base_controller.rb +43 -0
  53. data/app/controllers/asset_host_core/api/outputs_controller.rb +33 -0
  54. data/app/controllers/asset_host_core/application_controller.rb +43 -0
  55. data/app/controllers/asset_host_core/public_controller.rb +104 -0
  56. data/app/models/asset_host_core/api_user.rb +44 -0
  57. data/app/models/asset_host_core/api_user_permission.rb +6 -0
  58. data/app/models/asset_host_core/asset.rb +265 -0
  59. data/app/models/asset_host_core/asset_output.rb +69 -0
  60. data/app/models/asset_host_core/brightcove_video.rb +20 -0
  61. data/app/models/asset_host_core/output.rb +52 -0
  62. data/app/models/asset_host_core/permission.rb +19 -0
  63. data/app/models/asset_host_core/video.rb +8 -0
  64. data/app/models/asset_host_core/vimeo_video.rb +17 -0
  65. data/app/models/asset_host_core/youtube_video.rb +17 -0
  66. data/app/views/asset_host_core/admin/api_users/_form_fields.html.erb +5 -0
  67. data/app/views/asset_host_core/admin/api_users/edit.html.erb +26 -0
  68. data/app/views/asset_host_core/admin/api_users/index.html.erb +31 -0
  69. data/app/views/asset_host_core/admin/api_users/new.html.erb +17 -0
  70. data/app/views/asset_host_core/admin/api_users/show.html.erb +23 -0
  71. data/app/views/asset_host_core/admin/assets/index.html.erb +19 -0
  72. data/app/views/asset_host_core/admin/assets/metadata.html.erb +24 -0
  73. data/app/views/asset_host_core/admin/assets/show.html.erb +86 -0
  74. data/app/views/asset_host_core/admin/home/chooser.html.erb +49 -0
  75. data/app/views/asset_host_core/admin/outputs/_form_fields.html.erb +5 -0
  76. data/app/views/asset_host_core/admin/outputs/edit.html.erb +26 -0
  77. data/app/views/asset_host_core/admin/outputs/index.html.erb +27 -0
  78. data/app/views/asset_host_core/admin/outputs/new.html.erb +13 -0
  79. data/app/views/asset_host_core/admin/outputs/show.html.erb +17 -0
  80. data/app/views/asset_host_core/shared/_footerjs.html.erb +3 -0
  81. data/app/views/asset_host_core/shared/_navbar.html.erb +28 -0
  82. data/app/views/kaminari/_first_page.html.erb +3 -0
  83. data/app/views/kaminari/_gap.html.erb +3 -0
  84. data/app/views/kaminari/_last_page.html.erb +3 -0
  85. data/app/views/kaminari/_next_page.html.erb +3 -0
  86. data/app/views/kaminari/_page.html.erb +3 -0
  87. data/app/views/kaminari/_paginator.html.erb +17 -0
  88. data/app/views/kaminari/_prev_page.html.erb +3 -0
  89. data/app/views/layouts/asset_host_core/application.html.erb +54 -0
  90. data/app/views/layouts/asset_host_core/full_width.html.erb +32 -0
  91. data/app/views/layouts/asset_host_core/minimal.html.erb +45 -0
  92. data/config/initializers/simple_form.rb +142 -0
  93. data/config/initializers/simple_form_bootstrap.rb +45 -0
  94. data/config/locales/simple_form.en.yml +26 -0
  95. data/config/routes.rb +49 -0
  96. data/lib/asset_host_core.rb +38 -0
  97. data/lib/asset_host_core/config.rb +39 -0
  98. data/lib/asset_host_core/engine.rb +94 -0
  99. data/lib/asset_host_core/loaders.rb +34 -0
  100. data/lib/asset_host_core/loaders/asset_host.rb +30 -0
  101. data/lib/asset_host_core/loaders/base.rb +22 -0
  102. data/lib/asset_host_core/loaders/brightcove.rb +67 -0
  103. data/lib/asset_host_core/loaders/flickr.rb +114 -0
  104. data/lib/asset_host_core/loaders/url.rb +59 -0
  105. data/lib/asset_host_core/loaders/vimeo.rb +76 -0
  106. data/lib/asset_host_core/loaders/youtube.rb +90 -0
  107. data/lib/asset_host_core/model_methods.rb +61 -0
  108. data/lib/asset_host_core/paperclip.rb +4 -0
  109. data/lib/asset_host_core/paperclip/asset_thumbnail.rb +92 -0
  110. data/lib/asset_host_core/paperclip/attachment.rb +206 -0
  111. data/lib/asset_host_core/paperclip/trimmer.rb +33 -0
  112. data/lib/asset_host_core/resque_job.rb +13 -0
  113. data/lib/asset_host_core/version.rb +3 -0
  114. data/lib/tasks/asset_host_core_tasks.rake +4 -0
  115. data/spec/controllers/admin/api_users_controller_spec.rb +21 -0
  116. data/spec/controllers/admin/assets_controller_spec.rb +59 -0
  117. data/spec/controllers/admin/home_controller_spec.rb +4 -0
  118. data/spec/controllers/admin/outputs_controller_spec.rb +4 -0
  119. data/spec/controllers/api/assets_controller_spec.rb +133 -0
  120. data/spec/controllers/api/outputs_controller_spec.rb +51 -0
  121. data/spec/controllers/public_controller_spec.rb +4 -0
  122. data/spec/factories.rb +39 -0
  123. data/spec/features/api_users_spec.rb +78 -0
  124. data/spec/fixtures/api/brightcove/video.json +137 -0
  125. data/spec/fixtures/api/flickr/photos_getInfo.json +78 -0
  126. data/spec/fixtures/api/flickr/photos_getSizes.json +82 -0
  127. data/spec/fixtures/api/flickr/photos_licenses_getInfo.json +52 -0
  128. data/spec/fixtures/api/vimeo/video.json +28 -0
  129. data/spec/fixtures/api/youtube/discovery.json +5190 -0
  130. data/spec/fixtures/api/youtube/video.json +44 -0
  131. data/spec/fixtures/images/chipmunk.jpg +0 -0
  132. data/spec/fixtures/images/dude.jpg +0 -0
  133. data/spec/fixtures/images/ernie.jpg +0 -0
  134. data/spec/fixtures/images/fry.png +0 -0
  135. data/spec/fixtures/images/hat.jpg +0 -0
  136. data/spec/fixtures/images/spongebob.png +0 -0
  137. data/spec/fixtures/images/stars.jpg +0 -0
  138. data/spec/internal/app/controllers/application_controller.rb +16 -0
  139. data/spec/internal/app/controllers/sessions_controller.rb +24 -0
  140. data/spec/internal/app/models/user.rb +10 -0
  141. data/spec/internal/app/views/sessions/new.html.erb +14 -0
  142. data/spec/internal/config/database.yml +3 -0
  143. data/spec/internal/config/initializers/assethost_config.rb +57 -0
  144. data/spec/internal/config/routes.rb +7 -0
  145. data/spec/internal/db/combustion_test.sqlite +0 -0
  146. data/spec/internal/db/schema.rb +106 -0
  147. data/spec/internal/log/test.log +14769 -0
  148. data/spec/internal/public/favicon.ico +0 -0
  149. data/spec/internal/public/images/1_27f7745237849975ca90591c1fba5934_original. +0 -0
  150. data/spec/internal/public/images/1_7d33319deca787d5bb3f62ff06563ad2_original. +0 -0
  151. data/spec/internal/public/images/1_b6d48c8b1286104ce76649731e09645f_original. +0 -0
  152. data/spec/internal/public/images/1_b6d48c8b1286104ce76649731e09645f_original.jpg +0 -0
  153. data/spec/internal/public/images/1_b6d48c8b1286104ce76649731e09645f_original.txt +0 -0
  154. data/spec/internal/public/images/1_e179cbd27e07cb55042d0db36cdac095_original. +0 -0
  155. data/spec/internal/public/images/1_e669edd3dfd74be66fc38416e82e3a37_original. +0 -0
  156. data/spec/lib/asset_host_core/loaders/asset_host_spec.rb +33 -0
  157. data/spec/lib/asset_host_core/loaders/brightcove_spec.rb +51 -0
  158. data/spec/lib/asset_host_core/loaders/flickr_spec.rb +72 -0
  159. data/spec/lib/asset_host_core/loaders/url_spec.rb +42 -0
  160. data/spec/lib/asset_host_core/loaders/vimeo_spec.rb +51 -0
  161. data/spec/lib/asset_host_core/loaders/youtube_spec.rb +73 -0
  162. data/spec/lib/asset_host_core/loaders_spec.rb +4 -0
  163. data/spec/lib/asset_host_core/model_methods_spec.rb +4 -0
  164. data/spec/lib/asset_host_core/paperclip/asset_thumbnail_spec.rb +4 -0
  165. data/spec/lib/asset_host_core/paperclip/attachment_spec.rb +4 -0
  166. data/spec/lib/asset_host_core/resque_job_spec.rb +4 -0
  167. data/spec/lib/asset_host_core_spec.rb +4 -0
  168. data/spec/models/api_user_spec.rb +58 -0
  169. data/spec/models/asset_output_spec.rb +4 -0
  170. data/spec/models/asset_spec.rb +4 -0
  171. data/spec/models/output_spec.rb +4 -0
  172. data/spec/models/permission_spec.rb +4 -0
  173. data/spec/spec_helper.rb +30 -0
  174. data/spec/support/fixture_loader.rb +9 -0
  175. data/spec/support/param_helper.rb +14 -0
  176. data/spec/support/permission_matcher.rb +17 -0
  177. data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  178. data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  179. data/vendor/assets/images/jquery-ui/ui-bg_flat_10_000000_40x100.png +0 -0
  180. data/vendor/assets/images/jquery-ui/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  181. data/vendor/assets/images/jquery-ui/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  182. data/vendor/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png +0 -0
  183. data/vendor/assets/images/jquery-ui/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  184. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  185. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  186. data/vendor/assets/images/jquery-ui/ui-icons_222222_256x240.png +0 -0
  187. data/vendor/assets/images/jquery-ui/ui-icons_228ef1_256x240.png +0 -0
  188. data/vendor/assets/images/jquery-ui/ui-icons_ef8c08_256x240.png +0 -0
  189. data/vendor/assets/images/jquery-ui/ui-icons_ffd27a_256x240.png +0 -0
  190. data/vendor/assets/images/jquery-ui/ui-icons_ffffff_256x240.png +0 -0
  191. data/vendor/assets/javascripts/backbone.js +1158 -0
  192. data/vendor/assets/javascripts/backbone.modelbinding.js +475 -0
  193. data/vendor/assets/javascripts/exif.js +695 -0
  194. data/vendor/assets/javascripts/jquery-ui.js +5614 -0
  195. data/vendor/assets/javascripts/simplemodal.js +698 -0
  196. data/vendor/assets/javascripts/spin.jquery.js +81 -0
  197. data/vendor/assets/javascripts/spin.min.js +1 -0
  198. data/vendor/assets/javascripts/underscore.min.js +1 -0
  199. metadata +658 -0
@@ -0,0 +1,29 @@
1
+ #= require ./assethost
2
+ #= require underscore.min
3
+
4
+ #= require spin.jquery
5
+ #= require spin.min
6
+
7
+ #= require_self
8
+ #= require_directory ./clients/templates
9
+ #= require_directory ./clients
10
+
11
+ class AssetHost.Client
12
+ DefaultOptions:
13
+ attr: "data-assethost"
14
+
15
+ constructor: (options={}) ->
16
+ @options = _.defaults options, @DefaultOptions
17
+ @clients = []
18
+
19
+ clients = @clients
20
+
21
+ $ =>
22
+ ahAttr = @options.attr
23
+
24
+ # find all assethost elements and look for rich functionality
25
+ $("img[#{@options.attr}]").each ->
26
+ rich = $(this).attr ahAttr
27
+
28
+ if Client[rich]
29
+ clients.push new Client[rich](this)
@@ -0,0 +1,64 @@
1
+ window.BrightcoveVideos ?= {}
2
+
3
+ window.onTemplateLoaded = (id) ->
4
+ @player = brightcove.api.getExperience(id)
5
+ @modVP = @player.getModule(brightcove.api.modules.APIModules.VIDEO_PLAYER)
6
+
7
+ window.onTemplateReady = (event) ->
8
+ @BrightcoveVideos[@player.id].swap()
9
+
10
+
11
+
12
+ class AssetHost.Client.BrightcoveVideo
13
+ DefaultOptions:
14
+ playerKey: "AQ~~,AAAAmtVKbGE~,pW41hkPiaos27C7knwyeOWQgVlG4w7v5"
15
+ playerId: "1247178207001"
16
+ brightcoveJS: "http://admin.brightcove.com/js/BrightcoveExperiences.js"
17
+
18
+ template: JST['asset_host_core/clients/templates/brightcove_embed']
19
+
20
+ constructor: (el, options={}) ->
21
+ @opts = _.defaults options, @DefaultOptions
22
+ @el = $(el) # The asset
23
+
24
+ # we're given an img element. we'll stick an overlay with a play
25
+ # button on it, and then on click we'll launch the video
26
+
27
+ # get width and height from the img
28
+ @w = $(el).attr("width")
29
+ @h = $(el).attr("height")
30
+
31
+ # get videoid from data-ah-videoid attribute
32
+ @videoid = @el.attr("data-ah-videoid")
33
+
34
+ $(document).ready =>
35
+ @launch()
36
+
37
+ #----------
38
+
39
+ launch: ->
40
+ @el.parent().spin(color: "#fff", shadow: true)
41
+
42
+ # render template
43
+ @video = $ @template(
44
+ width: @w
45
+ height: @h
46
+ videoid: @videoid
47
+ playerid: @opts.playerId
48
+ playerkey: @opts.playerKey
49
+ )
50
+
51
+ window.BrightcoveVideos[$("object", @video).attr('id')] = @
52
+
53
+ @el.after @video
54
+
55
+ if window.brightcove?
56
+ brightcove.createExperiences()
57
+ else
58
+ $.getScript @opts.brightcoveJS, ->
59
+ brightcove.createExperiences()
60
+
61
+ swap: ->
62
+ @el.parent().spin(false)
63
+ @el.hide()
64
+ @video.show()
@@ -0,0 +1,18 @@
1
+ <div class="brightcove-video">
2
+ <object id="ah_bcove_<%= @videoid %>" class="BrightcoveExperience">
3
+ <param name="bgcolor" value="#FFFFFF" />
4
+ <param name="width" value="<%= @width %>" />
5
+ <param name="height" value="<%= @height %>" />
6
+ <param name="playerID" value="<%= @playerid %>" />
7
+ <param name="playerKey" value="<%= @playerkey %>" />
8
+ <param name="isVid" value="true" />
9
+ <param name="isUI" value="true" />
10
+ <param name="dynamicStreaming" value="true" />
11
+ <param name="@videoPlayer" value="<%= @videoid %>" />
12
+ <param name="wmode" value="transparent" />
13
+ <param name="autoStart" value ="false" />
14
+ <param name="includeAPI" value="true" />
15
+ <param name="templateLoadHandler" value="onTemplateLoaded" />
16
+ <param name="templateReadyHandler" value="onTemplateReady" />
17
+ </object>
18
+ </div>
@@ -0,0 +1 @@
1
+ <iframe src="http://player.vimeo.com/video/<%=@videoid%>?portrait=0&amp;byline=0" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
@@ -0,0 +1 @@
1
+ <iframe type='text/html' src='http://www.youtube.com/embed/<%=@videoid%>' frameborder='0' allowfullscreen='true' />
@@ -0,0 +1,21 @@
1
+ class AssetHost.Client.VimeoVideo
2
+ template: JST['asset_host_core/clients/templates/vimeo_embed']
3
+
4
+ constructor: (el, options={}) ->
5
+ @el = $(el)
6
+
7
+ # get videoid from data-ah-videoid attribute
8
+ @videoid = @el.attr("data-ah-videoid")
9
+
10
+ $(document).ready =>
11
+ @launch()
12
+
13
+ #----------
14
+
15
+ launch: ->
16
+ # render template
17
+ @html = @template(videoid: @videoid)
18
+ @swap()
19
+
20
+ swap: ->
21
+ @el.replaceWith @html
@@ -0,0 +1,21 @@
1
+ class AssetHost.Client.YoutubeVideo
2
+ template: JST['asset_host_core/clients/templates/youtube_embed']
3
+
4
+ constructor: (el, options={}) ->
5
+ @el = $(el)
6
+
7
+ # get videoid from data-ah-videoid attribute
8
+ @videoid = @el.attr("data-ah-videoid")
9
+
10
+ $(document).ready =>
11
+ @launch()
12
+
13
+ #----------
14
+
15
+ launch: ->
16
+ # render template
17
+ @html = @template(videoid: @videoid)
18
+ @swap()
19
+
20
+ swap: ->
21
+ @el.replaceWith @html
@@ -0,0 +1,235 @@
1
+ class AssetHost.CMSPlugin
2
+ DefaultOptions:
3
+ {
4
+ el: "",
5
+ server: '',
6
+ assets: [],
7
+ token: ''
8
+ }
9
+
10
+ #----------
11
+
12
+ constructor: (options) ->
13
+ @options = _(_({}).extend(this.DefaultOptions)).extend( options || {} )
14
+
15
+ # add in events
16
+ _.extend(this, Backbone.Events)
17
+
18
+ # cache values for extras
19
+ _(@options.extras).each (v,k) =>
20
+ if v and el = $( '#'+v )[0]
21
+ @options.extras[k] = el.value
22
+
23
+ # store existing form row info
24
+ @rows = []
25
+
26
+ # -- assemble our asset list from form data -- #
27
+ assetdata = []
28
+ for idx in _.range(@options.begins_with,100)
29
+ if el = $( _.template("#"+@options.assetID,{idx:idx,field:@options.id}) )[0]
30
+ asset = {
31
+ id: el.value,
32
+ caption: $( _.template("#"+@options.assetID,{idx:idx,field:@options.caption}) )[0].value,
33
+ #ORDER: $(_.template("#"+@options.assetID,{idx:idx,field:@options.order}) )[0].value
34
+ ORDER: idx
35
+ }
36
+
37
+ # stash row info
38
+ @rows.push {
39
+ idx: idx,
40
+ id: asset.id,
41
+ extras: _(@options.extras).map (v,field) =>
42
+ el = $( _.template("#"+@options.assetID,{idx:idx,field:field}) )[0]
43
+ { id: el.id, name: el.name, value: el.value }
44
+ }
45
+
46
+ assetdata.push(asset)
47
+ else
48
+ # nothing with this index, so go ahead and break
49
+ break
50
+
51
+ # -- load assets -- #
52
+
53
+ @assets = new AssetHost.Models.Assets(assetdata)
54
+
55
+ # load other asset data (tags, credit, etc)
56
+ @assets.each (a,idx) -> a.fetch({success: (a) => a.set({caption:assetdata[ idx ].caption});console.log("set caption to ",assetdata[ idx ].caption)})
57
+
58
+ # -- clone any hidden inputs -- #
59
+
60
+ @hiddens = $(@options.el).find("input[type=hidden]")
61
+
62
+ # -- initialize our views -- #
63
+
64
+ @assetsView = new AssetHost.CMSPlugin.CMSAssets({collection:@assets,args:@options,rows:@rows,hiddens:@hiddens})
65
+ $(@options.el).html @assetsView.el
66
+
67
+ window.addEventListener "message", (evt) =>
68
+ if evt.data != "LOADED"
69
+ found = {}
70
+
71
+ # reconsile our asset list to the returned list
72
+ _(evt.data).each (a,i) =>
73
+ # do we have this asset?
74
+ if asset = @assets.get(a.id)
75
+ # yes... check for changed caption
76
+ asset.set({caption:a.caption,ORDER:i})
77
+ else
78
+ # no, needs to be added
79
+ asset = new AssetHost.Models.Asset(a)
80
+ asset.fetch(success: (aobj)=>aobj.set({caption:a.caption,ORDER:i});@assets.add(aobj))
81
+
82
+ found[ a.id ] = true
83
+
84
+ # now check for removed assets
85
+ remove = []
86
+ @assets.each (a,i) =>
87
+ if found[a.get('id')]
88
+ # we're cool
89
+ else
90
+ # not in our return list... delete
91
+ remove.push(a)
92
+
93
+ for a in remove
94
+ @assets.remove(a)
95
+
96
+ @assets.sort()
97
+ @assetsView.render()
98
+
99
+ @trigger("assets",@assets.toJSON())
100
+ , false
101
+
102
+ #----------
103
+
104
+ @CMSAsset: Backbone.View.extend
105
+ tagName: "li"
106
+
107
+ template:
108
+ '''
109
+ <%= asset.tags ? asset.tags.thumb : "INVALID" %>
110
+ <b><%= asset.title %> (<%= asset.size %>)</b>
111
+ <p><%= asset.caption %></p>
112
+ <input type="hidden" id="<%= id.id %>" name="<%= id.name %>" value="<%= asset.id %>" />
113
+ <input type="hidden" id="<%= caption.id %>" name="<%= caption.name %>" value="<%= (asset.caption||"").replace(/"/g,'&quot;') %>" />
114
+ <input type="hidden" id="<%= order.id %>" name="<%= order.name %>" value="<%= idx+1 %>" />
115
+ <% _(extras).each(function(ex) { %>
116
+ <input type="hidden" id="<%= ex.id %>" name="<%= ex.name %>" value="<%= ex.value %>" />
117
+ <% }); %>
118
+ '''
119
+
120
+ initialize: ->
121
+ # if we get an invalid asset, remove it
122
+ if !@model.get("id")
123
+ @model.collection.remove(@model)
124
+ return false
125
+
126
+ $(@el).attr("data-asset-url",@model.get('url'))
127
+ @render()
128
+ @model.bind "change", => @render()
129
+
130
+ render: ->
131
+
132
+ if @model.get('tags')
133
+ idx = @model.get('ORDER')
134
+ #idx = @model.collection.indexOf(@model)
135
+
136
+ if @options.rows[idx]
137
+ extras = @options.rows[idx].extras
138
+ else
139
+ extras = _(@options.args.extras).map (v,k) => {
140
+ id: _.template(@options.args.assetID,{idx:idx,field:k}),
141
+ name: _.template(@options.args.assetName,{idx:idx,field:k}),
142
+ value: v
143
+ }
144
+
145
+ $( @el ).html( _.template @template, {
146
+ asset: @model.toJSON(),
147
+ idx: idx,
148
+ id: {
149
+ id: _.template(@options.args.assetID,{idx:idx,field:@options.args.id}),
150
+ name: _.template(@options.args.assetName,{idx:idx,field:@options.args.id})
151
+ },
152
+ caption: {
153
+ id: _.template(@options.args.assetID,{idx:idx,field:@options.args.caption}),
154
+ name: _.template(@options.args.assetName,{idx:idx,field:@options.args.caption})
155
+ },
156
+ order: {
157
+ id: _.template(@options.args.assetID,{idx:idx,field:@options.args.order}),
158
+ name: _.template(@options.args.assetName,{idx:idx,field:@options.args.order})
159
+ },
160
+ extras: extras
161
+ } )
162
+
163
+ return this
164
+
165
+ #----------
166
+
167
+ @CMSAssets: Backbone.View.extend
168
+ tagName: "ul"
169
+ events: { "click button": "_popup" }
170
+
171
+ initialize: ->
172
+ @_views = {}
173
+ @collection.bind "reset", =>
174
+ _(@_views).each (a) => $(a.el).detach(); @_views = {}
175
+
176
+ @collection.bind 'add', (f) =>
177
+ @_views[f.cid] = new AssetHost.CMSPlugin.CMSAsset({model:f,args:@options.args,rows:@options.rows})
178
+ @render()
179
+
180
+ @collection.bind 'remove', (f) =>
181
+ if @_views[f.cid]
182
+ @_views[f.cid].remove()
183
+ delete @_views[f.cid]
184
+
185
+ @render()
186
+
187
+ # now that all our events are up, render
188
+ @render()
189
+
190
+ _popup: (evt) ->
191
+ evt.originalEvent.stopPropagation()
192
+ evt.originalEvent.preventDefault()
193
+ newwindow = window.open("http://#{AssetHost.SERVER}/a/chooser", 'chooser', 'height=620,width=1000,scrollbars=1')
194
+
195
+ # attach a listener to wait for the LOADED message
196
+ window.addEventListener "message", (evt) =>
197
+ if evt.data == "LOADED"
198
+ # dispatch our event with the asset data
199
+ newwindow.postMessage @collection.toJSON(), "http://#{AssetHost.SERVER}"
200
+ , false
201
+
202
+ return false
203
+
204
+ render: ->
205
+ @collection.each (a) =>
206
+ @_views[a.cid] ?= new AssetHost.CMSPlugin.CMSAsset({model:a,args:@options.args,rows:@options.rows})
207
+
208
+ views = _(@_views).sortBy (a) => a.model.get("ORDER")
209
+ $(@el).html( _(views).map (v) -> v.el )
210
+
211
+ # add hiddens
212
+ #$(@el).append(@options.hiddens)
213
+
214
+ # we need to render any removed rows as empty, with extras and possibly DELETE
215
+ if (@collection.length < @options.rows.length)
216
+ for idx in _.range(@collection.length,@options.rows.length)
217
+ _(@options.rows[idx].extras).each( (ex) =>
218
+ $( @el ).append $("<input/>",{type:'hidden',name:ex.name,id:ex.id,value:ex.value})
219
+ )
220
+
221
+ if @options.args.delete
222
+ $( @el ).append $("<input/>",{
223
+ type: "hidden",
224
+ id: _.template(@options.args.assetID,{idx:idx,field:@options.args.delete}),
225
+ name: _.template(@options.args.assetName,{idx:idx,field:@options.args.delete}),
226
+ value: "on"
227
+ })
228
+
229
+ $(@el).append( $("<li/>").html( $('<button/>',{text:"Pop Up Asset Chooser"})))
230
+
231
+ if @options.args.count
232
+ if (el = $('#'+@options.args.count)[0]) and @collection.length > @options.rows.length
233
+ el.value = @collection.length
234
+
235
+ return this
@@ -0,0 +1,586 @@
1
+ class AssetHost.Models
2
+ constructor: ->
3
+
4
+ class @Asset extends Backbone.Model
5
+ urlRoot: "http://#{AssetHost.SERVER}#{AssetHost.PATH_PREFIX}/api/assets/"
6
+
7
+ modal: ->
8
+ @_modal ?= new AssetHost.Models.AssetModalView(model: @)
9
+
10
+ #----------
11
+
12
+ url: ->
13
+ url = if @isNew() then @urlRoot else @urlRoot + encodeURIComponent(@id)
14
+
15
+ if AssetHost.TOKEN
16
+ url = url + "?" + $.param({auth_token:AssetHost.TOKEN})
17
+
18
+ url
19
+
20
+ #----------
21
+
22
+ chopCaption: (count=100) ->
23
+ chopped = @get('caption')
24
+
25
+ if chopped and chopped.length > count
26
+ regstr = "^(.{#{count}}\\w*)\\W"
27
+ chopped = chopped.match(new RegExp(regstr))
28
+
29
+ if chopped
30
+ chopped = "#{chopped[1]}..."
31
+ else
32
+ chopped = @get('caption')
33
+
34
+ chopped
35
+
36
+ #----------
37
+
38
+ class @Assets extends Backbone.Collection
39
+ baseUrl: "#{AssetHost.PATH_PREFIX}/api/assets",
40
+ model: Models.Asset
41
+
42
+ # If we have an ORDER attribute, sort by that. Otherwise, sort by just
43
+ # the asset ID.
44
+ comparator: (asset) ->
45
+ asset.get("ORDER") || -Number(asset.get("id"))
46
+
47
+ #----------
48
+
49
+
50
+ class @PaginatedAssets extends @Assets
51
+ initialize: ->
52
+ _.bindAll(this, 'parse', 'url')
53
+
54
+ @_page = 1
55
+ @_query = ''
56
+ @per_page = 24
57
+ @total_entries = 0
58
+
59
+ @
60
+
61
+ parse: (resp, xhr) ->
62
+ @next_page = xhr.getResponseHeader('X-Next-Page')
63
+ @total_entries = xhr.getResponseHeader('X-Total-Entries')
64
+
65
+ resp
66
+
67
+ url: ->
68
+ @baseUrl + "?" + $.param(page: @_page, q: @_query)
69
+
70
+ query: (q=@_query) ->
71
+ @_query = q if q?
72
+ @_query
73
+
74
+ page: (p=null) ->
75
+ @_page = Number(p) if p? && p != ''
76
+ @_page
77
+
78
+ #----------
79
+
80
+ class @AssetDropAssetView extends Backbone.View
81
+ tagName: 'li'
82
+ template: JST['asset_host_core/templates/asset_drop_asset']
83
+ events:
84
+ 'click button.delete': "_remove"
85
+ 'click': '_click'
86
+
87
+ #----------
88
+
89
+ initialize: ->
90
+ @del_confirm = false
91
+ @del_timeout = null
92
+
93
+ @drop = @options.drop
94
+ @model.bind "change", => @render()
95
+ @render()
96
+
97
+ #----------
98
+
99
+ _remove: (evt) ->
100
+ if @del_confirm
101
+ # delete
102
+ clearTimeout @del_timeout
103
+
104
+ # remove our model...
105
+ _.defer => @drop.trigger 'remove', @model
106
+ else
107
+ target = $(evt.target)
108
+ target.text "Really Delete?"
109
+ @del_confirm = true
110
+
111
+ # set a reset timeout
112
+ @del_timeout = setTimeout =>
113
+ target.text "x"
114
+ @del_confirm = false
115
+ @del_timeout = null
116
+ , 2000
117
+
118
+ false
119
+
120
+ #----------
121
+
122
+ _click: (evt) ->
123
+ if not $(evt.currentTarget).hasClass("delete")
124
+ @drop.trigger 'click', @model
125
+
126
+ #----------
127
+
128
+ render: ->
129
+ $(@el).html @template
130
+ asset: @model.toJSON()
131
+ chop: @model.chopCaption()
132
+
133
+ $(@el).attr "data-asset-id", @model.get("id")
134
+ @
135
+
136
+ #----------
137
+
138
+ class @AssetDropView extends Backbone.View
139
+ tagName: "ul"
140
+ className: "assets"
141
+
142
+ initialize: ->
143
+ @_views = {}
144
+
145
+ @collection.bind 'add', (f) =>
146
+ @collection.sort()
147
+
148
+ @collection.bind 'remove', (f) =>
149
+ @collection.sort()
150
+
151
+ @collection.bind 'reset', (f) =>
152
+ _(@_views).each (av) => $(av.el).detach()
153
+ @_views = {}
154
+ @render()
155
+
156
+ #----------
157
+
158
+ render: ->
159
+ # set up views for each collection member
160
+ @collection.each (f) =>
161
+ # create a view unless one exists
162
+ @_views[f.cid] ?= new Models.AssetDropAssetView(model: f, drop: @)
163
+
164
+ # make sure all of our view elements are added
165
+ $(@el).append( _(@_views).map (v) -> v.el )
166
+
167
+ $(@el).sortable
168
+ update: (evt,ui) =>
169
+ _(@el.children).each (li,idx) =>
170
+ id = $(li).attr('data-asset-id')
171
+ @collection.get(id).attributes.ORDER = idx
172
+ @collection.sort()
173
+
174
+ @
175
+
176
+ #----------
177
+
178
+ class @AssetSearchView extends Backbone.View
179
+ className: "search_box"
180
+ template: JST['asset_host_core/templates/asset_search']
181
+ events:
182
+ 'click button': 'search',
183
+ 'keypress input:text': '_keypress'
184
+
185
+ initialize: ->
186
+ @collection.bind('all', => @render() )
187
+
188
+ _keypress: (e) ->
189
+ @search() if e.which == 13
190
+
191
+ search: ->
192
+ query = $(@el).find("input")[0].value
193
+ @trigger "search", query
194
+
195
+ render: ->
196
+ $(@el).html @template(query: @collection.query())
197
+ @
198
+
199
+ #----------
200
+
201
+ class @AssetBrowserAssetView extends Backbone.View
202
+ tagName: "li"
203
+ template: JST['asset_host_core/templates/browser_asset']
204
+ tipTemplate: JST['asset_host_core/templates/browser_asset_tip']
205
+
206
+ initialize: ->
207
+ @id = "ab_#{@model.get('id')}"
208
+ $(@el).attr("data-asset-url",@model.get('url'))
209
+
210
+ @render()
211
+
212
+ $(@el).find('button')[0].addEventListener "click", (evt) =>
213
+ @trigger "click", @model
214
+ true
215
+
216
+ # add tooltip
217
+ $(@el).tooltip
218
+ title: @tipTemplate(@model.toJSON())
219
+ html: true
220
+
221
+ @model.bind "change", => @render()
222
+
223
+ render: ->
224
+ $(@el).html @template(@model.toJSON())
225
+ $(@el).attr "draggable", true
226
+ @
227
+
228
+ #----------
229
+
230
+ class @AssetBrowserView extends Backbone.View
231
+ tagName: "ul"
232
+
233
+ initialize: ->
234
+ @_views = {}
235
+
236
+ @container = $("#content_right")
237
+
238
+ @collection.bind "reset", =>
239
+ _(@_views).each (a) => $(a.el).detach()
240
+ @_views = {}
241
+ @render()
242
+
243
+ pages: ->
244
+ @_pages ?= (new AssetHost.Models.PaginationLinks(@collection)).render()
245
+
246
+ loading: ->
247
+ $(@el).css(opacity: ".1")
248
+ @container.spin()
249
+
250
+ doneLoading: ->
251
+ $(@el).css(opacity: "1")
252
+ @container.spin(false)
253
+
254
+ render: ->
255
+ # set up views for each collection member
256
+ @collection.each (a) =>
257
+ # create a view unless one exists
258
+ @_views[a.cid] ?= new AssetHost.Models.AssetBrowserAssetView(model: a)
259
+ @_views[a.cid].bind "click", (a) => @trigger "click", a
260
+
261
+ # make sure all of our view elements are added
262
+ $(@el).append( _(@_views).map (v) -> v.el )
263
+
264
+ # clear loading status
265
+ @doneLoading()
266
+ @
267
+
268
+ #----------
269
+
270
+ class @AssetModalView extends Backbone.View
271
+ className: "modal"
272
+ events:
273
+ 'click a.select': '_select'
274
+ 'click a.admin': '_admin'
275
+ 'click a.close': 'close'
276
+
277
+ template: JST['asset_host_core/templates/asset_modal']
278
+
279
+ open: (options) ->
280
+ @options = options || {}
281
+ $(@render().el).modal()
282
+
283
+ $(@render().el).on "hide", => @options.close?()
284
+
285
+ close: ->
286
+ $(@el).modal('hide')
287
+
288
+ _select: ->
289
+ @close()
290
+ @model.trigger('selected',@model)
291
+
292
+ _admin: ->
293
+ @close()
294
+ @model.trigger('admin',@model)
295
+
296
+ render: ->
297
+ $(@el).html @template
298
+ asset: @model.toJSON()
299
+ select: if @options.select? then @options.select else true
300
+ admin: if @options.admin? then @options.admin else false
301
+
302
+ @
303
+
304
+ #----------
305
+
306
+ class @SaveAndCloseView extends Backbone.View
307
+ events: 'click button': 'saveAndClose'
308
+ template: JST['asset_host_core/templates/save_and_close_view']
309
+
310
+ initialize: ->
311
+ @collection.bind "all", => @render()
312
+ @render()
313
+
314
+ saveAndClose: ->
315
+ # make sure collection is sorted before we return it
316
+ @collection.sort()
317
+ @trigger 'saveAndClose', @collection.toJSON()
318
+
319
+ render: ->
320
+ $(@el).html @template(count: @collection.size())
321
+ @
322
+
323
+ #----------
324
+
325
+ class @PaginationLinks extends Backbone.View
326
+ className: "pagination pagination-centered"
327
+ template: JST['asset_host_core/templates/pagination_links']
328
+ linkTemplate: JST['asset_host_core/templates/pagination_link']
329
+
330
+ DefaultOptions:
331
+ inner_window: 3,
332
+ outer_window: 1,
333
+ prev_label: "&#8592;",
334
+ next_label: "&#8594;",
335
+ separator: " ",
336
+ spacer: "<li class='disabled'><a href='#'>...</a></li>"
337
+
338
+ events: 'click li': 'clickPage'
339
+
340
+ initialize: (@collection, options={}) ->
341
+ @options = _.defaults options, @DefaultOptions
342
+
343
+ @collection.bind "reset", => @render()
344
+ @collection.bind "add", => @render()
345
+ @collection.bind "change", => @render()
346
+
347
+ #----------
348
+
349
+ clickPage: (evt) ->
350
+ page = $(evt.currentTarget).attr("data-page")
351
+
352
+ if page
353
+ @trigger "page", page
354
+
355
+ render: ->
356
+ # what pages are we displaying?
357
+ pages = Math.floor( @collection.total_entries / @collection.per_page + 1)
358
+ current = @collection._page
359
+
360
+ rendered = {}
361
+ links = []
362
+
363
+ # start with outer_window from 1
364
+ _(_.range(1,1+@options.outer_window)).each (i) =>
365
+ links.push @linkTemplate
366
+ page: i
367
+ current: current==i
368
+
369
+
370
+ rendered[ i ] = true
371
+
372
+
373
+ # now try -inner_window from current
374
+ _(_.range(current-@options.inner_window,current)).each (i) =>
375
+ if i > 0 && !rendered[ i ]
376
+ if i-1 > 0 && !rendered[i-1]
377
+ links.push @options.spacer
378
+
379
+ links.push @linkTemplate
380
+ page: i
381
+ current: false
382
+
383
+
384
+ rendered[ i ] = true
385
+
386
+
387
+ # now try current
388
+ if !rendered[ current ]
389
+ if current-1 > 0 && !rendered[current-1]
390
+ links.push @options.spacer
391
+
392
+ links.push @linkTemplate
393
+ page: current
394
+ current: true
395
+
396
+
397
+ rendered[ current ] = true
398
+
399
+ # now try +inner_window from current
400
+ _(_.range(current+1,current+@options.inner_window+1)).each (i) =>
401
+ if i < pages && !rendered[ i ]
402
+ if i-1 > 0 && !rendered[i-1]
403
+ links.push @options.spacer
404
+
405
+ links.push @linkTemplate
406
+ page: i
407
+ current: false
408
+
409
+
410
+ rendered[ i ] = true
411
+
412
+
413
+ # and finally, -outer_window from last page
414
+ _(_.range(pages+1-@options.outer_window,pages+1)).each (i) =>
415
+ if i > 0 && !rendered[ i ]
416
+ if i-1 > 0 && !rendered[i-1]
417
+ links.push @options.spacer
418
+
419
+ links.push @linkTemplate
420
+ page: i
421
+ collection: @collection
422
+ current: current==i
423
+
424
+
425
+ rendered[ i ] = true
426
+
427
+
428
+ $(@el).html @template
429
+ current: current
430
+ pages: pages
431
+ links: links.join(@options.separator)
432
+ options: @options
433
+
434
+
435
+ @
436
+
437
+ #----------
438
+
439
+ @queuedSync: (method,model,success,error) ->
440
+ #
441
+
442
+ class @QueuedFile extends Backbone.Model
443
+ sync: Models.queuedSync
444
+
445
+ upload: ->
446
+ return false if @xhr
447
+
448
+ @xhr = new XMLHttpRequest
449
+
450
+ $(@xhr.upload).bind "progress", (evt) =>
451
+ evt = evt.originalEvent
452
+ @set {"PERCENT": if evt.lengthComputable then Math.floor(evt.loaded/evt.total*100) else evt.loaded}
453
+
454
+ $(@xhr.upload).bind "complete", (evt) =>
455
+ @set {"STATUS": "pending"}
456
+
457
+ @xhr.onreadystatechange = (req) =>
458
+ if @xhr.readyState == 4 && @xhr.status == 200
459
+ @set {"STATUS": "complete"}
460
+
461
+ if req.responseText != "ERROR"
462
+ @set {"ASSET": $.parseJSON(@xhr.responseText)}
463
+ @trigger "uploaded", this
464
+
465
+ @xhr.open('POST',this.collection.urlRoot, true)
466
+ @xhr.setRequestHeader('X_FILE_NAME', @get('file').name)
467
+ @xhr.setRequestHeader('CONTENT_TYPE', @get('file').type)
468
+ @xhr.setRequestHeader('HTTP_X_FILE_UPLOAD','true')
469
+
470
+ # and away we go...
471
+ @xhr.send @get('file')
472
+ @set {"STATUS": "uploading"}
473
+
474
+ readableSize: ->
475
+ return false if !@get('size')
476
+ size = @get('size')
477
+
478
+ units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
479
+ i = 0;
480
+
481
+ while size >= 1024
482
+ size /= 1024
483
+ ++i
484
+
485
+ size.toFixed(1) + ' ' + units[i];
486
+
487
+ #----------
488
+
489
+ class @QueuedFiles extends Backbone.Collection
490
+ model: Models.QueuedFile
491
+ urlRoot: "#{AssetHost.PATH_PREFIX}/a/assets/upload"
492
+
493
+ initialize: (models,options) ->
494
+ @urlRoot = options.urlRoot
495
+
496
+ #----------
497
+
498
+ class @QueuedFileView extends Backbone.View
499
+ events:
500
+ 'click button.remove': '_remove',
501
+ 'click button.upload': '_upload'
502
+
503
+ tagName: "li"
504
+ template: JST['asset_host_core/templates/queued_file']
505
+
506
+ initialize: ->
507
+ @model.bind "change", => @render()
508
+
509
+ @img = ''
510
+
511
+ # try to read file on disk
512
+ file = @model.get('file')
513
+ if file.type.match('image.*')
514
+ reader = new FileReader()
515
+
516
+ reader.onload = (e) =>
517
+ @img = $ "<img/>", {
518
+ class: "thumb",
519
+ src: e.target.result,
520
+ title: file.name
521
+ }
522
+
523
+ m = /^([^,]+),(.*)$/.exec(e.target.result)
524
+ @exif = EXIF.readFromBinaryFile(window.atob(m[2]))
525
+
526
+ @render()
527
+
528
+ reader.readAsDataURL(file)
529
+
530
+ @render()
531
+
532
+ _remove: (evt) ->
533
+ @model.collection.remove(@model)
534
+
535
+ _upload: (evt) ->
536
+ @model.upload()
537
+
538
+ render: ->
539
+ $(@el).attr('class',@model.get("STATUS"))
540
+
541
+ $(@el).html @template
542
+ exif: @exif
543
+ name: @model.get('name')
544
+ size: @model.readableSize()
545
+ STATUS: @model.get('STATUS')
546
+ PERCENT: @model.get('PERCENT')
547
+ xhr: if @model.xhr then true else false
548
+
549
+
550
+ $(@el).prepend(@img) if @img
551
+ @
552
+
553
+ #----------
554
+
555
+ class @QueuedFilesView extends Backbone.View
556
+ tagName: "ul"
557
+ className: "uploads"
558
+
559
+ initialize: ->
560
+ @_views = {}
561
+
562
+ @collection.bind 'add', (f) =>
563
+ @_views[f.cid] = new Models.QueuedFileView(model: f)
564
+ @render()
565
+
566
+ @collection.bind 'remove', (f) =>
567
+ $(@_views[f.cid].el).detach()
568
+ delete @_views[f.cid]
569
+ @render()
570
+
571
+ @collection.bind 'reset', (f) =>
572
+ @_views = {}
573
+
574
+ _reset: (f) ->
575
+ # do we need this?
576
+
577
+ render: ->
578
+ # set up views for each collection member
579
+ @collection.each (f) =>
580
+ # create a view unless one exists
581
+ @_views[f.cid] ?= new Models.QueuedFileView(model: f)
582
+
583
+ # make sure all of our view elements are added
584
+ $(@el).append( _(@_views).map (v) -> v.el )
585
+
586
+ @