1 /* 2 * JBoss, Home of Professional Open Source 3 * Copyright ${year}, Red Hat, Inc. and individual contributors 4 * by the @authors tag. See the copyright.txt in the distribution for a 5 * full listing of individual contributors. 6 * 7 * This is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU Lesser General Public License as 9 * published by the Free Software Foundation; either version 2.1 of 10 * the License, or (at your option) any later version. 11 * 12 * This software is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this software; if not, write to the Free 19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 21 */ 22 (function($, rf) { 23 24 rf.ui = rf.ui || {}; 25 26 rf.ui.FileUpload = function(id, options) { 27 this.id = id; 28 this.items = []; 29 this.submitedItems = []; 30 31 $.extend(this, options); 32 if (this.acceptedTypes) { 33 this.acceptedTypes = $.trim(this.acceptedTypes).toUpperCase().split(/\s*,\s*/); 34 } 35 if (this.maxFilesQuantity) { 36 this.maxFilesQuantity = parseInt($.trim(this.maxFilesQuantity)); 37 } 38 this.element = $(this.attachToDom()); 39 this.form = this.element.parents("form:first"); 40 var header = this.element.children(".rf-fu-hdr:first"); 41 var leftButtons = header.children(".rf-fu-btns-lft:first"); 42 this.addButton = leftButtons.children(".rf-fu-btn-add:first"); 43 this.uploadButton = this.addButton.next(); 44 this.clearButton = leftButtons.next().children(".rf-fu-btn-clr:first"); 45 this.inputContainer = this.addButton.find(".rf-fu-inp-cntr:first"); 46 this.input = this.inputContainer.children("input"); 47 this.list = header.next(); 48 this.element.bind('dragenter', function(e) {e.stopPropagation(); e.preventDefault();}); 49 this.element.bind('dragover', function(e) {e.stopPropagation(); e.preventDefault();}); 50 this.element.bind('drop', $.proxy(this.__addItemsFromDrop, this)); 51 52 this.hiddenContainer = this.list.next(); 53 this.cleanInput = this.input.clone(); 54 this.addProxy = $.proxy(this.__addItems, this); 55 this.input.change(this.addProxy); 56 this.addButton.mousedown(pressButton).mouseup(unpressButton).mouseout(unpressButton); 57 this.uploadButton.click($.proxy(this.__startUpload, this)).mousedown(pressButton) 58 .mouseup(unpressButton).mouseout(unpressButton); 59 this.clearButton.click($.proxy(this.__removeAllItems, this)).mousedown(pressButton) 60 .mouseup(unpressButton).mouseout(unpressButton); 61 if (this.onfilesubmit) { 62 rf.Event.bind(this.element, "onfilesubmit", new Function("event", this.onfilesubmit)); 63 } 64 if (this.ontyperejected) { 65 rf.Event.bind(this.element, "ontyperejected", new Function("event", this.ontyperejected)); 66 } 67 if (this.onuploadcomplete) { 68 rf.Event.bind(this.element, "onuploadcomplete", new Function("event", this.onuploadcomplete)); 69 } 70 if (this.onclear) { 71 rf.Event.bind(this.element, "onclear", new Function("event", this.onclear)); 72 } 73 if (this.onfileselect) { 74 rf.Event.bind(this.element, "onfileselect", new Function("event", this.onfileselect)); 75 } 76 }; 77 78 var UID = "rf_fu_uid"; 79 var UID_ALT = "rf_fu_uid_alt"; 80 var FAKE_PATH = "C:\\fakepath\\"; 81 var ITEM_HTML = '<div class="rf-fu-itm">' 82 + '<span class="rf-fu-itm-lft"><span class="rf-fu-itm-lbl"/><span class="rf-fu-itm-st" />' 83 + '<div class="progress progress-striped active">' 84 + '<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">' 85 + '<span></span></div></div></span>' 86 + '<span class="rf-fu-itm-rgh"><a href="javascript:void(0)" class="rf-fu-itm-lnk"/></span></div>'; 87 88 var ITEM_STATE = { 89 NEW: "new", 90 UPLOADING: "uploading", 91 DONE: "done", 92 SIZE_EXCEEDED: "sizeExceeded", 93 STOPPED: "stopped", 94 SERVER_ERROR_PROCESS: "serverErrorProc", 95 SERVER_ERROR_UPLOAD: "serverErrorUp" 96 }; 97 98 var pressButton = function(event) { 99 $(this).children(":first").css("background-position", "3px 3px").css("padding", "4px 4px 2px 22px"); 100 }; 101 102 var unpressButton = function(event) { 103 $(this).children(":first").css("background-position", "2px 2px").css("padding", "3px 5px 3px 21px"); 104 }; 105 106 rf.BaseComponent.extend(rf.ui.FileUpload); 107 108 function TypeRejectedException(fileName) { 109 this.name = "TypeRejectedException"; 110 this.message = "The type of file " + fileName + " is not accepted"; 111 this.fileName = fileName; 112 } 113 114 $.extend(rf.ui.FileUpload.prototype, (function () { 115 116 return { 117 name: "FileUpload", 118 119 doneLabel: "Done", 120 sizeExceededLabel: "File size is exceeded", 121 stoppedLabel: "", 122 serverErrorProcLabel: "Server error: error in processing", 123 serverErrorUpLabel: "Server error: upload failed", 124 clearLabel: "Clear", 125 deleteLabel: "Delete", 126 127 __addFiles : function(files) { 128 var context = { 129 acceptedFileNames: [], 130 rejectedFileNames: [] 131 }; 132 133 if (files) { 134 for (var i = 0 ; i < files.length; i++) { 135 this.__tryAddItem(context, files[i]); 136 137 if (this.maxFilesQuantity && this.__getTotalItemCount() >= this.maxFilesQuantity) { 138 this.addButton.hide(); 139 break; 140 } 141 } 142 } else { 143 var fileName = this.input.val(); 144 this.__tryAddItem(context, fileName); 145 } 146 147 if (context.rejectedFileNames.length > 0) { 148 rf.Event.fire(this.element, "ontyperejected", context.rejectedFileNames.join(',')); 149 } 150 151 if (this.immediateUpload) { 152 this.__startUpload(); 153 } 154 }, 155 156 __addItems : function() { 157 this.__addFiles(this.input.prop("files")); 158 }, 159 160 __addItemsFromDrop: function(dropEvent) { 161 dropEvent.stopPropagation(); 162 dropEvent.preventDefault(); 163 164 if (this.maxFilesQuantity && this.__getTotalItemCount() >= this.maxFilesQuantity) { 165 return; 166 } 167 168 this.__addFiles(dropEvent.originalEvent.dataTransfer.files); 169 }, 170 171 __tryAddItem: function(context, file) { 172 try { 173 if (this.__addItem(file)) { 174 context.acceptedFileNames.push(file.name); 175 } 176 } catch (e) { 177 if (e instanceof TypeRejectedException) { 178 context.rejectedFileNames.push(file.name); 179 } else { 180 throw e; 181 } 182 } 183 }, 184 185 __addItem: function(file) { 186 var fileName = file.name; 187 if (!navigator.platform.indexOf("Win")) { 188 fileName = fileName.match(/[^\\]*$/)[0]; 189 } else { 190 if (!fileName.indexOf(FAKE_PATH)) { 191 fileName = fileName.substr(FAKE_PATH.length); 192 } else { 193 fileName = fileName.match(/[^\/]*$/)[0]; 194 } 195 } 196 if (this.__accept(fileName) && (!this.noDuplicate || !this.__isFileAlreadyAdded(fileName))) { 197 this.input.remove(); 198 this.input.unbind("change", this.addProxy); 199 var item = new Item(this, file); 200 this.list.append(item.getJQuery()); 201 this.items.push(item); 202 this.input = this.cleanInput.clone(); 203 this.inputContainer.append(this.input); 204 this.input.change(this.addProxy); 205 this.__updateButtons(); 206 rf.Event.fire(this.element, "onfileselect", fileName); 207 return true; 208 } 209 210 return false; 211 }, 212 213 __removeItem: function(item) { 214 this.items.splice($.inArray(item, this.items), 1); 215 this.submitedItems.splice($.inArray(item, this.submitedItems), 1); 216 this.__updateButtons(); 217 rf.Event.fire(this.element, "onclear", [item.model]); 218 }, 219 220 __removeAllItems: function(item) { 221 var itemsRemoved = []; 222 for (var i in this.submitedItems) { 223 itemsRemoved.push(this.submitedItems[i].model); 224 } 225 for (var i in this.items) { 226 itemsRemoved.push(this.items[i].model); 227 } 228 this.list.empty(); 229 this.items.splice(0, this.items.length); 230 this.submitedItems.splice(0, this.submitedItems.length); 231 this.__updateButtons(); 232 rf.Event.fire(this.element, "onclear", itemsRemoved); 233 }, 234 235 __updateButtons: function() { 236 if (!this.loadableItem && this.list.children(".rf-fu-itm").size()) { 237 if (this.items.length) { 238 this.uploadButton.css("display", "inline-block"); 239 } else { 240 this.uploadButton.hide(); 241 } 242 this.clearButton.css("display", "inline-block"); 243 } else { 244 this.uploadButton.hide(); 245 this.clearButton.hide(); 246 } 247 if (this.maxFilesQuantity && this.__getTotalItemCount() >= this.maxFilesQuantity) { 248 this.addButton.hide(); 249 } else { 250 this.addButton.css("display", "inline-block"); 251 } 252 }, 253 254 __startUpload: function() { 255 if (!this.items.length) { 256 this.__finishUpload(); 257 return; 258 } 259 this.loadableItem = this.items.shift(); 260 this.__updateButtons(); 261 this.loadableItem.startUploading(); 262 }, 263 264 __accept: function(fileName) { 265 fileName = fileName.toUpperCase(); 266 var result = !this.acceptedTypes; 267 for (var i = 0; !result && i < this.acceptedTypes.length; i++) { 268 var extension = "." + this.acceptedTypes[i]; 269 270 if (extension === "." && fileName.indexOf(".") < 0) { 271 // no extension 272 result = true; 273 } else { 274 result = fileName.indexOf(extension, fileName.length - extension.length) !== -1; 275 } 276 } 277 if (!result) { 278 throw new TypeRejectedException(fileName); 279 } 280 return result; 281 }, 282 283 __isFileAlreadyAdded: function(fileName) { 284 var result = false; 285 for (var i = 0; !result && i < this.items.length; i++) { 286 result = this.items[i].model.name == fileName; 287 } 288 result = result || (this.loadableItem && this.loadableItem.model.name == fileName); 289 for (var i = 0; !result && i < this.submitedItems.length; i++) { 290 result = this.submitedItems[i].model.name == fileName; 291 } 292 return result; 293 }, 294 295 296 __getTotalItemCount : function() { 297 return this.__getItemCountByState(this.items, ITEM_STATE.NEW) 298 + this.__getItemCountByState(this.submitedItems, ITEM_STATE.DONE); 299 }, 300 301 __getItemCountByState : function(items) { 302 var statuses = {}; 303 var s = 0; 304 for ( var i = 1; i < arguments.length; i++) { 305 statuses[arguments[i]] = true; 306 } 307 for ( var i = 0; i < items.length; i++) { 308 if (statuses[items[i].model.state]) { 309 s++; 310 } 311 } 312 return s; 313 }, 314 315 __finishUpload : function() { 316 this.loadableItem = null; 317 this.__updateButtons(); 318 var items = []; 319 for (var i in this.submitedItems) { 320 items.push(this.submitedItems[i].model); 321 } 322 for (var i in this.items) { 323 items.push(this.items[i].model); 324 } 325 rf.Event.fire(this.element, "onuploadcomplete", items); 326 } 327 }; 328 })()); 329 330 331 var Item = function(fileUpload, file) { 332 this.fileUpload = fileUpload; 333 this.model = {name: file.name, state: ITEM_STATE.NEW, file: file}; 334 }; 335 336 $.extend(Item.prototype, { 337 getJQuery: function() { 338 this.element = $(ITEM_HTML); 339 var leftArea = this.element.children(".rf-fu-itm-lft:first"); 340 this.label = leftArea.children(".rf-fu-itm-lbl:first"); 341 this.state = this.label.nextAll(".rf-fu-itm-st:first"); 342 this.progressBar = leftArea.find(".progress-bar"); 343 this.progressBar.parent().hide(); 344 this.progressLabel = this.progressBar.find('span'); 345 this.link = leftArea.next().children("a"); 346 this.label.html(this.model.name); 347 this.link.html(this.fileUpload["deleteLabel"]); 348 this.link.click($.proxy(this.removeOrStop, this)); 349 return this.element; 350 }, 351 352 removeOrStop: function() { 353 this.element.remove(); 354 this.fileUpload.__removeItem(this); 355 }, 356 357 startUploading: function() { 358 this.state.css("display", "block"); 359 this.progressBar.parent().show(); 360 this.progressLabel.html("0 %"); 361 this.link.html(""); 362 this.model.state = ITEM_STATE.UPLOADING; 363 this.uid = Math.random(); 364 365 var formData = new FormData(this.fileUpload.form[0]); 366 fileName = this.model.file.name; 367 368 formData.append(this.fileUpload.id, this.model.file); 369 370 var originalAction = this.fileUpload.form.attr("action"), 371 delimiter = originalAction.indexOf("?") == -1 ? "?" : "&", 372 newAction = originalAction + delimiter + UID + "=" + this.uid + 373 "&javax.faces.partial.ajax=true" + 374 "&javax.faces.source=" + this.fileUpload.id + 375 "&javax.faces.partial.execute=" + this.fileUpload.id + 376 "&org.richfaces.ajax.component=" + this.fileUpload.id + 377 "&" + jsf.getViewState(this.fileUpload.form[0]); 378 379 if (jsf.getClientWindow && jsf.getClientWindow()) { 380 newAction += "&javax.faces.ClientWindow=" + jsf.getClientWindow(); 381 }; 382 383 this.xhr = new XMLHttpRequest(); 384 385 this.xhr.open('POST', newAction, true); 386 this.xhr.setRequestHeader('Faces-Request', 'partial/ajax'); 387 388 this.xhr.upload.onprogress = $.proxy(function(e) { 389 if (e.lengthComputable) { 390 var progress = Math.floor((e.loaded / e.total) * 100); 391 this.progressLabel.html( progress + " %" ); 392 this.progressBar.attr("aria-valuenow", progress); 393 this.progressBar.css("width", progress + "%"); 394 } 395 }, this); 396 397 this.xhr.upload.onerror = $.proxy(function (e) { 398 this.fileUpload.loadableItem = null; 399 this.finishUploading(ITEM_STATE.SERVER_ERROR_UPLOAD); 400 }, this); 401 402 this.xhr.onload = $.proxy(function (e) { 403 switch (e.target.status) { 404 case 413: 405 responseStatus = ITEM_STATE.SIZE_EXCEEDED; 406 break; 407 case 200: 408 responseStatus = ITEM_STATE.DONE; 409 break; 410 default: // 500 - error in processing parts 411 responseStatus = ITEM_STATE.SERVER_ERROR_PROCESS; 412 } 413 414 var responseContext = { 415 source: this.fileUpload.element[0], 416 element: this.fileUpload.element[0], 417 /* hack for MyFaces *