`
zccst
  • 浏览: 3322391 次
  • 性别: 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
分享到:
评论

相关推荐

    使用fileuploader.js实现文件上传

    `fileuploader.js`是一个轻量级的前端文件上传组件,它提供了友好的API和自定义选项,便于开发者集成到自己的项目中。该库的核心特性包括多文件选择、进度显示、错误处理和自定义样式等。通过阅读博文...

    fineuploader3.7.1.js及css.rar

    在这个"fineuploader3.7.1.js及css.rar"压缩包中,包含的主要组件是`jquery.fineuploader3.7.1.js`和`fineuploader.css`。这两个文件分别负责FineUploader的JavaScript逻辑和样式设计,使得在网页上实现文件上传变得...

    基于weui图片上传封装插件

    【标题】:基于weui图片上传封装插件 在现代Web开发中,用户交互和界面体验成为了关键要素,尤其在移动应用中。...通过深入理解这个插件的工作原理和API,开发者可以更好地将其融入到实际项目中,提升用户交互体验。

    diyUpload.js

    WebUploader是由阿里云开发的一个JavaScript文件上传组件,它具有异步上传、断点续传、多文件上传、预览、裁剪等功能。`diyUpload.js`则是对WebUploader进行了一次定制化改造,使得其更适应移动端的使用场景,简化了...

    Ajax+Flash多文件上传

    `Swiff.Uploader.fla` 和 `Swiff.Uploader.js` 是该组件的核心部分,其中 `.fla` 文件是Flash的源文件,用于编辑和编译Flash代码,`.js` 文件则是与JavaScript交互的接口,用于在网页中实例化和控制Flash对象。...

    swfUpload实例程序

    4. **初始化SWFUpload**:在JavaScript中调用`SWFUpload.init()`方法启动组件。 5. **事件监听**:绑定各种事件监听器,如`fileQueued`、`uploadProgress`和`uploadSuccess`,以处理上传过程中的各种情况。 6. **...

    百度的WebUploader组件实现普通文件的批量上传示例相关的js和css

    在这个"百度的WebUploader组件实现普通文件的批量上传示例相关的js和css"的压缩包中,主要包含的是用于实现这一功能的JavaScript库文件和CSS样式文件。 首先,我们要理解WebUploader的工作原理。它基于HTML5的File ...

    webuploader-demo

    【webuploader】是一个轻量级的前端上传组件,主要用于网页上的文件上传操作。它支持多浏览器,包括IE6+,并且提供了丰富的API和多种自定义配置选项,方便开发者实现各种复杂的上传需求。在这个"webuploader-demo...

    diyUpload 上传文件

    7. **第三方库**:像jQuery File Upload、Plupload、Dropzone.js等JavaScript库提供了丰富的上传功能,包括拖放上传、多文件上传等,简化了开发过程。 8. **响应式设计**:确保上传组件在不同设备和屏幕尺寸上都能...

    Ajax Uploader上传

    1. **Ajax原理**:Ajax的核心是 XMLHttpRequest 对象,它允许JavaScript在后台与服务器交换数据并更新部分网页内容,无需重新加载整个页面。通过创建XMLHttpRequest对象,打开连接,发送请求,监听状态变化,最终...

    Web_Uploader_demo.zip

    Web Uploader是一款强大的JavaScript文件上传组件,尤其在处理大文件上传和批量上传方面表现出色。在Web开发中,用户交互的文件上传功能是常见的需求,Web Uploader则为开发者提供了便利。这个“Web_Uploader_demo....

    ajaxfileupload,ajax上传附件

    2. **JavaScript设置**:引入AjaxFileUpload.js库,然后创建一个新的AjaxFileUpload实例。配置参数如上传URL、回调函数等。 ```javascript var uploader = new AjaxFileUpload(); uploader.settings.url = 'upload....

    文件上传实例

    WebUpload是一款轻量级、高效的文件上传组件,尤其适合在处理大量文件上传时提供良好的用户体验。 首先,我们需要理解WebUpload的基本概念。WebUpload是百度推出的一款JavaScript插件,它旨在简化文件上传流程,...

    jsp中js上传组件的使用

    总之,JSP中使用JS上传组件涉及前端与后端的紧密协作,理解组件的工作原理和API,以及良好的前后端通信实践,是实现高效文件上传功能的关键。通过选择合适的组件,并结合JSP和JavaScript的特性,我们可以为用户提供...

    WebUploader大文件上传

    WebUploader是一款强大的JavaScript文件上传组件,它专注于解决大文件上传的问题,尤其在处理多文件、断点续传以及预览功能上表现出色。这个组件由阿里云开发,旨在为Web应用提供灵活、高效的文件上传解决方案。 一...

    基于Ajax的Flash、HTML5上传组件(支持C#)

    2. `FileUploader.js`: JavaScript文件,实现了Ajax和HTML5上传的逻辑,以及与Flash组件的交互。 3. `UploadHandler.cs`: C#服务器端处理程序,负责接收并处理上传的文件。 4. `Demo.html`: 示例页面,展示了如何在...

    开源的多文件上传采用JavaScript插件

    4. "Swiff.Uploader.js" - 这可能是一个用于与Flash交互的JavaScript模块,因为Flash在上传大文件时曾经是常见的选择。 5. "fan.js", "Fx.ProgressBar.js" - 可能是Fancyupload的一部分,分别涉及用户界面的特定功能...

    element-ui ruoyi 图片上传可拖拽调整顺序组件

    Element UI 是一套为开发者、设计师和产品经理准备的基于 Vue.js 的开源组件库,而Ruoyi则是一个轻量级的后台管理系统框架。这个组件的实现主要涉及到以下几个关键知识点: 1. **Element UI组件库**: Element UI ...

    百度Web Uploader组件实现文件上传之分片上传(一)

    百度Web Uploader是一款强大的JavaScript文件上传组件,它支持多浏览器、拖拽上传、图片预览、进度显示等功能,并且提供了分片上传的能力,以应对大文件上传的需求。分片上传的主要原理是将大文件分割成多个小块(片...

    [上传下载]UPU(UGiA PHP UPLOADER) v0.21_upu.zip

    在本文中,我们将深入探讨这个组件的核心功能、使用场景、工作原理以及如何在实际项目中集成和配置。 1. **核心功能**: - 多文件上传:UPU支持用户同时上传多个文件,提高了用户交互体验。 - 文件类型限制:可以...

Global site tag (gtag.js) - Google Analytics