88669a99024986b19607fc611c9aa7b0568db485
[src/app-framework-demo.git] / afb-client / app / Frontend / widgets / FormInput / UploadFiles.js
1
2 /* 
3  * Copyright (C) 2015 "IoT.bzh"
4  * Author "Fulup Ar Foll"
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details..
15  * 
16  * Reference:
17  *   https://developer.mozilla.org/en/docs/Web/API/FileReader 
18  *   https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications#Using_hidden_file_input_elements_using_the_click%28%29_method
19  *   https://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs
20  *   https://www.terlici.com/2015/05/16/uploading-files-locally.html
21  *   https://github.com/nervgh/angular-file-upload/blob/master/src/services/FileUploader.js
22  *   https://stuk.github.io/jszip/documentation/howto/read_zip.html
23  *   http://onehungrymind.com/zip-parsing-jszip-angular/
24  *   http://stackoverflow.com/questions/15341912/how-to-go-from-blob-to-arraybuffer
25  *   
26  *   Bugs: zip file sent even when flag as invalid 
27  */
28
29  
30
31 (function() {
32 'use strict';
33
34 // WARNING: Angular ng-change does not work on input/file. Let's hook our callback through standard JS function
35 var tmpl =  '<input type="file" name="{{name}}-input" onchange="angular.element(this).scope().UpLoadFile(this.files)" accept="{{mimetype}}" style="display:none">'+
36             '<div class="upload-file" ng-click="imgClicked()">' +
37             '<img id="{{name}}-img" src="{{thumbnail}}">' +
38             '<range-slider ng-show="!noslider" id="{{name}}-slider" automatic=true inithook="SliderInitCB"></range-slider>' +
39             '</div>';
40     
41
42 // Service Create xform insert files in and Post it to url
43 function LoadFileSvc (scope, elem, posturl, files, thumbnailCB) {
44     var xmlReq = new XMLHttpRequest();
45     var xform  = new FormData();
46
47     // Update slider during Upload
48     xmlReq.upload.onprogress = function (event) {
49         var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0);
50         if (scope.slider) scope.slider.setValue (progress);
51     };
52
53     // Upload is finish let's notify controler callback
54     xmlReq.onload = function () {
55         elem.addClass ("success");
56         var response ={
57             status : xmlReq.status,
58             headers: xmlReq.getAllResponseHeaders() 
59         };
60         scope.callback (response);
61     };
62
63     xmlReq.onerror = function () {
64         elem.addClass ("error fail");
65         var response ={
66             status : xmlReq.status,
67             headers: xmlReq.getAllResponseHeaders() 
68         };
69         scope.callback (response);
70     };
71
72     xmlReq.onabort = function () {
73         elem.addClass ("error abort");
74         var response ={
75             status : xmlReq.status,
76             headers: xmlReq.getAllResponseHeaders() 
77         };
78         scope.callback (response);
79     };
80
81     for (var i = 0; i < files.length; i++) {
82         var file = files[i];
83         if (!file.type.match(scope.mimetype)) {
84             continue;
85         }
86
87         console.log ("Selected file=" + file.name + " size="+ file.size/1024 + " Type="+ file.type);
88
89         // File to upload is too big
90         if (file.size > scope.maxsize*1024) {
91             scope.thumbnail = scope.istoobig; // warning if image path is wrong nothing happen
92             scope.$apply('thumbnail'); // we short-circuit Angular resync Image
93             return;
94         }
95
96         // This is not an uploadable file
97         if(isNaN(file.size)) {
98             scope.thumbnail = scope.isnotvalid; 
99             scope.$apply('thumbnail');
100             return;
101         }
102
103         scope.Basename= file.name.split('/').reverse()[0];
104         scope.imgElem[0].file = file;
105
106         // If File is an image let display it now
107         if (thumbnailCB) {
108             var reader = new FileReader();
109             reader.readAsArrayBuffer(file);
110             reader.onload = function (target) {
111                 var status = thumbnailCB (target);
112                 //if (status) xform.append(scope.name, file, file.name);
113             };
114         } 
115         // if everything is OK let's add file to xform
116         xform.append(scope.name, file, file.name);
117     }
118
119
120     // everything looks OK let's Post it
121     xmlReq.open("POST", posturl , true);
122     xmlReq.send(xform);
123 }
124
125 angular.module('UploadFiles',['ConfigApp', 'ModalNotification', 'RangeSlider'])
126
127 .directive('uploadImage', function(ConfigApp,  JQemu, Notification) {
128     function mymethods(scope, elem, attrs) {
129         
130         // get widget image handle from template
131         scope.imgElem    = elem.find('img');
132         scope.inputElem  = elem.find('input');
133         
134         // Image was ckick let's simulate an input (file) click
135         scope.imgClicked = function () {
136             scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
137         };
138         
139         // Slider control handle registration after creation
140         scope.SliderInitCB=function (slider) {
141            scope.slider= slider; 
142         };
143         
144         // Upload is delegated to a shared function
145         scope.UpLoadFile=function (files) {
146             var readerCB = function (upload) {
147                 // scope.thumbnail = upload.target.result;
148                 scope.imgElem[0].src = window.URL.createObjectURL(new Blob([upload.target.result], {type: "image"}));                
149                 return true; // true activates post
150             };
151             var posturl = attrs.posturl + "?token=" + ConfigApp.session.token;
152             new LoadFileSvc (scope, elem, posturl, files, readerCB);
153         };
154
155         // Initiallize default values from attributes values
156         scope.name= attrs.name || 'avatar';
157         scope.category= attrs.category  || 'image';
158         scope.mimetype= (attrs.accept || 'image') + '/*';
159         scope.maxsize= attrs.maxsize || 100; // default max size 100KB
160         scope.regexp = new RegExp (attrs.accept+ '.*','i');
161
162         if (attrs.thumbnail) scope.thumbnail= ConfigApp.paths[scope.category] +  attrs.thumbnail;
163         else  scope.thumbnail=ConfigApp.paths[scope.category] + 'tux-bzh.png';
164         
165         if (attrs.thumbnail) scope.isnotvalid= ConfigApp.paths[scope.category] +  attrs.isnotvalid;
166         else  scope.isnotvalid=ConfigApp.paths[scope.category] + 'isnotvalid.png';
167
168         if (attrs.istoobig) scope.istoobig= ConfigApp.paths[scope.category] +  attrs.istoobig;
169         else  scope.istoobig=ConfigApp.paths[scope.category] + 'istoobig.png';
170         scope.noslider = attrs.noslider || false;
171
172         if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs);            
173     }
174     return {
175         restrict: 'E',
176         template: tmpl,
177         link: mymethods,
178         scope: {
179             callback : '='
180         }
181     };
182 })
183     
184 .directive('uploadAudio', function(ConfigApp,  JQemu, Notification) {
185     function mymethods(scope, elem, attrs) {
186         
187         // get widget image handle from template
188         scope.imgElem    = elem.find('img');
189         scope.inputElem  = elem.find('input');
190         
191         // Image was ckick let's simulate an input (file) click
192         scope.imgClicked = function () {
193             scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
194         };
195         
196         // Slider control handle registration after creation
197         scope.SliderInitCB=function (slider) {
198            scope.slider= slider; 
199         };
200         
201         // Upload is delegated to a shared function
202         scope.UpLoadFile=function (files) {
203             var posturl = attrs.posturl + "?token=" + ConfigApp.session.token;
204             new LoadFileSvc (scope, elem, posturl, files, false);
205         };
206
207         // Initiallize default values from attributes values
208         scope.name= attrs.name || 'audio';
209         scope.category= attrs.category  || 'audio';
210         scope.mimetype= (attrs.accept || 'audio') + '/*';
211         scope.maxsize= attrs.maxsize || 10000; // default max size 10MB
212         scope.regexp = new RegExp (attrs.accept+ '.*','i');
213
214         if (attrs.thumbnail) scope.thumbnail= ConfigApp.paths[scope.category] +  attrs.thumbnail;
215         else  scope.thumbnail=ConfigApp.paths[scope.category] + 'upload-music.png';
216         
217         if (attrs.thumbnail) scope.isnotvalid= ConfigApp.paths[scope.category] +  attrs.isnotvalid;
218         else  scope.isnotvalid=ConfigApp.paths[scope.category] + 'isnotvalid.png';
219
220         if (attrs.istoobig) scope.istoobig= ConfigApp.paths[scope.category] +  attrs.istoobig;
221         else  scope.istoobig=ConfigApp.paths[scope.category] + 'istoobig.png';
222         scope.noslider = attrs.noslider || false;
223
224         if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs);            
225     }
226     return {
227         restrict: 'E',
228         template: tmpl,
229         link: mymethods,
230         scope: {
231             callback : '='
232         }
233     };
234     
235 })
236
237 .directive('uploadAppli', function(ConfigApp,  JQemu, Notification) {
238     function mymethods(scope, elem, attrs) {
239         
240         // get widget image handle from template
241         scope.imgElem    = elem.find('img');
242         scope.inputElem  = elem.find('input');
243         
244         // Image was ckick let's simulate an input (file) click
245         scope.imgClicked = function () {
246             scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
247         };
248         
249         // Slider control handle registration after creation
250         scope.SliderInitCB=function (slider) {
251            scope.slider= slider; 
252         };
253         
254         // Upload is delegated to a shared function
255         scope.UpLoadFile=function (files) {
256                        
257             var readerCB = function (upload) {
258                 var zipapp = new JSZip(upload.target.result);
259                 var thumbnail = zipapp.file("afa-pkg/thumbnail.jpg");
260                 
261                 // Check is we have a thumbnail within loaded Zipfile
262                 if (!thumbnail) {
263                     console.log ("This is not a valid Application Framework APP");
264                     scope.thumbnail=ConfigApp.paths[scope.category] + 'isnotvalid.png';
265                     scope.$apply('thumbnail'); // we short-circuit Angular resync Image
266                     return false; // do not post zip on binder
267                 } 
268                 scope.imgElem[0].src = window.URL.createObjectURL(new Blob([thumbnail.asArrayBuffer()], {type: "image"}));                        
269                 return true; // true activates post
270             };
271             var posturl = attrs.posturl + "?token=" + ConfigApp.session.token;
272             new LoadFileSvc (scope, elem, posturl, files, readerCB);
273         };
274
275         // Initiallize default values from attributes values
276         scope.name= attrs.name || 'appli';
277         scope.category= attrs.category  || 'appli';
278         scope.mimetype= (attrs.accept || '.zip');
279         scope.maxsize= attrs.maxsize || 100000; // default max size 100MB
280         scope.regexp = new RegExp (attrs.accept+ '.*','i');
281
282         if (attrs.thumbnail) scope.thumbnail= ConfigApp.paths[scope.category] +  attrs.thumbnail;
283         else  scope.thumbnail=ConfigApp.paths[scope.category] + 'upload-appli.png';
284         
285         if (attrs.thumbnail) scope.isnotvalid= ConfigApp.paths[scope.category] +  attrs.isnotvalid;
286         else  scope.isnotvalid=ConfigApp.paths[scope.category] + 'isnotvalid.png';
287
288         if (attrs.istoobig) scope.istoobig= ConfigApp.paths[scope.category] +  attrs.istoobig;
289         else  scope.istoobig=ConfigApp.paths[scope.category] + 'istoobig.png';
290         scope.noslider = attrs.noslider || false;
291
292         if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs);            
293     }
294     return {
295         restrict: 'E',
296         template: tmpl,
297         link: mymethods,
298         scope: {
299             callback : '='
300         }
301     };
302     
303 });
304
305 console.log ("UploadFile Loaded");
306 })();