Skip to content

Instantly share code, notes, and snippets.

Created December 31, 2011 14:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/1544144 to your computer and use it in GitHub Desktop.
Save anonymous/1544144 to your computer and use it in GitHub Desktop.
File input UI like Gmail
goog.provide('arielarea.ui.FileInput');
goog.require('goog.array');
goog.require('goog.string');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.EventTarget');
goog.require('goog.events.FileDropHandler');
goog.require('goog.Timer');
goog.require('goog.ui.Component');
goog.require('goog.userAgent');
/**
* @constructor
* @extends {goog.ui.Component}
*/
arielarea.ui.FileInput = function (opt_domHelper) {
goog.base(this, opt_domHelper);
this.eh_ = new goog.events.EventHandler(this);
this.window_ = window;
};
goog.inherits(arielarea.ui.FileInput, goog.ui.Component);
arielarea.ui.FileInput.prototype.decorateInternal = function (element) {
this.elem_ = element;
this.inputName_ = element.name;
this.form_ = goog.dom.getAncestorByTagNameAndClass(element, 'form');
var dzElem = goog.dom.getElementByClass('arielarea-dropzone', this.form_);
this.dropZone_ = new arielarea.ui.FileInput.DropZone(dzElem, this);
this.appendDropZone(this.dropZone_);
this.eh_.listen(this.dropZone_, goog.events.FileDropHandler.EventType.DROP, function (e) {
var files = e.getBrowserEvent().dataTransfer.files;
this.handleDroppedFiles_(files, this.dropZone_);
});
this.eh_.listen(this.elem_, goog.events.EventType.CHANGE, function (e) {
var files = e.target.files;
this.handleDroppedFiles_(files, this.dropZone_);
e.target.value = null;
e.stopPropagation();
e.preventDefault();
});
var fiElem = goog.dom.getElementByClass('arielarea-fileitem', this.form_);
this.renderer_ = new arielarea.ui.FileInput.Renderer(fiElem);
this.showOnEnter();
this.hideOnLeave();
this.updateFilesOnRemove();
};
arielarea.ui.FileInput.prototype.disposeInternal = function () {
if (this.eh_) {
this.eh_.dispose();
this.eh_ = null;
}
this.clearFiles();
arielarea.ui.FileInput.superClass_.disposeInternal.call(this);
};
arielarea.ui.FileInput.prototype.appendDropZone = function (dropZone) {
goog.dom.insertSiblingBefore(dropZone.getElement(), this.elem_);
};
arielarea.ui.FileInput.prototype.setRenderer = function (renderer) {
this.renderer_ = renderer;
};
arielarea.ui.FileInput.prototype.getRenderer = function () {
return this.renderer_;
};
arielarea.ui.FileInput.prototype.getInputName = function () {
return this.inputName_;
};
arielarea.ui.FileInput.prototype.showOnEnter = function () {
this.eh_.listenOnce(this.window_, goog.events.EventType.DRAGENTER, function (e) {
this.dndContainsFiles_ = this.containsFiles_(e);
if (this.dndContainsFiles_) {
this.showDropZone();
}
});
};
/*
* This logic is copied from @link {goog.events.FileDropHandler#onDocDragEnter_}
*/
arielarea.ui.FileInput.prototype.containsFiles_ = function (dragEvent) {
var dt = dragEvent.getBrowserEvent().dataTransfer;
// Check whether the drag event contains files.
var dndContainsFiles = !!(dt &&
((dt.types &&
(goog.array.contains(dt.types, 'Files') ||
goog.array.contains(dt.types, 'public.file-url'))) ||
(dt.files && dt.files.length > 0)));
return dndContainsFiles;
};
arielarea.ui.FileInput.prototype.showDropZone = function () {
goog.style.showElement(this.elem_, false);
this.dropZone_.show(true);
this.shown_ = true;
};
arielarea.ui.FileInput.prototype.hideDropZone = function () {
this.dropZone_.show(false);
goog.style.showElement(this.elem_, true);
this.shown_ = false;
this.showOnEnter();
};
arielarea.ui.FileInput.prototype.hideOnLeave = function () {
if (goog.userAgent.GECKO) {
this.hideAfterWait();
} else {
this.eh_.listen(this.window_, goog.events.EventType.DRAGLEAVE, function (e) {
var event = e.getBrowserEvent();
if (this.isOutOfWindow_(event)) {
this.hideDropZone();
}
});
}
};
arielarea.ui.FileInput.prototype.hideAfterWait = function () {
this.eh_.listen(this.window_, goog.events.EventType.DRAGOVER, function (e) {
goog.Timer.clear(this.hideTimer_);
this.hideTimer_ = null;
this.hideTimer_ = goog.Timer.callOnce(goog.bind(function () {
this.hideDropZone();
this.hideTimer_ = null;
}, this), 750);
if (!this.shown_ && this.dndContainsFiles_) {
this.showDropZone();
}
}, true);
};
arielarea.ui.FileInput.prototype.handleDroppedFiles_ = function (files, dropZone) {
var inputName = this.getInputName();
goog.array.forEach(files, function (file) {
if (this.validateFile_(file)) {
var fileItem = new arielarea.ui.FileInput.FileItem(file, inputName);
dropZone.add(fileItem);
this.renderer_.renderFile(fileItem, this.elem_, this.eh_);
}
}, this);
this.hideDropZone();
};
arielarea.ui.FileInput.prototype.updateFilesOnRemove = function () {
this.eh_.listen(this.renderer_, arielarea.ui.FileInput.EventType.REMOVE, function (e) {
this.handleRemovedFile_(e.fileItem, this.dropZone_);
});
};
arielarea.ui.FileInput.prototype.handleRemovedFile_ = function (fileItem, dropZone) {
if (fileItem) {
dropZone.remove(fileItem);
}
};
arielarea.ui.FileInput.prototype.getFiles = function () {
var files = this.dropZone_.getFiles();
return files;
};
arielarea.ui.FileInput.prototype.clearFiles = function () {
this.dropZone_.clearFiles();
};
arielarea.ui.FileInput.prototype.validateFile_ = function (file) {
if (file.size === 0) {
return false;
}
if (file.type) {
return true;
}
var fileName = file.name || file.fileName;
var ext = (function getExtension (fileName) {
if (!fileName) {
return;
}
if (!goog.string.contains(fileName, '.')) {
return;
}
return /[^.]+$/.exec(fileName)[0];
}(fileName));
if (ext) {
file.extension = ext;
}
return true;
};
arielarea.ui.FileInput.prototype.isOutOfWindow_ = function (event) {
var x = event.clientX;
var y = event.clientY;
if (x <= 0 || y <= 0) {
return true;
}
var size = goog.style.getSize(this.window_);
return x >= size.width || y >= size.height;
};
arielarea.ui.FileInput.EventType = {
DROP: 'drop',
REMOVE: 'remove'
};
/**
* @constructor
* @extends {goog.events.FileDropHandler}
*/
arielarea.ui.FileInput.DropZone = function (element, owner) {
var elem = (element) ? element.cloneNode(true) : goog.dom.createDom(
'div', {
'class': 'arielarea-dropzone',
'style': 'display: none;'
});
goog.base(this, elem, true);
this.owner_ = owner;
this.elem_ = elem;
this.files_ = [];
};
goog.inherits(arielarea.ui.FileInput.DropZone, goog.events.FileDropHandler);
arielarea.ui.FileInput.DropZone.prototype.getElement = function () {
return this.elem_;
};
arielarea.ui.FileInput.DropZone.prototype.getFiles = function () {
return this.files_;
};
arielarea.ui.FileInput.DropZone.prototype.show = function (show) {
goog.style.showElement(this.getElement(), show);
};
arielarea.ui.FileInput.DropZone.prototype.add = function (fileItem) {
this.files_.push(fileItem);
};
arielarea.ui.FileInput.DropZone.prototype.remove = function (fileItem) {
goog.array.remove(this.files_, fileItem);
fileItem.dispose();
};
arielarea.ui.FileInput.DropZone.prototype.clearFiles = function () {
goog.array.forEach(this.files_, function (fileItem) {
fileItem.dispose();
});
this.files = [];
};
/**
* @constructor
* @extends {goog.Disposable}
*/
arielarea.ui.FileInput.FileItem = function (file, inputName) {
goog.base(this);
this.file_ = file;
this.fileName_ = file.name || file.fileName;
this.inputName_ = inputName;
this.elem_;
this.owner_;
};
goog.inherits(arielarea.ui.FileInput.FileItem, goog.Disposable);
arielarea.ui.FileInput.FileItem.prototype.disposeInternal = function () {
goog.dom.removeNode(this.element);
this.elem_ = null;
this.file_ = null;
};
arielarea.ui.FileInput.FileItem.prototype.getFileName = function () {
return this.fileName_;
};
arielarea.ui.FileInput.FileItem.prototype.setElement = function (element) {
this.elem_ = element;
};
/**
* @constructor
* @extends {goog.events.EventTarget}
*/
arielarea.ui.FileInput.Renderer = function (template) {
goog.base(this);
this.template_ = template;
goog.style.showElement(template, false);
};
goog.inherits(arielarea.ui.FileInput.Renderer, goog.events.EventTarget);
arielarea.ui.FileInput.Renderer.prototype.getFileTemplate = function () {
return this.template_;
};
arielarea.ui.FileInput.Renderer.prototype.renderFile = function (
fileItem, anchorElement, eventHandler) {
var fileName = fileItem.getFileName();
var newElem = this.getFileTemplate().cloneNode(true);
fileItem.setElement(newElem);
var itemName = goog.dom.getElementByClass('arielarea-itemname', newElem);
goog.dom.setTextContent(itemName, fileName);
var removeButton = goog.dom.getElementByClass('arielarea-removebtn', newElem);
eventHandler.listen(removeButton, goog.events.EventType.CLICK, function () {
goog.dom.removeNode(newElem);
this.dispatchEvent({
type: arielarea.ui.FileInput.EventType.REMOVE,
fileItem: fileItem
});
}, false, this);
goog.dom.insertSiblingAfter(newElem, anchorElement);
goog.style.showElement(newElem, true);
return newElem;
};
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Sample of arielarea.ui.FileInput</title>
<script src="/path/to/closure/goog/base.js"></script>
<script>
goog.require('goog.array');
goog.require('goog.string');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.EventTarget');
goog.require('goog.events.FileDropHandler');
goog.require('goog.Timer');
goog.require('goog.ui.Component');
goog.require('goog.userAgent');
</script>
<script src="/path/to/fileinput.js"></script>
<style>
.arielarea-dropzone {
width: 80%;
height: 80px;
border: 5px solid #bababa;
background: #dedede;
text-align: center;
line-height: 80px;
font-size: 32px;
color: #787878;
}
.arielarea-fileitem {
position: relative;
width: 200px;
height: 20px;
margin: 3px;
padding-left: 2px;
border: 3px solid #cacaca;
background: #fafafa;
text-align: left;
line-height: 20px;
color: #252525;
overflow: hidden;
}
.arielarea-removebtn {
position: absolute;
background: #f66767;
top: 0px;
right: 0px;
width: 20px;
height: 20px;
text-align: center;
line-height: 20px;
}
.arielarea-removebtn:hover {
cursor: pointer;
background: #f69797;
color: #989898;
}
</style>
</head>
<body>
<form>
<input type="file" name="fileinput-sample" id="fileinput-sample" class="arielarea-fileinput">
<div class="arielarea-dropzone" style="display: none;">Drop files here</div>
<div class="arielarea-fileitem" style="display: none;">
<div class="arielarea-itemname"></div>
<div class="arielarea-removebtn">x</div>
</div>
</form>
<script>
(function () {
var fi = new arielarea.ui.FileInput();
fi.decorate(goog.dom.getElement("fileinput-sample"));
}());
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment