rails-uploader 0.1.0 → 0.1.2

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