├── .gitignore ├── .npmignore ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── bower.json ├── demo ├── msl-dnd-file-input │ ├── index.html │ ├── script.js │ └── style.css ├── msl-dnd-folder-input │ ├── index.html │ ├── script.js │ └── style.css ├── msl-dnd-item │ ├── index.html │ ├── script.js │ └── style.css ├── msl-dnd-target │ ├── index.html │ ├── script.js │ └── style.css ├── msl-file-input │ ├── index.html │ ├── script.js │ └── style.css └── msl-folder-input │ ├── index.html │ ├── script.js │ └── style.css ├── dist ├── angular-uploads.js └── angular-uploads.min.js ├── karma-conf.js ├── package.json ├── src ├── msl-dnd-file-input.js ├── msl-dnd-folder-input.js ├── msl-dnd-item.js ├── msl-dnd-target.js ├── msl-file-input.js ├── msl-folder-input.js └── msl-uploads.js └── test └── unit ├── msl-dnd-file-input.js ├── msl-dnd-folder-input.js ├── msl-dnd-item.js ├── msl-dnd-target.js ├── msl-file-input.js └── msl-folder-input.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.DS_Store 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | test/ 3 | demo/ 4 | Gruntfile.js 5 | karma-conf.js 6 | .npmignore -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | karma: { 5 | unit: { 6 | configFile: 'karma-conf.js' 7 | } 8 | }, 9 | concat: { 10 | options: { 11 | separator: '\n\n' 12 | }, 13 | dist: { 14 | src: [ 15 | 'src/msl-uploads.js', 16 | 'src/msl-file-input.js', 17 | 'src/msl-folder-input.js', 18 | 'src/msl-dnd-file-input.js', 19 | 'src/msl-dnd-folder-input.js', 20 | 'src/msl-dnd-item.js', 21 | 'src/msl-dnd-target.js' 22 | ], 23 | dest: 'dist/<%= pkg.name %>.js' 24 | } 25 | }, 26 | uglify: { 27 | options: { 28 | banner: '/*! <%= pkg.name %> v. <%= pkg.version %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' 29 | }, 30 | build: { 31 | src: 'dist/<%= pkg.name %>.js', 32 | dest: 'dist/<%= pkg.name %>.min.js' 33 | } 34 | } 35 | }); 36 | 37 | grunt.loadNpmTasks('grunt-karma'); 38 | grunt.loadNpmTasks('grunt-contrib-concat'); 39 | grunt.loadNpmTasks('grunt-contrib-uglify'); 40 | 41 | grunt.registerTask('default', ['karma', 'concat', 'uglify']); 42 | grunt.registerTask('test', ['karma']); 43 | }; -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Marco Sami' Liceti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-uploads 2 | 3 | A bunch of [AngularJS](https://angularjs.org/) directives for beautiful upload UIs. 4 | 5 | ## Showcase 6 | 7 | You can see the directives in action [here](https://marcoliceti.github.io/angular-uploads/). 8 | 9 | ## How to get it 10 | 11 | ``` bash 12 | npm install angular-uploads 13 | ``` 14 | 15 | `npm` is a package manager distributed with [Node.js](https://nodejs.org/). More info [here](https://docs.npmjs.com/). 16 | 17 | If you prefer [Bower](http://bower.io/): 18 | 19 | ``` bash 20 | bower install msl-angular-uploads 21 | ``` 22 | 23 | ## How to develop it 24 | 25 | First of all, install the development dependencies. From the project root folder, type: 26 | 27 | ``` bash 28 | npm install 29 | ``` 30 | 31 | the dependencies will be installed in the `node_modules` folder. 32 | 33 | The `src` folder contains a file for each directive, plus a file for the module itself. The `test/unit` folder contains a [Jasmine](http://jasmine.github.io/) unit test for each directive. [Karma](http://karma-runner.github.io/) is used to run these tests: 34 | 35 | ``` bash 36 | karma start karma-conf.js 37 | ``` 38 | 39 | [Grunt](http://gruntjs.com/) is used to run the tests and (if all tests pass) _build_ the project, i.e. concat all source files and minify the result. You achieve this just by typing: 40 | 41 | ``` bash 42 | grunt 43 | ``` 44 | 45 | or: 46 | 47 | ``` bash 48 | grunt test 49 | ``` 50 | 51 | if you just want to run the tests. This is roughly the same as running the test with Karma as shown previously. 52 | 53 | The output of the build will be in the `dist` folder, both the minified (`angular-uploads.min.js`) and the un-minified versions (`angular-uploads.js`). 54 | 55 | Finally, in the `demo` folder you'll find an example-app for each directive. You may want to take a look at these apps. To avoid failures related to browsers policies about pages served from `file:///`, it's better to use a local HTTP server. Just type from the project root folder: 56 | 57 | ``` bash 58 | node_modules/http-server/bin/http-server 59 | ``` 60 | and go to [http://localhost:8080/demo/](http://localhost:8080/demo/). 61 | 62 | ## How to use it 63 | 64 | Include the script in your page, e.g.: 65 | 66 | ``` html 67 | 68 | ``` 69 | 70 | and don't forget do declare the `msl.uploads` dependency in your AngularJS app: 71 | 72 | ``` javascript 73 | angular.module('myApp', ['msl.uploads']); 74 | ``` 75 | 76 | If you clone this repository you'll find a `demo` folder with a small example-app for each directive. 77 | After reading this file, it's a good idea to take a look at these apps too. To avoid failures related 78 | to browsers policies about pages served from `file:///`, it's better to use a local HTTP server. If 79 | you have cloned this repository, you can simply type (from the project root folder): 80 | 81 | ``` bash 82 | node_modules/http-server/bin/http-server 83 | ``` 84 | and go to [http://localhost:8080/demo/](http://localhost:8080/demo/). 85 | 86 | The directives in the `angular-uploads` package are: 87 | 88 | * msl-file-input 89 | * msl-folder-input 90 | * msl-dnd-file-input 91 | * msl-dnd-folder-input 92 | * msl-dnd-item 93 | * msl-dnd-target 94 | 95 | ### What `msl-file-input` and `msl-folder-input` do 96 | 97 | `msl-file-input` and `msl-folder-input` turn an ordinary container element (e.g. a `div` or a `button`) into a file selection component, just like ``, but allow for better CSS styling ([there are no 98 | elegant ways](http://developer.telerik.com/featured/comprehensive-guide-styling-file-inputs/) to customize the look of ``). `msl-file-input` is used to select files. You can select more 99 | than one file if you also use the `multiple` attribute along with the directive. `msl-folder-input` is for folder 100 | selection, i.e. to select all files inside a folder. _This works only on Google Chrome_. On other browsers the 101 | `disabled` attribute will be applied. 102 | 103 | ### What `msl-dnd-file-input` and `msl-dnd-folder-input` do 104 | 105 | `msl-dnd-file-input` and `msl-dnd-folder-input` also turn ordinary container elements into file selection components, but they work through drag and drop. `msl-dnd-file-input` accepts mutiple files. 106 | `msl-dnd-folder-input` recursively select files inside the folders that you drag and drop. _It works only 107 | on Google Chrome_. On other browsers `msl-dnd-folder-input` will behave just like `msl-dnd-file-input`. 108 | 109 | ### How to use `msl-file-input`, `msl-folder-input`, `msl-dnd-file-input` and `msl-dnd-folder-input` 110 | 111 | `msl-file-input`, `msl-folder-input`, `msl-dnd-file-input` and `msl-dnd-folder-input` all require a _file 112 | selection handler_, i.e. a function that will be called when files are selected. This function takes a 113 | [FileList](https://developer.mozilla.org/en-US/docs/Web/API/FileList) object containing the selected files. You 114 | expose this function through your controller's scope. For example: 115 | 116 | `HTML` 117 | 118 | ``` html 119 | 120 | ``` 121 | 122 | `Javascript / AngularJS Controller` 123 | 124 | ``` javascript 125 | $scope.myHandler = function (files) { 126 | // Do something with the files 127 | } 128 | ``` 129 | 130 | ### What `msl-dnd-item` and `msl-dnd-target` do 131 | 132 | `msl-dnd-item` makes an element _draggable_, and allows to _link_ that element to a scope variable. 133 | `msl-dnd-target` allows to specify a handler function (exposed through your scope) that will be invoked 134 | when the `msl-dnd-item` will be dropped on that `msl-dnd-target`. This function will receive as an 135 | argument the same variable linked to the `msl-dnd-item`. 136 | 137 | ### How to use `msl-dnd-item` and `msl-dnd-target` 138 | 139 | Example: 140 | 141 | `HTML` 142 | 143 | ``` html 144 |
Foo
145 |
Drag Foo here
146 | ``` 147 | 148 | `Javascript / AngularJS Controller` 149 | 150 | ``` javascript 151 | $scope.foo = 'bar'; 152 | 153 | $scope.myHandler = function (arg) { 154 | console.log(arg); // will print 'bar' 155 | } 156 | ``` 157 | 158 | ### Styling on drag over 159 | 160 | On drag over `msl-dnd-folder-input` and `msl-dnd-target` will apply a `.msl-drag-over` class for styling purposes. A more elegant solution will be provided in future by [CSS Selectors Level 4](https://www.w3.org/TR/selectors4/#drag-pseudos). 161 | 162 | ### Additional notes 163 | 164 | All directives throw an error if you don't provide the required scope variable / handler function, or if 165 | you provide something that doesn't exist in your scope. 166 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-uploads", 3 | "homepage": "https://marcoliceti.github.io/angular-uploads/", 4 | "authors": [ 5 | "marcoliceti " 6 | ], 7 | "description": "A bunch of AngularJS directives for beautiful upload UIs", 8 | "keywords": [ 9 | "angular", 10 | "upload", 11 | "uploads" 12 | ], 13 | "license": "MIT", 14 | "ignore": [ 15 | "src", 16 | "test", 17 | "demo", 18 | "node_modules", 19 | "bower_components", 20 | "Gruntfile.js", 21 | "karma-conf.js", 22 | "package.json", 23 | ".gitignore", 24 | ".git", 25 | ".npmignore" 26 | ], 27 | "dependencies": { 28 | "angular": "~1.4.4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /demo/msl-dnd-file-input/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | msl-dnd-file-input Demo 6 | 7 | 8 | 9 |
Drag files here
10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/msl-dnd-file-input/script.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('mslDndFileInputDemoApp', ['msl.uploads']); 2 | 3 | app.controller('DemoController', ['$scope', function ($scope) { 4 | $scope.files = []; 5 | 6 | $scope.fileSelectionHandler = function (files) { 7 | for (var i = 0; i < files.length; i++) $scope.files.push(files[i]); 8 | }; 9 | }]); -------------------------------------------------------------------------------- /demo/msl-dnd-file-input/style.css: -------------------------------------------------------------------------------- 1 | div { 2 | border: 3px solid rgb(127, 127, 127); 3 | border-radius: 10px; 4 | width: 25%; 5 | padding: 20px; 6 | background: rgb(224, 224, 224); 7 | } 8 | 9 | .msl-drag-over { 10 | border: 5px dashed rgb(127, 127, 127); 11 | } -------------------------------------------------------------------------------- /demo/msl-dnd-folder-input/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | msl-dnd-folder-input Demo 6 | 7 | 8 | 9 |
Drag folder here
10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/msl-dnd-folder-input/script.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('mslDndFolderInputDemoApp', ['msl.uploads']); 2 | 3 | app.controller('DemoController', ['$scope', function ($scope) { 4 | $scope.files = []; 5 | 6 | $scope.fileSelectionHandler = function (files) { 7 | for (var i = 0; i < files.length; i++) $scope.files.push(files[i]); 8 | }; 9 | }]); -------------------------------------------------------------------------------- /demo/msl-dnd-folder-input/style.css: -------------------------------------------------------------------------------- 1 | div { 2 | border: 3px solid rgb(127, 127, 127); 3 | border-radius: 10px; 4 | width: 25%; 5 | padding: 20px; 6 | background: rgb(224, 224, 224); 7 | } 8 | 9 | .msl-drag-over { 10 | border: 5px dashed rgb(127, 127, 127); 11 | } -------------------------------------------------------------------------------- /demo/msl-dnd-item/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | msl-dnd-item Demo 6 | 7 | 8 | 9 |
10 |
11 | {{k}}: {{v}} 12 |
13 |
14 |
15 |
16 | {{k}}: {{v}} 17 |
18 |
19 |
20 |

Drag person here

21 | 22 | 23 | 24 | 25 |
{{v}}
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /demo/msl-dnd-item/script.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('mslDndItemDemoApp', ['msl.uploads']); 2 | 3 | app.controller('DemoController', ['$scope', function ($scope) { 4 | $scope.john = { 5 | first_name: 'John', 6 | last_name: 'Doe' 7 | }; 8 | $scope.jane = { 9 | first_name: 'Jane', 10 | last_name: 'Doe' 11 | }; 12 | 13 | $scope.persons = []; 14 | 15 | $scope.addPerson = function (person) { 16 | $scope.persons.push(person); 17 | }; 18 | }]); -------------------------------------------------------------------------------- /demo/msl-dnd-item/style.css: -------------------------------------------------------------------------------- 1 | div { 2 | display: inline-block; 3 | border: 3px solid rgb(127, 127, 127); 4 | border-radius: 10px; 5 | padding: 20px; 6 | margin: 10px; 7 | background: rgb(224, 224, 224); 8 | } 9 | 10 | div:nth-child(3) { 11 | display: block; 12 | width: 25%; 13 | } 14 | 15 | .msl-drag-over { 16 | border: 5px dashed rgb(127, 127, 127); 17 | } -------------------------------------------------------------------------------- /demo/msl-dnd-target/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | msl-dnd-target Demo 6 | 7 | 8 | 9 |
10 |
11 | {{k}}: {{v}} 12 |
13 |
14 |
15 |
16 | {{k}}: {{v}} 17 |
18 |
19 |
20 |

Drag person here

21 | 22 | 23 | 24 | 25 |
{{v}}
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /demo/msl-dnd-target/script.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('mslDndTargetDemoApp', ['msl.uploads']); 2 | 3 | app.controller('DemoController', ['$scope', function ($scope) { 4 | $scope.john = { 5 | first_name: 'John', 6 | last_name: 'Doe' 7 | }; 8 | $scope.jane = { 9 | first_name: 'Jane', 10 | last_name: 'Doe' 11 | }; 12 | 13 | $scope.persons = []; 14 | 15 | $scope.addPerson = function (person) { 16 | $scope.persons.push(person); 17 | }; 18 | }]); -------------------------------------------------------------------------------- /demo/msl-dnd-target/style.css: -------------------------------------------------------------------------------- 1 | div { 2 | display: inline-block; 3 | border: 3px solid rgb(127, 127, 127); 4 | border-radius: 10px; 5 | padding: 20px; 6 | margin: 10px; 7 | background: rgb(224, 224, 224); 8 | } 9 | 10 | div:nth-child(3) { 11 | display: block; 12 | width: 25%; 13 | } 14 | 15 | .msl-drag-over { 16 | border: 5px dashed rgb(127, 127, 127); 17 | } -------------------------------------------------------------------------------- /demo/msl-file-input/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | msl-file-input Demo 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/msl-file-input/script.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('mslFileInputDemoApp', ['msl.uploads']); 2 | 3 | app.controller('DemoController', ['$scope', function ($scope) { 4 | $scope.files = []; 5 | 6 | $scope.fileSelectionHandler = function (files) { 7 | for (var i = 0; i < files.length; i++) $scope.files.push(files[i]); 8 | }; 9 | }]); -------------------------------------------------------------------------------- /demo/msl-file-input/style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcoliceti/angular-uploads/f804a13dc909b3276ff8d08d242f53ad8b263163/demo/msl-file-input/style.css -------------------------------------------------------------------------------- /demo/msl-folder-input/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | msl-folder-input Demo 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/msl-folder-input/script.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('mslFolderInputDemoApp', ['msl.uploads']); 2 | 3 | app.controller('DemoController', ['$scope', function ($scope) { 4 | $scope.files = []; 5 | 6 | $scope.fileSelectionHandler = function (files) { 7 | for (var i = 0; i < files.length; i++) $scope.files.push(files[i]); 8 | }; 9 | }]); -------------------------------------------------------------------------------- /demo/msl-folder-input/style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcoliceti/angular-uploads/f804a13dc909b3276ff8d08d242f53ad8b263163/demo/msl-folder-input/style.css -------------------------------------------------------------------------------- /dist/angular-uploads.js: -------------------------------------------------------------------------------- 1 | var msl_upload = angular.module('msl.uploads', []); 2 | 3 | msl_upload.directive('mslFileInput', function () { 4 | return { 5 | restrict: 'A', 6 | link: function (scope, element, attributes) { 7 | var handler = attributes['mslFileInput']; 8 | if (!handler) throw 'msl-file-input: You should specify a file selection handler'; 9 | if (!scope[handler]) throw 'msl-file-input: The specified handler doesn\'t exist in your scope'; 10 | 11 | element.removeAttr('multiple'); 12 | element.append(''); 13 | var hidden_file_input = element.children().eq(-1); 14 | hidden_file_input.bind('change', function (event) { 15 | var files = event.target.files; 16 | scope.$apply(function () { 17 | scope[handler](files); 18 | event.target.value = null; // reset file input 19 | }); 20 | }); 21 | element.bind('click', function (event) { 22 | hidden_file_input[0].click(); 23 | }); 24 | } 25 | }; 26 | }); 27 | 28 | 29 | msl_upload.directive('mslFolderInput', function () { 30 | function folderUploadAvailable() { 31 | var dummy = document.createElement('input'); 32 | return 'webkitdirectory' in dummy; 33 | } 34 | 35 | return { 36 | restrict: 'A', 37 | link: function (scope, element, attributes) { 38 | var handler = attributes['mslFolderInput']; 39 | if (!handler) throw 'msl-folder-input: You should specify a folder selection handler'; 40 | if (!scope[handler]) throw 'msl-folder-input: The specified handler doesn\'t exist in your scope'; 41 | 42 | if (folderUploadAvailable()) { 43 | element.append(''); 44 | var hidden_file_input = element.children().eq(-1); 45 | hidden_file_input.bind('change', function (event) { 46 | var files = event.target.files; 47 | scope.$apply(function () { 48 | scope[handler](files); 49 | event.target.value = null; // reset file input 50 | }); 51 | }); 52 | element.bind('click', function (event) { 53 | hidden_file_input[0].click(); 54 | }); 55 | } else { 56 | element.prop('disabled', true); 57 | } 58 | } 59 | }; 60 | }); 61 | 62 | 63 | msl_upload.directive('mslDndFileInput', function () { 64 | return { 65 | restrict: 'A', 66 | link: function (scope, element, attributes) { 67 | var handler = attributes['mslDndFileInput']; 68 | if (!handler) throw 'msl-dnd-file-input: You should specify a file selection handler'; 69 | if (!scope[handler]) throw 'msl-dnd-file-input: The specified handler doesn\'t exist in your scope'; 70 | 71 | element.bind('dragover', function (event) { 72 | event.preventDefault(); 73 | element.addClass('msl-drag-over'); 74 | }); 75 | element.bind('dragleave', function (event) { 76 | element.removeClass('msl-drag-over'); 77 | }); 78 | element.bind('drop', function (event) { 79 | event.preventDefault(); 80 | element.removeClass('msl-drag-over'); 81 | var handler = attributes['mslDndFileInput']; 82 | var files = event.dataTransfer.files; 83 | scope.$apply(function () { scope[handler](files); }); 84 | }); 85 | } 86 | }; 87 | }); 88 | 89 | msl_upload.directive('mslDndFolderInput', function () { 90 | function folderUploadAvailable() { 91 | var dummy = document.createElement('input'); 92 | return 'webkitdirectory' in dummy; 93 | } 94 | 95 | return { 96 | restrict: 'A', 97 | link: function (scope, element, attributes) { 98 | var handler = attributes['mslDndFolderInput']; 99 | if (!handler) throw 'msl-dnd-folder-input: You should specify a folder selection handler'; 100 | if (!scope[handler]) throw 'msl-dnd-folder-input: The specified handler doesn\'t exist in your scope'; 101 | 102 | function exploreFolder(item) { 103 | if (item.isFile) { 104 | item.file(function (file) { 105 | scope.$apply(function () { scope[handler]([file]); }); 106 | }); 107 | } else if (item.isDirectory) { 108 | var directory_reader = item.createReader(); 109 | directory_reader.readEntries(function(entries) { 110 | for (var i = 0; i < entries.length; i++) { 111 | var entry = entries[i]; 112 | exploreFolder(entry); 113 | } 114 | }); 115 | } 116 | }; 117 | 118 | element.bind('dragover', function (event) { 119 | event.preventDefault(); 120 | element.addClass('msl-drag-over'); 121 | }); 122 | element.bind('dragleave', function (event) { 123 | element.removeClass('msl-drag-over'); 124 | }); 125 | element.bind('drop', function (event) { 126 | event.preventDefault(); 127 | element.removeClass('msl-drag-over'); 128 | if (folderUploadAvailable()) { 129 | var roots = event.dataTransfer.items; 130 | for (var i = 0; i < roots.length; i++) { 131 | var root = roots[i].webkitGetAsEntry(); 132 | exploreFolder(root); 133 | } 134 | } else { 135 | var files = event.dataTransfer.files; 136 | scope.$apply(function () { scope[handler](files); }); 137 | } 138 | }); 139 | } 140 | }; 141 | }); 142 | 143 | msl_upload.directive('mslDndItem', function () { 144 | return { 145 | restrict: 'A', 146 | link: function (scope, element, attributes) { 147 | var binded_object_name = attributes['mslDndItem']; 148 | if (!binded_object_name) throw 'msl-dnd-item: You should specify a scope variable'; 149 | var binded_object = scope[binded_object_name]; 150 | if (!binded_object) throw 'msl-dnd-item: The specified scope variable doesn\'t exist'; 151 | 152 | element.prop('draggable', true); 153 | element.bind('dragstart', function (event) { 154 | var as_json = JSON.stringify(binded_object); 155 | event.dataTransfer.setData('text', as_json); 156 | }); 157 | } 158 | }; 159 | }); 160 | 161 | msl_upload.directive('mslDndTarget', function () { 162 | return { 163 | restrict: 'A', 164 | link: function (scope, element, attributes) { 165 | var handler = attributes['mslDndTarget']; 166 | if (!handler) throw 'msl-dnd-target: You should specify a drop handler'; 167 | if (!scope[handler]) throw 'msl-dnd-target: The specified handler doesn\'t exist in your scope'; 168 | 169 | element.bind('dragover', function (event) { 170 | event.preventDefault(); // otherwise drop won't fire 171 | element.addClass('msl-drag-over'); 172 | }); 173 | 174 | element.bind('dragleave', function (event) { 175 | element.removeClass('msl-drag-over'); 176 | }); 177 | 178 | element.bind('drop', function (event) { 179 | element.removeClass('msl-drag-over'); 180 | var data_as_string = event.dataTransfer.getData('text'); 181 | var data = JSON.parse(data_as_string); 182 | scope.$apply(function () { 183 | scope[handler](data); 184 | }); 185 | }); 186 | } 187 | }; 188 | }); -------------------------------------------------------------------------------- /dist/angular-uploads.min.js: -------------------------------------------------------------------------------- 1 | /*! angular-uploads v. 1.0.3 2016-02-15 */ 2 | var msl_upload=angular.module("msl.uploads",[]);msl_upload.directive("mslFileInput",function(){return{restrict:"A",link:function(a,b,c){var d=c.mslFileInput;if(!d)throw"msl-file-input: You should specify a file selection handler";if(!a[d])throw"msl-file-input: The specified handler doesn't exist in your scope";b.removeAttr("multiple"),b.append('');var e=b.children().eq(-1);e.bind("change",function(b){var c=b.target.files;a.$apply(function(){a[d](c),b.target.value=null})}),b.bind("click",function(a){e[0].click()})}}}),msl_upload.directive("mslFolderInput",function(){function a(){var a=document.createElement("input");return"webkitdirectory"in a}return{restrict:"A",link:function(b,c,d){var e=d.mslFolderInput;if(!e)throw"msl-folder-input: You should specify a folder selection handler";if(!b[e])throw"msl-folder-input: The specified handler doesn't exist in your scope";if(a()){c.append('');var f=c.children().eq(-1);f.bind("change",function(a){var c=a.target.files;b.$apply(function(){b[e](c),a.target.value=null})}),c.bind("click",function(a){f[0].click()})}else c.prop("disabled",!0)}}}),msl_upload.directive("mslDndFileInput",function(){return{restrict:"A",link:function(a,b,c){var d=c.mslDndFileInput;if(!d)throw"msl-dnd-file-input: You should specify a file selection handler";if(!a[d])throw"msl-dnd-file-input: The specified handler doesn't exist in your scope";b.bind("dragover",function(a){a.preventDefault(),b.addClass("msl-drag-over")}),b.bind("dragleave",function(a){b.removeClass("msl-drag-over")}),b.bind("drop",function(d){d.preventDefault(),b.removeClass("msl-drag-over");var e=c.mslDndFileInput,f=d.dataTransfer.files;a.$apply(function(){a[e](f)})})}}}),msl_upload.directive("mslDndFolderInput",function(){function a(){var a=document.createElement("input");return"webkitdirectory"in a}return{restrict:"A",link:function(b,c,d){function e(a){if(a.isFile)a.file(function(a){b.$apply(function(){b[f]([a])})});else if(a.isDirectory){var c=a.createReader();c.readEntries(function(a){for(var b=0;b'); 11 | var hidden_file_input = element.children().eq(-1); 12 | hidden_file_input.bind('change', function (event) { 13 | var files = event.target.files; 14 | scope.$apply(function () { 15 | scope[handler](files); 16 | event.target.value = null; // reset file input 17 | }); 18 | }); 19 | element.bind('click', function (event) { 20 | hidden_file_input[0].click(); 21 | }); 22 | } 23 | }; 24 | }); 25 | -------------------------------------------------------------------------------- /src/msl-folder-input.js: -------------------------------------------------------------------------------- 1 | msl_upload.directive('mslFolderInput', function () { 2 | function folderUploadAvailable() { 3 | var dummy = document.createElement('input'); 4 | return 'webkitdirectory' in dummy; 5 | } 6 | 7 | return { 8 | restrict: 'A', 9 | link: function (scope, element, attributes) { 10 | var handler = attributes['mslFolderInput']; 11 | if (!handler) throw 'msl-folder-input: You should specify a folder selection handler'; 12 | if (!scope[handler]) throw 'msl-folder-input: The specified handler doesn\'t exist in your scope'; 13 | 14 | if (folderUploadAvailable()) { 15 | element.append(''); 16 | var hidden_file_input = element.children().eq(-1); 17 | hidden_file_input.bind('change', function (event) { 18 | var files = event.target.files; 19 | scope.$apply(function () { 20 | scope[handler](files); 21 | event.target.value = null; // reset file input 22 | }); 23 | }); 24 | element.bind('click', function (event) { 25 | hidden_file_input[0].click(); 26 | }); 27 | } else { 28 | element.prop('disabled', true); 29 | } 30 | } 31 | }; 32 | }); 33 | -------------------------------------------------------------------------------- /src/msl-uploads.js: -------------------------------------------------------------------------------- 1 | var msl_upload = angular.module('msl.uploads', []); -------------------------------------------------------------------------------- /test/unit/msl-dnd-file-input.js: -------------------------------------------------------------------------------- 1 | describe('Directive msl-dnd-file-input', function() { 2 | var $compile, $rootScope; 3 | 4 | beforeEach(module('msl.uploads')); 5 | beforeEach(inject(function(_$compile_, _$rootScope_) { 6 | $compile = _$compile_; 7 | $rootScope = _$rootScope_; 8 | })); 9 | 10 | it('adds a \'msl-drag-over\' class on \'dragover\' events', function() { 11 | var handler = 'handler'; 12 | $rootScope.handler = function () {}; 13 | var element = $compile('
')($rootScope); 14 | $rootScope.$digest(); 15 | 16 | element.triggerHandler('dragover'); 17 | expect(element.hasClass('msl-drag-over')).toBeTruthy(); 18 | }); 19 | 20 | it('removes the \'msl-drag-over\' class on \'dragleave\' events', function() { 21 | var handler = 'handler'; 22 | $rootScope.handler = function () {}; 23 | var element = $compile('
')($rootScope); 24 | $rootScope.$digest(); 25 | 26 | element.triggerHandler('dragover'); 27 | expect(element.hasClass('msl-drag-over')).toBeTruthy(); 28 | element.triggerHandler('dragleave'); 29 | expect(element.hasClass('msl-drag-over')).toBeFalsy(); 30 | }); 31 | 32 | it('removes the \'msl-drag-over\' class on \'drop\' events', function() { 33 | var handler = 'handler'; 34 | $rootScope.handler = function () {}; 35 | var element = $compile('
')($rootScope); 36 | $rootScope.$digest(); 37 | 38 | element.triggerHandler('dragover'); 39 | expect(element.hasClass('msl-drag-over')).toBeTruthy(); 40 | element.triggerHandler($.Event('drop', { 41 | dataTransfer: { 42 | files: [] 43 | } 44 | })); 45 | expect(element.hasClass('msl-drag-over')).toBeFalsy(); 46 | }); 47 | 48 | it('allows to bind a handler for \'drop\' events', function() { 49 | var handler = 'handler'; 50 | $rootScope.handler = function () {}; 51 | var element = $compile('
')($rootScope); 52 | $rootScope.$digest(); 53 | 54 | spyOn($rootScope, handler); 55 | element.triggerHandler($.Event('drop', { 56 | dataTransfer: { 57 | files: ['foo', 'bar', 'baz'] 58 | } 59 | })); 60 | expect($rootScope[handler]).toHaveBeenCalledWith(['foo', 'bar', 'baz']); 61 | }); 62 | 63 | it('throws if you don\'t provide a handler', function() { 64 | function compileWithoutHandler() { 65 | $compile('
')($rootScope); 66 | } 67 | expect(compileWithoutHandler).toThrow(); 68 | }); 69 | 70 | it('throws complain if you provide a missing handler', function() { 71 | function compileWithMissingHandler() { 72 | var handler = 'handler'; 73 | $rootScope[handler] = undefined; 74 | $compile('
')($rootScope); 75 | } 76 | expect(compileWithMissingHandler).toThrow(); 77 | }); 78 | }); -------------------------------------------------------------------------------- /test/unit/msl-dnd-folder-input.js: -------------------------------------------------------------------------------- 1 | describe('Directive msl-dnd-folder-input', function() { 2 | var $compile, $rootScope; 3 | 4 | beforeEach(module('msl.uploads')); 5 | beforeEach(inject(function(_$compile_, _$rootScope_) { 6 | $compile = _$compile_; 7 | $rootScope = _$rootScope_; 8 | })); 9 | 10 | function folderUploadAvailable() { 11 | var dummy = document.createElement('input'); 12 | return 'webkitdirectory' in dummy; 13 | } 14 | var folder_upload_available = folderUploadAvailable(); 15 | 16 | function mockWebkitFile(name) { 17 | return { 18 | name: name, 19 | isFile: true, 20 | file: function (callback) { callback(this.name); }, 21 | webkitGetAsEntry: function () { return this; } 22 | }; 23 | } 24 | function mockWebkitDirectory(name) { 25 | return { 26 | name: name, 27 | isDirectory: true, 28 | content: [], 29 | createReader: function () { 30 | var me = this; 31 | return { 32 | readEntries: function (callback) { callback(me.content); } 33 | }; 34 | }, 35 | webkitGetAsEntry: function () { return this; } 36 | }; 37 | } 38 | 39 | it('adds a \'msl-drag-over\' class on \'dragover\' events', function() { 40 | var handler = 'handler'; 41 | $rootScope.handler = function () {}; 42 | var element = $compile('
')($rootScope); 43 | $rootScope.$digest(); 44 | 45 | element.triggerHandler('dragover'); 46 | expect(element.hasClass('msl-drag-over')).toBeTruthy(); 47 | }); 48 | 49 | it('removes the \'msl-drag-over\' class on \'dragleave\' events', function() { 50 | var handler = 'handler'; 51 | $rootScope.handler = function () {}; 52 | var element = $compile('
')($rootScope); 53 | $rootScope.$digest(); 54 | 55 | element.triggerHandler('dragover'); 56 | expect(element.hasClass('msl-drag-over')).toBeTruthy(); 57 | element.triggerHandler('dragleave'); 58 | expect(element.hasClass('msl-drag-over')).toBeFalsy(); 59 | }); 60 | 61 | it('removes the \'msl-drag-over\' class on \'drop\' events', function() { 62 | var handler = 'handler'; 63 | $rootScope.handler = function () {}; 64 | var element = $compile('
')($rootScope); 65 | $rootScope.$digest(); 66 | 67 | element.triggerHandler('dragover'); 68 | expect(element.hasClass('msl-drag-over')).toBeTruthy(); 69 | element.triggerHandler($.Event('drop', { 70 | dataTransfer: { 71 | items: [] 72 | } 73 | })); 74 | expect(element.hasClass('msl-drag-over')).toBeFalsy(); 75 | }); 76 | 77 | if (folder_upload_available) it('allows to bind a handler for \'drop\' events', function() { 78 | var handler = 'handler'; 79 | $rootScope.handler = function () {}; 80 | var element = $compile('
')($rootScope); 81 | $rootScope.$digest(); 82 | 83 | spyOn($rootScope, handler); 84 | var items = [mockWebkitFile('foo')]; 85 | element.triggerHandler($.Event('drop', { 86 | dataTransfer: { 87 | items: items 88 | } 89 | })); 90 | expect($rootScope[handler]).toHaveBeenCalledWith(['foo']); 91 | }); 92 | 93 | if (folder_upload_available) it('allows you to select all the content inside a folder', function() { 94 | var handler = 'handler'; 95 | $rootScope.handler = function () {}; 96 | var element = $compile('
')($rootScope); 97 | $rootScope.$digest(); 98 | 99 | spyOn($rootScope, handler); 100 | var a_dir = mockWebkitDirectory('a_dir'); 101 | a_dir.content.push(mockWebkitFile('foo')); 102 | a_dir.content.push(mockWebkitFile('bar')); 103 | var items = [a_dir]; 104 | element.triggerHandler($.Event('drop', { 105 | dataTransfer: { 106 | items: items 107 | } 108 | })); 109 | expect($rootScope[handler]).toHaveBeenCalledWith(['foo']); 110 | expect($rootScope[handler]).toHaveBeenCalledWith(['bar']); 111 | }); 112 | 113 | if (folder_upload_available) it('works even with multiple folders', function() { 114 | var handler = 'handler'; 115 | $rootScope.handler = function () {}; 116 | var element = $compile('
')($rootScope); 117 | $rootScope.$digest(); 118 | 119 | spyOn($rootScope, handler); 120 | var a_dir = mockWebkitDirectory('a_dir'); 121 | a_dir.content.push(mockWebkitFile('foo')); 122 | a_dir.content.push(mockWebkitFile('bar')); 123 | var another_dir = mockWebkitDirectory('another_dir'); 124 | another_dir.content.push(mockWebkitFile('baz')); 125 | var items = [a_dir, another_dir]; 126 | element.triggerHandler($.Event('drop', { 127 | dataTransfer: { 128 | items: items 129 | } 130 | })); 131 | expect($rootScope[handler]).toHaveBeenCalledWith(['foo']); 132 | expect($rootScope[handler]).toHaveBeenCalledWith(['bar']); 133 | expect($rootScope[handler]).toHaveBeenCalledWith(['baz']); 134 | }); 135 | 136 | if (folder_upload_available) it('works even with a mix of files and folders', function() { 137 | var handler = 'handler'; 138 | $rootScope.handler = function () {}; 139 | var element = $compile('
')($rootScope); 140 | $rootScope.$digest(); 141 | 142 | spyOn($rootScope, handler); 143 | var a_dir = mockWebkitDirectory('a_dir'); 144 | a_dir.content.push(mockWebkitFile('foo')); 145 | a_dir.content.push(mockWebkitFile('bar')); 146 | var items = [a_dir, mockWebkitFile('baz')]; 147 | element.triggerHandler($.Event('drop', { 148 | dataTransfer: { 149 | items: items 150 | } 151 | })); 152 | expect($rootScope[handler]).toHaveBeenCalledWith(['foo']); 153 | expect($rootScope[handler]).toHaveBeenCalledWith(['bar']); 154 | expect($rootScope[handler]).toHaveBeenCalledWith(['baz']); 155 | }); 156 | 157 | if (folder_upload_available) it('recursively selects all files inside the folder', function() { 158 | var handler = 'handler'; 159 | $rootScope.handler = function () {}; 160 | var element = $compile('
')($rootScope); 161 | $rootScope.$digest(); 162 | 163 | spyOn($rootScope, handler); 164 | var a_dir = mockWebkitDirectory('a_dir'); 165 | var another_dir = mockWebkitDirectory('another_dir'); 166 | another_dir.content.push(mockWebkitFile('baz')); 167 | a_dir.content.push(another_dir); 168 | var items = [a_dir]; 169 | element.triggerHandler($.Event('drop', { 170 | dataTransfer: { 171 | items: items 172 | } 173 | })); 174 | expect($rootScope[handler]).toHaveBeenCalledWith(['baz']); 175 | }); 176 | 177 | if (!folder_upload_available) it('can behave like \'msl-dnd-file-input\' when the browser lacks folder upload support', function() { 178 | var handler = 'handler'; 179 | $rootScope.handler = function () {}; 180 | var element = $compile('
')($rootScope); 181 | $rootScope.$digest(); 182 | 183 | spyOn($rootScope, handler); 184 | element.triggerHandler($.Event('drop', { 185 | dataTransfer: { 186 | files: ['foo', 'bar', 'baz'] 187 | } 188 | })); 189 | expect($rootScope[handler]).toHaveBeenCalledWith(['foo', 'bar', 'baz']); 190 | }); 191 | 192 | it('throws if you don\'t provide a handler', function() { 193 | function compileWithoutHandler() { 194 | $compile('
')($rootScope); 195 | } 196 | expect(compileWithoutHandler).toThrow(); 197 | }); 198 | 199 | it('throws if you provide a missing handler', function() { 200 | function compileWithMissingHandler() { 201 | var handler = 'handler'; 202 | $rootScope[handler] = undefined; 203 | $compile('
')($rootScope); 204 | } 205 | expect(compileWithMissingHandler).toThrow(); 206 | }); 207 | }); -------------------------------------------------------------------------------- /test/unit/msl-dnd-item.js: -------------------------------------------------------------------------------- 1 | describe('Directive msl-dnd-item', function() { 2 | var $compile, $rootScope; 3 | 4 | beforeEach(module('msl.uploads')); 5 | beforeEach(inject(function(_$compile_, _$rootScope_) { 6 | $compile = _$compile_; 7 | $rootScope = _$rootScope_; 8 | })); 9 | 10 | it('on \'dragstart\' sets the \'event.dataTransfer\' property with the JSON representation of the indicated scope value', function() { 11 | var foo = { bar: 'bar' }; 12 | $rootScope.foo = foo; 13 | var element = $compile('
')($rootScope); 14 | $rootScope.$digest(); 15 | var data_transfer = { 16 | setData: function () {} 17 | }; 18 | spyOn(data_transfer, 'setData'); 19 | element.triggerHandler($.Event('dragstart', { 20 | dataTransfer: data_transfer 21 | })); 22 | expect(data_transfer.setData).toHaveBeenCalledWith('text', JSON.stringify(foo)); 23 | }); 24 | 25 | it('throws if you don\'t provide a scope variable', function() { 26 | function compileWithoutVariable() { 27 | $compile('
')($rootScope); 28 | } 29 | expect(compileWithoutVariable).toThrow(); 30 | }); 31 | 32 | it('throws if you provide a missing scope variable', function() { 33 | function compileWithMissingVariable() { 34 | $rootScope.foo = undefined; 35 | $compile('
')($rootScope); 36 | } 37 | expect(compileWithMissingVariable).toThrow(); 38 | }); 39 | }); -------------------------------------------------------------------------------- /test/unit/msl-dnd-target.js: -------------------------------------------------------------------------------- 1 | describe('Directive msl-dnd-target', function() { 2 | var $compile, $rootScope; 3 | 4 | beforeEach(module('msl.uploads')); 5 | beforeEach(inject(function(_$compile_, _$rootScope_) { 6 | $compile = _$compile_; 7 | $rootScope = _$rootScope_; 8 | })); 9 | 10 | it('on \'dragover\' adds a \'msl-drag-over\' class', function() { 11 | var handler = 'handler'; 12 | $rootScope[handler] = function () {}; 13 | var element = $compile('
')($rootScope); 14 | $rootScope.$digest(); 15 | 16 | element.triggerHandler('dragover'); 17 | expect(element.hasClass('msl-drag-over')).toBeTruthy(); 18 | }); 19 | 20 | it('on \'dragleave\' removes the \'msl-drag-over\' class', function() { 21 | var handler = 'handler'; 22 | $rootScope[handler] = function () {}; 23 | var element = $compile('
')($rootScope); 24 | $rootScope.$digest(); 25 | 26 | element.triggerHandler('dragover'); 27 | expect(element.hasClass('msl-drag-over')).toBeTruthy(); 28 | element.triggerHandler('dragleave'); 29 | expect(element.hasClass('msl-drag-over')).toBeFalsy(); 30 | }); 31 | 32 | it('on \'drop\' removes the \'msl-drag-over\' class', function() { 33 | var handler = 'handler'; 34 | $rootScope[handler] = function () {}; 35 | var element = $compile('
')($rootScope); 36 | $rootScope.$digest(); 37 | 38 | element.triggerHandler('dragover'); 39 | expect(element.hasClass('msl-drag-over')).toBeTruthy(); 40 | element.triggerHandler($.Event('drop', { 41 | dataTransfer: { 42 | getData: function () { return null; } 43 | } 44 | })); 45 | expect(element.hasClass('msl-drag-over')).toBeFalsy(); 46 | }); 47 | 48 | it('on \'drop\' removes the \'msl-drag-over\' class', function() { 49 | var handler = 'handler'; 50 | $rootScope[handler] = function () {}; 51 | var element = $compile('
')($rootScope); 52 | $rootScope.$digest(); 53 | 54 | element.triggerHandler('dragover'); 55 | expect(element.hasClass('msl-drag-over')).toBeTruthy(); 56 | element.triggerHandler($.Event('drop', { 57 | dataTransfer: { 58 | getData: function () { return null; } 59 | } 60 | })); 61 | expect(element.hasClass('msl-drag-over')).toBeFalsy(); 62 | }); 63 | 64 | it('on \'drop\' invokes the provided handler', function() { 65 | var handler = 'handler'; 66 | $rootScope[handler] = function () {}; 67 | var element = $compile('
')($rootScope); 68 | $rootScope.$digest(); 69 | 70 | spyOn($rootScope, handler); 71 | var foo = { bar: 'bar' }; 72 | element.triggerHandler($.Event('drop', { 73 | dataTransfer: { 74 | getData: function () { return JSON.stringify(foo); } 75 | } 76 | })); 77 | expect($rootScope[handler]).toHaveBeenCalledWith({ bar: 'bar' }); 78 | }); 79 | 80 | it('throws if you don\'t provide a handler', function() { 81 | function compileWithoutHandler() { 82 | $compile('
')($rootScope); 83 | } 84 | expect(compileWithoutHandler).toThrow(); 85 | }); 86 | 87 | it('throws if you provide a missing handler', function() { 88 | function compileWithMissingHandler() { 89 | var handler = 'handler'; 90 | $rootScope[handler] = undefined; 91 | $compile('
')($rootScope); 92 | } 93 | expect(compileWithMissingHandler).toThrow(); 94 | }); 95 | }); -------------------------------------------------------------------------------- /test/unit/msl-file-input.js: -------------------------------------------------------------------------------- 1 | describe('Directive msl-file-input', function() { 2 | var $compile, $rootScope; 3 | 4 | beforeEach(module('msl.uploads')); 5 | beforeEach(inject(function(_$compile_, _$rootScope_) { 6 | $compile = _$compile_; 7 | $rootScope = _$rootScope_; 8 | })); 9 | 10 | it('removes the \'multiple\' attribute if present', function() { 11 | var handler = 'handler'; 12 | $rootScope[handler] = function () {}; 13 | var element = $compile('')($rootScope); 14 | $rootScope.$digest(); 15 | expect(element.attr('multiple')).toBeUndefined(); 16 | }); 17 | 18 | it('passes the \'multiple\' attribute (if present) to the appended input', function() { 19 | var handler = 'handler'; 20 | $rootScope[handler] = function () {}; 21 | var element = $compile('')($rootScope); 22 | $rootScope.$digest(); 23 | expect(element.attr('multiple')).toBeFalsy(); 24 | var input = element.children().eq(-1); 25 | expect(input.attr('multiple')).toBeTruthy(); 26 | 27 | var element = $compile('')($rootScope); 28 | $rootScope.$digest(); 29 | expect(element.attr('multiple')).toBeFalsy(); 30 | input = element.children().eq(-1); 31 | expect(input.attr('multiple')).toBeFalsy(); 32 | }); 33 | 34 | it('appends a hidden file input', function() { 35 | var handler = 'handler'; 36 | $rootScope[handler] = function () {}; 37 | var element = $compile('')($rootScope); 38 | $rootScope.$digest(); 39 | var input = element.children().eq(-1); 40 | expect(input.attr('type')).toEqual('file'); 41 | expect(input.css('display')).toEqual('none'); 42 | }); 43 | 44 | it('allows to bind a handler for the \'change\' event of the appended input', function() { 45 | var handler = 'handler'; 46 | $rootScope[handler] = function () {}; 47 | var element = $compile('')($rootScope); 48 | $rootScope.$digest(); 49 | 50 | spyOn($rootScope, handler); 51 | var input = element.children().eq(-1); 52 | input.triggerHandler('change'); 53 | expect($rootScope[handler]).toHaveBeenCalledWith(jasmine.any(FileList)); 54 | 55 | input.triggerHandler($.Event('change', { 56 | target: { 57 | files: ['foo', 'bar', 'baz'] 58 | } 59 | })); 60 | expect($rootScope[handler]).toHaveBeenCalledWith(['foo', 'bar', 'baz']); 61 | }); 62 | 63 | it('throws if you forget to provide a handler', function() { 64 | function compileWithoutHandler() { 65 | $compile('')($rootScope); 66 | } 67 | expect(compileWithoutHandler).toThrow(); 68 | }); 69 | 70 | it('throws if you provide a missing handler', function() { 71 | function compileWithMissingHandler() { 72 | var handler = 'handler'; 73 | $rootScope[handler] = undefined; 74 | $compile('')($rootScope); 75 | } 76 | expect(compileWithMissingHandler).toThrow(); 77 | }); 78 | 79 | it('when the container is clicked, triggers a click on the appended input', function() { 80 | var handler = 'handler'; 81 | $rootScope[handler] = function () {}; 82 | var element = $compile('')($rootScope); 83 | $rootScope.$digest(); 84 | 85 | var input = element.children().eq(-1); 86 | var click_on_input = spyOnEvent(input, 'click'); 87 | element.triggerHandler('click'); 88 | expect(click_on_input).toHaveBeenTriggered(); 89 | }); 90 | }); -------------------------------------------------------------------------------- /test/unit/msl-folder-input.js: -------------------------------------------------------------------------------- 1 | describe('Directive msl-folder-input', function() { 2 | var $compile, $rootScope; 3 | 4 | beforeEach(module('msl.uploads')); 5 | beforeEach(inject(function(_$compile_, _$rootScope_) { 6 | $compile = _$compile_; 7 | $rootScope = _$rootScope_; 8 | })); 9 | 10 | function folderUploadAvailable() { 11 | var dummy = document.createElement('input'); 12 | return 'webkitdirectory' in dummy; 13 | } 14 | 15 | var folder_upload_available = folderUploadAvailable(); 16 | 17 | if (folder_upload_available) it('appends a hidden file input', function() { 18 | var handler = 'handler'; 19 | $rootScope[handler] = function () {}; 20 | var element = $compile('')($rootScope); 21 | $rootScope.$digest(); 22 | 23 | var input = element.children().eq(-1); 24 | expect(input.prop('type')).toEqual('file'); 25 | expect(input.css('display')).toEqual('none'); 26 | }); 27 | 28 | if (folder_upload_available) it('appends a hidden file input with \'webkitdirectory\' attribute (that enables folder selection)', function() { 29 | var handler = 'handler'; 30 | $rootScope[handler] = function () {}; 31 | var element = $compile('')($rootScope); 32 | $rootScope.$digest(); 33 | 34 | var input = element.children().eq(-1); 35 | expect(input.prop('webkitdirectory')).not.toBeUndefined(); 36 | }); 37 | 38 | if (folder_upload_available) it('allows to bind a handler for the \'change\' event of the appended input', function() { 39 | var handler = 'handler'; 40 | $rootScope[handler] = function () {}; 41 | var element = $compile('')($rootScope); 42 | $rootScope.$digest(); 43 | 44 | spyOn($rootScope, handler); 45 | var input = element.children().eq(-1); 46 | input.triggerHandler('change'); 47 | expect($rootScope[handler]).toHaveBeenCalledWith(jasmine.any(FileList)); 48 | 49 | input.triggerHandler($.Event('change', { 50 | target: { 51 | files: ['foo', 'bar', 'baz'] 52 | } 53 | })); 54 | expect($rootScope[handler]).toHaveBeenCalledWith(['foo', 'bar', 'baz']); 55 | }); 56 | 57 | it('throws if you forget to provide a handler', function() { 58 | function compileWithoutHandler() { 59 | $compile('')($rootScope); 60 | } 61 | expect(compileWithoutHandler).toThrow(); 62 | }); 63 | 64 | it('throws if you provide a missing handler', function() { 65 | function compileWithMissingHandler() { 66 | var handler = 'handler'; 67 | $rootScope[handler] = undefined; 68 | $compile('')($rootScope); 69 | } 70 | expect(compileWithMissingHandler).toThrow(); 71 | }); 72 | 73 | if (folder_upload_available) it('when the container is clicked, triggers a click on the appended input (IE compatibility)', function() { 74 | var handler = 'handler'; 75 | $rootScope[handler] = function () {}; 76 | var element = $compile('')($rootScope); 77 | $rootScope.$digest(); 78 | 79 | var input = element.children().eq(-1); 80 | var click_on_input = spyOnEvent(input, 'click'); 81 | element.triggerHandler('click'); 82 | expect(click_on_input).toHaveBeenTriggered(); 83 | }); 84 | 85 | if (!folder_upload_available) it('just sets the element disabled if folder upload is not available on current browser', function() { 86 | var handler = 'handler'; 87 | $rootScope[handler] = function () {}; 88 | var element = $compile('')($rootScope); 89 | $rootScope.$digest(); 90 | 91 | expect(element.prop('disabled')).toBeTruthy(); 92 | }); 93 | }); --------------------------------------------------------------------------------