',\r\n\r\n classes: {\r\n // used to get elements from templates\r\n button: 'qq-upload-button',\r\n drop: 'qq-upload-drop-area',\r\n dropActive: 'qq-upload-drop-area-active',\r\n list: 'qq-upload-list',\r\n\r\n file: 'qq-upload-file',\r\n spinner: 'qq-upload-spinner',\r\n size: 'qq-upload-size',\r\n cancel: 'qq-upload-cancel',\r\n message: 'qq-upload-message',\r\n\r\n // added to list item when upload completes\r\n // used in css to hide progress spinner\r\n success: 'qq-upload-success',\r\n fail: 'qq-upload-fail'\r\n },\r\n messages: {\r\n //serverError: \"Some files were not uploaded, please contact support and/or try again.\",\r\n countError: \"Max {count} fil(er) får laddas upp.\",\r\n typeError: \"{file} är inte en godkänd filtyp.\",\r\n sizeError: \"{file} är för stor, max storlek på filer är {sizeLimit}.\",\r\n emptyError: \"{file} är tom, var god välj en fil med innehåll.\"\r\n },\r\n labels: {\r\n buttonText: 'Bifoga filer',\r\n buttonTitle: 'Bifoga filer'\r\n },\r\n showMessage: function (message) {\r\n var msg = this.element.querySelectorAll('.qq-upload-message');\r\n\r\n msg.forEach(function (element) {\r\n element.querySelectorAll('span').forEach(function (span) {\r\n span.parentNode.removeChild(span);\r\n });\r\n\r\n var newSpan = document.createElement('span');\r\n newSpan.innerHTML = message;\r\n element.insertAdjacentElement('afterbegin', newSpan);\r\n\r\n if (message.length > 0) {\r\n element.classList.remove('qq-hidden');\r\n\r\n element.querySelectorAll('a.close').forEach(function (link) {\r\n link.addEventListener('click', function (evt) {\r\n element.classList.add('qq-hidden');\r\n evt.preventDefault();\r\n });\r\n });\r\n }\r\n });\r\n\r\n }\r\n };\r\n\r\n qq.extend(this._options, o);\r\n\r\n this._element = this._options.element;\r\n\r\n if (this._element) {\r\n\r\n if (this._element.nodeType != 1) {\r\n throw new Error('element param of FileUploader should be dom node');\r\n }\r\n\r\n this._element.innerHTML =\r\n this._options.template\r\n .replace('{buttonText}', this._options.labels.buttonText)\r\n .replace('{buttonTitle}', this._options.labels.buttonTitle);\r\n\r\n // number of files being uploaded\r\n this._filesInProgress = 0;\r\n\r\n // easier access\r\n this._classes = this._options.classes;\r\n\r\n this._handler = this._createUploadHandler();\r\n\r\n this._bindCancelEvent();\r\n\r\n var self = this;\r\n this._button = new qq.UploadButton({\r\n element: this._getElement('button'),\r\n showMessage: this._options.showMessage, //knowit-hack\r\n multiple: qq.UploadHandlerXhr.isSupported(),\r\n onChange: function (input) {\r\n self._onInputChange(input);\r\n }\r\n });\r\n\r\n this._setupDragDrop();\r\n }\r\n};\r\n\r\nqq.FileUploader.prototype = {\r\n setParams: function (params) {\r\n this._options.params = params;\r\n },\r\n /**\r\n * Returns true if some files are being uploaded, false otherwise\r\n */\r\n isUploading: function () {\r\n return !!this._filesInProgress;\r\n },\r\n /**\r\n * Gets one of the elements listed in this._options.classes\r\n * \r\n * First optional element is root for search,\r\n * this._element is default value.\r\n *\r\n * Usage\r\n * 1. this._getElement('button');\r\n * 2. this._getElement(item, 'file'); \r\n **/\r\n _getElement: function (parent, type) {\r\n if (typeof parent == 'string') {\r\n // parent was not passed\r\n type = parent;\r\n parent = this._element;\r\n }\r\n\r\n var element = qq.getByClass(parent, this._options.classes[type])[0];\r\n\r\n if (!element) {\r\n throw new Error('element not found ' + type);\r\n }\r\n\r\n return element;\r\n },\r\n _error: function (code, fileName, fileSize) {\r\n var message = this._options.messages[code];\r\n message = message.replace('{file}', this._formatFileName(fileName));\r\n message = message.replace('{extensions}', this._options.allowedExtensions.join(', '));\r\n message = message.replace('{sizeLimit}', this._formatSize(this._options.sizeLimit));\r\n message = message.replace('{count}', this._options.maxCount);\r\n\r\n if (fileSize) {\r\n message = message.replace('{fileSize}', this._formatSize(fileSize));\r\n }\r\n this._options.showMessage(message);\r\n },\r\n _formatFileName: function (name) {\r\n if (name.length > 33) {\r\n name = name.slice(0, 19) + '...' + name.slice(-13);\r\n }\r\n return name;\r\n },\r\n _isAllowedExtension: function (fileName) {\r\n var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';\r\n\r\n console.debug('EXT: ' + ext);\r\n console.debug('EXT Allowed: ' + this._options.allowedExtensions.join());\r\n var allowed = this._options.allowedExtensions;\r\n\r\n if (!allowed.length) { return true; }\r\n\r\n for (var i = 0; i < allowed.length; i++) {\r\n if (allowed[i].toLowerCase() == ext) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n },\r\n _setupDragDrop: function () {\r\n function isValidDrag(e) {\r\n\r\n if (!self._options.allowDragDrop)\r\n return false;\r\n\r\n var dt = e.dataTransfer,\r\n // do not check dt.types.contains in webkit, because it crashes safari 4 \r\n isWebkit = navigator.userAgent.indexOf(\"AppleWebKit\") > -1;\r\n\r\n // dt.effectAllowed is none in Safari 5\r\n // dt.types.contains check is for firefox \r\n return dt && dt.effectAllowed != 'none' &&\r\n (dt.files || (!isWebkit && dt.types.contains && dt.types.contains('Files')));\r\n }\r\n\r\n var self = this,\r\n dropArea = this._getElement('drop');\r\n\r\n dropArea.style.display = 'none';\r\n\r\n var hideTimeout;\r\n qq.attach(document, 'dragenter', function (e) {\r\n e.preventDefault();\r\n });\r\n\r\n qq.attach(document, 'dragover', function (e) {\r\n if (isValidDrag(e)) {\r\n\r\n if (hideTimeout) {\r\n clearTimeout(hideTimeout);\r\n }\r\n\r\n if (dropArea == e.target || qq.contains(dropArea, e.target)) {\r\n var effect = e.dataTransfer.effectAllowed;\r\n if (effect == 'move' || effect == 'linkMove') {\r\n e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed) \r\n } else {\r\n e.dataTransfer.dropEffect = 'copy'; // for Chrome\r\n }\r\n qq.addClass(dropArea, self._classes.dropActive);\r\n e.stopPropagation();\r\n } else {\r\n dropArea.style.display = 'block';\r\n e.dataTransfer.dropEffect = 'none';\r\n }\r\n\r\n e.preventDefault();\r\n }\r\n });\r\n\r\n qq.attach(document, 'dragleave', function (e) {\r\n if (isValidDrag(e)) {\r\n\r\n if (dropArea == e.target || qq.contains(dropArea, e.target)) {\r\n qq.removeClass(dropArea, self._classes.dropActive);\r\n e.stopPropagation();\r\n } else {\r\n\r\n if (hideTimeout) {\r\n clearTimeout(hideTimeout);\r\n }\r\n\r\n hideTimeout = setTimeout(function () {\r\n dropArea.style.display = 'none';\r\n }, 77);\r\n }\r\n }\r\n });\r\n\r\n qq.attach(dropArea, 'drop', function (e) {\r\n dropArea.style.display = 'none';\r\n self._uploadFileList(e.dataTransfer.files);\r\n e.preventDefault();\r\n });\r\n },\r\n _createUploadHandler: function () {\r\n var self = this,\r\n handlerClass;\r\n\r\n if (qq.UploadHandlerXhr.isSupported()) {\r\n handlerClass = 'UploadHandlerXhr';\r\n } else {\r\n handlerClass = 'UploadHandlerForm';\r\n }\r\n\r\n var handler = new qq[handlerClass]({\r\n action: this._options.action,\r\n onProgress: function (id, fileName, loaded, total) {\r\n // is only called for xhr upload\r\n self._updateProgress(id, loaded, total);\r\n },\r\n onComplete: function (id, fileName, result) {\r\n self._filesInProgress--;\r\n // mark completed\r\n var item = self._getItemByFileId(id);\r\n qq.remove(self._getElement(item, 'cancel'));\r\n qq.remove(self._getElement(item, 'spinner'));\r\n\r\n if (result.success) {\r\n qq.addClass(item, self._classes.success);\r\n } else {\r\n qq.addClass(item, self._classes.fail);\r\n\r\n if (result.error) {\r\n self._options.showMessage(result.error);\r\n }\r\n }\r\n\r\n self._options.onComplete(id, fileName, result);\r\n }\r\n });\r\n\r\n return handler;\r\n },\r\n _onInputChange: function (input) {\r\n\r\n if (this._handler instanceof qq.UploadHandlerXhr) {\r\n if (this._testMaxCount(input.files.length))\r\n this._uploadFileList(input.files);\r\n\r\n } else {\r\n if (this._testMaxCount(1))\r\n if (this._validateFile(input)) {\r\n this._uploadFile(input);\r\n }\r\n\r\n }\r\n\r\n this._button.reset();\r\n },\r\n _uploadFileList: function (files) {\r\n\r\n var valid = true;\r\n var i = files.length;\r\n\r\n while (i--) {\r\n if (!this._validateFile(files[i])) {\r\n valid = false;\r\n break;\r\n }\r\n }\r\n\r\n if (valid) {\r\n var i = files.length;\r\n while (i--) { this._uploadFile(files[i]); }\r\n }\r\n },\r\n\r\n _testMaxCount: function (addingCount) {\r\n var list = this._getElement('list');\r\n var uploadFiles = list.querySelectorAll('.qq-upload-file').length;\r\n\r\n if ((uploadFiles + addingCount) > this._options.maxCount) {\r\n this._error(\"countError\", \"\");\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n ,\r\n _uploadFile: function (fileContainer) {\r\n var id = this._handler.add(fileContainer);\r\n var name = this._handler.getName(id);\r\n this._options.onSubmit(id, name);\r\n this._addToList(id, name);\r\n this._handler.upload(id, this._options.params);\r\n },\r\n _validateFile: function (file) {\r\n var name, size;\r\n\r\n if (file.value) {\r\n // it is a file input \r\n // get input value and remove path to normalize\r\n name = file.value.replace(/.*(\\/|\\\\)/, \"\");\r\n } else {\r\n // fix missing properties in Safari\r\n name = file.fileName != null ? file.fileName : file.name;\r\n size = file.fileSize != null ? file.fileSize : file.size;\r\n }\r\n\r\n if (!this._isAllowedExtension(name)) {\r\n this._error('typeError', name);\r\n return false;\r\n\r\n } else if (size === 0) {\r\n this._error('emptyError', name);\r\n return false;\r\n\r\n } else if (size && this._options.sizeLimit && size > this._options.sizeLimit) {\r\n this._error('sizeError', name, size);\r\n return false;\r\n }\r\n\r\n return true;\r\n },\r\n\r\n _addToList: function (id, fileName) {\r\n var item = qq.toElement(this._options.fileTemplate);\r\n item.qqFileId = id;\r\n\r\n var fileElement = this._getElement(item, 'file');\r\n qq.setText(fileElement, this._formatFileName(fileName));\r\n this._getElement(item, 'size').style.display = 'none';\r\n\r\n this._getElement('list').appendChild(item);\r\n\r\n this._filesInProgress++;\r\n\r\n // hack...\r\n var listElement = this._getElement('list');\r\n var noFilesElements = listElement.querySelectorAll('.qq-no-files');\r\n noFilesElements.forEach(function (element) {\r\n if (element.closest('li')) {\r\n element.closest('li').remove();\r\n }\r\n });\r\n\r\n document.querySelectorAll('.qq-upload-message').forEach(function (element) {\r\n element.classList.add('qq-hidden');\r\n });\r\n\r\n // let the world now we have added an item to the list\r\n this._options.onFileListChanged();\r\n\r\n },\r\n\r\n _updateProgress: function (id, loaded, total) {\r\n var item = this._getItemByFileId(id);\r\n var size = this._getElement(item, 'size');\r\n size.style.display = 'inline';\r\n\r\n var text;\r\n if (loaded != total) {\r\n text = Math.round(loaded / total * 100) + '% from ' + this._formatSize(total);\r\n } else {\r\n text = this._formatSize(total);\r\n }\r\n\r\n qq.setText(size, text);\r\n },\r\n _formatSize: function (bytes) {\r\n var i = -1;\r\n do {\r\n bytes = bytes / 1024;\r\n i++;\r\n } while (bytes > 99);\r\n\r\n return Math.max(bytes, 0.1).toFixed(1) + [' kb', ' mb', 'GB', 'TB', 'PB', 'EB'][i];\r\n },\r\n _getItemByFileId: function (id) {\r\n var item = this._getElement('list').firstChild;\r\n\r\n // there can't be text nodes in our dynamically created list\r\n // because of that we can safely use nextSibling\r\n while (item) {\r\n if (item.qqFileId == id) {\r\n return item;\r\n }\r\n\r\n item = item.nextSibling;\r\n }\r\n },\r\n /**\r\n * delegate click event for cancel link \r\n **/\r\n _bindCancelEvent: function () {\r\n var self = this,\r\n list = this._getElement('list');\r\n\r\n qq.attach(list, 'click', function (e) {\r\n e = e || window.event;\r\n var target = e.target || e.srcElement;\r\n\r\n if (qq.hasClass(target, self._classes.cancel)) {\r\n qq.preventDefault(e);\r\n\r\n var item = target.parentNode;\r\n self._handler.cancel(item.qqFileId);\r\n qq.remove(item);\r\n if (self._filesInProgress > 0)\r\n self._filesInProgress--;\r\n }\r\n });\r\n }\r\n};\r\n\r\nqq.UploadButton = function (o) {\r\n this._options = {\r\n element: null,\r\n // if set to true adds multiple attribute to file input \r\n multiple: false,\r\n // name attribute of file input\r\n name: 'file',\r\n onChange: function (input) { },\r\n hoverClass: 'qq-upload-button-hover',\r\n focusClass: 'qq-upload-button-focus'\r\n };\r\n\r\n qq.extend(this._options, o);\r\n\r\n this._element = this._options.element;\r\n\r\n // make button suitable container for input\r\n qq.css(this._element, {\r\n position: 'relative',\r\n overflow: 'hidden',\r\n // Make sure browse button is in the right side\r\n // in Internet Explorer\r\n direction: 'ltr'\r\n });\r\n\r\n this._input = this._createInput();\r\n};\r\n\r\nqq.UploadButton.prototype = {\r\n /* returns file input element */\r\n getInput: function () {\r\n return this._input;\r\n },\r\n /* cleans/recreates the file input */\r\n reset: function () {\r\n if (this._input.parentNode) {\r\n qq.remove(this._input);\r\n }\r\n\r\n qq.removeClass(this._element, this._options.focusClass);\r\n this._input = this._createInput();\r\n },\r\n _createInput: function () {\r\n var input = document.createElement(\"input\");\r\n\r\n if (this._options.multiple) {\r\n input.setAttribute(\"multiple\", \"multiple\");\r\n }\r\n\r\n input.setAttribute(\"type\", \"file\");\r\n input.setAttribute(\"name\", this._options.name);\r\n\r\n qq.css(input, {\r\n position: 'absolute',\r\n // in Opera only 'browse' button\r\n // is clickable and it is located at\r\n // the right side of the input\r\n right: 0,\r\n top: 0,\r\n zIndex: 1,\r\n //fontSize: '460px',\r\n margin: 0,\r\n padding: 0,\r\n cursor: 'pointer',\r\n opacity: 0\r\n });\r\n\r\n this._element.appendChild(input);\r\n\r\n var self = this;\r\n qq.attach(input, 'change', function () {\r\n self._options.onChange(input);\r\n });\r\n\r\n qq.attach(input, 'mouseover', function () {\r\n qq.addClass(self._element, self._options.hoverClass);\r\n });\r\n qq.attach(input, 'mouseout', function () {\r\n qq.removeClass(self._element, self._options.hoverClass);\r\n });\r\n qq.attach(input, 'focus', function () {\r\n qq.addClass(self._element, self._options.focusClass);\r\n });\r\n qq.attach(input, 'blur', function () {\r\n qq.removeClass(self._element, self._options.focusClass);\r\n });\r\n //knowit-hack\r\n qq.attach(input, 'click', function () {\r\n self._options.showMessage('');\r\n });\r\n\r\n qq.attach(input, 'keyup', function (key) {\r\n if (key.which == 13) {\r\n var e = new Event('keydown', {\r\n bubbles: true,\r\n cancelable: true\r\n });\r\n this.dispatchEvent(e);\r\n }\r\n });\r\n\r\n // IE and Opera, unfortunately have 2 tab stops on file input\r\n // which is unacceptable in our case, disable keyboard access\r\n if (window.attachEvent) {\r\n // it is IE or Opera\r\n input.setAttribute('tabIndex', \"-1\");\r\n }\r\n\r\n return input;\r\n }\r\n};\r\n\r\n/**\r\n* Class for uploading files using form and iframe\r\n*/\r\nqq.UploadHandlerForm = function (o) {\r\n this._options = {\r\n // URL of the server-side upload script,\r\n // should be on the same domain to get response\r\n action: '/upload',\r\n // fires for each file, when iframe finishes loading\r\n onComplete: function (id, fileName, response) { }\r\n };\r\n qq.extend(this._options, o);\r\n\r\n this._inputs = {};\r\n};\r\nqq.UploadHandlerForm.prototype = {\r\n /**\r\n * Adds file input to the queue\r\n * Returns id to use with upload, cancel\r\n **/\r\n add: function (fileInput) {\r\n fileInput.setAttribute('name', 'qqfile');\r\n var id = 'qq-upload-handler-iframe' + qq.getUniqueId();\r\n\r\n this._inputs[id] = fileInput;\r\n\r\n // remove file input from DOM\r\n if (fileInput.parentNode) {\r\n qq.remove(fileInput);\r\n }\r\n\r\n return id;\r\n },\r\n /**\r\n * Sends the file identified by id and additional query params to the server\r\n * @param {Object} params name-value string pairs\r\n */\r\n upload: function (id, params) {\r\n var input = this._inputs[id];\r\n\r\n if (!input) {\r\n throw new Error('file with passed id was not added, or already uploaded or cancelled');\r\n }\r\n\r\n var fileName = this.getName(id);\r\n\r\n var iframe = this._createIframe(id);\r\n var form = this._createForm(iframe, params);\r\n form.appendChild(input);\r\n\r\n var self = this;\r\n this._attachLoadEvent(iframe, function () {\r\n self._options.onComplete(id, fileName, self._getIframeContentJSON(iframe));\r\n\r\n delete self._inputs[id];\r\n // timeout added to fix busy state in FF3.6\r\n setTimeout(function () {\r\n qq.remove(iframe);\r\n }, 1);\r\n });\r\n\r\n form.submit();\r\n qq.remove(form);\r\n\r\n return id;\r\n },\r\n cancel: function (id) {\r\n if (id in this._inputs) {\r\n delete this._inputs[id];\r\n }\r\n\r\n var iframe = document.getElementById(id);\r\n if (iframe) {\r\n // to cancel request set src to something else\r\n // we use src=\"javascript:false;\" because it doesn't\r\n // trigger ie6 prompt on https\r\n iframe.setAttribute('src', 'javascript:false;');\r\n\r\n qq.remove(iframe);\r\n }\r\n },\r\n getName: function (id) {\r\n // get input value and remove path to normalize\r\n return this._inputs[id].value.replace(/.*(\\/|\\\\)/, \"\");\r\n },\r\n _attachLoadEvent: function (iframe, callback) {\r\n qq.attach(iframe, 'load', function () {\r\n // when we remove iframe from dom\r\n // the request stops, but in IE load\r\n // event fires\r\n if (!iframe.parentNode) {\r\n return;\r\n }\r\n\r\n // fixing Opera 10.53\r\n if (iframe.contentDocument &&\r\n iframe.contentDocument.body &&\r\n iframe.contentDocument.body.innerHTML == \"false\") {\r\n // In Opera event is fired second time\r\n // when body.innerHTML changed from false\r\n // to server response approx. after 1 sec\r\n // when we upload file with iframe\r\n return;\r\n }\r\n\r\n callback();\r\n });\r\n },\r\n /**\r\n * Returns json object received by iframe from server.\r\n */\r\n _getIframeContentJSON: function (iframe) {\r\n // iframe.contentWindow.document - for IE<7\r\n var doc = iframe.contentDocument ? iframe.contentDocument : iframe.contentWindow.document,\r\n response;\r\n\r\n try {\r\n response = eval(\"(\" + doc.body.innerHTML + \")\");\r\n } catch (err) {\r\n response = {};\r\n }\r\n\r\n return response;\r\n },\r\n /**\r\n * Creates iframe with unique name\r\n */\r\n _createIframe: function (id) {\r\n // We can't use following code as the name attribute\r\n // won't be properly registered in IE6, and new window\r\n // on form submit will open\r\n // var iframe = document.createElement('iframe');\r\n // iframe.setAttribute('name', id);\r\n\r\n var iframe = qq.toElement('');\r\n // src=\"javascript:false;\" removes ie6 prompt on https\r\n\r\n iframe.setAttribute('id', id);\r\n\r\n iframe.style.display = 'none';\r\n document.body.appendChild(iframe);\r\n\r\n return iframe;\r\n },\r\n /**\r\n * Creates form, that will be submitted to iframe\r\n */\r\n _createForm: function (iframe, params) {\r\n // We can't use the following code in IE6\r\n // var form = document.createElement('form');\r\n // form.setAttribute('method', 'post');\r\n // form.setAttribute('enctype', 'multipart/form-data');\r\n // Because in this case file won't be attached to request\r\n var form = qq.toElement('');\r\n\r\n var queryString = '?';\r\n for (var key in params) {\r\n queryString += '&' + key + '=' + encodeURIComponent(params[key]);\r\n }\r\n\r\n form.setAttribute('action', this._options.action + queryString);\r\n form.setAttribute('target', iframe.name);\r\n form.style.display = 'none';\r\n document.body.appendChild(form);\r\n\r\n return form;\r\n }\r\n};\r\n\r\n/**\r\n* Class for uploading files using xhr\r\n*/\r\nqq.UploadHandlerXhr = function (o) {\r\n this._options = {\r\n // url of the server-side upload script,\r\n // should be on the same domain\r\n action: '/upload',\r\n onProgress: function (id, fileName, loaded, total) { },\r\n onComplete: function (id, fileName, response) { }\r\n };\r\n qq.extend(this._options, o);\r\n\r\n this._files = [];\r\n this._xhrs = [];\r\n};\r\n\r\n// static method\r\nqq.UploadHandlerXhr.isSupported = function () {\r\n return typeof File != \"undefined\" &&\r\n typeof (new XMLHttpRequest()).upload != \"undefined\";\r\n};\r\n\r\nqq.UploadHandlerXhr.prototype = {\r\n /**\r\n * Adds file to the queue\r\n * Returns id to use with upload, cancel\r\n **/\r\n add: function (file) {\r\n return this._files.push(file) - 1;\r\n },\r\n /**\r\n * Sends the file identified by id and additional query params to the server\r\n * @param {Object} params name-value string pairs\r\n */\r\n upload: function (id, params) {\r\n var file = this._files[id],\r\n name = this.getName(id),\r\n size = this.getSize(id);\r\n\r\n if (!file) {\r\n throw new Error('file with passed id was not added, or already uploaded or cancelled');\r\n }\r\n\r\n var xhr = this._xhrs[id] = new XMLHttpRequest();\r\n var self = this;\r\n\r\n xhr.upload.onprogress = function (e) {\r\n if (e.lengthComputable) {\r\n self._options.onProgress(id, name, e.loaded, e.total);\r\n }\r\n };\r\n\r\n xhr.onreadystatechange = function () {\r\n // the request was aborted/cancelled\r\n if (!self._files[id]) {\r\n return;\r\n }\r\n\r\n if (xhr.readyState == 4) {\r\n\r\n self._options.onProgress(id, name, size, size);\r\n\r\n if (xhr.status == 200) {\r\n var response;\r\n\r\n try {\r\n response = eval(\"(\" + xhr.responseText + \")\");\r\n } catch (err) {\r\n response = {};\r\n }\r\n\r\n self._options.onComplete(id, name, response);\r\n\r\n } else {\r\n self._options.onComplete(id, name, {});\r\n }\r\n\r\n self._files[id] = null;\r\n self._xhrs[id] = null;\r\n }\r\n };\r\n\r\n // build query string\r\n var queryString = '?filename=' + encodeURIComponent(name);\r\n for (var key in params) {\r\n queryString += '&' + key + '=' + encodeURIComponent(params[key]);\r\n }\r\n\r\n xhr.open(\"POST\", this._options.action + queryString, true);\r\n xhr.send(file);\r\n },\r\n cancel: function (id) {\r\n this._files[id] = null;\r\n\r\n if (this._xhrs[id]) {\r\n this._xhrs[id].abort();\r\n this._xhrs[id] = null;\r\n }\r\n },\r\n getName: function (id) {\r\n // fix missing name in Safari 4\r\n var file = this._files[id];\r\n return file.fileName != null ? file.fileName : file.name;\r\n },\r\n getSize: function (id) {\r\n // fix missing size in Safari 4\r\n var file = this._files[id];\r\n return file.fileSize != null ? file.fileSize : file.size;\r\n }\r\n};\r\n\r\n//\r\n// Helper functions\r\n//\r\n\r\nvar qq = qq || {};\r\n\r\n//\r\n// Useful generic functions\r\n\r\n/**\r\n* Adds all missing properties from obj2 to obj1\r\n*/\r\nqq.extend = function (obj1, obj2) {\r\n for (var prop in obj2) {\r\n obj1[prop] = obj2[prop];\r\n }\r\n};\r\n\r\n/**\r\n* @return {Number} unique id\r\n*/\r\nqq.getUniqueId = (function () {\r\n var id = 0;\r\n return function () {\r\n return id++;\r\n };\r\n})();\r\n\r\n//\r\n// Events\r\n\r\nqq.attach = function (element, type, fn) {\r\n if (element.addEventListener) {\r\n element.addEventListener(type, fn, false);\r\n } else if (element.attachEvent) {\r\n element.attachEvent('on' + type, fn);\r\n }\r\n};\r\nqq.detach = function (element, type, fn) {\r\n if (element.removeEventListener) {\r\n element.removeEventListener(type, fn, false);\r\n } else if (element.attachEvent) {\r\n element.detachEvent('on' + type, fn);\r\n }\r\n};\r\n\r\nqq.preventDefault = function (e) {\r\n if (e.preventDefault) {\r\n e.preventDefault();\r\n } else {\r\n e.returnValue = false;\r\n }\r\n};\r\n//\r\n// Node manipulations\r\n\r\n/**\r\n* Insert node a before node b.\r\n*/\r\nqq.insertBefore = function (a, b) {\r\n b.parentNode.insertBefore(a, b);\r\n};\r\nqq.remove = function (element) {\r\n element.parentNode.removeChild(element);\r\n};\r\n\r\nqq.contains = function (parent, descendant) {\r\n if (parent.contains) {\r\n return parent.contains(descendant);\r\n } else {\r\n return !!(descendant.compareDocumentPosition(parent) & 8);\r\n }\r\n};\r\n\r\n/**\r\n* Creates and returns element from html string\r\n* Uses innerHTML to create an element\r\n*/\r\nqq.toElement = (function () {\r\n var div = document.createElement('div');\r\n return function (html) {\r\n div.innerHTML = html;\r\n var element = div.firstChild;\r\n div.removeChild(element);\r\n return element;\r\n };\r\n})();\r\n\r\n//\r\n// Node properties and attributes\r\n\r\n/**\r\n* Sets styles for an element.\r\n* Fixes opacity in IE6-8.\r\n*/\r\nqq.css = function (element, styles) {\r\n if (styles.opacity != null) {\r\n if (typeof element.style.opacity != 'string' && typeof (element.filters) != 'undefined') {\r\n styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';\r\n }\r\n }\r\n qq.extend(element.style, styles);\r\n};\r\nqq.hasClass = function (element, name) {\r\n var re = new RegExp('(^| )' + name + '( |$)');\r\n return re.test(element.className);\r\n};\r\nqq.addClass = function (element, name) {\r\n if (!qq.hasClass(element, name)) {\r\n element.className += ' ' + name;\r\n }\r\n};\r\nqq.removeClass = function (element, name) {\r\n var re = new RegExp('(^| )' + name + '( |$)');\r\n element.className = element.className.replace(re, ' ').replace(/^\\s+|\\s+$/g, \"\");\r\n};\r\nqq.setText = function (element, text) {\r\n element.innerText = text;\r\n element.textContent = text;\r\n};\r\n\r\n//\r\n// Selecting elements\r\n\r\nqq.children = function (element) {\r\n var children = [],\r\n child = element.firstChild;\r\n\r\n while (child) {\r\n if (child.nodeType == 1) {\r\n children.push(child);\r\n }\r\n child = child.nextSibling;\r\n }\r\n\r\n return children;\r\n};\r\n\r\nqq.getByClass = function (element, className) {\r\n if (element.querySelectorAll) {\r\n return element.querySelectorAll('.' + className);\r\n }\r\n\r\n var result = [];\r\n var candidates = element.getElementsByTagName(\"*\");\r\n var len = candidates.length;\r\n\r\n for (var i = 0; i < len; i++) {\r\n if (qq.hasClass(candidates[i], className)) {\r\n result.push(candidates[i]);\r\n }\r\n }\r\n return result;\r\n};\r\n\r\nexport {qq};","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","import 'core-js/stable';\r\n\r\nimport { qq } from \"./fileuploader/fileuploader\";\r\n\r\n/* \r\nUse function KitfileUploader to attach file uploader to an element.\r\n\r\nWraps upload functionallity in kit.filesystem.fileuploader.js (qq.FileUplader).\r\n\r\nUploaded files are stored in the current user Session object. Retrieve/manipulate stored files \r\non server with FileUploader.Instance...(SPV.Prt.Web.SPVse.SPVModules.FileSystem.BL) helper functions: \r\n- AddFileToStore(string storeKey, CachedFileInfo fileinfo)\r\n- GetUploadedFiles(string storeKey)\r\n- RemoveFileFromStore(string storeKey, Guid fileGuid)\r\n- RemoveAllFilesFromStore(string storeKey)\r\n- StoredFilesToJSon(string storeKey, out string json)\r\n\r\nBy default the html element id will serve as file store session key. The store key \r\ncan also be set in option (KitFileUploader({params: {storeKey : '' }})\r\n\r\nFileUploader.cshtml has a variable uploadFileOptions-\r\nOnly one uploader can be created at a page\r\n*/\r\n\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n var uploadElement = document.querySelector(\".qq-upload-drop-area\");// \r\n if (uploadElement) {\r\n if (!window.location.origin) {\r\n window.location.origin = window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : '');\r\n }\r\n new KitFileUploader(uploadElement, window.uploadFileOptions);\r\n }\r\n});\r\n\r\n/* KIT UPLOADER OBJECT */\r\nfunction KitFileUploader(element, options) {\r\n var $this = this;\r\n\r\n $this.element = element;\r\n $this.element.classList.add('qq-uploader');\r\n\r\n //first make sure storeKey is set\r\n $this.options = Object.assign({ params: { storeKey: $this.element.getAttribute('id') } }, options);\r\n $this.storeKey = $this.options.params.storeKey;\r\n\r\n if (!$this.storeKey) {\r\n $this.element.innerHTML =\r\n 'debug: KitFileUploader storeKey option cannot be empty. Make sure selected element(s) has a valid id-attribute or specify storeKey when calling $KitFileUploader.';\r\n return false;\r\n }\r\n\r\n $this.options = Object.assign(\r\n {\r\n element: $this.element,\r\n baseUrl: '',\r\n action: '/api/uploadfile/',\r\n onComplete: function (id, fileName, response) {\r\n $this.onFileUploadComplete(id, fileName, response, $this.storeKey);\r\n },\r\n onFileListChanged: function () { },\r\n sizeLimit: 20971520 /*20 MB*/,\r\n previewEnabled: false,\r\n },\r\n $this.options\r\n );\r\n\r\n /* kit.filesystem.fileuploader.js */\r\n $this.uploader = new qq.FileUploader(this.options);\r\n\r\n //update file list in case of postback etc\r\n $this.updateFileList(\r\n {\r\n storeKey: this.storeKey,\r\n },\r\n function (response) {\r\n $this.renderFileList(response.files);\r\n\r\n if (response.files.length > 0) $this.options.onFileListChanged();\r\n }\r\n );\r\n\r\n return this;\r\n}\r\n\r\n/* KIT UPLOADER OBJECT PROTOTYPE */\r\nKitFileUploader.prototype = {\r\n /**\r\n * Called when a file upload request is completed\r\n * @param {*} id\r\n * @param {*} fileName\r\n * @param {*} response\r\n */\r\n onFileUploadComplete: function (id, fileName, response) {\r\n var $this = this;\r\n\r\n $this.updateFileList({ storeKey: $this.storeKey }, function (response) {\r\n $this.renderFileList(response.files);\r\n });\r\n },\r\n\r\n /**\r\n * Get info about uploaded files from backend.\r\n * @param {*} settings\r\n * @param {*} onSuccess\r\n * @param {*} onError\r\n */\r\n updateFileList: function (settings, onSuccess, onError) {\r\n fileUploadAjax(\r\n /* url to handler */\r\n '/api/getuploadedfilesinfo/?storeKey=' + settings.storeKey,\r\n\r\n /* request settings */\r\n null,\r\n\r\n /* on request success */\r\n function (response) {\r\n if (onSuccess) onSuccess(response);\r\n },\r\n\r\n /* on request fail */\r\n function (jqxhr, status, error) {\r\n if (onError) onError(jqxhr, status, error);\r\n },\r\n\r\n /* async */\r\n true,\r\n\r\n /* method */\r\n 'GET',\r\n );\r\n },\r\n\r\n /**\r\n * Render file list markup.\r\n * @param {*} files\r\n * @returns\r\n */\r\n renderFileList: function (files) {\r\n var $this = this;\r\n\r\n if ($this.uploader.isUploading()) return;\r\n\r\n var previewsrc;\r\n var ul = this.element.querySelector('ul.qq-upload-list');\r\n if (!ul) return;\r\n while (ul.firstChild) {\r\n ul.removeChild(ul.firstChild);\r\n }\r\n\r\n if (files.length == 0) {\r\n ul.insertAdjacentHTML('beforeend', '
Du har inte bifogat några filer
');\r\n }\r\n\r\n for (var i = 0; i < files.length; i++) {\r\n var fileName = files[i].FileName;\r\n var previewsrc = '';\r\n\r\n if (files[i].PreviewLink && $this.options.previewEnabled) {\r\n fileName =\r\n '' +\r\n files[i].FileName +\r\n '';\r\n }\r\n\r\n ul.insertAdjacentHTML(\r\n 'beforeend',\r\n '