`
zccst
  • 浏览: 3292042 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

uploader.js组件原理

阅读更多
作者:zccst

2014-12-04
重复提交时的bug
(1)除file字段外的其他字段多次重复添加时出现重复。
需要在添加时先检查是否已经添加过,如果添加过就更新,没添加过再append。



(2)file字段第二次添加时filename为空。






2014-11-14

效果图:

优点:多浏览器统一样式。(原生input type="file"在不同浏览器下表现不一致,对于很多系统这是不能接受的)


html:
<span class="button_box1"><a href="javascript:;" name="file" id="file" style=""><span>选择文件</span></a></span>
<span class="batch-upload-filename"></span>



js:上传文件初始化
//初始化文件上传组件
initUploader:function(){
	var _this = this;
	var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
    var splitPath = function(filename) {
      	return splitPathRe.exec(filename).slice(1);
    };

	//Excel上传控件
    this.batch_uploader = new Uploader({
	    trigger: '#file',
	    name: 'file',
	    action: CREATE_EXCELFEED_URL,
	    accept: 'application/vnd.ms-excel',
	    data: {},
	    dataType:'json',
	    multiple: false
    }).change(function(files) {
	    for (var i = 0; i < files.length; i++) {
	        var fileType = splitPath(files[i].name)[3];
	        if (fileType !== ".xls") {
	          	alert("文件格式错误, 请上传.xls格式文件!");
	        } else {
	          	$('.batch-upload-filename').html(files[i].name);
	        }
	    }
    }).success(function(response) {
		    	
    	/**/
	    var res;
	    if($.type(response) === "string"){ // IE浏览器走这个逻辑
    		res = $.parseJSON(response);
    	}else if($.type(response) === "object"){//FF,Chrome走这个逻辑
    		res = response;
    	}else{//奇怪的第三种情况
    		res = $.parseJSON($(response).text());
    	}
	    /*旧实现方式
	    if(/msie/.test(navigator.userAgent.toLowerCase())) {
	    	if($.type(response) === "string"){
	    		res = $.parseJSON(response);
	    	}else{
	    		res = $.parseJSON($(response).text());
	    	}
	    } else {
	    	console.log(response, $.type(response));
	    }*/
	    
	    if (res.flag == 0) {
	    	//成功后刷新
	    	_this.createSuccessCallback();
	    } else {
	    	var msg = res.msg || [];
	        alert("上传失败,原因:"+ (msg.length !== 0 ? msg.join('\r\n') : '未知。') );
	    }
		    	
	    //如果选择文件立即上传时
	    //_this.$el.find('.batch-upload-filename').html(' 未选择文件');
    }).error(function(file) {
      	alert("上传"+file+"失败,请重试。");
    });
},


js:上传文件提交
if(this.batch_uploader._uploaders[0]._files) {
	this.$el.find(".errormsg").hide();
        this.batch_uploader._uploaders[0].form.append(_this.createInputs(params));
        this.batch_uploader.submit();
			        
} else {
    	this.$el.find(".errormsg").html("请先选择要上传的文件,再提交!").show();
        return false;
}



需要后端配合的是:
content-type:text/html。不能是text/javascript,踩过的坑坑,深深的痛。




接下来是组件分析
简单说一下实现原理,通过用户在初始化中填入的设置,默认创建一个透明的form表单和iframe。其中form的target设置为iframe的name,目的是为了跳转。巧妙的是该表单的宽高和看得见的上传按钮宽高完全一致,但zIndex高10(自己设置的)

用户点击按钮时,实际上是在点击表单的input输入框。而负责样式的a标签则没有任何事件。

为透明度为0的input输入框添加了change事件,用户一旦选择文件,则会触发该事件。可以做的事情,比如校验,将文件名写入右侧的span,让用户看上去选择了一个文件。

提交:分两种情况,IE和非IE。
如果是非IE,则使用$.ajax上传,同时设置一个xhr,还可以查看上传进度。
如果是IE,则使用form+iframe方式,并且注册iframe的onload监听事件,服务器端将返回结果放置到iframe中后,onload监听事件需要做的事情是:拿到后端返回的数据,做判断,然后传给setting里注册的回调函数success或error。


里面涉及好几个知识点,比如:
1,FileList
2,formData
3,取iframe的值
var doc = self.iframe[0].contentDocument ? self.iframe[0].contentDocument : self.iframe[0].contentWindow.document;
var str = doc.body.innerHTML;




为了方便看,附两张图:
一个是settings



一个是Uploader对象在setup时的this



一个是Uploader对象在submit时的this(区别是多了_files)



uploader.js组件

define(function(require, exports, module){
//var $ = require('jquery');
var iframeCount = 0;

function Uploader(options) {
  if (!(this instanceof Uploader)) {
    return new Uploader(options);
  }
  if (isString(options)) {
    options = {trigger: options};
  }

  var settings = {
    trigger: null,
    name: null,
    action: null,
    data: null,
    accept: null,
    change: null,
    error: null,
    multiple: true,
    success: null
  };
  if (options) {
    $.extend(settings, options);
  }
  var $trigger = $(settings.trigger);

  settings.action = settings.action || $trigger.data('action') || '/upload';
  settings.name = settings.name || $trigger.attr('name') || $trigger.data('name') || 'file';
  settings.data = settings.data || parse($trigger.data('data'));
  settings.accept = settings.accept || $trigger.data('accept');
  settings.success = settings.success || $trigger.data('success');
  this.settings = settings;

  this.setup();
  this.bind();
}

// initialize
// create input, form, iframe
Uploader.prototype.setup = function() {
  this.form = $(
    '<form class="earth-upload" method="post" enctype="multipart/form-data"'
    + 'target="" action="' + this.settings.action + '"></form>'
  );

  this.iframe = newIframe();//<iframe name="iframe-uploader-0" style="display: none;">
  //<form class="earth-upload" action="/feed/createExcelFeed.action" target="iframe-uploader-0" enctype="multipart/form-data" method="post"></form>
  this.form.attr('target', this.iframe.attr('name'));
  var data = this.settings.data;
  
  this.form.append(createInputs(data));
  
  if (window.FormData) {
	//<input value="formdata" name="_uploader_" type="hidden">
    this.form.append(createInputs({'_uploader_': 'formdata'}));
  } else {
	//<input value="iframe" name="_uploader_" type="hidden">
    this.form.append(createInputs({'_uploader_': 'iframe'}));
  }

  var input = document.createElement('input');
  input.type = 'file';
  input.name = this.settings.name;
  if (this.settings.accept) {
    input.accept = this.settings.accept;
  }
  if (this.settings.multiple) {
    input.multiple = true;
    input.setAttribute('multiple', 'multiple');
  }
  this.input = $(input);
  //<input type="file" name="file" accept="application/vnd.ms-excel">
  
  var $trigger = $(this.settings.trigger);
  this.input.attr('hidefocus', true).css({
    position: 'absolute',
    top: 0,
    right: 0,
    opacity: 0,
    outline: 0,
    cursor: 'pointer',
    height: $trigger.outerHeight(),
    fontSize: Math.max(64, $trigger.outerHeight() * 5)
  });
  //<input type="file" name="file" accept="application/vnd.ms-excel" hidefocus="true" style="position: absolute; top: 0px; right: 0px; opacity: 0; outline: 0px none; cursor: pointer; height: 30px; font-size: 150px;">
  
  this.form.append(this.input);
  
  this.form.css({
    position: 'absolute',
    top: $trigger.offset().top,
    left: $trigger.offset().left,
    overflow: 'hidden',
    width: $trigger.outerWidth(),
    height: $trigger.outerHeight(),
    zIndex: findzIndex($trigger) + 10
  }).appendTo('body');
  
  /*
   * <form action="/feed/createExcelFeed.action" target="iframe-uploader-0" enctype="multipart/form-data" method="post" class="earth-upload" style="position: absolute; top: 177px; left: 371px; overflow: hidden; width: 80px; height: 30px; z-index: 1037;">
   * 	<input type="hidden" name="_uploader_" value="formdata">
   * 	<input type="file" name="file" accept="application/vnd.ms-excel" hidefocus="true" style="position: absolute; top: 0px; right: 0px; opacity: 0; outline: 0px none; cursor: pointer; height: 30px; font-size: 150px;">
   * </form>
   * */
  return this;
};

// bind events
Uploader.prototype.bind = function() {
  var self = this;
  var $trigger = $(self.settings.trigger);
  $trigger.mouseenter(function() {
    self.form.css({
      top: $trigger.offset().top,
      left: $trigger.offset().left,
      width: $trigger.outerWidth(),
      height: $trigger.outerHeight()
    });
  });
  self.bindInput();
};

Uploader.prototype.bindInput = function() {
  var self = this;
  self.input.change(function(e) {
    // ie9 don't support FileList Object
    // http://stackoverflow.com/questions/12830058/ie8-input-type-file-get-files
    self._files = this.files || [{
      name: e.target.value
    }];// files 是一个 FileList 对象(类似于NodeList对象)
    
    var file = self.input.val();
    if (self.settings.change) {
      self.settings.change.call(self, self._files);
    } else if (file) {
      return self.submit();
    }
  });
};

// handle submit event
// prepare for submiting form
Uploader.prototype.submit = function() {
  var self = this;
  if (window.FormData && self._files) {
    // build a FormData
    var form = new FormData(self.form.get(0));
    // use FormData to upload
    form.append(self.settings.name, self._files);

    var optionXhr;
    if (self.settings.progress) {
      // fix the progress target file
      var files = self._files;
      optionXhr = function() {
        var xhr = $.ajaxSettings.xhr();
        if (xhr.upload) {
          xhr.upload.addEventListener('progress', function(event) {
            var percent = 0;
            var position = event.loaded || event.position; /*event.position is deprecated*/
            var total = event.total;
            if (event.lengthComputable) {
                percent = Math.ceil(position / total * 100);
            }
            self.settings.progress(event, position, total, percent, files);
          }, false);
        }
        return xhr;
      };
    }
    
    $.ajax({
      url: self.settings.action,
      type: 'post',
      processData: false,
      contentType: false,
      data: form,
      xhr: optionXhr,
      context: this,
      dataType:self.settings.dataType,
      success: self.settings.success,
      error: self.settings.error
    });
    return this;
  } else {
    // iframe upload
    self.iframe = newIframe();
    self.form.attr('target', self.iframe.attr('name'));
    $('body').append(self.iframe);
    self.iframe.one('load', function() {
    	var doc = self.iframe[0].contentDocument 
	 		? self.iframe[0].contentDocument
	 		: self.iframe[0].contentWindow.document;
	 	var str = doc.body.innerHTML;
	 	if(str){
	 		if (self.settings.success) {
	 			self.settings.success(str);
 	        }
	 	}else{
	 		if (self.settings.error) {
	 			self.settings.error(self.input.val());
 	        }
	 	}
	 	/*实现方法二
      // https://github.com/blueimp/jQuery-File-Upload/blob/9.5.6/js/jquery.iframe-transport.js#L102
      // Fix for IE endless progress bar activity bug
      // (happens on form submits to iframe targets):
      $('<iframe src="javascript:false;"></iframe>')
        .appendTo(self.form)
        .remove();
      
      var response = $(this).contents().find('body').html();
      $(this).remove();
      if (!response) {
        if (self.settings.error) {
          self.settings.error(self.input.val());
        }
      } else {
        if (self.settings.success) {
          self.settings.success(response);
        }
      }*/
    });
    self.form.submit();
  }
  return this;
};

Uploader.prototype.refreshInput = function() {
  //replace the input element, or the same file can not to be uploaded
  var newInput = this.input.clone();
  this.input.before(newInput);
  this.input.off('change');
  this.input.remove();
  this.input = newInput;
  this.bindInput();
};

// handle change event
// when value in file input changed
Uploader.prototype.change = function(callback) {
  if (!callback) {
    return this;
  }
  this.settings.change = callback;
  return this;
};

// handle when upload success
Uploader.prototype.success = function(callback) {
  var me = this;
  this.settings.success = function(response) {
    me.refreshInput();
    if (callback) {
      callback(response);
    }
  };

  return this;
};

// handle when upload success
Uploader.prototype.error = function(callback) {
  var me = this;
  this.settings.error = function(response) {
    if (callback) {
      me.refreshInput();
      callback(response);
    }
  };
  return this;
};

// enable
Uploader.prototype.enable = function(){
  this.input.prop('disabled', false);
  this.input.css('cursor', 'pointer');
};

// disable
Uploader.prototype.disable = function(){
  this.input.prop('disabled', true);
  this.input.css('cursor', 'not-allowed');
};

// Helpers
// -------------

function isString(val) {
  return Object.prototype.toString.call(val) === '[object String]';
}

function createInputs(data) {
  if (!data) return [];

  var inputs = [], i;
  for (var name in data) {
    i = document.createElement('input');
    i.type = 'hidden';
    i.name = name;
    i.value = data[name];
    inputs.push(i);
  }
  return inputs;
}

function parse(str) {
  if (!str) return {};
  var ret = {};

  var pairs = str.split('&');
  var unescape = function(s) {
    return decodeURIComponent(s.replace(/\+/g, ' '));
  };

  for (var i = 0; i < pairs.length; i++) {
    var pair = pairs[i].split('=');
    var key = unescape(pair[0]);
    var val = unescape(pair[1]);
    ret[key] = val;
  }

  return ret;
}

function findzIndex($node) {
  var parents = $node.parentsUntil('body');
  var zIndex = 0;
  for (var i = 0; i < parents.length; i++) {
    var item = parents.eq(i);
    if (item.css('position') !== 'static') {
      zIndex = parseInt(item.css('zIndex'), 10) || zIndex;
    }
  }
  return zIndex;
}

function newIframe() {
  var iframeName = 'iframe-uploader-' + iframeCount;
  var iframe = $('<iframe src="" id="' + iframeName + '" name="' + iframeName + '"></iframe>').hide();
  iframeCount += 1;
  return iframe;
}

function MultipleUploader(options) {
  if (!(this instanceof MultipleUploader)) {
    return new MultipleUploader(options);
  }

  if (isString(options)) {
    options = {trigger: options};
  }
  var $trigger = $(options.trigger);

  var uploaders = [];
  $trigger.each(function(i, item) {
    options.trigger = item;
    uploaders.push(new Uploader(options));
  });
  this._uploaders = uploaders;
}
MultipleUploader.prototype.submit = function() {
  $.each(this._uploaders, function(i, item) {
    item.submit();
  });
  return this;
};
MultipleUploader.prototype.change = function(callback) {
  $.each(this._uploaders, function(i, item) {
    item.change(callback);
  });
  return this;
};
MultipleUploader.prototype.success = function(callback) {
  $.each(this._uploaders, function(i, item) {
    item.success(callback);
  });
  return this;
};
MultipleUploader.prototype.error = function(callback) {
  $.each(this._uploaders, function(i, item) {
    item.error(callback);
  });
  return this;
};
MultipleUploader.prototype.enable = function (){
  $.each(this._uploaders, function (i, item){
    item.enable();
  });
  return this;
};
MultipleUploader.prototype.disable = function (){
  $.each(this._uploaders, function (i, item){
    item.disable();
  });
  return this;
};
MultipleUploader.Uploader = Uploader;

module.exports = MultipleUploader;

});





如果您觉得本文的内容对您的学习有所帮助,您可以微信:
  • 大小: 3.1 KB
  • 大小: 52.1 KB
  • 大小: 61.9 KB
  • 大小: 25 KB
分享到:
评论

相关推荐

    jqweui图片上传插件jquery-weui.uploader.js

    jqweui图片上传插件jquery-weui.uploader.js 实现 多图上传,预览,删除,初始化控件,图片赋值加载,获取图片数据。压缩包包含jquery-weui.uploader.js,weui以及示例demo

    uploader.js使用说明

    文档里有附件,封装的webuploader,对象形式传参,一句话调用,自定义上传流程回调函数,带进度条,本地删除,远程删除,MD5校验等

    一个Vue.js上传组件,由simple-uploader.js提供支持.zip

    一个Vue.js上传组件,由simple-uploader.js提供支持.zip

    Uploader.swf

    WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。...两套运行时,同样的调用方式,可供用户任意选用。 采用大文件分片并发上传,...uploader.swf用于webuploader的初始化

    Cute WebUI AjaxUploader注册文件(AjaxUploader.lic)

    Cute WebUI AjaxUploader注册文件(AjaxUploader.lic) AjaxUploader.lic 注册版文件,测试可用。

    vue-uploader:由simple-uploader.js驱动的Vue.js上传组件

    由驱动的Vue.js上传组件 特征 将文件夹和文件视为File 暂停/继续上传 恢复上传 错误处理 使用文件夹阅读器拖放 自定义上传按钮 资料夹上载 队列管理 文件验证 上传进度 剩余时间 块上传 安装 npm install vue-...

    fineuploader3.7.1.js及css.rar

    该文件包内包含jquery.fineuploader3.7.1.js和fineuploader.css 该包用于用户学习、使用关于文件上传相关的内容时下载。

    使用fileuploader.js实现文件上传

    NULL 博文链接:https://chenwes.iteye.com/blog/1525161

    最新版fineuploader.js

    最新版fineuploader.js

    jquery.html5uploader.min.js下载.rar

    系统不支持FLASH的swf上传后,用Html5上传所有的JS适用于多图上传,

    多文件上传插件fine-uploader.js

    NULL 博文链接:https://jm1999.iteye.com/blog/2243337

    uploader.js

    WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。在现代的浏览器里面能充分发挥HTML5的优势,同时又不摒弃主流IE浏览器,沿用原来的FLASH运行时,兼容IE6+,iOS 6+,...

    stats_uploader.exe

    stats_uploader.exe

    前端项目-simple-uploader.zip

    前端项目-simple-uploader,A HTML5 upload component without UI

    appuploader.exe

    Appuploader是一个IOS开发助手,可以快速,轻松地生成ios开发证书,不需要钥匙串助手; appuploader可以批量上传屏幕截图并将ipa文件上传到Apple商店,在windows,linux或mac上,不需要应用程序加载器和mac计算机。 ...

    两个Uploader.rar

    使用MicroPython File Uploader或者uPyCraf进行开发 个人使用觉得MicroPython File Uploader适合指令开发,而uPyCraft更适合文件类型的开发。 绝对有用!

    ImageUploader.rar

    ImageUploader.rar 图片批量上传

    A2Uploader.zip

    A2Uploader.zip

    异步文件上传组件Uploader.zip

    Uploader是非常强大的异步文件上传组件,支持ajax、iframe、flash三套方案,实现浏览器的全兼容,调用非常简单,内置多套主题支持 和常用插件,比如验证、图片预览、进度条等,广泛应用于淘宝网,比如退款系统、...

Global site tag (gtag.js) - Google Analytics