uploadifive源码学习

一、简介

Uploadify是一个基于JQuery的多文件上传JS组件,高度定制,两个版本可供选择。flash版本在最新的Safari等不再支持flash的浏览器或者一些手机浏览器中就无法正常的加载使用,因此推荐使用html5版本!

最近,想了解JavaScript的编码规范,所以根据用过的Uploadifive作为参考,看如何组织js代码以及如果开发jquery的扩展。

二、代码结构概览

如果从第一行开始读代码,会摸不着头脑,所以先看一下他的整体布局。

(function($) {

	var methods = {
		//所有上传用到的方法和属性
		init:function(options){},
		debug : function() {},
		clearQueue : function() {}//清理队列
		cancel : fucntion(file, fast){},//取消文件上传
		upload : function(file, keepVars) {},
		destroy : function() {},
	}
    $.fn.uploadifive = function(method) {

        if (methods[method]) {
            return methods[method].apply(this,Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error('The method ' + method + ' does not exist in $.uploadify');
        }

    }
})(jQuery);

知识点:
1.$.extend为类级别的扩展,如:

//扩展
$.extend({
	req:function(url){return true}
})
//使用
$.req('xxxx')

2.$.fn.extend为对象级别的扩展,如:

//扩展
$.fn.extend({
	validate:function(){return true}
})
//使用
$("#id").validate();

3.callapply通常用于动态改变this,调用其他对象的方法,区别在于call参数逐个传递,apply将所有参数放入数组(arguments)行传递

三、解析

1.首先扩展中对传入的method参数进行判断,如果存在这个方法就直接调用相应的methods对象中的方法,否则如果是个对象,就调用methods的init初始化方法。

2.init 方法

 var $this = $(this);//当前被绑定的input的context object
	 $this.data('uploadifive', {
            // 这里初始化的是文件上传队列、多文件上传、文件数量等记录用的
            //属性         
     });
 //暂存uploadifive 并保存在$data中
 var $data = $this.data('uploadifive');
 //将用户绑定上传组件传入的参数与默认的参数进行合并
 var settings = $data.settings = $.extend({},options);
 //计算用户传入的配置参数fileSizeLimit
 if (isNaN(settings.fileSizeLimit)) {
	//如果有kb等单位,这里根据用户输入的是KB MB GB进行文件大小计算
 } else {
	//否则以kb为单位
	settings.fileSizeLimit = settings.fileSizeLimit * 1024;
 }
 //创建type为file的input模板保存为$data的一个属性,这个模板会在后面取代用户页面原有的input 作为一个可点击的透明层存在
 $data.inputTemplate = $('<input type="file">')
 .css({
      'font-size' : settings.height + 'px',
      'opacity'   : 0,
      'position'  : 'absolute',
      'right'     : '-3px',
      'top'       : '-3px',
      'z-index'   : 999 
  });
  //接下来初始化一系列方法绑定到$data上
  //创建一个新的input 会用到上面的inputTemplate
  $data.createInput = function(){...}
  //清除一input
  $data.destroyInput = function(){...}
  //拖动上传方法
  $data.drop = function(e) {...}
  //检查文件在队列中是否存在方法
  $data.fileExistsInQueue = function(file) {...}
  //删除队列中一个已经存在文件
  $data.removeExistingFile = function(file) {...}
  //接下来初始化创建itemTemplate模板,这个模板就是开始上传时,会有一个进度  条以及一个x点击取消按钮,保存至$data.queueItem中
  if (settings.itemTemplate == false) {
     $data.queueItem = $('模板div代码');
  } else {
      $data.queueItem = $(settings.itemTemplate);
  }
  //添加文件到文件队列中的方法初始化
  $data.addQueueItem = function(file) {...}
  //上传队列中移除其中一个,多文件上传时候用到
  $data.removeQueueItem = function(file, instant, delay) {...}
  //计算待上传文件的数量方法
  $data.filesToUpload = function() {...}
  //检查文件是否存在的方法
  $data.checkExists = function(file){...}
  //真正实现文件流上传的方法
  $data.uploadFile = function(file, uploadAll) {...}
  //更新文件上传进度的方法
  $data.progress = function(e, file) {...}
  //错误信息触发
  $data.error = function(errorType, file, uploadAll) {}
  //文件上传完成后触发的方法
  $data.uploadComplete = function(e, file, uploadAll) {}
  //全部文件上传完成触发的方法
  $data.queueComplete = function() {}

截止到现在,上面的代码都是方法的定义,模板的定义全部保存在$data中,页面中还没有任何的效果,接下来开始执行一些列跟页面文件上传展现相关初始化操作。

//判断是否支持HTML5
if (window.File && window.FileList && window.Blob && (window.FileReader || window.FormData)) {

下面这部分的操作就是创建一个按钮,设置基本的按钮的样式,如果用户传入了自定 义的设置参数,则相应的赋值。按钮其实是在透明的input file上的一个div

	settings.id = 'uploadifive-' + $this.attr('id');
	
	// Wrap the file input in a div with overflow set to hidden
	$data.button = $('<div id="' + settings.id + '" class="uploadifive-button">' + settings.buttonText + '</div>');
	if (settings.buttonClass) $data.button.addClass(settings.buttonClass);
	
	// Style the button wrapper
	$data.button.css({
	    'height'      : settings.height,
	    'line-height' : settings.height + 'px', 
	    'overflow'    : 'hidden',
	    'position'    : 'relative',
	    'text-align'  : 'center', 
	    'width'       : settings.width
	});
	// Insert the button above the file input
	$this.before($data.button)
	// Add the file input to the button
	.appendTo($data.button) //将原有的input type=file 插入到div中并display none
	// Modify the styles of the file input
	.hide();
}

到现在按钮可以显示在页面上了,但是上传的操作还没有初始化、绑定。接下来调用上面已经初始化好的createInput方法,创建一个新的input type file以便添加用户的一些配置信息,如允许的文件类型,是否多文件上传等。

$data.createInput.call($this);

createInput方法内容:

$data.createInput = function() {
	//获取之前初始化好的保存在$data中的input type file模板
	var input = $data.inputTemplate.clone();
	//设置input name multip accept等属性
	...
	//对创建的input绑定change事件
	input.bind('change', function() {
		//初始化队列记录参数
		...
		//判断是否超过了设置的上传队列数量限制,超过了则回调报错,否则进行添加入队列处理
		if(超过了限制){
			onError();
		}else{
			//遍历files逐个添加到上传队列
			for (var n = 0; n < limit; n++) {
               file = this.files[n];
               $data.addQueueItem(file);
            }
            $data.inputs[inputName] = this;
            $data.createInput();//重新创建input并绑定change事件
		}
	}
	//将绑定好事件的input插入到之前创建的button 就是div层里面
	$data.button.append(input);
}

然后代码回到$data.createInput.call($this);的调用位置接着往下走,可以看到有一个是否设置了queueID的判断,就是如果设置了queueID就直接创建一个div容器,这里面就是上传时,上传状态进度等信息出现的地方,如果没有,则默认在上传按钮下面创建一个有默认id和class的容器,暂时保存在$data.queueEl中。

if (!settings.queueID) {
	settings.queueID = settings.id + '-queue';
    $data.queueEl = $('<div id="' + settings.queueID + '" class="uploadifive-queue" />');
    $data.button.after($data.queueEl);
} else {
    $data.queueEl = $('#' + settings.queueID);
}

uploadifive还支持拖动上传,下面的代码是绑定拖动上传的监听事件

if (settings.dnd) {
	//dragleave
	//dragenter
	//dragover
	//drop
}

初始化操作已经完成,等待上传的触发!
当点击选择图片->选择一个文件->确定上传,则触发了createInput中绑定的change事件,这里是一个过渡,只是做了队列文件数量的验证,然后遍历文件对象调用$data.addQueueItem(file).

addQueueItem,添加file对象到队列中,事实上多文件上传是html5支持的,所以并不是uploadifive的特性。这个方法完成的事情包括:

  1. 判断队列中是否有重复文件,有则移除重复
  2. 创建上传进度信息显示,并绑定取消上传的点击事件
  3. file.queueItem.data('file', file); 暂存文件流,在后面真正的文件上传时候要获取文件流
if(onAddQueueItem方法 没有被覆盖){
	完成上面所述的1,2,3操作
}
//如果用户绑定时有onAddQueueItem则触发回调
...
//文件大小判断
...

执行完暂存的加入队列的操作,回到createInput的绑定change事件内继续执行,如果配置了自动上传,调用upload方法。

if (settings.auto) {
	 methods.upload.call($this);
}

下面进入upload方法。upload方法首先拿到所有uploadifive的配置信息,然后判断是否直接传入了file文件流,是则调用真正的上传方法uploadFile

if (file) {
	$data.uploadFile.call($this, file);
} else {
	if(没有超过上传文件文件数量限制){
		//循环上传队列中没有错误和没有完成的文件
        $('#' + settings.queueID).find('.uploadifive-queue-item').not('.error, .complete').each(function() {
        /**
         * $(this)代表的是当前的上传队列的对象,而在addQueeuItem中 有
         * 这样一句 file.queueItem.data('file', file);就是缓存了数据
         * 所以这里可以直接访问到暂存数据
         */
         _file = $(this).data('file');
         // Check if the simUpload limit was reached
         //是否配置有重复文件检查脚本链接checkScript参数,则check,否则调用真正的uploadFile方法
         if (settings.checkScript) {
             _file.checking = true;
             skipFile = $data.checkExists(_file);
             _file.checking = false;
             if (!skipFile) {
                 $data.uploadFile(_file, true);
             }
         } else {
             $data.uploadFile(_file, true);
         }
        });
        if (队列中文件都上传完成) {
            $data.queueComplete();
        }
	}
}

uploadFile中使用了两种上传方式,如果支持FormData则使用异步的ajax方式上传,否则使用FileReader以二进制流的方式上传。这种文件上传的方式还是值得学习一下的。

//创建XMLHttpRequest对象 
xhr = file.xhr = new XMLHttpRequest();
if(支持FormData){
	//创建表单
	xhr.open(settings.method, settings.uploadScript, true);
    // On progress function
     xhr.upload.addEventListener('progress', function(e) {
         if (e.lengthComputable) {
	         //调用progress方法,progress方法就是计算上传完成百分比
             $data.progress(e, file);
         }
     }, false);
}else{
	//以二进制流方法上传
	var reader = new FileReader();
	...
}

最后上传完成调用queueComplete方法,整个流程走完了。

四、总结

简化版的执行流程是:
绑定input,定制参数->初始化上传方法、属性->createInput创建上传按钮->绑定change事件->文件上传触发change->文件天加到队列调用addQueueItem->上传调用upload->upload必要验证后调用真正的上传方法uploadFile->上传过程中调用progress计算进度->上传完成调用queueComplete方法.

附粗陋流程图:
uploaifive

posted on 2016-09-22 11:25  BigNerd  阅读(2149)  评论(0编辑  收藏  举报

导航