ample_assets 0.0.1

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 (114) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.textile +68 -0
  3. data/Rakefile +38 -0
  4. data/app/assets/images/ample_assets/bg-noise.png +0 -0
  5. data/app/assets/images/ample_assets/btn-next.png +0 -0
  6. data/app/assets/images/ample_assets/btn-prev.png +0 -0
  7. data/app/assets/images/ample_assets/btn-select-files.png +0 -0
  8. data/app/assets/images/ample_assets/cancel.png +0 -0
  9. data/app/assets/images/ample_assets/closelabel.png +0 -0
  10. data/app/assets/images/ample_assets/collapse.png +0 -0
  11. data/app/assets/images/ample_assets/gravity.png +0 -0
  12. data/app/assets/images/ample_assets/icon_other.gif +0 -0
  13. data/app/assets/images/ample_assets/icon_pdf.gif +0 -0
  14. data/app/assets/images/ample_assets/icon_swf.gif +0 -0
  15. data/app/assets/images/ample_assets/loading.gif +0 -0
  16. data/app/assets/images/ample_assets/play.png +0 -0
  17. data/app/assets/images/ample_assets/prev-next.png +0 -0
  18. data/app/assets/images/ample_assets/reload.png +0 -0
  19. data/app/assets/images/ample_assets/remove.png +0 -0
  20. data/app/assets/javascripts/ample_assets.js +1 -0
  21. data/app/assets/javascripts/ample_assets/application.js +18 -0
  22. data/app/assets/javascripts/ample_assets/classes/ample_assets_gravity.js.coffee +86 -0
  23. data/app/assets/javascripts/ample_assets/classes/ample_assets_toolbar.js.coffee +769 -0
  24. data/app/assets/javascripts/ample_assets/classes/ample_assets_uploadify.js.coffee +30 -0
  25. data/app/assets/javascripts/ample_assets/files.js.coffee +5 -0
  26. data/app/assets/stylesheets/ample_assets.css +3 -0
  27. data/app/assets/stylesheets/ample_assets/application.css +8 -0
  28. data/app/assets/stylesheets/ample_assets/bourbon/css3/_border-radius.scss +59 -0
  29. data/app/assets/stylesheets/ample_assets/bourbon/css3/_box-shadow.scss +11 -0
  30. data/app/assets/stylesheets/ample_assets/custom_mixins.scss +47 -0
  31. data/app/assets/stylesheets/ample_assets/facebox.css +79 -0
  32. data/app/assets/stylesheets/ample_assets/files.css.scss +750 -0
  33. data/app/assets/swf/ample_assets/expressInstall.swf +0 -0
  34. data/app/assets/swf/ample_assets/uploadify.swf +0 -0
  35. data/app/controllers/ample_assets/application_controller.rb +4 -0
  36. data/app/controllers/ample_assets/files_controller.rb +109 -0
  37. data/app/helpers/ample_assets/application_helper.rb +4 -0
  38. data/app/models/ample_assets/file.rb +79 -0
  39. data/app/views/ample_assets/files/_drop.html.erb +12 -0
  40. data/app/views/ample_assets/files/_file.html.erb +1 -0
  41. data/app/views/ample_assets/files/_file.js.erb +3 -0
  42. data/app/views/ample_assets/files/index.html.erb +6 -0
  43. data/app/views/ample_assets/files/new.html.erb +21 -0
  44. data/app/views/ample_assets/files/recent.html.erb +1 -0
  45. data/app/views/ample_assets/files/show.html.erb +1 -0
  46. data/app/views/layouts/ample_assets/application.html.erb +14 -0
  47. data/config/routes.rb +27 -0
  48. data/lib/ample_assets.rb +26 -0
  49. data/lib/ample_assets/configuration.rb +73 -0
  50. data/lib/ample_assets/custom_processor.rb +29 -0
  51. data/lib/ample_assets/devise_constraint.rb +9 -0
  52. data/lib/ample_assets/engine.rb +36 -0
  53. data/lib/ample_assets/form_builder.rb +19 -0
  54. data/lib/ample_assets/form_helper.rb +14 -0
  55. data/lib/ample_assets/plugin_methods.rb +11 -0
  56. data/lib/ample_assets/version.rb +3 -0
  57. data/lib/ample_assets/view_helper.rb +80 -0
  58. data/lib/generators/ample_assets/install_generator.rb +25 -0
  59. data/lib/generators/ample_assets/templates/migration.rb +16 -0
  60. data/lib/generators/ample_assets/utils.rb +15 -0
  61. data/test/dummy/Rakefile +7 -0
  62. data/test/dummy/app/assets/images/rails.png +0 -0
  63. data/test/dummy/app/assets/javascripts/application.js +11 -0
  64. data/test/dummy/app/assets/sample.pdf +0 -0
  65. data/test/dummy/app/assets/stylesheets/application.css +8 -0
  66. data/test/dummy/app/controllers/application_controller.rb +7 -0
  67. data/test/dummy/app/controllers/public/pages_controller.rb +32 -0
  68. data/test/dummy/app/controllers/public_controller.rb +6 -0
  69. data/test/dummy/app/helpers/application_helper.rb +11 -0
  70. data/test/dummy/app/models/page.rb +7 -0
  71. data/test/dummy/app/views/layouts/application.html.erb +17 -0
  72. data/test/dummy/app/views/public/index.html.erb +0 -0
  73. data/test/dummy/app/views/public/pages/_form.html.erb +18 -0
  74. data/test/dummy/app/views/public/pages/edit.html.erb +1 -0
  75. data/test/dummy/app/views/public/pages/index.html.erb +8 -0
  76. data/test/dummy/app/views/public/pages/new.html.erb +1 -0
  77. data/test/dummy/app/views/public/pages/show.html.erb +3 -0
  78. data/test/dummy/config.ru +4 -0
  79. data/test/dummy/config/application.rb +45 -0
  80. data/test/dummy/config/boot.rb +10 -0
  81. data/test/dummy/config/database.yml +25 -0
  82. data/test/dummy/config/environment.rb +5 -0
  83. data/test/dummy/config/environments/development.rb +30 -0
  84. data/test/dummy/config/environments/production.rb +60 -0
  85. data/test/dummy/config/environments/test.rb +39 -0
  86. data/test/dummy/config/initializers/ample_assets.rb +4 -0
  87. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  88. data/test/dummy/config/initializers/inflections.rb +10 -0
  89. data/test/dummy/config/initializers/mime_types.rb +5 -0
  90. data/test/dummy/config/initializers/secret_token.rb +7 -0
  91. data/test/dummy/config/initializers/session_store.rb +8 -0
  92. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  93. data/test/dummy/config/locales/en.yml +5 -0
  94. data/test/dummy/config/routes.rb +8 -0
  95. data/test/dummy/db/migrate/20120105034123_create_pages.rb +9 -0
  96. data/test/dummy/db/migrate/20120106192233_create_ample_assets_tables.rb +16 -0
  97. data/test/dummy/db/schema.rb +37 -0
  98. data/test/dummy/public/404.html +26 -0
  99. data/test/dummy/public/422.html +26 -0
  100. data/test/dummy/public/500.html +26 -0
  101. data/test/dummy/public/favicon.ico +0 -0
  102. data/test/dummy/script/rails +6 -0
  103. data/test/functional/ample_assets/files_controller_test.rb +49 -0
  104. data/test/integration/ample_assets/admin_test.rb +96 -0
  105. data/test/integration/ample_assets/public_test.rb +7 -0
  106. data/test/integration_test_helper.rb +24 -0
  107. data/test/test_helper.rb +25 -0
  108. data/test/unit/ample_assets/configuration_test.rb +35 -0
  109. data/test/unit/ample_assets/file_test.rb +14 -0
  110. data/test/unit/ample_assets/plugin_methods_test.rb +30 -0
  111. data/test/unit/helpers/ample_assets/form_builder_test.rb +11 -0
  112. data/test/unit/helpers/ample_assets/form_helper_test.rb +23 -0
  113. data/test/unit/helpers/ample_assets/view_helper_test.rb +128 -0
  114. metadata +395 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2011 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,68 @@
1
+ h1. Ample Assets
2
+
3
+ AmpleAssets is a mountable engine that provides drag & drop file-management for Rails 3.2 applications.
4
+
5
+ !http://ample.github.com/ample_assets/images/assets.png!
6
+
7
+ h3. Example Usage
8
+
9
+ Add the following to your Gemfile and bundle...
10
+
11
+ <code>
12
+ gem 'ample_assets', :git => 'https://github.com/ample/ample_assets.git'
13
+ </code>
14
+
15
+ Run the generator to copy over the necessary migrations...
16
+
17
+ <code>
18
+ rails g ample_assets:install
19
+ </code>
20
+
21
+ You'll need to create a foreign_key on the model with which you plan to integrate AmpleAssets. For the purposes of this example, let's assume your model is named @Post@...
22
+
23
+ <code>rails g migration add_file_id_to_posts file_id:integer</code>
24
+
25
+ Now you're ready to migrate...
26
+
27
+ <code>rake db:migrate</code>
28
+
29
+ Add the following plugin to your model...
30
+
31
+ <pre><code>class Post < ActiveRecord::Base
32
+ ...
33
+ has_asset
34
+ ...
35
+ end</code></pre>
36
+
37
+ In both of your manifest files (stylesheets & javascripts) require ample_assets, for example...
38
+
39
+ <pre><code>/**
40
+ *= require ample_assets
41
+ */
42
+ </code></pre>
43
+
44
+ Add the engine to your app's routes.rb file...
45
+
46
+ <code>
47
+ mount AmpleAssets::Engine => "/ample_assets", :as => "ample_assets"
48
+ </code>
49
+
50
+ To invoke the asset toolbar, include the following in any view...
51
+
52
+ <code>
53
+ <%= assets_toolbar %>
54
+ </code>
55
+
56
+ To invoke a new drop_target, include the following within a form attached to your model...
57
+
58
+ <pre><code><%= form_for current_page do |f| %>
59
+ <%= f.asset_drop(:file_id) %>
60
+ <% end %>
61
+ </code></pre>
62
+
63
+ h3. Configuration
64
+
65
+ <pre><code>AmpleAssets.configure do |config|
66
+ config.mount_at = YOUR PATH PREFIX
67
+ end
68
+ </code></pre>
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'AmpleAssets'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.libs << 'test'
33
+ t.pattern = 'test/**/*_test.rb'
34
+ t.verbose = true
35
+ t.ruby_opts << '-rturn'
36
+ end
37
+
38
+ task :default => :test
@@ -0,0 +1 @@
1
+ //= require ./ample_assets/application
@@ -0,0 +1,18 @@
1
+ // This is a manifest file that'll be compiled into including all the files listed below.
2
+ // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
3
+ // be included in the compiled file accessible from http://example.com/assets/application.js
4
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
5
+ // the compiled file.
6
+ //
7
+ //= require jquery
8
+ //= require jquery-ui
9
+ //= require jquery_ujs
10
+ //= require jquery.ample_panels
11
+ //= require jquery.facebox
12
+ //= require jquery.uploadify
13
+ //= require moustache
14
+ //= require pdfobject
15
+ //= require swfobject
16
+ //= require coffee_cup
17
+ //= require_tree ./classes
18
+ //= require ./files
@@ -0,0 +1,86 @@
1
+ # **AmpleAssets** is drag and drop file management for Rails applications.
2
+ #
3
+ class window.AmpleAssetsGravity extends CoffeeCup
4
+
5
+ default_options:
6
+ debug: false
7
+ uid: false
8
+ url: "/ample_assets/files/{{ id }}/gravity"
9
+ min_width: 100
10
+ min_height: 100
11
+ max_width: 480
12
+ max_height: 300
13
+
14
+ init: ->
15
+ @log "init()"
16
+ @handle = $("#asset-gravity-handle")
17
+ @image = $(".asset-media img")
18
+ @test()
19
+
20
+ test: ->
21
+ if @image.width() > 0
22
+ @html()
23
+ else
24
+ @log "@image not loaded"
25
+ setTimeout (=> @test()), 500
26
+
27
+ html: ->
28
+ @log "html()"
29
+ # Don't show this for tiny images
30
+ return false unless @image.height() > @options.min_width && @image.height() > @options.min_height
31
+ @gravity = $("#file_attachment_gravity").val() || "c"
32
+ @gravity_grid =
33
+ top: @image.height() / 3
34
+ left: @image.width() / 3
35
+ @image_pos = @image.position()
36
+ @image_center =
37
+ top: (@image_pos.top + (@image.height() / 2) - (@handle.height() / 2))
38
+ left: (@image_pos.left + (@image.width() / 2) - (@handle.width() / 2))
39
+ @handle.css
40
+ position: "absolute"
41
+ left: (@image_center.left - @gravity_grid.left) + (@gravity_grid.left * @gravities()[@gravity][0])
42
+ top: (@image_center.top - @gravity_grid.top) + (@gravity_grid.top * @gravities()[@gravity][1])
43
+ @handle.draggable
44
+ containment: @image
45
+ grid: [@gravity_grid.left, @gravity_grid.top]
46
+ scroll: false,
47
+ start: (event, ui) =>
48
+ $('div.ui-droppable, textarea.ui-droppable').addClass('asset-inactive-target')
49
+ stop: (event, ui) =>
50
+ $('div.ui-droppable, textarea.ui-droppable').removeClass('asset-inactive-target')
51
+
52
+ left = ui.position.left
53
+ top = ui.position.top
54
+
55
+ # Account for images smaller than max width and/or height.
56
+ left = Math.abs(((@options.max_width - @image.width()) / 2) - left) if @image.width() < @options.max_width
57
+ top = Math.abs(((@options.max_height - @image.height()) / 2) - top) if @image.height() < @options.max_height
58
+
59
+ gravity_pos = [ Math.floor(left / @gravity_grid.left), Math.floor(top / @gravity_grid.top) ]
60
+
61
+ for key, value of @gravities()
62
+ if value[0] is gravity_pos[0] and value[1] is gravity_pos[1]
63
+ gravity = key
64
+ break
65
+ $("#file_attachment_gravity").val(gravity);
66
+ @submit(gravity)
67
+ @log gravity
68
+ @handle.fadeIn()
69
+
70
+ submit: (gravity) ->
71
+ @log "submit(#{gravity})"
72
+ $.post @options.url, { gravity: gravity }, (e) =>
73
+ $("a[data-uid='#{@options.uid}']").attr('data-gravity',gravity)
74
+ $('#asset-gravity-notification').show().delay(2500).fadeOut()
75
+
76
+ gravities: ->
77
+ nw: [ 0, 0 ]
78
+ n: [ 1, 0 ]
79
+ ne: [ 2, 0 ]
80
+ w: [ 0, 1 ]
81
+ c: [ 1, 1 ]
82
+ e: [ 2, 1 ]
83
+ sw: [ 0, 2 ]
84
+ s: [ 1, 2 ]
85
+ se: [ 2, 2 ]
86
+
@@ -0,0 +1,769 @@
1
+ # **AmpleAssets** is drag and drop file management for Rails applications.
2
+ #
3
+ class window.AmpleAssetsToolbar extends CoffeeCup
4
+
5
+ default_options:
6
+ debug: false
7
+ expanded: false
8
+ id: "ample-assets"
9
+ handle_text: 'Assets'
10
+ expanded_height: 180
11
+ collapsed_height: 0
12
+ base_url: '/ample_assets'
13
+ search_url: '/files/search'
14
+ thumb_url: '/files/thumbs'
15
+ show_url: '/files/{{ id }}'
16
+ touch_url: '/files/{{ id }}/touch'
17
+ gravity_url: '/files/{{ id }}/gravity'
18
+ onInit: ->
19
+ onExpand: ->
20
+ onCollapse: ->
21
+ panels_options:
22
+ debug: false
23
+ width: 950
24
+ height: 100
25
+ orientation: 'vertical'
26
+ key_orientation: 'vertical'
27
+ keyboard_nav: true
28
+ auto: false
29
+ parent: 'div'
30
+ children: 'div.page'
31
+ pages_options:
32
+ interval: 5000
33
+ width: 81
34
+ height: 81
35
+ enabled: true
36
+ distance: 10
37
+ auto: false
38
+ orientation: 'horizontal'
39
+ key_orientation: 'horizontal'
40
+ per_page: 10
41
+ pages: [
42
+ {
43
+ id: 'recent-assets',
44
+ title: 'Recently Viewed',
45
+ url: '',
46
+ panels: false
47
+ }
48
+ ]
49
+
50
+ # Initialize product toolbar and drop targets.
51
+ init: ->
52
+ @options.onInit()
53
+ super
54
+ @setup()
55
+ @events()
56
+
57
+ # Setup global parameters & class options.
58
+ set_options: (opts) ->
59
+ @current = 0
60
+ @keys_enabled = true
61
+ @reloading = false
62
+ @searching = false
63
+ @loaded = false
64
+ super
65
+
66
+ # Build structure and stylize layout of toolbar, setup drag, drop and search logic.
67
+ # Opens first tab if toolbar is expanded on init.
68
+ setup: ->
69
+ id = @options.id
70
+ layout = Mustache.to_html(@tpl('layout'),{ id: id, pages: @get_pages(), tabs: @get_pages('tab') })
71
+ @handle = Mustache.to_html(@tpl('handle'),{ id: id, title: @options.handle_text })
72
+ html = $(layout)
73
+ $('body').append(html).append(@handle)
74
+ @style()
75
+ @drag_drop()
76
+ @search()
77
+ @goto(0) if @options.expanded
78
+
79
+ # Set initial styles on toolbar elements.
80
+ style: ->
81
+ handle_opts =
82
+ position: 'absolute'
83
+ bottom: 0
84
+ right: 0
85
+ $("##{@options.id}-handle").css(handle_opts)
86
+
87
+ @loading = $("##{@options.id}-tabs span.asset-loading")
88
+ $("##{@options.id} .container").css('height',200)
89
+ if @options.expanded
90
+ $("##{@options.id}").css({height:@options.expanded_height});
91
+
92
+ # Reloads tab identified by `i`
93
+ reload: (i) ->
94
+ @log "reload(#{i})"
95
+ @reloading = true
96
+ if i < @options.pages.length - 1
97
+ @empty(i)
98
+ @options.pages[i]['loaded'] = false
99
+ @options.pages[i]['pages_loaded'] = false
100
+ @options.pages[i]['last_request_empty'] = false
101
+ $(@options.pages[i]['panel_selector']).amplePanels('goto', 0)
102
+ @goto(i)
103
+ @enable_panel(i)
104
+
105
+ # Empties the contents of the page identified by `i`
106
+ empty: (i) ->
107
+ @log "empty(#{i})"
108
+ selector = "##{@options.id} .pages .page:nth-child(#{(i+1)})"
109
+ selector += " ul" if @options.pages[i]['panels']
110
+ $(selector).empty()
111
+
112
+ # Hide and deactivate current page, load next page identified by `i`
113
+ goto: (i) ->
114
+ @log "goto(#{i})"
115
+ @show(i)
116
+ @disable_panels()
117
+ @activate(i)
118
+ @load(i) unless @already_loaded(i)
119
+ @enable_panel(i) if @already_loaded(i)
120
+
121
+ # Hide all pages, show page identified by `i`
122
+ show: (i) ->
123
+ $("##{@options.id} .pages .page").hide()
124
+ $("##{@options.id} .pages .page:nth-child(#{i+1}), ##{@options.id} .pages .page:nth-child(#{i+1}) ul").show()
125
+
126
+ # Implement drag & droppable instances, with appropriate callbacks.
127
+ drag_drop: ->
128
+ base_url = @options.base_url
129
+ thumb_url = @options.thumb_url
130
+ ref = this
131
+
132
+ # Note the use of liveDraggable here. See extended plugin at the bottom of this file.
133
+ $(".draggable").liveDraggable
134
+ appendTo: "body"
135
+ helper: "clone"
136
+ start: ->
137
+ $('div.ui-droppable, textarea.ui-droppable').addClass('asset-drop-target')
138
+ stop: ->
139
+ $('div.ui-droppable, textarea.ui-droppable').removeClass('asset-drop-target')
140
+
141
+ $("textarea").droppable
142
+ activeClass: "asset-notice"
143
+ hoverClass: "asset-success"
144
+ drop: (event, ui) ->
145
+ unless $(ui.helper).data('role') == 'gravity'
146
+ ref.target_textarea = this
147
+ ref.resize_modal(ui.draggable)
148
+
149
+ $(".droppable").droppable
150
+ activeClass: "asset-notice"
151
+ hoverClass: "asset-success"
152
+ drop: (event, ui) ->
153
+ unless $(ui.helper).data('role') == 'gravity'
154
+ $(this).html ui.draggable.clone()
155
+ asset_id = $(ui.draggable).attr("id").split("-")[1]
156
+ $(this).parent().children().first().val asset_id
157
+ $(this).parent().find('a.asset-remove').removeClass('hide').show()
158
+
159
+ # Build html for modal windows wherein users can resize the asset's dimensions & geometry.
160
+ # Executes when dropping a file into a textarea. The first argument is the response
161
+ # from the droppable callbacks defined above.
162
+ resize_modal: (el) ->
163
+ uid = $(el).attr("data-uid")
164
+ size = $(el).attr("data-size")
165
+ orientation = $(el).attr("data-orientation")
166
+ base_url = @options.base_url
167
+ thumb_url = @options.thumb_url
168
+ geometry = '100x>'
169
+ opts =
170
+ src: "#{base_url}#{thumb_url}/#{geometry}?uid=#{uid}"
171
+ orientation: orientation
172
+ dimensions: size
173
+ uid: uid
174
+ html = Mustache.to_html(@tpl('drop'), opts)
175
+ $.facebox("<div class=\"asset-detail\">#{html}</div>")
176
+
177
+ # Removes active state from all tabs, adds it back for tab identified by `i`
178
+ activate: (i) ->
179
+ @log "activate(#{i})"
180
+ @current = i
181
+ tabs = $("##{@options.id} a.tab")
182
+ tabs.removeClass('on')
183
+ tabs.eq(i).addClass('on')
184
+
185
+ # Highlight & load next tab (right) by incrementing `@current`
186
+ next: ->
187
+ if @current < @options.pages.length - 1
188
+ @log "next()"
189
+ @current += 1
190
+ @goto(@current)
191
+
192
+ # Highlight & load previous tab (left) by decrementing `@current`
193
+ previous: ->
194
+ unless @current == 0
195
+ @log "previous()"
196
+ @current -= 1
197
+ @goto(@current)
198
+
199
+ # Loops through all pages, generates HTML and returns concatenated string of everything.
200
+ get_pages: (tpl = 'page') ->
201
+ html = ''
202
+ $.each @options.pages, (idx,el) =>
203
+ el['classes'] = 'first-child' if idx == 0
204
+ html += Mustache.to_html @tpl(tpl), el
205
+ html
206
+
207
+ # Expands and collapses asset toolbar.
208
+ toggle: ->
209
+ el = $("##{@options.id}")
210
+ if @options.expanded
211
+ @options.expanded = false
212
+ $('body').animate {'padding-bottom': 0}, "fast"
213
+ el.animate {height: @options.collapsed_height}, "fast", =>
214
+ @collapse()
215
+ @options.onCollapse()
216
+ el.trigger('collapse')
217
+ else
218
+ $("##{@options.id}-handle").hide()
219
+ @options.expanded = true
220
+ $('body').animate {'padding-bottom': @options.expanded_height}, "fast"
221
+ el.animate {height: @options.expanded_height}, "fast", =>
222
+ @expand()
223
+ @options.onExpand()
224
+ el.trigger('expand')
225
+
226
+ # Loads contents of page identified by `i`
227
+ load: (i) ->
228
+ @log "load(#{i})"
229
+ ref = this
230
+ load_next_page = true
231
+ load_next_page = false if @options.pages[i]['last_request_empty']
232
+ load_next_page = true if @reloading
233
+
234
+ if @options.pages[i]['url'] && load_next_page
235
+ @loading.show()
236
+ url = @next_page_url(i)
237
+ data_type = @options.pages[i]['data_type'] if @options.pages[i]['data_type']
238
+ $.get url, (response, xhr) ->
239
+ ref.loading.hide()
240
+ ref.options.pages[i]['loaded'] = true
241
+ # If response is empty, let users know by loading an empty notification.
242
+ if $.trim(response) == '' || response.length == 0
243
+ ref.options.pages[i]['last_request_empty'] = true
244
+ ref.load_empty(i) if ref.reloading || !ref.options.pages[i]['panel_selector']
245
+ else
246
+ switch data_type
247
+ when "json"
248
+ # Parse json for requests of that type.
249
+ ref.load_json i, response
250
+ when "html"
251
+ else
252
+ # Parse html by default or for requests of that specific type.
253
+ ref.load_html i, response
254
+ , data_type
255
+ else
256
+ # Notify console if we couldn't load a page due to a missing URL.
257
+ @log "ERROR --> Couldn't load page because there was no url" unless @options.pages[i]['last_request_empty']
258
+
259
+ # For empty requests, insert notification text into page identified by `i`
260
+ load_empty: (i) ->
261
+ @log "load_empty(#{i})"
262
+ empty = Mustache.to_html(@tpl('empty'))
263
+ @load_html(i, empty)
264
+ @loading.hide()
265
+ $('li.empty').css('width',$('.ampn').first().width())
266
+ $('li.empty a').click =>
267
+ @goto(@options.pages.length-2)
268
+
269
+ # Load html content returned as `response` by XHR request into page identified by `i`
270
+ load_html: (i, response) ->
271
+ @log "load(#{i}) html"
272
+ selector = "##{@options.id} .pages .page:nth-child(#{(i+1)})"
273
+ selector += " ul" if @options.pages[i]['panels'] || @searching
274
+ $(selector).html(response).show()
275
+ @panels(i)
276
+
277
+ # Parse `response` as json data and build list-items for each element contained therein.
278
+ # This method assumes page identified by `i` contains an ample_panels instance.
279
+ load_json: (i, response) ->
280
+ @log "load(#{i}) json"
281
+ panels_loaded = if @options.pages[i]['panel_selector'] then true else false
282
+ ref = this
283
+ selector = "##{@options.id} .pages .page:nth-child(#{(i+1)}) ul"
284
+ $.each response, (j,el) ->
285
+ link = ref.build(el)
286
+ li = $('<li class="file"></li>').append(link)
287
+ if panels_loaded
288
+ $(selector).amplePanels('append', li)
289
+ else
290
+ $(selector).append(li)
291
+ ref.load_img(li.find('a'), el.sizes.tn)
292
+ $(selector).show()
293
+ @panels(i) unless panels_loaded
294
+ if @reloading
295
+ @reloading = false
296
+ @controls()
297
+
298
+ # Parse `response` from search results as json data.
299
+ load_results: (response) ->
300
+ @log "load_results()"
301
+ i = @options.pages.length - 1
302
+
303
+ if response.length > 0
304
+ # Build list-item for each item returned from search query.
305
+ $.each response, (j,el) =>
306
+ link = @build(el)
307
+ li = $('<li class="file"></li>').append(link)
308
+ $("#asset-results ul").amplePanels('append', li)
309
+ @load_img(link, el.sizes.tn)
310
+ else
311
+ # No results were returned, inject no-results verbiage.
312
+ no_results = Mustache.to_html(@tpl('no_results'))
313
+ @load_html(i, no_results)
314
+ @loading.hide()
315
+ $('li.empty').css('width',$('.ampn').first().width())
316
+
317
+ @options.pages[i]['panel_selector'] = "#asset-results ul"
318
+ @active_panel = $(@options.pages[i]['panel_selector'])
319
+ @searching = false
320
+ @loading.hide()
321
+ @controls()
322
+
323
+ # Builds each asset instance with proper attributes.
324
+ build: (el) ->
325
+ ref = this
326
+ show_url = Mustache.to_html @options.show_url, { id: el.id }
327
+ link = $("<a href=\"#{@options.base_url}#{show_url}\" draggable=\"true\"></a>")
328
+ .attr('id',"file-#{el.id}")
329
+ .attr('data-uid',"#{el.uid}")
330
+ .attr('data-filename',"#{el.filename}")
331
+ .attr('data-gravity', el.gravity)
332
+ .addClass('draggable')
333
+ if el.document == 'true'
334
+ link.addClass('document')
335
+ else
336
+ link.attr('data-orientation',el.orientation)
337
+ link.attr('data-size',el.size)
338
+ link.click ->
339
+ # Open a modal window on any asset instance's click event.
340
+ ref.modal_open(el)
341
+ false
342
+ link
343
+
344
+ # Opens modal window instance for asset detail.
345
+ modal_open: (data) ->
346
+ @modal_active = true
347
+ if data.document == 'true'
348
+ # Asset is a document, so lets instantiate PDFObject for viewing inline.
349
+ html = Mustache.to_html @tpl('pdf'),
350
+ filename: data.uid,
351
+ id: data.id,
352
+ mime_type: data.mime_type
353
+ $.facebox("<div class=\"asset-detail\">#{html}</div>")
354
+ myPDF = new PDFObject(
355
+ url: data.url
356
+ pdfOpenParams:
357
+ view: "Fit"
358
+ ).embed("pdf")
359
+ else
360
+ # Asset is an image, lets display it inline, according to its orientation.
361
+ geometry = if data.orientation == 'portrait' then 'x300>' else '480x>'
362
+ url = "#{@options.base_url}#{@options.thumb_url}/#{geometry}?uid=#{data.uid}"
363
+ delete_url = Mustache.to_html @options.show_url, { id: data.id }
364
+ gravity_url = Mustache.to_html @options.gravity_url, { id: data.id }
365
+ gravity = $("a[data-uid='#{data.uid}']").first().attr('data-gravity')
366
+ keywords = ""
367
+ html = Mustache.to_html @tpl('show'),
368
+ filename: data.filename,
369
+ size: data.size,
370
+ mime_type: data.mime_type,
371
+ keywords: keywords,
372
+ src: url,
373
+ orientation: data.orientation,
374
+ id: data.id,
375
+ uid: data.uid,
376
+ gravity: gravity,
377
+ delete_url: "#{@options.base_url}#{delete_url}",
378
+ gravity_url: "#{@options.base_url}#{gravity_url}"
379
+ $.facebox("<div class=\"asset-detail\">#{html}</div>")
380
+ # Update the asset timestamp.
381
+ @touch(data)
382
+
383
+ # Create new image element from `src`, insert into `el` and fadeIn opacity.
384
+ load_img: (el,src) ->
385
+ img = new Image()
386
+ $(img).load(->
387
+ $(this).hide()
388
+ $(el).html this
389
+ $(this).fadeIn()
390
+ ).attr src: src
391
+
392
+ # Generates the next URL for paginated record sets.
393
+ next_page_url: (i) ->
394
+ @options.pages[i]['pages_loaded'] = 0 unless @options.pages[i]['pages_loaded']
395
+ @options.pages[i]['pages_loaded'] += 1
396
+ "#{@options.pages[i]['url']}?page=#{@options.pages[i]['pages_loaded']}"
397
+
398
+ # By touching asset records, we update the timestamp value which ensures our recently
399
+ # viewed tab contains accurate results. Called from `modal_open()`
400
+ touch: (el) ->
401
+ @log "touch()"
402
+ touch_url = Mustache.to_html @options.touch_url, { id: el.id }
403
+ $.post "#{@options.base_url}#{touch_url}"
404
+
405
+ # Instantiate an amplePanels instance within page `i` if `@options.pages[i]['panels']` is true.
406
+ panels: (i) ->
407
+ ref = this
408
+ if @options.pages[i]['panels']
409
+ @log "panels(#{i})"
410
+ el = "##{@options.id} .pages .page:nth-child(#{(i+1)}) ul"
411
+ @options.pages[i]['panel_selector'] = el
412
+ @active_panel = el
413
+ @options.pages[i][''] = $(el).attr('id',"#{@options.pages[i]['id']}-panel")
414
+ $(el).parent().addClass('panels')
415
+ @controls()
416
+ $(el).amplePanels(@options.pages_options)
417
+ .bind 'slide_horizontal', (e,d,dir) ->
418
+ ref.load(i) if dir == 'next'
419
+
420
+ # Disable all panels, preventing any loading or key-driven actions from taking place within any amplePanels instance.
421
+ disable_panels: ->
422
+ @log "disable_panels()"
423
+ ref = this
424
+ @controls(false)
425
+ $.each @options.pages, (i,el) ->
426
+ $(ref.options.pages[i]['panel_selector']).amplePanels('disable') if ref.options.pages[i]['panel_selector']
427
+
428
+ # Enable panels instance contained with page identified by `i`.
429
+ # This allows key-events and previous/next actions to be executed.
430
+ enable_panel: (i) ->
431
+ @log "enable_panel(#{i})"
432
+ if @options.pages[i]['panel_selector']
433
+ @active_panel = @options.pages[i]['panel_selector']
434
+ $(@options.pages[i]['panel_selector']).amplePanels('enable')
435
+ @controls()
436
+
437
+ # Toggles display of left/right arrows which control amplePanels paging event, determined by `display`.
438
+ controls: (display=true) ->
439
+ @log "controls(#{display})"
440
+ display = false if $(@active_panel).find('li').length < @options.pages_options.per_page
441
+ switch display
442
+ when true
443
+ $('nav.controls').show()
444
+ when false
445
+ $('nav.controls').hide()
446
+
447
+ # Evaluates whether URL attached to page `i` has been loaded yet. Returns `boolean`.
448
+ already_loaded: (i) ->
449
+ typeof @options.pages[i]['loaded'] == 'boolean' && @options.pages[i]['loaded']
450
+
451
+ # Removes the asset from drop-target identified by `el`.
452
+ remove: (el) ->
453
+ parent = $(el).parent()
454
+ parent.find('.droppable').empty().html('<span>Drag Asset Here</span>')
455
+ parent.find('input').val('')
456
+ $(el).hide()
457
+
458
+ # Called upon successful DELETE request for a specific asset. Removes any instances of
459
+ # asset identified by `id`, closes the modal window and reloads the first tab.
460
+ delete: (id) ->
461
+ @log "delete(#{id})"
462
+ $("a#file-#{id}").parent().remove()
463
+ $(document).trigger('close.facebox')
464
+ @reload(0)
465
+ false
466
+
467
+ # Upon collapse, we disable panels.
468
+ collapse: ->
469
+ $("##{@options.id}-handle").css('bottom',-35).show().animate({'bottom': 0},'fast')
470
+ @disable_panels()
471
+
472
+ # Expands the asset toolbar and reenables the currently loaded tab.
473
+ expand: ->
474
+ @goto(@current)
475
+
476
+ # Setup all associated events.
477
+ events: ->
478
+ @modal_events()
479
+ @global_events()
480
+ @field_events()
481
+ @drop_events()
482
+ @drag_events()
483
+ @reload_events()
484
+ @resize_events()
485
+ @key_events()
486
+ @tab_events()
487
+ ref = this
488
+ # Collapse toolbar
489
+ $("##{@options.id} a.collapse").live 'click', =>
490
+ @toggle()
491
+ # Reload the first tab following a successful upload.
492
+ $('body').bind 'ample_uploadify.complete', =>
493
+ @reload(0)
494
+ # Bind event to succesful deletion of an asset.
495
+ $("a.asset-delete").live 'ajax:success', ->
496
+ id = parseInt $(this).attr('data-id')
497
+ ref.delete(id)
498
+ # Bind live event to any asset-remove element.
499
+ $("a.asset-remove").live 'click', ->
500
+ ref.remove(this)
501
+ false
502
+ # Bind `toggle()` method to toolbar handle.
503
+ $("##{@options.id}-handle").live 'click', =>
504
+ @toggle()
505
+ false
506
+
507
+ # Bind events for global left/right arrows to currently active panel's `previous()` and `next()` methods.
508
+ global_events: ->
509
+ $('a.global.next').click =>
510
+ $(@active_panel).amplePanels('next')
511
+ $('a.global.previous').click =>
512
+ $(@active_panel).amplePanels('previous')
513
+
514
+ # TODO: kill key events during drag?
515
+ drag_events: ->
516
+ @log "drag_events()"
517
+
518
+ # Open modal window when clicking an asset contained with a drop-target.
519
+ drop_events: ->
520
+ ref = this
521
+ $('.asset-drop .droppable a').live 'click', ->
522
+ id = $(this).attr("href")
523
+ $.get $(this).attr("href"), (response) ->
524
+ ref.modal_open(response)
525
+ , 'json'
526
+ false
527
+
528
+ # Toggle the active state of key_events when user focuses / blurs on textareas or input fields.
529
+ field_events: ->
530
+ @log "field_events()"
531
+ $('textarea, input').live 'blur', =>
532
+ @keys_enabled = true
533
+ $('textarea, input').live 'focus', =>
534
+ @keys_enabled = false
535
+
536
+ # Bind `reload()` method to assets-reload button.
537
+ reload_events: ->
538
+ @log "reload_events()"
539
+ reload = $('<a href="#" class="assets-reload"><span></span></a>')
540
+ reload.appendTo('.asset-refresh').click (e) =>
541
+ @reload(@current)
542
+
543
+ # Builds the markup for an asset dropped into a textarea.
544
+ resize_events: ->
545
+ $('.asset-resize').live 'click', =>
546
+ constraints = $('#asset-constraints').val()
547
+ uid = $('#asset-uid').val()
548
+ width = $('#asset-width').val()
549
+ height = $('#asset-height').val()
550
+ alt = $('#asset-alt').val()
551
+ geometry = "#{width}x#{height}#{constraints}"
552
+ if constraints == '#' && (width == '' || height == '')
553
+ alert 'Can\'t resize image using this geometry. Please select another option or supply a value for both width and height.'
554
+ else
555
+ url = encodeURI "#{@options.base_url}#{@options.thumb_url}/#{geometry}?uid=#{uid}"
556
+ url = url.replace('#','%23')
557
+ textile = "!#{url}(#{alt})!"
558
+ html = "<img src=\"#{url}\" alt=\"#{alt}\" />"
559
+ $(@target_textarea).insertAtCaret (if $(@target_textarea).hasClass('textile') then textile else html)
560
+ $(document).trigger('close.facebox')
561
+
562
+ # Toggles params when modal window is opened or closed.
563
+ modal_events: ->
564
+ @modal_active = false
565
+ $(document).bind 'afterClose.facebox', =>
566
+ @keys_enabled = true
567
+ @modal_active = false
568
+ $(document).bind 'loading.facebox', =>
569
+ @keys_enabled = false
570
+ @modal_active = true
571
+
572
+ # Setup amplePanels instance for search results and bind search field to methods that
573
+ # execute request and parse response.
574
+ search: ->
575
+ @log 'search()'
576
+ search_url = "#{@options.base_url}#{@options.search_url}"
577
+ i = ($("##{@options.id} .pages .page").length - 1)
578
+ ref = this
579
+ $('#asset-results ul').attr('id','assets-result-list').amplePanels(@options.pages_options)
580
+ @options.pages[i] = { loaded: true }
581
+ $('#asset-search').bind 'change', ->
582
+ $("#asset-results ul").amplePanels('empty')
583
+ ref.loading.show()
584
+ ref.controls(false)
585
+ ref.show(i)
586
+ ref.activate(i)
587
+ ref.searching = true
588
+ $('.asset-results').show()
589
+ $.post search_url, $(this).serialize(), (response) ->
590
+ ref.load_results(response)
591
+ , 'json'
592
+
593
+ # Bind events to tabs.
594
+ tab_events: ->
595
+ tabs = $("##{@options.id} a.tab")
596
+ ref = this
597
+ $.each tabs, (idx, el) ->
598
+ $(this).addClass('on') if idx == 0
599
+ $(el).click ->
600
+ ref.goto(idx)
601
+ false
602
+
603
+ # Controls all user keyboard events. Binds as neccesary and
604
+ # prevents interaction when key functions are disabled.
605
+ key_events: ->
606
+ ref = this
607
+ previous = 37
608
+ next = 39
609
+ up = 38
610
+ down = 40
611
+ escape = 27
612
+
613
+ # Why does this need to be on keyup?
614
+ $(document).keyup (e) =>
615
+ return unless @keys_enabled
616
+ switch e.keyCode
617
+ when escape
618
+ @toggle() unless @modal_active
619
+ e.stopPropagation();
620
+
621
+ # Keydown events.
622
+ $(document).keydown (e) =>
623
+ return unless @keys_enabled
624
+ if @active_panel
625
+ switch e.keyCode
626
+ when previous
627
+ $(@active_panel).amplePanels('previous')
628
+ when next
629
+ $(@active_panel).amplePanels('next')
630
+ when up
631
+ @previous()
632
+ when down
633
+ @next()
634
+ e.stopPropagation();
635
+
636
+ # Returns Mustache template for template defined by `view`
637
+ tpl: (view) ->
638
+ @tpls()[view]
639
+
640
+ # Returns object containing all Mustache templates.
641
+ tpls: ->
642
+ # Layout returns the HTML structure of the main asset toolbar.
643
+ layout: '
644
+ <div id="{{ id }}"><div class="background">
645
+ <a href="#" class="collapse">Close</a>
646
+ <div class="container">
647
+ <div id="{{ id }}-tabs" class="tabs">
648
+ <div class="asset-refresh"></div>
649
+ <div class="asset-search">
650
+ <input type="text" id="asset-search" name="q" placeholder="Enter keywords..." />
651
+ <label for="asset-search">Search</label>
652
+ </div>
653
+ {{{ tabs }}}
654
+ <a href="#" data-role="asset-search-results" class="tab asset-results">Results</a>
655
+ <span class="asset-loading"></span>
656
+ </div>
657
+ <div id="{{ id }}-pages" class="pages">
658
+ {{{ pages }}}
659
+ <div id="asset-results" class="page panels">
660
+ <ul></ul>
661
+ </div>
662
+ </div>
663
+ <nav class="controls">
664
+ <a href="#" class="global previous">Previous</a>
665
+ <a href="#" class="global next">Next</a>
666
+ </nav>
667
+ </div></div>
668
+ </div>'
669
+ # Handle returns HTML for the asset toolbar toggle handle.
670
+ handle: '<a href="#" id="{{ id }}-handle" class="handle">{{ title }}</a>'
671
+ # Tab returns HTML for each tab instance.
672
+ tab: '<a href="#" data-role="{{ id }}" class="tab {{ classes }}">{{ title }}</a>'
673
+ # Page returns generic HTML structure for each page.
674
+ page: '
675
+ <div id="{{ id }}" class="page">
676
+ <ul></ul>
677
+ </div>'
678
+ # Show represents the HTML used within the modal window detail view for non-document assets.
679
+ show: '
680
+ <div class="asset-detail">
681
+ <div class="asset-media {{ orientation }}">
682
+ <img src="{{ src }}" />
683
+ </div>
684
+ <input id="file_attachment_gravity" name="file[attachment_gravity]" type="hidden" value="{{ gravity }}">
685
+ <div id="asset-gravity-handle" style="display:none" data-role="gravity"></div>
686
+ <script type="text/javascript">
687
+ $(document).ready(function() {
688
+ new AmpleAssetsGravity({url: "{{ gravity_url }}", uid: "{{ uid }}"});
689
+ });
690
+ </script>
691
+ <div id="asset-gravity-notification">Asset updated successfully.</div>
692
+ <a href="{{ delete_url }}" class="asset-delete" data-id="{{ id }}" data-method="delete" data-confirm="Are you sure?" data-remote="true">Delete This Asset?</a>
693
+ <h3>{{ filename }}</h3><hr />
694
+ <ul>
695
+ <li>Original Dimensions: <strong>{{ size }}</strong></li>
696
+ <li>MimeType: <strong>{{ mime_type }}</strong></li>
697
+ <li>Orientation: <strong>{{ orientation }}</strong></li>
698
+ </ul>
699
+ <p>{{ keywords }}</p>
700
+ </div>'
701
+ # PDF represents the HTML used within the modal window detail view for document assets.
702
+ pdf: '
703
+ <div class="asset-detail">
704
+ <div id="pdf" class="asset-media"></div>
705
+ <a href="{{ delete_url }}" class="asset-delete" data-id="{{ id }}" data-method="delete" data-confirm="Are you sure?" data-remote="true">Delete This Asset?</a>
706
+ <h3>{{ filename }}</h3><hr />
707
+ <ul>
708
+ <li>MimeType: <strong>{{ mime_type }}</strong></li>
709
+ </ul>
710
+ <p>{{ keywords }}</p>
711
+ </div>'
712
+ # There's no content within this panels instance...
713
+ empty: '<li class="empty">Oops. There\'s nothing here. You should <a href="#">upload something</a>.</li>'
714
+ # Your search query returned an empty result set...
715
+ no_results: '<li class="empty">Sorry. Your search returned zero results.</li>'
716
+ # Drop returns HTML used in the modal window resize view. This is used when dropping an asset onto a textarea.
717
+ drop: '
718
+ <div class="asset-selection">
719
+ <div class="asset-media {{ orientation }}">
720
+ <img src="{{ src }}" />
721
+ </div>
722
+ <div class="asset-dimensions">
723
+ <p><label>Image Dimensions</label> ({{ dimensions }}, {{ orientation }})</p>
724
+ <p><select id="asset-constraints" name="asset-constraints">
725
+ <option value="">Maintain aspect ratio</option>
726
+ <option value="!">Force resize, don\'t maintain aspect ratio</option>
727
+ <option value=">">Resize only if image larger than this</option>
728
+ <option value="<">Resize only if image smaller than this</option>
729
+ <option value="^">Resize to minimum x,y, maintain aspect ratio</option>
730
+ <option value="#">Resize, crop if necessary to maintain aspect ratio</option>
731
+ </select></p>
732
+ <p><input type="hidden" id="asset-dimensions-target" name="asset-dimensions-target" value="" />
733
+ <input type="hidden" id="asset-uid" name="asset-uid" value="{{ uid }}" />
734
+ <input type="text" id="asset-width" name="asset-width" value="480" /> <span>x</span>
735
+ <input type="text" id="asset-height" name="asset-height" value="" /><br />
736
+ <input type="text" id="asset-alt" name="asset-alt" value="" placeholder="Alt text" />
737
+ <input type="submit" id="asset-resize" name="asset-resize" class="asset-resize" value="Insert" /></p>
738
+
739
+ </div>
740
+ <hr class="space" />
741
+ </div>'
742
+
743
+
744
+ # Extend draggable to elements added to the DOM after page load.
745
+ jQuery.fn.liveDraggable = (opts) ->
746
+ @live "mouseover", ->
747
+ $(this).data("init", true).draggable opts unless $(this).data("init")
748
+
749
+ # Insert `value` at the cursor position of the currently focused textarea or input field.
750
+ jQuery.fn.insertAtCaret = (value) ->
751
+ @each (i) ->
752
+ if document.selection
753
+ @focus()
754
+ sel = document.selection.createRange()
755
+ sel.text = value
756
+ @focus()
757
+ else if @selectionStart or @selectionStart is "0"
758
+ startPos = @selectionStart
759
+ endPos = @selectionEnd
760
+ scrollTop = @scrollTop
761
+ @value = @value.substring(0, startPos) + value + @value.substring(endPos, @value.length)
762
+ @focus()
763
+ @selectionStart = startPos + value.length
764
+ @selectionEnd = startPos + value.length
765
+ @scrollTop = scrollTop
766
+ else
767
+ @value += value
768
+ @focus()
769
+