UNPKG

knockout-file-bindings

Version:

HTML5 File bindings for knockout js with drag and drop and custom input buttons

355 lines (316 loc) 15.2 kB
/* * knockout-file-bindings * Copyright 2014 Muhammad Safraz Razik * All Rights Reserved. * Use, reproduction, distribution, and modification of this code is subject to the terms and * conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php * * Author: Muhammad Safraz Razik * Project: https://github.com/adrotec/knockout-file-bindings */ (function (factory) { // Module systems magic dance. if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { // CommonJS or Node: hard-coded dependency on "knockout" factory(require("knockout")); } else if (typeof define === "function" && define["amd"]) { // AMD anonymous module with hard-coded dependency on "knockout" define(["knockout"], factory); } else { // <script> tag: use the global `ko` object, attaching a `mapping` property factory(ko); } }(function (ko) { var fileBindings = { customFileInputSystemOptions: { wrapperClass: 'custom-file-input-wrapper', fileNameClass: 'custom-file-input-file-name', buttonGroupClass: 'custom-file-input-button-group', buttonClass: 'custom-file-input-button', clearButtonClass: 'custom-file-input-clear-button', buttonTextClass: 'custom-file-input-button-text', }, defaultOptions: { wrapperClass: 'input-group', fileNameClass: 'disabled form-control', noFileText: 'No file chosen', buttonGroupClass: 'input-group-btn', buttonClass: 'btn btn-primary', clearButtonClass: 'btn btn-default', buttonText: 'Choose File', changeButtonText: 'Change', clearButtonText: 'Clear', fileName: true, clearButton: true, onClear: function(fileData, options) { if (typeof fileData.clear === 'function') { fileData.clear(); } } }, } function extendOptions(defaultOptions, newOptions) { var options = {}; for (var prop in defaultOptions) { options[prop] = typeof newOptions[prop] !== 'undefined' ? newOptions[prop] : defaultOptions[prop]; } return options; } function addRemoveCssClass(element, cssClasses, type){ var cssClasses = Array.isArray(cssClasses) ? cssClasses : cssClasses.split(' '); cssClasses.forEach(function(cssClass){ element.classList[type](cssClass); }); return element; } function addCssClass(element, cssClasses){ return addRemoveCssClass(element, cssClasses, 'add'); } function removeCssClass(element, cssClasses){ return addRemoveCssClass(element, cssClasses, 'remove'); } function hasCssClass(element, cssClass){ return element.classList.contains(cssClass); } var windowURL = window.URL || window.webkitURL; ko.bindingHandlers.fileInput = { init: function(element, valueAccessor) { element.onchange = function() { var fileData = ko.utils.unwrapObservable(valueAccessor()) || {}; if (fileData.dataUrl) { fileData.dataURL = fileData.dataUrl; } if (fileData.objectUrl) { fileData.objectURL = fileData.objectUrl; } fileData.file = fileData.file || ko.observable(); fileData.fileArray = fileData.fileArray || ko.observableArray([]); var file = this.files[0]; fileData.fileArray([]); if (file) { var fileArray = []; for(var i = 0; i < this.files.length; i++){ // FileList is not an array fileArray.push(this.files[i]); } fileData.fileArray(fileArray); // set it once for subscriptions to work properly fileData.file(file); } if (!fileData.clear) { fileData.clear = function() { ['objectURL', 'base64String', 'binaryString', 'text', 'dataURL', 'arrayBuffer'].forEach(function(property, i) { if (fileData[property + 'Array'] && ko.isObservable(fileData[property + 'Array'])) { var values = fileData[property + 'Array']; while(values().length) { var val = values.splice(0, 1); if (property == 'objectURL') { windowURL.revokeObjectURL(val); } } } if (fileData[property] && ko.isObservable(fileData[property])) { fileData[property](null); } }); element.value = ''; fileData.fileArray([]); fileData.file(null); } } if (ko.isObservable(valueAccessor())) { valueAccessor()(fileData); } }; element.onchange(); ko.utils.domNodeDisposal.addDisposeCallback(element, function() { var fileData = ko.utils.unwrapObservable(valueAccessor()) || {}; fileData.clear = undefined; }); }, update: function(element, valueAccessor, allBindingsAccessor) { var fileData = ko.utils.unwrapObservable(valueAccessor()); function fillData(file, index){ if (fileData.objectURL && ko.isObservable(fileData.objectURL)) { var newUrl = file && windowURL.createObjectURL(file); if (newUrl) { var oldUrl = fileData.objectURL(); if (oldUrl) { windowURL.revokeObjectURL(oldUrl); } fileData.objectURL(newUrl); } } if (fileData.base64String && ko.isObservable(fileData.base64String)) { if (!(fileData.dataURL && ko.isObservable(fileData.dataURL))) { fileData.dataURL = ko.observable(); // adding on demand } } if (fileData.base64StringArray && ko.isObservable(fileData.base64StringArray)) { if (!(fileData.dataURLArray && ko.isObservable(fileData.dataURLArray))) { fileData.dataURLArray = ko.observableArray(); } } ['binaryString', 'text', 'dataURL', 'arrayBuffer'].forEach(function(property){ var method = 'readAs' + (property.substr(0, 1).toUpperCase() + property.substr(1)); if (property != 'dataURL' && !(fileData[property] && ko.isObservable(fileData[property]))) { return true; } if (!file) { return true; } var reader = new FileReader(); reader.onload = function(e) { function fillDataToProperty(result, prop){ if (index == 0 && fileData[prop] && ko.isObservable(fileData[prop])) { fileData[prop](result); } if(fileData[prop + 'Array'] && ko.isObservable(fileData[prop + 'Array'])){ if(index == 0){ fileData[prop + 'Array']([]); } fileData[prop + 'Array'].push(result); } } fillDataToProperty(e.target.result, property); if (method == 'readAsDataURL' && (fileData.base64String || fileData.base64StringArray)) { var resultParts = e.target.result.split(","); if (resultParts.length === 2) { fillDataToProperty(resultParts[1], 'base64String'); } } }; reader[method](file); }); } fileData.fileArray().forEach(function(file, index){ fillData(file, index); }) } }; ko.bindingHandlers.fileDrag = { update: function(element, valueAccessor, allBindingsAccessor) { var fileData = ko.utils.unwrapObservable(valueAccessor()) || {}; if (!element.getAttribute("file-drag-injected")) { addCssClass(element, 'filedrag'); element.ondragover = element.ondragleave = element.ondrop = function(e) { e.stopPropagation(); e.preventDefault(); if(e.type == 'dragover'){ addCssClass(element, 'hover'); } else { removeCssClass(element, 'hover'); } if (e.type == 'drop' && e.dataTransfer) { var files = e.dataTransfer.files; var file = files[0]; fileData.fileArray([]); if (file) { var fileArray = []; for(var i = 0; i < files.length; i++){ fileArray.push(files[i]); } fileData.fileArray(fileArray); fileData.file(file); if (ko.isObservable(valueAccessor())) { valueAccessor()(fileData); } } } }; element.setAttribute("file-drag-injected", 1); } } }; ko.bindingHandlers.customFileInput = { init: function(element, valueAccessor, allBindingsAccessor) { var options = ko.utils.unwrapObservable(valueAccessor()); if (options === false) { return; } if (typeof options !== 'object') { options = {}; } var sysOpts = fileBindings.customFileInputSystemOptions; var defOpts = fileBindings.defaultOptions; options = extendOptions(defOpts, options); var wrapper = addCssClass(document.createElement('span'), [sysOpts.wrapperClass, options.wrapperClass]); var buttonGroup = addCssClass(document.createElement('span'), [sysOpts.buttonGroupClass, options.buttonGroupClass]); var button = addCssClass(document.createElement('span'), sysOpts.buttonClass); buttonGroup.appendChild(button); wrapper.appendChild(buttonGroup); element.parentNode.insertBefore(wrapper, element); button.appendChild(element); if(options.fileName){ var fileNameInput = document.createElement('input'); fileNameInput.setAttribute('type', 'text'); fileNameInput.setAttribute('disabled', 'disabled'); buttonGroup.parentNode.insertBefore(addCssClass(fileNameInput, sysOpts.fileNameClass), buttonGroup); if(hasCssClass(buttonGroup, 'btn-group')){ addCssClass(removeCssClass(buttonGroup, 'btn-group'), 'input-group-btn'); } } else { if(hasCssClass(buttonGroup, 'input-group-btn')){ addCssClass(removeCssClass(buttonGroup, 'input-group-btn'), 'btn-group'); } } element.parentNode.insertBefore(addCssClass(document.createElement('span'), sysOpts.buttonTextClass), element); }, update: function(element, valueAccessor, allBindingsAccessor) { var options = ko.utils.unwrapObservable(valueAccessor()); if (options === false) { return; } if (typeof options !== 'object') { options = {}; } var sysOpts = fileBindings.customFileInputSystemOptions; var defOpts = fileBindings.defaultOptions; options = extendOptions(defOpts, options); var allBindings = allBindingsAccessor(); if (!allBindings.fileInput) { return; } var fileData = ko.utils.unwrapObservable(allBindings.fileInput) || {}; var file = ko.utils.unwrapObservable(fileData.file); var button = element.parentNode; var buttonGroup = button.parentNode; var wrapper = buttonGroup.parentNode; addCssClass(button, ko.utils.unwrapObservable(options.buttonClass)); var buttonText = button.querySelector('.' + sysOpts.buttonTextClass); buttonText.innerText = ko.utils.unwrapObservable(file ? options.changeButtonText : options.buttonText); if(options.fileName){ var fileNameInput = wrapper.querySelector('.' + sysOpts.fileNameClass); addCssClass(fileNameInput, ko.utils.unwrapObservable(options.fileNameClass)); if (file && file.name) { if(fileData.fileArray().length > 2){ fileNameInput.value = fileData.fileArray().length + ' files'; } else { fileNameInput.value = fileData.fileArray().map(function(f){ return f.name }).join(', '); } } else { fileNameInput.value = ko.utils.unwrapObservable(options.noFileText); } } var clearButton = buttonGroup.querySelector('.' + sysOpts.clearButtonClass); if (!clearButton) { clearButton = addCssClass(document.createElement('span'), sysOpts.clearButtonClass); clearButton.onclick = function(e){ options.onClear(fileData, options); } buttonGroup.appendChild(clearButton); } clearButton.innerText = ko.utils.unwrapObservable(options.clearButtonText); addCssClass(clearButton, ko.utils.unwrapObservable(options.clearButtonClass)); if (file && options.clearButton && file.name) { } else { clearButton.parentNode.removeChild(clearButton); } } }; ko.fileBindings = fileBindings; return fileBindings; }));