rails-uploader 0.1.0 → 0.1.2

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.
data/README.md CHANGED
@@ -75,6 +75,29 @@ Find asset by foreign key or guid:
75
75
  @user.fileupload_asset(:picture)
76
76
  ```
77
77
 
78
+ ### Mongoid
79
+
80
+ No parent asset model is required, one only has to `include Uploader::Asset::Mongoid` into the
81
+ model that should act like an asset:
82
+
83
+ ``` ruby
84
+ class Picture
85
+ include Mongoid::Document
86
+ include Uploader::Asset::Mongoid
87
+
88
+ belongs_to :user
89
+ end
90
+
91
+ class User
92
+ include Mongoid::Document
93
+ include Uploader::Fileuploads
94
+
95
+ has_one :picture, :as => :assetable
96
+
97
+ fileuploads :picture
98
+ end
99
+ ```
100
+
78
101
  ### Include assets
79
102
 
80
103
  Javascripts:
@@ -113,6 +136,16 @@ or FormBuilder:
113
136
  <%= f.input :pictures, :as => :uploader, :input_html => {:sortable => true} %>
114
137
  ```
115
138
 
139
+ #### Confirming deletions
140
+
141
+ This is only working in Formtastic and FormBuilder:
142
+
143
+ ``` ruby
144
+ # formtastic
145
+ <%= f.input :picture, :as => :uploader, :confirm_delete => true %>
146
+ # the i18n lookup key would be en.formtastic.delete_confirmations.picture
147
+ ```
148
+
116
149
  ## Contributing
117
150
 
118
151
  1. Fork it
@@ -1,9 +1,8 @@
1
+ //= require uploader/jquery.ui.widget
1
2
  //= require uploader/locales/en
2
3
  //= require uploader/tmpl.min
3
4
  //= require uploader/load-image.min
4
- //= require uploader/jquery.ui.widget
5
5
  //= require uploader/jquery.iframe-transport
6
6
  //= require uploader/jquery.fileupload
7
7
  //= require uploader/jquery.fileupload-ui
8
- //= require uploader/init
9
8
 
@@ -4,21 +4,20 @@
4
4
  <div class="uploader-files"></div>
5
5
 
6
6
  <div class="uploader-dnd-hints">
7
- <button class="uploader-button gray fileinput-button">
7
+ <span class="uploader-button gray fileinput-button">
8
8
  <span><%= I18n.t('uploader.button') %></span>
9
9
  <%= fields_for field.object do |f| -%>
10
10
  <%= f.fields_for field.method_name, field.klass.new do |m| -%>
11
11
  <%= m.file_field(:data, field.input_html) %>
12
12
  <% end -%>
13
13
  <% end -%>
14
- </button>
14
+ </span>
15
15
 
16
16
  <div class="uploader-dnd-hint">
17
17
  <%= I18n.t('uploader.or') %><span><%= I18n.t('uploader.drop') %></span>
18
18
  </div>
19
19
  </div>
20
20
 
21
- <%= render :partial => "uploader/#{field.theme}/init", :locals => {:field => field} %>
22
21
  <%= render :partial => "uploader/#{field.theme}/upload", :locals => {:field => field} %>
23
22
  <%= render :partial => "uploader/#{field.theme}/download", :locals => {:field => field} %>
24
23
  <%= render :partial => "uploader/#{field.theme}/sortable", :locals => {:field => field} if field.sortable? %>
@@ -28,7 +27,7 @@
28
27
  var uploader, container;
29
28
  container = $("#<%= field.id %> div.uploader-files");
30
29
 
31
- $('#<%= field.id %> input[type="file"]').bind("init.uploader", function(e){
30
+ $('#<%= field.id %> input[type="file"]').each(function(){
32
31
  $(this).fileupload({
33
32
  dataType: 'json',
34
33
  dropZone: $("#<%= field.id %>"),
@@ -3,3 +3,4 @@ en:
3
3
  button: "Choose file"
4
4
  drop: "Drag&Drop files here"
5
5
  or: "or"
6
+ confirm_delete: "Are you sure?"
@@ -1,42 +1,85 @@
1
1
  module Uploader
2
2
  module Asset
3
- # Save asset
4
- # Usage:
5
- #
6
- # class Asset < ActiveRecord::Base
7
- # include Uploader::Asset
8
- #
9
- # def uploader_create(params, request = nil)
10
- # self.user = request.env['warden'].user
11
- # super
12
- # end
13
- # end
14
- #
15
- def uploader_create(params, request = nil)
16
- self.guid = params[:guid]
17
- self.assetable_type = params[:assetable_type]
18
- self.assetable_id = params[:assetable_id]
19
- save
3
+ def self.included(base)
4
+ base.send(:include, Uploader::Asset::AssetProcessor)
20
5
  end
21
-
22
- # Destroy asset
23
- # Usage (cancan example):
24
- #
25
- # class Asset < ActiveRecord::Base
26
- # include Uploader::Asset
27
- #
28
- # def uploader_destroy(params, request = nil)
29
- # ability = Ability.new(request.env['warden'].user)
30
- # if ability.can? :delete, self
31
- # super
32
- # else
33
- # errors.add(:id, :access_denied)
34
- # end
35
- # end
36
- # end
37
- #
38
- def uploader_destroy(params, request)
39
- destroy
6
+
7
+ module Mongoid
8
+ def self.included(base)
9
+ base.send(:include, Uploader::Asset::AssetProcessor)
10
+
11
+ base.instance_eval do
12
+ field :guid, type: String
13
+ end
14
+ end
15
+
16
+ def as_json(options = {})
17
+ json_data = super
18
+ json_data['filename'] = File.basename(data.path)
19
+ json_data['size'] = data.file.size
20
+ json_data['id'] = json_data['_id']
21
+
22
+ if data.respond_to?(:thumb)
23
+ json_data['thumb_url'] = data.thumb.url
24
+ end
25
+
26
+ json_data
27
+ end
28
+
29
+ def assetable_id_format(assetable_id)
30
+ Moped::BSON::ObjectId.from_string(assetable_id)
31
+ end
32
+
33
+ class << self
34
+ def include_root_in_json
35
+ false
36
+ end
37
+ end
38
+ end
39
+
40
+ module AssetProcessor
41
+ # Save asset
42
+ # Usage:
43
+ #
44
+ # class Asset < ActiveRecord::Base
45
+ # include Uploader::Asset
46
+ #
47
+ # def uploader_create(params, request = nil)
48
+ # self.user = request.env['warden'].user
49
+ # super
50
+ # end
51
+ # end
52
+ #
53
+ def uploader_create(params, request = nil)
54
+ self.guid = params[:guid]
55
+ self.assetable_type = params[:assetable_type]
56
+ self.assetable_id = assetable_id_format(params[:assetable_id]) if params[:assetable_id]
57
+ save
58
+ end
59
+
60
+ # Destroy asset
61
+ # Usage (cancan example):
62
+ #
63
+ # class Asset < ActiveRecord::Base
64
+ # include Uploader::Asset
65
+ #
66
+ # def uploader_destroy(params, request = nil)
67
+ # ability = Ability.new(request.env['warden'].user)
68
+ # if ability.can? :delete, self
69
+ # super
70
+ # else
71
+ # errors.add(:id, :access_denied)
72
+ # end
73
+ # end
74
+ # end
75
+ #
76
+ def uploader_destroy(params, request)
77
+ destroy
78
+ end
79
+ end
80
+
81
+ def assetable_id_format(assetable_id)
82
+ assetable_id
40
83
  end
41
84
  end
42
85
  end
@@ -3,7 +3,17 @@ module Uploader
3
3
  def self.included(base)
4
4
  base.send :extend, SingletonMethods
5
5
  end
6
-
6
+
7
+ module Mongoid
8
+ def self.included(base)
9
+ base.send :include, Uploader::Fileuploads
10
+ end
11
+
12
+ def self.include_root_in_json
13
+ false
14
+ end
15
+ end
16
+
7
17
  module SingletonMethods
8
18
  # Join ActiveRecord object with uploaded file
9
19
  # Usage:
@@ -77,7 +87,11 @@ module Uploader
77
87
 
78
88
  def fileupload_multiple?(method)
79
89
  association = self.class.reflect_on_association(method.to_sym)
80
- !!(association && association.collection?)
90
+
91
+ # many? for Mongoid, :collection? for AR
92
+ method = association.respond_to?(:many?) ? :many? : :collection?
93
+
94
+ !!(association && association.send(method))
81
95
  end
82
96
 
83
97
  # Find or build new asset object
@@ -13,7 +13,7 @@ module Uploader
13
13
  #
14
14
  def initialize(object_name, method_name, template, options = {}) #:nodoc:
15
15
  options = { :object_name => object_name, :method_name => method_name }.merge(options)
16
-
16
+
17
17
  @template, @options = template, options.dup
18
18
 
19
19
  @theme = (@options.delete(:theme) || "default")
@@ -9,4 +9,11 @@ class UploaderInput
9
9
  builder.uploader_field(method, input_html_options)
10
10
  end
11
11
  end
12
+
13
+ def input_html_options
14
+ data = super
15
+ data[:confirm_delete] = options[:confirm_delete]
16
+ data[:confirm_message] = localized_string(method, method, :delete_confirmation) || I18n.t('uploader.confirm_delete')
17
+ data
18
+ end
12
19
  end
@@ -1,3 +1,3 @@
1
1
  module Uploader
2
- VERSION = "0.1.0".freeze
2
+ VERSION = "0.1.2".freeze
3
3
  end
@@ -6,3 +6,4 @@ FactoryGirl.define do
6
6
  content "MyText"
7
7
  end
8
8
  end
9
+
@@ -0,0 +1,17 @@
1
+ # Tell Mongoid which environment this configuration is for.
2
+ test:
3
+ # This starts the session configuration settings. You may have as
4
+ # many sessions as you like, but you must have at least 1 named
5
+ # 'default'.
6
+ sessions:
7
+ # Define the default session.
8
+ default:
9
+ # A session can have any number of hosts. Usually 1 for a single
10
+ # server setup, and at least 3 for a replica set. Hosts must be
11
+ # an array of host:port pairs. This session is single server.
12
+ hosts:
13
+ - localhost:27017
14
+ # Define the default database name.
15
+ database: rails_uploader_test
16
+ options:
17
+ include_root_in_json: false
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ class MongoidArticle
4
+ include Mongoid::Document
5
+ include Uploader::Fileuploads
6
+
7
+ has_one :mongoid_picture, :as => :assetable
8
+
9
+ fileuploads :mongoid_picture
10
+ end
11
+
12
+ class MongoidPicture
13
+ include Mongoid::Document
14
+ include Uploader::Asset::Mongoid
15
+
16
+ belongs_to :assetable, polymorphic: true
17
+ end
18
+
19
+ describe Uploader::Asset::Mongoid do
20
+ before do
21
+ @guid = 'guid'
22
+ @picture = MongoidPicture.create!(:guid => @guid, :assetable_type => 'MongoidArticle')
23
+ end
24
+
25
+ it 'should find asset by guid' do
26
+ asset = MongoidArticle.fileupload_find("mongoid_picture", @picture.guid)
27
+ asset.should == @picture
28
+ end
29
+
30
+ it "should update asset target_id by guid" do
31
+ MongoidArticle.fileupload_update(1000, @picture.guid, :mongoid_picture)
32
+ @picture.reload
33
+ @picture.assetable_id.should == 1000
34
+ @picture.guid.should be_nil
35
+ end
36
+
37
+ after do
38
+ MongoidPicture.destroy_all
39
+ end
40
+ end
@@ -29,6 +29,10 @@ end
29
29
  # Load support files
30
30
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
31
31
 
32
+ # Mongoid
33
+ require 'mongoid'
34
+ Mongoid.load!('spec/mongoid.yml')
35
+
32
36
  RSpec.configure do |config|
33
37
  # Remove this line if you don't want RSpec's should and should_not
34
38
  # methods or matchers
@@ -39,14 +43,17 @@ RSpec.configure do |config|
39
43
  config.mock_with :rspec
40
44
 
41
45
  config.before(:suite) do
42
- DatabaseCleaner.strategy = :truncation
46
+ DatabaseCleaner[:active_record].strategy = :truncation
47
+ DatabaseCleaner[:mongoid].strategy = :truncation
43
48
  end
44
49
 
45
50
  config.before(:all) do
46
- DatabaseCleaner.start
51
+ DatabaseCleaner[:active_record].start
52
+ DatabaseCleaner[:mongoid].start
47
53
  end
48
54
 
49
55
  config.after(:all) do
50
- DatabaseCleaner.clean
56
+ DatabaseCleaner[:active_record].clean
57
+ DatabaseCleaner[:mongoid].clean
51
58
  end
52
59
  end
@@ -1,5 +1,5 @@
1
1
  /*
2
- * jQuery File Upload File Processing Plugin 1.2.1
2
+ * jQuery File Upload File Processing Plugin 1.2.3
3
3
  * https://github.com/blueimp/jQuery-File-Upload
4
4
  *
5
5
  * Copyright 2012, Sebastian Tschan
@@ -62,9 +62,13 @@
62
62
  // fileupload widget (via file input selection, drag & drop or add
63
63
  // API call). See the basic file upload widget for more information:
64
64
  add: function (e, data) {
65
- $(this).fileupload('process', data).done(function () {
66
- data.submit();
67
- });
65
+ if (data.autoUpload || (data.autoUpload !== false &&
66
+ ($(this).data('blueimp-fileupload') ||
67
+ $(this).data('fileupload')).options.autoUpload)) {
68
+ $(this).fileupload('process', data).done(function () {
69
+ data.submit();
70
+ });
71
+ }
68
72
  }
69
73
  },
70
74
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * jQuery File Upload User Interface Plugin 7.3
2
+ * jQuery File Upload User Interface Plugin 7.4.4
3
3
  * https://github.com/blueimp/jQuery-File-Upload
4
4
  *
5
5
  * Copyright 2010, Sebastian Tschan
@@ -263,7 +263,7 @@
263
263
  // Callback for upload progress events:
264
264
  progress: function (e, data) {
265
265
  if (data.context) {
266
- var progress = parseInt(data.loaded / data.total * 100, 10);
266
+ var progress = Math.floor(data.loaded / data.total * 100);
267
267
  data.context.find('.progress')
268
268
  .attr('aria-valuenow', progress)
269
269
  .find('.bar').css(
@@ -275,7 +275,7 @@
275
275
  // Callback for global upload progress events:
276
276
  progressall: function (e, data) {
277
277
  var $this = $(this),
278
- progress = parseInt(data.loaded / data.total * 100, 10),
278
+ progress = Math.floor(data.loaded / data.total * 100),
279
279
  globalProgressNode = $this.find('.fileupload-progress'),
280
280
  extendedProgressNode = globalProgressNode
281
281
  .find('.progress-extended');
@@ -328,15 +328,16 @@
328
328
  var that = $(this).data('blueimp-fileupload') ||
329
329
  $(this).data('fileupload');
330
330
  if (data.url) {
331
- $.ajax(data);
332
- that._adjustMaxNumberOfFiles(1);
331
+ $.ajax(data).done(function () {
332
+ that._transition(data.context).done(
333
+ function () {
334
+ $(this).remove();
335
+ that._adjustMaxNumberOfFiles(1);
336
+ that._trigger('destroyed', e, data);
337
+ }
338
+ );
339
+ });
333
340
  }
334
- that._transition(data.context).done(
335
- function () {
336
- $(this).remove();
337
- that._trigger('destroyed', e, data);
338
- }
339
- );
340
341
  }
341
342
  },
342
343
 
@@ -422,7 +423,7 @@
422
423
 
423
424
  _formatTime: function (seconds) {
424
425
  var date = new Date(seconds * 1000),
425
- days = parseInt(seconds / 86400, 10);
426
+ days = Math.floor(seconds / 86400);
426
427
  days = days ? days + 'd ' : '';
427
428
  return days +
428
429
  ('0' + date.getUTCHours()).slice(-2) + ':' +
@@ -519,6 +520,12 @@
519
520
  // so we have to resolve manually:
520
521
  dfd.resolveWith(node);
521
522
  }
523
+ node.on('remove', function () {
524
+ // If the element is removed before the
525
+ // transition finishes, transition events are
526
+ // not triggered, resolve manually:
527
+ dfd.resolveWith(node);
528
+ });
522
529
  },
523
530
  {
524
531
  maxWidth: options.previewMaxWidth,
@@ -595,8 +602,7 @@
595
602
  var button = $(e.currentTarget);
596
603
  this._trigger('destroy', e, $.extend({
597
604
  context: button.closest('.template-download'),
598
- type: 'DELETE',
599
- dataType: this.options.dataType
605
+ type: 'DELETE'
600
606
  }, button.data()));
601
607
  },
602
608
 
@@ -607,7 +613,7 @@
607
613
 
608
614
  _transition: function (node) {
609
615
  var dfd = $.Deferred();
610
- if ($.support.transition && node.hasClass('fade')) {
616
+ if ($.support.transition && node.hasClass('fade') && node.is(':visible')) {
611
617
  node.bind(
612
618
  $.support.transition.end,
613
619
  function (e) {
@@ -632,27 +638,28 @@
632
638
  this._on(fileUploadButtonBar.find('.start'), {
633
639
  click: function (e) {
634
640
  e.preventDefault();
635
- filesList.find('.start button').click();
641
+ filesList.find('.start').click();
636
642
  }
637
643
  });
638
644
  this._on(fileUploadButtonBar.find('.cancel'), {
639
645
  click: function (e) {
640
646
  e.preventDefault();
641
- filesList.find('.cancel button').click();
647
+ filesList.find('.cancel').click();
642
648
  }
643
649
  });
644
650
  this._on(fileUploadButtonBar.find('.delete'), {
645
651
  click: function (e) {
646
652
  e.preventDefault();
647
- filesList.find('.delete input:checked')
648
- .siblings('button').click();
653
+ filesList.find('.toggle:checked')
654
+ .closest('.template-download')
655
+ .find('.delete').click();
649
656
  fileUploadButtonBar.find('.toggle')
650
657
  .prop('checked', false);
651
658
  }
652
659
  });
653
660
  this._on(fileUploadButtonBar.find('.toggle'), {
654
661
  change: function (e) {
655
- filesList.find('.delete input').prop(
662
+ filesList.find('.toggle').prop(
656
663
  'checked',
657
664
  $(e.currentTarget).is(':checked')
658
665
  );
@@ -662,7 +669,8 @@
662
669
 
663
670
  _destroyButtonBarEventHandlers: function () {
664
671
  this._off(
665
- this.element.find('.fileupload-buttonbar button'),
672
+ this.element.find('.fileupload-buttonbar')
673
+ .find('.start, .cancel, .delete'),
666
674
  'click'
667
675
  );
668
676
  this._off(
@@ -674,9 +682,9 @@
674
682
  _initEventHandlers: function () {
675
683
  this._super();
676
684
  this._on(this.options.filesContainer, {
677
- 'click .start a': this._startHandler,
678
- 'click .cancel a': this._cancelHandler,
679
- 'click .delete a': this._deleteHandler
685
+ 'click .start': this._startHandler,
686
+ 'click .cancel': this._cancelHandler,
687
+ 'click .delete': this._deleteHandler
680
688
  });
681
689
  this._initButtonBarEventHandlers();
682
690
  },
@@ -1,5 +1,5 @@
1
1
  /*
2
- * jQuery File Upload Plugin 5.21.1
2
+ * jQuery File Upload Plugin 5.28.8
3
3
  * https://github.com/blueimp/jQuery-File-Upload
4
4
  *
5
5
  * Copyright 2010, Sebastian Tschan
@@ -33,21 +33,6 @@
33
33
  $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader);
34
34
  $.support.xhrFormDataFileUpload = !!window.FormData;
35
35
 
36
- // The form.elements propHook is added to filter serialized elements
37
- // to not include file inputs in jQuery 1.9.0.
38
- // This hooks directly into jQuery.fn.serializeArray.
39
- // For more info, see http://bugs.jquery.com/ticket/13306
40
- $.propHooks.elements = {
41
- get: function (form) {
42
- if ($.nodeName(form, 'form')) {
43
- return $.grep(form.elements, function (elem) {
44
- return !$.nodeName(elem, 'input') || elem.type !== 'file';
45
- });
46
- }
47
- return null;
48
- }
49
- };
50
-
51
36
  // The fileupload widget listens for change events on file input fields defined
52
37
  // via fileInput setting and paste or drop events of the given dropZone.
53
38
  // In addition to the default jQuery Widget methods, the fileupload widget
@@ -127,6 +112,8 @@
127
112
  progressInterval: 100,
128
113
  // Interval in milliseconds to calculate progress bitrate:
129
114
  bitrateInterval: 500,
115
+ // By default, uploads are started automatically when adding files:
116
+ autoUpload: true,
130
117
 
131
118
  // Additional form data to be sent along with the file uploads can be set
132
119
  // using this option, which accepts an array of objects with name and
@@ -151,7 +138,11 @@
151
138
  // handlers using jQuery's Deferred callbacks:
152
139
  // data.submit().done(func).fail(func).always(func);
153
140
  add: function (e, data) {
154
- data.submit();
141
+ if (data.autoUpload || (data.autoUpload !== false &&
142
+ ($(this).data('blueimp-fileupload') ||
143
+ $(this).data('fileupload')).options.autoUpload)) {
144
+ data.submit();
145
+ }
155
146
  },
156
147
 
157
148
  // Other callbacks:
@@ -224,7 +215,7 @@
224
215
  ],
225
216
 
226
217
  _BitrateTimer: function () {
227
- this.timestamp = +(new Date());
218
+ this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
228
219
  this.loaded = 0;
229
220
  this.bitrate = 0;
230
221
  this.getBitrate = function (now, loaded, interval) {
@@ -252,7 +243,7 @@
252
243
  if ($.isArray(options.formData)) {
253
244
  return options.formData;
254
245
  }
255
- if (options.formData) {
246
+ if ($.type(options.formData) === 'object') {
256
247
  formData = [];
257
248
  $.each(options.formData, function (name, value) {
258
249
  formData.push({name: name, value: value});
@@ -270,10 +261,35 @@
270
261
  return total;
271
262
  },
272
263
 
264
+ _initProgressObject: function (obj) {
265
+ var progress = {
266
+ loaded: 0,
267
+ total: 0,
268
+ bitrate: 0
269
+ };
270
+ if (obj._progress) {
271
+ $.extend(obj._progress, progress);
272
+ } else {
273
+ obj._progress = progress;
274
+ }
275
+ },
276
+
277
+ _initResponseObject: function (obj) {
278
+ var prop;
279
+ if (obj._response) {
280
+ for (prop in obj._response) {
281
+ if (obj._response.hasOwnProperty(prop)) {
282
+ delete obj._response[prop];
283
+ }
284
+ }
285
+ } else {
286
+ obj._response = {};
287
+ }
288
+ },
289
+
273
290
  _onProgress: function (e, data) {
274
291
  if (e.lengthComputable) {
275
- var now = +(new Date()),
276
- total,
292
+ var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
277
293
  loaded;
278
294
  if (data._time && data.progressInterval &&
279
295
  (now - data._time < data.progressInterval) &&
@@ -281,16 +297,19 @@
281
297
  return;
282
298
  }
283
299
  data._time = now;
284
- total = data.total || this._getTotal(data.files);
285
- loaded = parseInt(
286
- e.loaded / e.total * (data.chunkSize || total),
287
- 10
300
+ loaded = Math.floor(
301
+ e.loaded / e.total * (data.chunkSize || data._progress.total)
288
302
  ) + (data.uploadedBytes || 0);
289
- this._loaded += loaded - (data.loaded || data.uploadedBytes || 0);
290
- data.lengthComputable = true;
291
- data.loaded = loaded;
292
- data.total = total;
293
- data.bitrate = data._bitrateTimer.getBitrate(
303
+ // Add the difference from the previously loaded state
304
+ // to the global loaded counter:
305
+ this._progress.loaded += (loaded - data._progress.loaded);
306
+ this._progress.bitrate = this._bitrateTimer.getBitrate(
307
+ now,
308
+ this._progress.loaded,
309
+ data.bitrateInterval
310
+ );
311
+ data._progress.loaded = data.loaded = loaded;
312
+ data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
294
313
  now,
295
314
  loaded,
296
315
  data.bitrateInterval
@@ -301,16 +320,7 @@
301
320
  this._trigger('progress', e, data);
302
321
  // Trigger a global progress event for all current file uploads,
303
322
  // including ajax calls queued for sequential file uploads:
304
- this._trigger('progressall', e, {
305
- lengthComputable: true,
306
- loaded: this._loaded,
307
- total: this._total,
308
- bitrate: this._bitrateTimer.getBitrate(
309
- now,
310
- this._loaded,
311
- data.bitrateInterval
312
- )
313
- });
323
+ this._trigger('progressall', e, this._progress);
314
324
  }
315
325
  },
316
326
 
@@ -334,8 +344,14 @@
334
344
  }
335
345
  },
336
346
 
347
+ _isInstanceOf: function (type, obj) {
348
+ // Cross-frame instanceof check
349
+ return Object.prototype.toString.call(obj) === '[object ' + type + ']';
350
+ },
351
+
337
352
  _initXHRData: function (options) {
338
- var formData,
353
+ var that = this,
354
+ formData,
339
355
  file = options.files[0],
340
356
  // Ignore non-multipart setting if not supported:
341
357
  multipart = options.multipart || !$.support.xhrFileUpload,
@@ -370,7 +386,7 @@
370
386
  });
371
387
  }
372
388
  } else {
373
- if (options.formData instanceof FormData) {
389
+ if (that._isInstanceOf('FormData', options.formData)) {
374
390
  formData = options.formData;
375
391
  } else {
376
392
  formData = new FormData();
@@ -384,12 +400,10 @@
384
400
  formData.append(paramName, options.blob, file.name);
385
401
  } else {
386
402
  $.each(options.files, function (index, file) {
387
- // Files are also Blob instances, but some browsers
388
- // (Firefox 3.6) support the File API but not Blobs.
389
403
  // This check allows the tests to run with
390
404
  // dummy objects:
391
- if ((window.Blob && file instanceof Blob) ||
392
- (window.File && file instanceof File)) {
405
+ if (that._isInstanceOf('File', file) ||
406
+ that._isInstanceOf('Blob', file)) {
393
407
  formData.append(
394
408
  options.paramName[index] || paramName,
395
409
  file,
@@ -434,7 +448,7 @@
434
448
  options.dataType = 'postmessage ' + (options.dataType || '');
435
449
  }
436
450
  } else {
437
- this._initIframeSettings(options, 'iframe');
451
+ this._initIframeSettings(options);
438
452
  }
439
453
  },
440
454
 
@@ -495,6 +509,21 @@
495
509
  return options;
496
510
  },
497
511
 
512
+ // jQuery 1.6 doesn't provide .state(),
513
+ // while jQuery 1.8+ removed .isRejected() and .isResolved():
514
+ _getDeferredState: function (deferred) {
515
+ if (deferred.state) {
516
+ return deferred.state();
517
+ }
518
+ if (deferred.isResolved()) {
519
+ return 'resolved';
520
+ }
521
+ if (deferred.isRejected()) {
522
+ return 'rejected';
523
+ }
524
+ return 'pending';
525
+ },
526
+
498
527
  // Maps jqXHR callbacks to the equivalent
499
528
  // methods of the given Promise object:
500
529
  _enhancePromise: function (promise) {
@@ -519,6 +548,36 @@
519
548
  return this._enhancePromise(promise);
520
549
  },
521
550
 
551
+ // Adds convenience methods to the callback arguments:
552
+ _addConvenienceMethods: function (e, data) {
553
+ var that = this;
554
+ data.submit = function () {
555
+ if (this.state() !== 'pending') {
556
+ data.jqXHR = this.jqXHR =
557
+ (that._trigger('submit', e, this) !== false) &&
558
+ that._onSend(e, this);
559
+ }
560
+ return this.jqXHR || that._getXHRPromise();
561
+ };
562
+ data.abort = function () {
563
+ if (this.jqXHR) {
564
+ return this.jqXHR.abort();
565
+ }
566
+ return that._getXHRPromise();
567
+ };
568
+ data.state = function () {
569
+ if (this.jqXHR) {
570
+ return that._getDeferredState(this.jqXHR);
571
+ }
572
+ };
573
+ data.progress = function () {
574
+ return this._progress;
575
+ };
576
+ data.response = function () {
577
+ return this._response;
578
+ };
579
+ },
580
+
522
581
  // Parses the Range header from the server response
523
582
  // and returns the uploaded bytes:
524
583
  _getUploadedBytes: function (jqXHR) {
@@ -563,7 +622,8 @@
563
622
  // The chunk upload method:
564
623
  upload = function () {
565
624
  // Clone the options object for each chunk upload:
566
- var o = $.extend({}, options);
625
+ var o = $.extend({}, options),
626
+ currentLoaded = o._progress.loaded;
567
627
  o.blob = slice.call(
568
628
  file,
569
629
  ub,
@@ -585,10 +645,10 @@
585
645
  .done(function (result, textStatus, jqXHR) {
586
646
  ub = that._getUploadedBytes(jqXHR) ||
587
647
  (ub + o.chunkSize);
588
- // Create a progress event if upload is done and no progress
589
- // event has been invoked for this chunk, or there has been
590
- // no progress event with loaded equaling total:
591
- if (!o.loaded || o.loaded < o.total) {
648
+ // Create a progress event if no final progress event
649
+ // with loaded equaling total has been triggered
650
+ // for this chunk:
651
+ if (o._progress.loaded === currentLoaded) {
592
652
  that._onProgress($.Event('progress', {
593
653
  lengthComputable: true,
594
654
  loaded: ub - o.uploadedBytes,
@@ -640,61 +700,66 @@
640
700
  this._trigger('start');
641
701
  // Set timer for global bitrate progress calculation:
642
702
  this._bitrateTimer = new this._BitrateTimer();
703
+ // Reset the global progress values:
704
+ this._progress.loaded = this._progress.total = 0;
705
+ this._progress.bitrate = 0;
643
706
  }
707
+ // Make sure the container objects for the .response() and
708
+ // .progress() methods on the data object are available
709
+ // and reset to their initial state:
710
+ this._initResponseObject(data);
711
+ this._initProgressObject(data);
712
+ data._progress.loaded = data.loaded = data.uploadedBytes || 0;
713
+ data._progress.total = data.total = this._getTotal(data.files) || 1;
714
+ data._progress.bitrate = data.bitrate = 0;
644
715
  this._active += 1;
645
716
  // Initialize the global progress values:
646
- this._loaded += data.uploadedBytes || 0;
647
- this._total += this._getTotal(data.files);
717
+ this._progress.loaded += data.loaded;
718
+ this._progress.total += data.total;
648
719
  },
649
720
 
650
721
  _onDone: function (result, textStatus, jqXHR, options) {
651
- if (!this._isXHRUpload(options) || !options.loaded ||
652
- options.loaded < options.total) {
653
- var total = this._getTotal(options.files) || 1;
654
- // Create a progress event for each iframe load,
655
- // or if there has been no progress event with
656
- // loaded equaling total for XHR uploads:
722
+ var total = options._progress.total,
723
+ response = options._response;
724
+ if (options._progress.loaded < total) {
725
+ // Create a progress event if no final progress event
726
+ // with loaded equaling total has been triggered:
657
727
  this._onProgress($.Event('progress', {
658
728
  lengthComputable: true,
659
729
  loaded: total,
660
730
  total: total
661
731
  }), options);
662
732
  }
663
- options.result = result;
664
- options.textStatus = textStatus;
665
- options.jqXHR = jqXHR;
733
+ response.result = options.result = result;
734
+ response.textStatus = options.textStatus = textStatus;
735
+ response.jqXHR = options.jqXHR = jqXHR;
666
736
  this._trigger('done', null, options);
667
737
  },
668
738
 
669
739
  _onFail: function (jqXHR, textStatus, errorThrown, options) {
670
- options.jqXHR = jqXHR;
671
- options.textStatus = textStatus;
672
- options.errorThrown = errorThrown;
673
- this._trigger('fail', null, options);
740
+ var response = options._response;
674
741
  if (options.recalculateProgress) {
675
742
  // Remove the failed (error or abort) file upload from
676
743
  // the global progress calculation:
677
- this._loaded -= options.loaded || options.uploadedBytes || 0;
678
- this._total -= options.total || this._getTotal(options.files);
744
+ this._progress.loaded -= options._progress.loaded;
745
+ this._progress.total -= options._progress.total;
679
746
  }
747
+ response.jqXHR = options.jqXHR = jqXHR;
748
+ response.textStatus = options.textStatus = textStatus;
749
+ response.errorThrown = options.errorThrown = errorThrown;
750
+ this._trigger('fail', null, options);
680
751
  },
681
752
 
682
753
  _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
683
754
  // jqXHRorResult, textStatus and jqXHRorError are added to the
684
755
  // options object via done and fail callbacks
685
- this._active -= 1;
686
756
  this._trigger('always', null, options);
687
- if (this._active === 0) {
688
- // The stop callback is triggered when all uploads have
689
- // been completed, equivalent to the global ajaxStop event:
690
- this._trigger('stop');
691
- // Reset the global progress values:
692
- this._loaded = this._total = 0;
693
- this._bitrateTimer = null;
694
- }
695
757
  },
696
758
 
697
759
  _onSend: function (e, data) {
760
+ if (!data.submit) {
761
+ this._addConvenienceMethods(e, data);
762
+ }
698
763
  var that = this,
699
764
  jqXHR,
700
765
  aborted,
@@ -714,32 +779,32 @@
714
779
  }).fail(function (jqXHR, textStatus, errorThrown) {
715
780
  that._onFail(jqXHR, textStatus, errorThrown, options);
716
781
  }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
717
- that._sending -= 1;
718
782
  that._onAlways(
719
783
  jqXHRorResult,
720
784
  textStatus,
721
785
  jqXHRorError,
722
786
  options
723
787
  );
788
+ that._sending -= 1;
789
+ that._active -= 1;
724
790
  if (options.limitConcurrentUploads &&
725
791
  options.limitConcurrentUploads > that._sending) {
726
792
  // Start the next queued upload,
727
793
  // that has not been aborted:
728
- var nextSlot = that._slots.shift(),
729
- isPending;
794
+ var nextSlot = that._slots.shift();
730
795
  while (nextSlot) {
731
- // jQuery 1.6 doesn't provide .state(),
732
- // while jQuery 1.8+ removed .isRejected():
733
- isPending = nextSlot.state ?
734
- nextSlot.state() === 'pending' :
735
- !nextSlot.isRejected();
736
- if (isPending) {
796
+ if (that._getDeferredState(nextSlot) === 'pending') {
737
797
  nextSlot.resolve();
738
798
  break;
739
799
  }
740
800
  nextSlot = that._slots.shift();
741
801
  }
742
802
  }
803
+ if (that._active === 0) {
804
+ // The stop callback is triggered when all uploads have
805
+ // been completed, equivalent to the global ajaxStop event:
806
+ that._trigger('stop');
807
+ }
743
808
  });
744
809
  return jqXHR;
745
810
  };
@@ -805,12 +870,9 @@
805
870
  var newData = $.extend({}, data);
806
871
  newData.files = fileSet ? element : [element];
807
872
  newData.paramName = paramNameSet[index];
808
- newData.submit = function () {
809
- newData.jqXHR = this.jqXHR =
810
- (that._trigger('submit', e, this) !== false) &&
811
- that._onSend(e, this);
812
- return this.jqXHR;
813
- };
873
+ that._initResponseObject(newData);
874
+ that._initProgressObject(newData);
875
+ that._addConvenienceMethods(e, newData);
814
876
  result = that._trigger('add', e, newData);
815
877
  return result;
816
878
  });
@@ -987,44 +1049,50 @@
987
1049
  },
988
1050
 
989
1051
  _onPaste: function (e) {
990
- var cbd = e.originalEvent.clipboardData,
991
- items = (cbd && cbd.items) || [],
1052
+ var items = e.originalEvent && e.originalEvent.clipboardData &&
1053
+ e.originalEvent.clipboardData.items,
992
1054
  data = {files: []};
993
- $.each(items, function (index, item) {
994
- var file = item.getAsFile && item.getAsFile();
995
- if (file) {
996
- data.files.push(file);
1055
+ if (items && items.length) {
1056
+ $.each(items, function (index, item) {
1057
+ var file = item.getAsFile && item.getAsFile();
1058
+ if (file) {
1059
+ data.files.push(file);
1060
+ }
1061
+ });
1062
+ if (this._trigger('paste', e, data) === false ||
1063
+ this._onAdd(e, data) === false) {
1064
+ return false;
997
1065
  }
998
- });
999
- if (this._trigger('paste', e, data) === false ||
1000
- this._onAdd(e, data) === false) {
1001
- return false;
1002
1066
  }
1003
1067
  },
1004
1068
 
1005
1069
  _onDrop: function (e) {
1006
1070
  var that = this,
1007
- dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer,
1071
+ dataTransfer = e.dataTransfer = e.originalEvent &&
1072
+ e.originalEvent.dataTransfer,
1008
1073
  data = {};
1009
1074
  if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
1010
1075
  e.preventDefault();
1076
+ this._getDroppedFiles(dataTransfer).always(function (files) {
1077
+ data.files = files;
1078
+ if (that._trigger('drop', e, data) !== false) {
1079
+ that._onAdd(e, data);
1080
+ }
1081
+ });
1011
1082
  }
1012
- this._getDroppedFiles(dataTransfer).always(function (files) {
1013
- data.files = files;
1014
- if (that._trigger('drop', e, data) !== false) {
1015
- that._onAdd(e, data);
1016
- }
1017
- });
1018
1083
  },
1019
1084
 
1020
1085
  _onDragOver: function (e) {
1021
- var dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer;
1022
- if (this._trigger('dragover', e) === false) {
1023
- return false;
1024
- }
1025
- if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1) {
1026
- dataTransfer.dropEffect = 'copy';
1027
- e.preventDefault();
1086
+ var dataTransfer = e.dataTransfer = e.originalEvent &&
1087
+ e.originalEvent.dataTransfer;
1088
+ if (dataTransfer) {
1089
+ if (this._trigger('dragover', e) === false) {
1090
+ return false;
1091
+ }
1092
+ if ($.inArray('Files', dataTransfer.types) !== -1) {
1093
+ dataTransfer.dropEffect = 'copy';
1094
+ e.preventDefault();
1095
+ }
1028
1096
  }
1029
1097
  },
1030
1098
 
@@ -1084,12 +1152,23 @@
1084
1152
  this._initSpecialOptions();
1085
1153
  this._slots = [];
1086
1154
  this._sequence = this._getXHRPromise(true);
1087
- this._sending = this._active = this._loaded = this._total = 0;
1155
+ this._sending = this._active = 0;
1156
+ this._initProgressObject(this);
1088
1157
  this._initEventHandlers();
1089
1158
  },
1090
1159
 
1091
- _destroy: function () {
1092
- this._destroyEventHandlers();
1160
+ // This method is exposed to the widget API and allows to query
1161
+ // the number of active uploads:
1162
+ active: function () {
1163
+ return this._active;
1164
+ },
1165
+
1166
+ // This method is exposed to the widget API and allows to query
1167
+ // the widget upload progress.
1168
+ // It returns an object with loaded, total and bitrate properties
1169
+ // for the running uploads:
1170
+ progress: function () {
1171
+ return this._progress;
1093
1172
  },
1094
1173
 
1095
1174
  // This method is exposed to the widget API and allows adding files
@@ -35,7 +35,7 @@
35
35
  line-height: 31px;
36
36
  }
37
37
  .uploader-button {
38
- display: block;
38
+ display: inline-block;
39
39
  background: #972da0;
40
40
  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#982da1', endColorstr='#892891');
41
41
  background: -webkit-linear-gradient(left top, left bottom, #982da1, #892891);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-uploader
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,8 +10,24 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-02-24 00:00:00.000000000 Z
13
+ date: 2013-04-09 00:00:00.000000000 Z
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: jquery-ui-rails
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
15
31
  - !ruby/object:Gem::Dependency
16
32
  name: sqlite3
17
33
  requirement: !ruby/object:Gem::Requirement
@@ -44,6 +60,22 @@ dependencies:
44
60
  - - ! '>='
45
61
  - !ruby/object:Gem::Version
46
62
  version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: mongoid
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
47
79
  description: Rails HTML5 FileUpload helpers
48
80
  email: superp1987@gmail.com
49
81
  executables: []
@@ -54,10 +86,8 @@ files:
54
86
  - app/views/uploader/default/_upload.html.erb
55
87
  - app/views/uploader/default/_sortable.html.erb
56
88
  - app/views/uploader/default/_container.html.erb
57
- - app/views/uploader/default/_init.html.erb
58
89
  - app/views/uploader/default/_download.html.erb
59
90
  - app/assets/stylesheets/uploader/application.css
60
- - app/assets/javascripts/uploader/init.js
61
91
  - app/assets/javascripts/uploader/application.js
62
92
  - app/controllers/uploader/attachments_controller.rb
63
93
  - lib/uploader.rb
@@ -134,11 +164,13 @@ files:
134
164
  - spec/dummy/README.rdoc
135
165
  - spec/dummy/script/rails
136
166
  - spec/spec_helper.rb
167
+ - spec/mongoid_spec.rb
137
168
  - spec/uploader_spec.rb
138
169
  - spec/requests/attachments_controller_spec.rb
139
170
  - spec/factories/articles.rb
140
171
  - spec/factories/assets.rb
141
172
  - spec/factories/files/rails.png
173
+ - spec/mongoid.yml
142
174
  - spec/fileuploads_spec.rb
143
175
  homepage: https://github.com/superp/rails-uploader
144
176
  licenses: []
@@ -160,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
192
  version: '0'
161
193
  requirements: []
162
194
  rubyforge_project: rails-uploader
163
- rubygems_version: 1.8.23
195
+ rubygems_version: 1.8.25
164
196
  signing_key:
165
197
  specification_version: 3
166
198
  summary: Rails file upload implementation with jQuery-File-Upload
@@ -204,10 +236,11 @@ test_files:
204
236
  - spec/dummy/README.rdoc
205
237
  - spec/dummy/script/rails
206
238
  - spec/spec_helper.rb
239
+ - spec/mongoid_spec.rb
207
240
  - spec/uploader_spec.rb
208
241
  - spec/requests/attachments_controller_spec.rb
209
242
  - spec/factories/articles.rb
210
243
  - spec/factories/assets.rb
211
244
  - spec/factories/files/rails.png
245
+ - spec/mongoid.yml
212
246
  - spec/fileuploads_spec.rb
213
- has_rdoc:
@@ -1,5 +0,0 @@
1
- (function() {
2
- $ = jQuery;
3
- $('input[type="file"].uploader').trigger("init.uploader");
4
- }).call(this);
5
-
@@ -1,9 +0,0 @@
1
- <script type="text/javascript">
2
- (function(d, s, id) {
3
- var js, fjs = d.getElementsByTagName(s)[0];
4
- if (d.getElementById(id)) return;
5
- js = d.createElement(s); js.id = id;
6
- js.src = "/assets/uploader/application.js";
7
- fjs.parentNode.appendChild(js);
8
- }(document, 'script', 'uploader-jssdk'));
9
- </script>