├── .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 | Select file
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 |
11 |
12 | {{file.name}}
13 |
14 |
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 |
11 |
12 | {{file.name}}
13 |
14 |
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 | {{v}}
24 |
25 |
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 | {{v}}
24 |
25 |
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 | Select files
10 |
11 |
12 | {{file.name}}
13 |
14 |
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 | Select folder
10 |
11 |
12 | {{file.name}}
13 |
14 |
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 | });
--------------------------------------------------------------------------------