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: "serverError" 95 }; 96 97 var pressButton = function(event) { 98 $(this).children(":first").css("background-position", "3px 3px").css("padding", "4px 4px 2px 22px"); 99 }; 100 101 var unpressButton = function(event) { 102 $(this).children(":first").css("background-position", "2px 2px").css("padding", "3px 5px 3px 21px"); 103 }; 104 105 rf.BaseComponent.extend(rf.ui.FileUpload); 106 107 function TypeRejectedException(fileName) { 108 this.name = "TypeRejectedException"; 109 this.message = "The type of file " + fileName + " is not accepted"; 110 this.fileName = fileName; 111 } 112 113 $.extend(rf.ui.FileUpload.prototype, (function () { 114 115 return { 116 name: "FileUpload", 117 118 doneLabel: "Done", 119 sizeExceededLabel: "File size is exceeded", 120 stoppedLabel: "", 121 serverErrorLabel: "Server error", 122 clearLabel: "Clear", 123 deleteLabel: "Delete", 124 125 __addFiles : function(files) { 126 var context = { 127 acceptedFileNames: [], 128 rejectedFileNames: [] 129 }; 130 131 if (files) { 132 for (var i = 0 ; i < files.length; i++) { 133 this.__tryAddItem(context, files[i]); 134 135 if (this.maxFilesQuantity && this.__getTotalItemCount() >= this.maxFilesQuantity) { 136 this.addButton.hide(); 137 break; 138 } 139 } 140 } else { 141 var fileName = this.input.val(); 142 this.__tryAddItem(context, fileName); 143 } 144 145 if (context.rejectedFileNames.length > 0) { 146 rf.Event.fire(this.element, "ontyperejected", context.rejectedFileNames.join(',')); 147 } 148 149 if (this.immediateUpload) { 150 this.__startUpload(); 151 } 152 }, 153 154 __addItems : function() { 155 this.__addFiles(this.input.prop("files")); 156 }, 157 158 __addItemsFromDrop: function(dropEvent) { 159 dropEvent.stopPropagation(); 160 dropEvent.preventDefault(); 161 162 if (this.maxFilesQuantity && this.__getTotalItemCount() >= this.maxFilesQuantity) { 163 return; 164 } 165 166 this.__addFiles(dropEvent.originalEvent.dataTransfer.files); 167 }, 168 169 __tryAddItem: function(context, file) { 170 try { 171 if (this.__addItem(file)) { 172 context.acceptedFileNames.push(file.name); 173 } 174 } catch (e) { 175 if (e instanceof TypeRejectedException) { 176 context.rejectedFileNames.push(file.name); 177 } else { 178 throw e; 179 } 180 } 181 }, 182 183 __addItem: function(file) { 184 var fileName = file.name; 185 if (!navigator.platform.indexOf("Win")) { 186 fileName = fileName.match(/[^\\]*$/)[0]; 187 } else { 188 if (!fileName.indexOf(FAKE_PATH)) { 189 fileName = fileName.substr(FAKE_PATH.length); 190 } else { 191 fileName = fileName.match(/[^\/]*$/)[0]; 192 } 193 } 194 if (this.__accept(fileName) && (!this.noDuplicate || !this.__isFileAlreadyAdded(fileName))) { 195 this.input.remove(); 196 this.input.unbind("change", this.addProxy); 197 var item = new Item(this, file); 198 this.list.append(item.getJQuery()); 199 this.items.push(item); 200 this.input = this.cleanInput.clone(); 201 this.inputContainer.append(this.input); 202 this.input.change(this.addProxy); 203 this.__updateButtons(); 204 rf.Event.fire(this.element, "onfileselect", fileName); 205 return true; 206 } 207 208 return false; 209 }, 210 211 __removeItem: function(item) { 212 this.items.splice($.inArray(item, this.items), 1); 213 this.submitedItems.splice($.inArray(item, this.submitedItems), 1); 214 this.__updateButtons(); 215 rf.Event.fire(this.element, "onclear", [item.model]); 216 }, 217 218 __removeAllItems: function(item) { 219 var itemsRemoved = []; 220 for (var i in this.submitedItems) { 221 itemsRemoved.push(this.submitedItems[i].model); 222 } 223 for (var i in this.items) { 224 itemsRemoved.push(this.items[i].model); 225 } 226 this.list.empty(); 227 this.items.splice(0, this.items.length); 228 this.submitedItems.splice(0, this.submitedItems.length); 229 this.__updateButtons(); 230 rf.Event.fire(this.element, "onclear", itemsRemoved); 231 }, 232 233 __updateButtons: function() { 234 if (!this.loadableItem && this.list.children(".rf-fu-itm").size()) { 235 if (this.items.length) { 236 this.uploadButton.css("display", "inline-block"); 237 } else { 238 this.uploadButton.hide(); 239 } 240 this.clearButton.css("display", "inline-block"); 241 } else { 242 this.uploadButton.hide(); 243 this.clearButton.hide(); 244 } 245 if (this.maxFilesQuantity && this.__getTotalItemCount() >= this.maxFilesQuantity) { 246 this.addButton.hide(); 247 } else { 248 this.addButton.css("display", "inline-block"); 249 } 250 }, 251 252 __startUpload: function() { 253 if (!this.items.length) { 254 this.__finishUpload(); 255 return; 256 } 257 this.loadableItem = this.items.shift(); 258 this.__updateButtons(); 259 this.loadableItem.startUploading(); 260 }, 261 262 __accept: function(fileName) { 263 fileName = fileName.toUpperCase(); 264 var result = !this.acceptedTypes; 265 for (var i = 0; !result && i < this.acceptedTypes.length; i++) { 266 var extension = "." + this.acceptedTypes[i]; 267 268 if (extension === "." && fileName.indexOf(".") < 0) { 269 // no extension 270 result = true; 271 } else { 272 result = fileName.indexOf(extension, fileName.length - extension.length) !== -1; 273 } 274 } 275 if (!result) { 276 throw new TypeRejectedException(fileName); 277 } 278 return result; 279 }, 280 281 __isFileAlreadyAdded: function(fileName) { 282 var result = false; 283 for (var i = 0; !result && i < this.items.length; i++) { 284 result = this.items[i].model.name == fileName; 285 } 286 result = result || (this.loadableItem && this.loadableItem.model.name == fileName); 287 for (var i = 0; !result && i < this.submitedItems.length; i++) { 288 result = this.submitedItems[i].model.name == fileName; 289 } 290 return result; 291 }, 292 293 294 __getTotalItemCount : function() { 295 return this.__getItemCountByState(this.items, ITEM_STATE.NEW) 296 + this.__getItemCountByState(this.submitedItems, ITEM_STATE.DONE); 297 }, 298 299 __getItemCountByState : function(items) { 300 var statuses = {}; 301 var s = 0; 302 for ( var i = 1; i < arguments.length; i++) { 303 statuses[arguments[i]] = true; 304 } 305 for ( var i = 0; i < items.length; i++) { 306 if (statuses[items[i].model.state]) { 307 s++; 308 } 309 } 310 return s; 311 }, 312 313 __finishUpload : function() { 314 this.loadableItem = null; 315 this.__updateButtons(); 316 var items = []; 317 for (var i in this.submitedItems) { 318 items.push(this.submitedItems[i].model); 319 } 320 for (var i in this.items) { 321 items.push(this.items[i].model); 322 } 323 rf.Event.fire(this.element, "onuploadcomplete", items); 324 } 325 }; 326 })()); 327 328 329 var Item = function(fileUpload, file) { 330 this.fileUpload = fileUpload; 331 this.model = {name: file.name, state: ITEM_STATE.NEW, file: file}; 332 }; 333 334 $.extend(Item.prototype, { 335 getJQuery: function() { 336 this.element = $(ITEM_HTML); 337 var leftArea = this.element.children(".rf-fu-itm-lft:first"); 338 this.label = leftArea.children(".rf-fu-itm-lbl:first"); 339 this.state = this.label.nextAll(".rf-fu-itm-st:first"); 340 this.progressBar = leftArea.find(".progress-bar"); 341 this.progressBar.parent().hide(); 342 this.progressLabel = this.progressBar.find('span'); 343 this.link = leftArea.next().children("a"); 344 this.label.html(this.model.name); 345 this.link.html(this.fileUpload["deleteLabel"]); 346 this.link.click($.proxy(this.removeOrStop, this)); 347 return this.element; 348 }, 349 350 removeOrStop: function() { 351 this.element.remove(); 352 this.fileUpload.__removeItem(this); 353 }, 354 355 startUploading: function() { 356 this.state.css("display", "block"); 357 this.progressBar.parent().show(); 358 this.progressLabel.html("0 %"); 359 this.link.html(""); 360 this.model.state = ITEM_STATE.UPLOADING; 361 this.uid = Math.random(); 362 363 var formData = new FormData(this.fileUpload.form[0]); 364 fileName = this.model.file.name; 365 366 formData.append(this.fileUpload.id, this.model.file); 367 368 var originalAction = this.fileUpload.form.attr("action"), 369 delimiter = originalAction.indexOf("?") == -1 ? "?" : "&", 370 newAction = originalAction + delimiter + UID + "=" + this.uid + 371 "&javax.faces.partial.ajax=true" + 372 "&javax.faces.source=" + this.fileUpload.id + 373 "&javax.faces.partial.execute=" + this.fileUpload.id + 374 "&org.richfaces.ajax.component=" + this.fileUpload.id + 375 "&javax.faces.ViewState=" + this.fileUpload.form.find('input[name="javax.faces.ViewState"]').val(); 376 377 if (jsf.getClientWindow && jsf.getClientWindow()) { 378 newAction += "&javax.faces.ClientWindow=" + jsf.getClientWindow(); 379 }; 380 381 this.xhr = new XMLHttpRequest(); 382 383 this.xhr.open('POST', newAction, true); 384 this.xhr.setRequestHeader('Faces-Request', 'partial/ajax'); 385 386 this.xhr.upload.onprogress = $.proxy(function(e) { 387 if (e.lengthComputable) { 388 var progress = Math.floor((e.loaded / e.total) * 100); 389 this.progressLabel.html( progress + " %" ); 390 this.progressBar.attr("aria-valuenow", progress); 391 this.progressBar.css("width", progress + "%"); 392 } 393 }, this); 394 395 this.xhr.upload.onerror = $.proxy(function (e) { 396 this.finishUploading(ITEM_STATE.SERVER_ERROR); 397 }, this); 398 399 this.xhr.onload = $.proxy(function (e) { 400 switch (e.target.status) { 401 case 413: 402 responseStatus = ITEM_STATE.SIZE_EXCEEDED; 403 break; 404 case 200: 405 responseStatus = ITEM_STATE.DONE; 406 break; 407 default: // 500 - error in processing parts 408 responseStatus = ITEM_STATE.SERVER_ERROR; 409 } 410 411 var responseContext = { 412 source: this.fileUpload.element[0], 413 element: this.fileUpload.element[0], 414 /* hack for MyFaces *