1
0
Fork 0

Added a drag & drop upload zip field. Added explaination texts for zip and github

This commit is contained in:
Michael Hackstein 2014-12-15 16:59:12 +01:00
parent 2c026cecb4
commit 88e304fce0
5 changed files with 712 additions and 57 deletions

View File

@ -0,0 +1,534 @@
/*!
* jQuery Upload File Plugin
* version: 3.1.0
* @requires jQuery v1.5 or later & form plugin
* Copyright (c) 2013 Ravishanker Kusuma
* http://hayageek.com/
*/
(function ($) {
if ($.fn.ajaxForm == undefined) {
$.getScript("http://malsup.github.io/jquery.form.js");
}
var feature = {};
feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
feature.formdata = window.FormData !== undefined;
$.fn.uploadFile = function (options) {
// This is the easiest way to have default options.
var s = $.extend({
// These are the defaults.
url: "",
method: "POST",
enctype: "multipart/form-data",
formData: null,
returnType: null,
allowedTypes: "*",
fileName: "file",
formData: {},
dynamicFormData: function () {
return {};
},
maxFileSize: -1,
multiple: true,
dragDrop: true,
autoSubmit: true,
showCancel: true,
showAbort: true,
showDone: true,
showDelete:false,
showError: true,
showStatusAfterSuccess: true,
showStatusAfterError: true,
showFileCounter:true,
fileCounterStyle:"). ",
showProgress:false,
onSelect:function(files){ return true;},
onSubmit: function (files, xhr) {},
onSuccess: function (files, response, xhr) {},
onError: function (files, status, message) {},
deleteCallback: false,
afterUploadAll: false,
uploadButtonClass: "ajax-file-upload",
dragDropStr: "<span><b>Drag &amp; Drop Files</b></span>",
abortStr: "Abort",
cancelStr: "Cancel",
deletelStr: "Delete",
doneStr: "Done",
multiDragErrorStr: "Multiple File Drag &amp; Drop is not allowed.",
extErrorStr: "is not allowed. Allowed extensions: ",
sizeErrorStr: "is not allowed. Allowed Max size: ",
uploadErrorStr: "Upload is not allowed"
}, options);
this.fileCounter = 1;
this.fCounter = 0; //failed uploads
this.sCounter = 0; //success uploads
this.tCounter = 0; //total uploads
var formGroup = "ajax-file-upload-" + (new Date().getTime());
this.formGroup = formGroup;
this.hide();
this.errorLog = $("<div></div>"); //Writing errors
this.after(this.errorLog);
this.responses = [];
if (!feature.formdata) //check drag drop enabled.
{
s.dragDrop = false;
}
var obj = this;
var uploadLabel = $('<div>' + $(this).html() + '</div>');
$(uploadLabel).addClass(s.uploadButtonClass);
//wait form ajax Form plugin and initialize
(function checkAjaxFormLoaded() {
if ($.fn.ajaxForm) {
if (s.dragDrop) {
var dragDrop = $('<div class="ajax-upload-dragdrop" style="vertical-align:top;"></div>');
$(obj).before(dragDrop);
$(dragDrop).append(uploadLabel);
$(dragDrop).append($(s.dragDropStr));
setDragDropHandlers(obj, s, dragDrop);
} else {
$(obj).before(uploadLabel);
}
createCutomInputFile(obj, formGroup, s, uploadLabel);
} else window.setTimeout(checkAjaxFormLoaded, 10);
})();
this.startUpload = function () {
$("." + this.formGroup).each(function (i, items) {
if ($(this).is('form')) $(this).submit();
});
}
this.stopUpload = function () {
$(".ajax-file-upload-red").each(function (i, items) {
if ($(this).hasClass(obj.formGroup)) $(this).click();
});
}
this.getResponses = function () {
return this.responses;
}
var checking = false;
function checkPendingUploads() {
if (s.afterUploadAll && !checking) {
checking = true;
(function checkPending() {
if (obj.sCounter != 0 && (obj.sCounter + obj.fCounter == obj.tCounter)) {
s.afterUploadAll(obj);
checking = false;
} else window.setTimeout(checkPending, 100);
})();
}
}
function setDragDropHandlers(obj, s, ddObj) {
ddObj.on('dragenter', function (e) {
e.stopPropagation();
e.preventDefault();
$(this).css('border', '2px solid #A5A5C7');
});
ddObj.on('dragover', function (e) {
e.stopPropagation();
e.preventDefault();
});
ddObj.on('drop', function (e) {
$(this).css('border', '2px dotted #A5A5C7');
e.preventDefault();
obj.errorLog.html("");
var files = e.originalEvent.dataTransfer.files;
if (!s.multiple && files.length > 1) {
if (s.showError) $("<div style='color:red;'>" + s.multiDragErrorStr + "</div>").appendTo(obj.errorLog);
return;
}
if(s.onSelect(files) == false)
return;
serializeAndUploadFiles(s, obj, files);
});
$(document).on('dragenter', function (e) {
e.stopPropagation();
e.preventDefault();
});
$(document).on('dragover', function (e) {
e.stopPropagation();
e.preventDefault();
ddObj.css('border', '2px dotted #A5A5C7');
});
$(document).on('drop', function (e) {
e.stopPropagation();
e.preventDefault();
ddObj.css('border', '2px dotted #A5A5C7');
});
}
function getSizeStr(size) {
var sizeStr = "";
var sizeKB = size / 1024;
if (parseInt(sizeKB) > 1024) {
var sizeMB = sizeKB / 1024;
sizeStr = sizeMB.toFixed(2) + " MB";
} else {
sizeStr = sizeKB.toFixed(2) + " KB";
}
return sizeStr;
}
function serializeData(extraData) {
var serialized = [];
if (jQuery.type(extraData) == "string") {
serialized = extraData.split('&');
} else {
serialized = $.param(extraData).split('&');
}
var len = serialized.length;
var result = [];
var i, part;
for (i = 0; i < len; i++) {
serialized[i] = serialized[i].replace(/\+/g, ' ');
part = serialized[i].split('=');
result.push([decodeURIComponent(part[0]), decodeURIComponent(part[1])]);
}
return result;
}
function serializeAndUploadFiles(s, obj, files) {
for (var i = 0; i < files.length; i++) {
if (!isFileTypeAllowed(obj, s, files[i].name)) {
if (s.showError) $("<div style='color:red;'><b>" + files[i].name + "</b> " + s.extErrorStr + s.allowedTypes + "</div>").appendTo(obj.errorLog);
continue;
}
if (s.maxFileSize != -1 && files[i].size > s.maxFileSize) {
if (s.showError) $("<div style='color:red;'><b>" + files[i].name + "</b> " + s.sizeErrorStr + getSizeStr(s.maxFileSize) + "</div>").appendTo(obj.errorLog);
continue;
}
var ts = s;
var fd = new FormData();
var fileName = s.fileName.replace("[]", "");
fd.append(fileName, files[i]);
var extraData = s.formData;
if (extraData) {
var sData = serializeData(extraData);
for (var j = 0; j < sData.length; j++) {
if (sData[j]) {
fd.append(sData[j][0], sData[j][1]);
}
}
}
ts.fileData = fd;
var pd = new createProgressDiv(obj, s);
var fileNameStr="";
if(s.showFileCounter)
fileNameStr = obj.fileCounter + s.fileCounterStyle + files[i].name
else
fileNameStr = files[i].name;
pd.filename.html(fileNameStr);
var form = $("<form style='display:block; position:absolute;left: 150px;' class='" + obj.formGroup + "' method='" + s.method + "' action='" + s.url + "' enctype='" + s.enctype + "'></form>");
form.appendTo('body');
var fileArray = [];
fileArray.push(files[i].name);
ajaxFormSubmit(form, ts, pd, fileArray, obj);
obj.fileCounter++;
}
}
function isFileTypeAllowed(obj, s, fileName) {
var fileExtensions = s.allowedTypes.toLowerCase().split(",");
var ext = fileName.split('.').pop().toLowerCase();
if (s.allowedTypes != "*" && jQuery.inArray(ext, fileExtensions) < 0) {
return false;
}
return true;
}
function createCutomInputFile(obj, group, s, uploadLabel) {
var fileUploadId = "ajax-upload-id-" + (new Date().getTime());
var form = $("<form method='" + s.method + "' action='" + s.url + "' enctype='" + s.enctype + "'></form>");
var fileInputStr = "<input type='file' id='" + fileUploadId + "' name='" + s.fileName + "'/>";
if (s.multiple) {
if (s.fileName.indexOf("[]") != s.fileName.length - 2) // if it does not endwith
{
s.fileName += "[]";
}
fileInputStr = "<input type='file' id='" + fileUploadId + "' name='" + s.fileName + "' multiple/>";
}
var fileInput = $(fileInputStr).appendTo(form);
fileInput.change(function () {
obj.errorLog.html("");
var fileExtensions = s.allowedTypes.toLowerCase().split(",");
var fileArray = [];
if (this.files) //support reading files
{
for (i = 0; i < this.files.length; i++)
{
fileArray.push(this.files[i].name);
}
if(s.onSelect(this.files) == false)
return;
} else {
var filenameStr = $(this).val();
var flist = [];
fileArray.push(filenameStr);
if (!isFileTypeAllowed(obj, s, filenameStr)) {
if (s.showError) $("<div style='color:red;'><b>" + filenameStr + "</b> " + s.extErrorStr + s.allowedTypes + "</div>").appendTo(obj.errorLog);
return;
}
//fallback for browser without FileAPI
flist.push({name:filenameStr,size:'NA'});
if(s.onSelect(flist) == false)
return;
}
uploadLabel.unbind("click");
form.hide();
createCutomInputFile(obj, group, s, uploadLabel);
form.addClass(group);
if (feature.fileapi && feature.formdata) //use HTML5 support and split file submission
{
form.removeClass(group); //Stop Submitting when.
var files = this.files;
serializeAndUploadFiles(s, obj, files);
} else {
var fileList = "";
for (var i = 0; i < fileArray.length; i++) {
if(s.showFileCounter)
fileList += obj.fileCounter + s.fileCounterStyle + fileArray[i]+"<br>";
else
fileList += fileArray[i]+"<br>";;
obj.fileCounter++;
}
var pd = new createProgressDiv(obj, s);
pd.filename.html(fileList);
ajaxFormSubmit(form, s, pd, fileArray, obj);
}
});
form.css({'margin':0,'padding':0});
var uwidth=$(uploadLabel).width()+10;
if(uwidth == 10)
uwidth =120;
var uheight=uploadLabel.height()+10;
if(uheight == 10)
uheight = 35;
uploadLabel.css({position: 'relative',overflow:'hidden',cursor:'default'});
fileInput.css({position: 'absolute','cursor':'pointer',
'top': '0px',
'width': uwidth,
'height':uheight,
'left': '0px',
'z-index': '100',
'opacity': '0.0',
'filter':'alpha(opacity=0)',
'-ms-filter':"alpha(opacity=0)",
'-khtml-opacity':'0.0',
'-moz-opacity':'0.0'
});
form.appendTo(uploadLabel);
//dont hide it, but move it to
/* form.css({
margin: 0,
padding: 0,
display: 'block',
position: 'absolute',
left: '50px'
});
if (navigator.appVersion.indexOf("MSIE ") != -1) //IE Browser
{
uploadLabel.attr('for', fileUploadId);
} else {
uploadLabel.click(function () {
fileInput.click();
});
}*/
}
function createProgressDiv(obj, s) {
this.statusbar = $("<div class='ajax-file-upload-statusbar'></div>");
this.filename = $("<div class='ajax-file-upload-filename'></div>").appendTo(this.statusbar);
this.progressDiv = $("<div class='ajax-file-upload-progress'>").appendTo(this.statusbar).hide();
this.progressbar = $("<div class='ajax-file-upload-bar " + obj.formGroup + "'></div>").appendTo(this.progressDiv);
this.abort = $("<div class='ajax-file-upload-red " + obj.formGroup + "'>" + s.abortStr + "</div>").appendTo(this.statusbar).hide();
this.cancel = $("<div class='ajax-file-upload-red'>" + s.cancelStr + "</div>").appendTo(this.statusbar).hide();
this.done = $("<div class='ajax-file-upload-green'>" + s.doneStr + "</div>").appendTo(this.statusbar).hide();
this.del = $("<div class='ajax-file-upload-red'>" + s.deletelStr + "</div>").appendTo(this.statusbar).hide();
obj.errorLog.after(this.statusbar);
return this;
}
function ajaxFormSubmit(form, s, pd, fileArray, obj) {
var currentXHR = null;
var options = {
cache: false,
contentType: false,
processData: false,
forceSync: false,
data: s.formData,
formData: s.fileData,
dataType: s.returnType,
beforeSubmit: function (formData, $form, options) {
if (s.onSubmit.call(this, fileArray) != false) {
var dynData = s.dynamicFormData();
if (dynData) {
var sData = serializeData(dynData);
if (sData) {
for (var j = 0; j < sData.length; j++) {
if (sData[j]) {
if (s.fileData != undefined) options.formData.append(sData[j][0], sData[j][1]);
else options.data[sData[j][0]] = sData[j][1];
}
}
}
}
obj.tCounter += fileArray.length;
//window.setTimeout(checkPendingUploads, 1000); //not so critical
checkPendingUploads();
return true;
}
pd.statusbar.append("<div style='color:red;'>" + s.uploadErrorStr + "</div>");
pd.cancel.show()
form.remove();
pd.cancel.click(function () {
pd.statusbar.remove();
});
return false;
},
beforeSend: function (xhr, o) {
pd.progressDiv.show();
pd.cancel.hide();
pd.done.hide();
if (s.showAbort) {
pd.abort.show();
pd.abort.click(function () {
xhr.abort();
});
}
if (!feature.formdata) //For iframe based push
{
pd.progressbar.width('5%');
} else pd.progressbar.width('1%'); //Fix for small files
},
uploadProgress: function (event, position, total, percentComplete) {
//Fix for smaller file uploads in MAC
if(percentComplete > 98) percentComplete =98;
var percentVal = percentComplete + '%';
if (percentComplete > 1) pd.progressbar.width(percentVal)
if(s.showProgress)
{
pd.progressbar.html(percentVal);
pd.progressbar.css('text-align', 'center');
}
},
success: function (data, message, xhr) {
obj.responses.push(data);
pd.progressbar.width('100%')
if(s.showProgress)
{
pd.progressbar.html('100%');
pd.progressbar.css('text-align', 'center');
}
pd.abort.hide();
s.onSuccess.call(this, fileArray, data, xhr);
if (s.showStatusAfterSuccess) {
if (s.showDone) {
pd.done.show();
pd.done.click(function () {
pd.statusbar.hide("slow");
pd.statusbar.remove();
});
} else {
pd.done.hide();
}
if(s.showDelete)
{
pd.del.show();
pd.del.click(function () {
if(s.deleteCallback) s.deleteCallback.call(this, data,pd);
});
}
else
{
pd.del.hide();
}
} else {
pd.statusbar.hide("slow");
pd.statusbar.remove();
}
form.remove();
obj.sCounter += fileArray.length;
},
error: function (xhr, status, errMsg) {
pd.abort.hide();
if (xhr.statusText == "abort") //we aborted it
{
pd.statusbar.hide("slow");
} else {
s.onError.call(this, fileArray, status, errMsg);
if (s.showStatusAfterError) {
pd.progressDiv.hide();
pd.statusbar.append("<span style='color:red;'>ERROR: " + errMsg + "</span>");
} else {
pd.statusbar.hide();
pd.statusbar.remove();
}
}
form.remove();
obj.fCounter += fileArray.length;
}
};
if (s.autoSubmit) {
form.ajaxSubmit(options);
} else {
if (s.showCancel) {
pd.cancel.show();
pd.cancel.click(function () {
form.remove();
pd.statusbar.remove();
});
}
form.ajaxForm(options);
}
}
return this;
}
}(jQuery));

View File

@ -67,7 +67,9 @@
</div> </div>
<div class="tab-pane" id="github"> <div class="tab-pane" id="github">
<div>The repository has to be public. The version has to be a git tag LINKTOTAG.</div> <div>
Download a foxx application from a public <a href="https://www.github.com">github.com</a> repository. In order to define a version please add a <a href="https://git-scm.com/book/en/v2/Git-Basics-Tagging">git tag</a> to your repository using the following format: "v1.2.3". To connect to github your username and the name of the repository are sufficient.
</div>
<div> <div>
<table> <table>
<tr class="tableRow"> <tr class="tableRow">
@ -91,19 +93,15 @@
</div> </div>
<div class="tab-pane" id="zip"> <div class="tab-pane" id="zip">
<div>Todo how ZIP.</div>
<div> <div>
<table> <p>
<tr class="tableRow"> Upload a ZIP-packed foxx application to ArangoDB.
<th class="collectionInfoTh"> The ZIP file has to be created from the folder containing the manifest.json file of your App, do not include the databases folder structure created by ArangoDB.
File*: This is also compatible with the download format for ZIP files on <a href="https://www.github.com">github.com</a>.
</th> Remember to change to a new version number in the manifest.json when uploading an update.
<th class="collectionInfoTh"> </p>
<input id="zip-file" name="zip-file" type="file"/>
</th>
</tr>
</table>
</div> </div>
<div id="upload-foxx-zip">Upload Foxx.zip</div>
</div> </div>
</div> </div>
</script> </script>

View File

@ -371,7 +371,40 @@
///// NEW CODE ///// NEW CODE
installFoxxFromZip: function() { installFoxxFromZip: function(files, data) {
var self = this;
$.ajax({
type: "POST",
async: false,
url: "/_admin/aardvark/foxxes/inspect",
data: JSON.stringify(data),
contentType: "application/json"
}).done(function(res) {
$.ajax({
type: "POST",
async: false,
url: '/_admin/foxx/fetch',
data: JSON.stringify({
name: res.name,
version: res.version,
filename: res.filename
}),
processData: false
}).done(function (result) {
if (result.error === false) {
window.modalView.hide();
self.showConfigureDialog(res.configuration, res.name, res.version);
}
}).fail(function (err) {
var error = JSON.parse(err.responseText);
arangoHelper.arangoError("Error: " + error.errorMessage);
});
}).fail(function(err) {
var error = JSON.parse(err.responseText);
arangoHelper.arangoError("Error: " + error.error);
});
self.hideImportModal();
/*
var self = this; var self = this;
if (this.allowUpload) { if (this.allowUpload) {
this.showSpinner(); this.showSpinner();
@ -384,53 +417,14 @@
contentType: 'application/octet-stream', contentType: 'application/octet-stream',
complete: function(res) { complete: function(res) {
if (res.readyState === 4) { if (res.readyState === 4) {
if (res.status === 201) { if (res.status === 201)
$.ajax({
type: "POST",
async: false,
url: "/_admin/aardvark/foxxes/inspect",
data: res.responseText,
contentType: "application/json"
}).done(function(res) {
console.log("Peter2", res);
$.ajax({
type: "POST",
async: false,
url: '/_admin/foxx/fetch',
data: JSON.stringify({
name: res.name,
version: res.version,
filename: res.filename
}),
processData: false
}).done(function (result) {
console.log("Peter", result);
if (result.error === false) {
window.modalView.hide();
self.showConfigureDialog(res.configuration, res.name, res.version);
}
}).fail(function (err) {
self.hideSpinner();
var error = JSON.parse(err.responseText);
arangoHelper.arangoError("Error: " + error.errorMessage);
});
}).fail(function(err) {
self.hideSpinner();
var error = JSON.parse(err.responseText);
arangoHelper.arangoError("Error: " + error.error);
});
delete self.file;
self.allowUpload = false;
self.hideSpinner();
self.hideImportModal();
return;
}
} }
self.hideSpinner(); self.hideSpinner();
arangoHelper.arangoError("Upload error"); arangoHelper.arangoError("Upload error");
} }
}); });
} }
*/
}, },
installFoxxFromStore: function(e) { installFoxxFromStore: function(e) {
@ -685,9 +679,9 @@
event.preventDefault(); event.preventDefault();
var buttons = []; var buttons = [];
var modalEvents = { var modalEvents = {
"click #infoTab a": this.switchModalButton.bind(this), "click #infoTab a" : this.switchModalButton.bind(this),
"click .install-app" : this.installFoxxFromStore.bind(this), "click .install-app" : this.installFoxxFromStore.bind(this),
"change #zip-file" : this.uploadSetup.bind(this) "change #zip-file" : this.uploadSetup.bind(this)
}; };
buttons.push( buttons.push(
window.modalView.createSuccessButton("Generate", this.addAppAction.bind(this)) window.modalView.createSuccessButton("Generate", this.addAppAction.bind(this))
@ -706,6 +700,11 @@
minimumResultsForSearch: -1, minimumResultsForSearch: -1,
width: "336px" width: "336px"
}); });
$("#upload-foxx-zip").uploadFile({
url: "/_api/upload",
allowedTypes: "zip",
onSuccess: this.installFoxxFromZip.bind(this)
});
var listTempl = this.appStoreTemplate; var listTempl = this.appStoreTemplate;
$.get("foxxes/fishbowl", function(list) { $.get("foxxes/fishbowl", function(list) {
var table = $("#appstore-content"); var table = $("#appstore-content");

View File

@ -0,0 +1,123 @@
.ajax-file-upload-statusbar {
border: 1px solid #0ba1b5;
margin-top: 10px;
width: 420px;
margin-right: 10px;
margin: 5px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
padding: 5px 5px 5px 5px
}
.ajax-file-upload-filename {
width: 100%;
height: auto;
margin: 0 5px 5px 10px;
color: #807579
}
.ajax-file-upload-progress {
margin: 0 10px 5px 10px;
position: relative;
width: 250px;
border: 1px solid #ddd;
padding: 1px;
border-radius: 3px;
display: inline-block
}
.ajax-file-upload-bar {
background-color: #0ba1b5;
width: 0;
height: 20px;
border-radius: 3px;
color:#FFFFFF;
}
.ajax-file-upload-percent {
position: absolute;
display: inline-block;
top: 3px;
left: 48%
}
.ajax-file-upload-red {
-moz-box-shadow: inset 0 39px 0 -24px #e67a73;
-webkit-box-shadow: inset 0 39px 0 -24px #e67a73;
box-shadow: inset 0 39px 0 -24px #e67a73;
background-color: #e4685d;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
display: inline-block;
color: #fff;
font-family: arial;
font-size: 13px;
font-weight: normal;
padding: 4px 15px;
text-decoration: none;
text-shadow: 0 1px 0 #b23e35;
cursor: pointer;
vertical-align: top;
margin-right:5px;
}
.ajax-file-upload-green {
background-color: #77b55a;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
margin: 0;
padding: 0;
display: inline-block;
color: #fff;
font-family: arial;
font-size: 13px;
font-weight: normal;
padding: 4px 15px;
text-decoration: none;
cursor: pointer;
text-shadow: 0 1px 0 #5b8a3c;
vertical-align: top;
margin-right:5px;
}
.ajax-file-upload {
font-family: Arial, Helvetica, sans-serif;
font-size: 16px;
font-weight: bold;
padding: 15px 20px;
cursor:pointer;
line-height:20px;
height:25px;
margin:0 10px 10px 0;
display: inline-block;
background: #fff;
border: 1px solid #e8e8e8;
color: #888;
text-decoration: none;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-moz-box-shadow: 0 2px 0 0 #e8e8e8;
-webkit-box-shadow: 0 2px 0 0 #e8e8e8;
box-shadow: 0 2px 0 0 #e8e8e8;
padding: 6px 10px 4px 10px;
color: #fff;
background: #2f8ab9;
border: none;
-moz-box-shadow: 0 2px 0 0 #13648d;
-webkit-box-shadow: 0 2px 0 0 #13648d;
box-shadow: 0 2px 0 0 #13648d;
vertical-align:middle;
}
.ajax-file-upload:hover {
background: #3396c9;
-moz-box-shadow: 0 2px 0 0 #15719f;
-webkit-box-shadow: 0 2px 0 0 #15719f;
box-shadow: 0 2px 0 0 #15719f;
}
.ajax-upload-dragdrop {
border:2px dotted #A5A5C7;
width:420px;
color: #DADCE3;
text-align:left;
vertical-align:middle;
padding:10px 10px 0px 10px;
}

View File

@ -215,6 +215,7 @@
"frontend/js/lib/jquery.wiggle.min.js", "frontend/js/lib/jquery.wiggle.min.js",
"frontend/js/lib/jquery.contextmenu.js", "frontend/js/lib/jquery.contextmenu.js",
"frontend/js/lib/jquery.hotkeys.js", "frontend/js/lib/jquery.hotkeys.js",
"frontend/js/lib/jquery.uploadfile.js",
"frontend/js/lib/select2.min.js", "frontend/js/lib/select2.min.js",
"frontend/js/lib/handlebars-1.0.rc.1.js", "frontend/js/lib/handlebars-1.0.rc.1.js",
"frontend/js/lib/underscore.js", "frontend/js/lib/underscore.js",