Implemented client upload with rangeslider and zip open
authorFulup Ar Foll <fulup@iot.bzh>
Sun, 20 Dec 2015 20:04:34 +0000 (21:04 +0100)
committerFulup Ar Foll <fulup@iot.bzh>
Sun, 20 Dec 2015 20:04:34 +0000 (21:04 +0100)
25 files changed:
afb-client/app/Backend/RestApis/PostMockApi.js
afb-client/app/Frontend/app.js
afb-client/app/Frontend/etc/ConfigApp.js [moved from afb-client/app/Frontend/services/ConfigApp.js with 76% similarity]
afb-client/app/Frontend/etc/routes.js [moved from afb-client/app/Frontend/tmp/routes.js with 100% similarity]
afb-client/app/Frontend/images/appli/isnotvalid.png [new file with mode: 0644]
afb-client/app/Frontend/images/appli/istoobig.png [new file with mode: 0644]
afb-client/app/Frontend/images/appli/upload-appli.png [new file with mode: 0644]
afb-client/app/Frontend/images/audio/istoobig.png [new file with mode: 0644]
afb-client/app/Frontend/images/audio/upload-music.png [new file with mode: 0644]
afb-client/app/Frontend/images/avatars/istoobig.png [new file with mode: 0644]
afb-client/app/Frontend/pages/Home/Home.html
afb-client/app/Frontend/pages/Home/HomeModule.js
afb-client/app/Frontend/pages/Sample/Sample.html
afb-client/app/Frontend/pages/Sample/SampleModule.js
afb-client/app/Frontend/services/JQueryEmu.js
afb-client/app/Frontend/widgets/FormInput/FormInput.scss
afb-client/app/Frontend/widgets/FormInput/UploadFile.js [deleted file]
afb-client/app/Frontend/widgets/FormInput/UploadFiles.js [new file with mode: 0644]
afb-client/app/Frontend/widgets/FormInput/newjavascript.js [new file with mode: 0644]
afb-client/app/Frontend/widgets/Notifications/TokenRefreshSvc.js
afb-client/app/Frontend/widgets/RangeSliders/RangeSliderMod.js [new file with mode: 0644]
afb-client/app/Frontend/widgets/RangeSliders/Rangeslider.scss [new file with mode: 0644]
afb-client/app/etc/AppDefaults.js
afb-client/bower.json
afb-client/gulpfile.js

index 6299f39..022f774 100644 (file)
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * References: https://github.com/expressjs/multer
  */
 
 var fs = require('fs');
 var multer = require('multer');
  
 function NewApi(handle, prefix) {
-    var self=this;
-    handle.trace (this,1, "Mock PostApi url=%s", prefix +'/ping');
-    var upload = multer({ dest: '/tmp/uploads/' });
+    var scope=this; // make sure not to loose object context in async callback
     
-    handle.app.post(prefix +'/upload', upload.single('avatar'), function (req, res) {
-        handle.trace (self, 1, "%s/upload file=", prefix, req.file.originalname);
-        var upload = multer({ dest: '/tmp/uploads/' });
+    // defined upload directory and check it's a valid one
+    var upload = multer({ dest: handle.config.UPLOAD_DIR});
+    // WARNING: single('avatar') should match with <upload-image name="avatar">
+    handle.app.post(prefix +'/upload-image', upload.single('avatar'), function (req, res) {
         
+        handle.trace (scope, 1, "%s/upload file=%s dest=%s/%s", prefix, req.file.originalname, req.file.destination, req.file.filename);
+        res.send({"jtype": "TEST_message", "status": "success", "info": "done"});
+    });
+    
+    // WARNING: single('music') should match with <upload-audio name="music">
+    handle.app.post(prefix +'/upload-music', upload.single('music'), function (req, res) {
+        
+        handle.trace (scope, 1, "%s/upload file=%s dest=%s/%s", prefix, req.file.originalname, req.file.destination, req.file.filename);
+        res.send({"jtype": "TEST_message", "status": "success", "info": "done"});
+    });
+    
+    // WARNING: single('appli') should match with <upload-audio name="appli">
+    handle.app.post(prefix +'/upload-appli', upload.single('appli'), function (req, res) {
+        
+        handle.trace (scope, 1, "%s/upload file=%s dest=%s/%s", prefix, req.file.originalname, req.file.destination, req.file.filename);
         res.send({"jtype": "TEST_message", "status": "success", "info": "done"});
     });
     
index e539062..5e99d77 100644 (file)
     'JQueryEmu',
     'HomeModule',
     'SampleModule',
-    'UploadFile',
+    'UploadFiles',
     'LinkButton',
     'TokenRefresh',
+    'RangeSlider',
     'ModalNotification'
   ])
     .config(config)
   ;
 
   config.$inject = ['$urlRouterProvider', '$locationProvider'];
+  
+  console.log ("***location=" + window.location + " search" + window.search)
 
-  function config($urlProvider, $locationProvider) {
+  function config($urlProvider, $locationProvider, ConfigApp) {
     $urlProvider.otherwise('/home');
 
     // https://docs.angularjs.org/error/$location/nobase
@@ -41,5 +44,5 @@
     FastClick.attach(document.body);
   }
 
-console.log ("@@APPNAME@@ Loaded");
+console.log ("opa=@@APPNAME@@ Loaded");
 })();
similarity index 76%
rename from afb-client/app/Frontend/services/ConfigApp.js
rename to afb-client/app/Frontend/etc/ConfigApp.js
index 310ef88..22cf220 100644 (file)
@@ -5,13 +5,16 @@
     angular.module('ConfigApp', [])
 
             // Factory is a singleton and share its context within all instances.
-            .factory('ConfigApp', function () {
+            .factory('ConfigApp', function ($location, $window) {
 
+                // console.log ("URL="+ $location.url() + " Query=" + location.href+ " window=" + document.referrer);
 
                 var myConfig = {
                     paths: { // Warning paths should end with /
-                        images : 'images/',
-                        avatars: 'images/avatars/'
+                        image : 'images/',
+                        avatar: 'images/avatars/',
+                        audio : 'images/audio/',
+                        appli : 'images/appli/'
                     },
                     
                     api: { // Warning paths should end with /
diff --git a/afb-client/app/Frontend/images/appli/isnotvalid.png b/afb-client/app/Frontend/images/appli/isnotvalid.png
new file mode 100644 (file)
index 0000000..ee40d2a
Binary files /dev/null and b/afb-client/app/Frontend/images/appli/isnotvalid.png differ
diff --git a/afb-client/app/Frontend/images/appli/istoobig.png b/afb-client/app/Frontend/images/appli/istoobig.png
new file mode 100644 (file)
index 0000000..5614073
Binary files /dev/null and b/afb-client/app/Frontend/images/appli/istoobig.png differ
diff --git a/afb-client/app/Frontend/images/appli/upload-appli.png b/afb-client/app/Frontend/images/appli/upload-appli.png
new file mode 100644 (file)
index 0000000..a35fd3a
Binary files /dev/null and b/afb-client/app/Frontend/images/appli/upload-appli.png differ
diff --git a/afb-client/app/Frontend/images/audio/istoobig.png b/afb-client/app/Frontend/images/audio/istoobig.png
new file mode 100644 (file)
index 0000000..5614073
Binary files /dev/null and b/afb-client/app/Frontend/images/audio/istoobig.png differ
diff --git a/afb-client/app/Frontend/images/audio/upload-music.png b/afb-client/app/Frontend/images/audio/upload-music.png
new file mode 100644 (file)
index 0000000..2006ef0
Binary files /dev/null and b/afb-client/app/Frontend/images/audio/upload-music.png differ
diff --git a/afb-client/app/Frontend/images/avatars/istoobig.png b/afb-client/app/Frontend/images/avatars/istoobig.png
new file mode 100644 (file)
index 0000000..5614073
Binary files /dev/null and b/afb-client/app/Frontend/images/avatars/istoobig.png differ
index 6eda66d..25bb983 100644 (file)
@@ -1,5 +1,4 @@
-<!-- comment -->
-
+<!-- Foundation Annotations generate tmp/route.js -->
 ---
 name: myhome
 url:  /home
@@ -31,6 +30,6 @@ animationIn: slideInRight
     </div>
 </div>
 
-<!--
+
 <link-button href="sample" icon="fi-home" label="sample"></link-button>
--->
+
index 30e796e..6ebaefc 100644 (file)
@@ -29,10 +29,10 @@ angular.module('HomeModule', ['SubmitButton', 'TokenRefresh'])
 
             // Make sure we clean everything when Open/Close is called
             if (apiname === "APIcreate" || apiname === "APIreset") {
-                scope["APIreset"]='';
-                scope["APIcreate"]='';
-                scope["APIrefresh"]='';
-                scope["APIcheck"]='';
+                scope.APIreset  ='';
+                scope.APIcreate ='';
+                scope.APIrefresh='';
+                scope.APIcheck  ='';
             }
             scope[apiname]="success";
             
index e7e9164..35523a9 100644 (file)
@@ -1,5 +1,4 @@
-<!-- comment -->
-
+<!-- Foundation Annotations generate tmp/route.js -->
 ---
 name: mysample
 url:  /sample
@@ -8,16 +7,28 @@ animationIn: slideInRight
 ---
 
 <h1><img class="logo" src="images/logo/triskel_iot_bzhx250.png" alt="IoT.bzh Logo" style="height:150px;">
-    Not Working
+    Post File Upload
 </h1>
 
 <div class="sample-box box-content">
     
-    <upload-file name="avatar" category="avatars" icon="tux-visitor.png"></upload-file>
+    <!-- Usage: upload-xxxxx
+         name     = [xxxxxx] is use a field label for xform input field. Should match with server side
+         category = [avatar] should match to a valid directory of thumbnail within AppConfig.path
+         thumbnail= [tux-bzh.png] a valid image within AppConfig.paths.[category]
+         istoobig = [istoobig.png] used image from AppConfig.paths.[category] when file is oversized
+         maxsize  = [xxx] maximum size in KB [default max depend on upload-type]
+         accept = [image] acceptable accept for upload
+    -->
+    <upload-image name="avatar" category="avatar" thumbnail="tux-visitor.png" maxsize="100" 
+        posturl="/api/post/upload-image" callback="ctrl.FileUploaded" accept="image">
+    </upload-image>
     
-    <submit-button class="sample-button"  icon="fi-zoom-in" label="Vol+"   clicked="ctrl.MuteOn" ></submit-button>
-    <submit-button class="sample-button" icon="fi-zoom-out" label="Vol-" clicked="ctrl.MuteOff" ></submit-button>
-    <submit-button class="home-button"  icon="fi-upload" label="Refresh"   clicked="ctrl.UploadFile" ></submit-button>
+    <!-- Warning: name=xxx should match with what server expect [used as xform input name -->
+    <upload-audio  name=music posturl="/api/post/upload-music" callback="ctrl.FileUploaded"></upload-audio>
+
+    <!-- Warning: name=xxx should match with what server expect [used as xform input name -->
+    <upload-appli  name=appli posturl="/api/post/upload-appli" callback="ctrl.FileUploaded"></upload-appli>
 
 </div>
 
index 2a2e777..8ae82ea 100644 (file)
@@ -2,46 +2,16 @@
 'use strict';
 
 // list all rependencies within the page + controler if needed
-angular.module('SampleModule', ['SubmitButton','UploadFile'])
+angular.module('SampleModule', ['SubmitButton','UploadFiles'])
 
   .controller('SampleController', function ($http) {
-        var self = this; // I hate JavaScript
-        this.status='muted-off';
+        var scope = this; // I hate JavaScript
 
-        console.log ("sample controller");
-
-        this.MuteOn = function() {
-           console.log ("Muted");
-            // send AJAX request to server
-            var handler = $http.post('/api/dbus/ping', {type:'mute', action: "on"});
-            
-            handler.success(function(response, errcode, headers, config) {
-                self.status = 'muted-on';                
-            });
-
-            handler.error(function(status, errcode, headers) {
-                console.log ("Oops /api/dbus/pring err=" + errcode);
-                self.status = 'muted-error';                
-            });
-        };
+        console.log ("sample Init");
         
-        this.MuteOff = function() {
-           console.log ("UnMuted"); 
-            // send AJAX request to server
-            var handler = $http.post('/api/dbus/ping', {type:'mute', action: "off"});
-            
-            handler.success(function(response, errcode, headers, config) {
-               self.status = 'muted-off';                
-            });
-
-            handler.error(function(status, errcode, headers) {
-                console.log ("Oops /api/dbus/ping err=" + errcode);
-                self.status = 'muted-error';                
-            });
-            
+        scope.FileUploaded = function (response) {
+           console.log ("FileUploaded response=%s", JSON.stringify(response));
         };
-
    });
 
 console.log ("SampleControler Loaded");
index 5112052..6d6e338 100644 (file)
@@ -1,14 +1,39 @@
+/* 
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * Usage:
+ * 
+ *   // mouse event probably point on icon and not on button div
+ *   ... ng-click="LockChannel($event)
+     var target= angular.element(event.currentTarget);
+     var button= JQemu.FindInParent (target, 'div');
+ * 
+ */
+
+
 (function () {
     'use strict';
 
+
+          
     // _all modules only reference dependencies
     angular.module('JQueryEmu', [])
 
             // Factory is a singleton and share its context within all instances.
             .factory('JQemu', function () {
 
-                // JQueryLight cannot search a tag within ancestrors
-                var parent = function (element, selector) {
+                var FindInParent = function (element, selector) {
                     var parent = element;
                     var search = selector.toUpperCase();
                     while (parent[0]) {
                     }
                 };
                 
-                // JQueryLight cannot search by type
-                var  findByType= function (element, selector) {
+                var  FindByTag= function (element, tag, selector) {
+                    var search = selector.toLowerCase();
+                    var type   = tag.toLowerCase()+ "Name";
+                    var children = element.children();
+                    while (children[0]) {
+                        if (search === children[0][type]) {
+                            return children;
+                        }  // HTMLDivElement properties
+                        children = children.next();
+                    }
+                };
+                
+                var  FindByClass= function (element, selector) {
                     var search = selector.toLowerCase();
                     var children = element.children();
                     while (children[0]) {
-                        if (search === children[0].type) {
+                        if (children.hasClass(search)) {
                             return children;
                         }  // HTMLDivElement properties
                         children = children.next();
@@ -32,8 +68,9 @@
                 };
 
                 var myMethods = {
-                    parent: parent,
-                    findByType: findByType
+                    FindInParent: FindInParent,
+                    FindByTag: FindByTag,
+                    FindByClass: FindByClass
                 };
 
                 return myMethods;
index 37519fd..77aed6e 100644 (file)
@@ -7,11 +7,33 @@
 
 @import "app/ibz-mixins";
 
-upload-file {
-    height: 5rem;
+.upload-file {
     display: inline-block;
     float: right;
+    height : 5rem;
+    width  : 5rem;
+    margin: 0.5rem;
+
     img { height: inherit;}
+    
+    .ibz-range-slider {
+        height: 10% !important;
+        border-radius: 5px;
+        background-color: lightgrey !important;
+    
+        .range-slider-handle {
+            width: 10% !important;
+            height: 100% !important;
+            margin-top: .2rem;
+            background-color: purple !important;
+        }
+
+        .range-slider-active-segment {
+            height: 80% !important;
+            background-color: lightgreen;
+        }
+    }
+
 }
 
 input-text {
diff --git a/afb-client/app/Frontend/widgets/FormInput/UploadFile.js b/afb-client/app/Frontend/widgets/FormInput/UploadFile.js
deleted file mode 100644 (file)
index 9a2f031..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-
-/* 
- * Copyright (C) 2015 "IoT.bzh"
- * Author "Fulup Ar Foll"
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details..
- * 
- * Reference: 
- *   https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications#Using_hidden_file_input_elements_using_the_click%28%29_method
- *   https://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs
- *   https://www.terlici.com/2015/05/16/uploading-files-locally.html
- *   https://github.com/nervgh/angular-file-upload/blob/master/src/services/FileUploader.js
- */
-
-   
-function changeInput() {
-     console.log ('input imgClicked'); 
-}   
-
-(function() {
-'use strict';
-
-// WARNING: Angular ng-change does not work on input/file. Let's hook our callback through standard JS function
-var tmpl = '<form target="null" action="/api/afbs/file-upload" method="post" enctype="multipart/form-data" >'+
-           '<input type="file" name="{{name}}" onchange="angular.element(this).scope().UpLoadFile(this.files)" accept="{{mime}}/*" style="display" >'+
-           '<input type="submit" class="submit" style="display" > ' +
-           '</form>' + 
-           '<img id="{{name}}-img" src="{{imagepath}}" ng-click="imgClicked()">' ;
-
-function basename(path) {
-   return path.split('/').reverse()[0];
-}
-
-angular.module('UploadFile',['ConfigApp'])
-
-.directive('uploadFile', function(ConfigApp, $http, JQemu) {
-    function mymethods(scope, elem, attrs) {
-        // get widget image handle from template
-        scope.imgElem    = elem.find('img');
-        scope.inputElem  = elem.find('input');
-        scope.submitElem = JQemu.findByType (elem.children(), "submit");
-
-        
-        // Image was ckick let's simulate an input (file) click
-        scope.imgClicked = function () {
-            scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
-        };
-        
-        // upload file to server 
-        scope.UpLoadFile= function(files) {
-            
-
-            for (var i = 0; i < files.length; i++) {
-                var file = files[i];
-                console.log ("Selected file=" + file.name + " size="+ file.size/1024);
-                var mimeType = /image.*/;  // build regular expression from Mime
-                if (!file.type.match(mimeType)) {
-                    continue;
-                }
-                         
-                if (file.size > scope.sizemax*1024) {
-                    scope.imagepath = scope.istoobig; // warning is path is wrong nothing happen
-                    scope.$apply('imagepath'); // we short-circuit Angular resync Image
-                } else {
-
-                    scope.basename=basename(file.name);
-                    scope.imgElem[0].file = file;
-
-                    var reader = new FileReader();
-                    reader.readAsDataURL(file);
-                    reader.onload = function (upload) {
-                        scope.imagepath = upload.target.result;
-                        scope.$apply('imagepath'); // we short-circuit Angular resync image
-                        scope.submitElem[0].click(); // Warning Angular TriggerEvent does not work!!!
-                    };
-                }
-            }
-        };
-
-        // Initiallize default values from attributes values
-        if (attrs.icon) scope.imagepath= ConfigApp.paths[attrs.category] +  attrs.icon;
-        else  scope.imagepath=ConfigApp.paths.avatars + 'tux-bzh.png';
-        
-        if (attrs.istoobig) scope.istoobig= ConfigApp.paths[attrs.category] +  attrs.istoobig;
-        else  scope.istoobig=ConfigApp.paths.avatars + 'istoobig.jpg';
-        
-        scope.name= attrs.name || 'avatar';
-        scope.mime= attrs.mime || 'image';
-        scope.sizemax= attrs.sizemax || 100; // default max size 100KB
-    
-    }
-    
-    return {
-        restrict: 'E',
-        template: tmpl,
-        link: mymethods,
-        scope: {
-            callback : '='
-        }
-    };
-});
-
-console.log ("UploadFile Loaded");
-})();
diff --git a/afb-client/app/Frontend/widgets/FormInput/UploadFiles.js b/afb-client/app/Frontend/widgets/FormInput/UploadFiles.js
new file mode 100644 (file)
index 0000000..6c68960
--- /dev/null
@@ -0,0 +1,309 @@
+
+/* 
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details..
+ * 
+ * Reference:
+ *   https://developer.mozilla.org/en/docs/Web/API/FileReader 
+ *   https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications#Using_hidden_file_input_elements_using_the_click%28%29_method
+ *   https://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs
+ *   https://www.terlici.com/2015/05/16/uploading-files-locally.html
+ *   https://github.com/nervgh/angular-file-upload/blob/master/src/services/FileUploader.js
+ *   https://stuk.github.io/jszip/documentation/howto/read_zip.html
+ *   http://onehungrymind.com/zip-parsing-jszip-angular/
+ */
+
+   
+function changeInput() {
+     console.log ('input imgClicked'); 
+}   
+
+(function() {
+'use strict';
+
+// WARNING: Angular ng-change does not work on input/file. Let's hook our callback through standard JS function
+var tmpl =  '<input type="file" name="{{name}}-input" onchange="angular.element(this).scope().UpLoadFile(this.files)" accept="{{mimetype}}" style="display:none">'+
+            '<div class="upload-file" ng-click="imgClicked()">' +
+            '<img id="{{name}}-img" src="{{thumbnail}}">' +
+            '<range-slider ng-show="!noslider" id="{{name}}-slider" automatic=true inithook="SliderInitCB"></range-slider>' +
+            '</div>';
+    
+
+function Basename(path) {
+   return path.split('/').reverse()[0];
+}
+
+// Service Create xform insert files in and Post it to url
+function LoadFileSvc (scope, elem, posturl, files, thumbnailCB) {
+    var xmlReq = new XMLHttpRequest();
+    var xform  = new FormData();
+
+    // Update slider during Upload
+    xmlReq.upload.onprogress = function (event) {
+        var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0);
+        if (scope.slider) scope.slider.setValue (progress);
+    };
+
+    // Upload is finish let's notify controler callback
+    xmlReq.onload = function () {
+        elem.addClass ("success");
+        var response ={
+            status : xmlReq.status,
+            headers: xmlReq.getAllResponseHeaders() 
+        };
+        scope.callback (response);
+    };
+
+    xmlReq.onerror = function () {
+        elem.addClass ("error fail");
+        var response ={
+            status : xmlReq.status,
+            headers: xmlReq.getAllResponseHeaders() 
+        };
+        scope.callback (response);
+    };
+
+    xmlReq.onabort = function () {
+        elem.addClass ("error abort");
+        var response ={
+            status : xmlReq.status,
+            headers: xmlReq.getAllResponseHeaders() 
+        };
+        scope.callback (response);
+    };
+
+    for (var i = 0; i < files.length; i++) {
+        var file = files[i];
+        if (!file.type.match(scope.mimetype)) {
+            continue;
+        }
+
+        console.log ("Selected file=" + file.name + " size="+ file.size/1024 + " Type="+ file.type);
+
+        // File to upload is too big
+        if (file.size > scope.maxsize*1024) {
+            scope.thumbnail = scope.istoobig; // warning if image path is wrong nothing happen
+            scope.$apply('thumbnail'); // we short-circuit Angular resync Image
+            return;
+        }
+
+        // This is not an uploadable file
+        if(isNaN(file.size)) {
+            scope.thumbnail = scope.isnotvalid; 
+            scope.$apply('thumbnail');
+            return;
+        }
+
+        scope.Basename=Basename(file.name);
+        scope.imgElem[0].file = file;
+
+        // If File is an image let display it now
+        if (thumbnailCB) {
+            var reader = new FileReader();
+            reader.readAsArrayBuffer(file);
+            reader.onload = thumbnailCB;
+        }
+
+        // if everything is OK let's add file to xform
+        xform.append(scope.name, file, file.name);
+    }
+
+
+    // everything looks OK let's Post it
+    xmlReq.open("POST", posturl , true);
+    xmlReq.send(xform);
+};
+
+angular.module('UploadFiles',['ConfigApp', 'ModalNotification', 'RangeSlider'])
+
+.directive('uploadImage', function(ConfigApp,  JQemu, Notification) {
+    function mymethods(scope, elem, attrs) {
+        
+        // get widget image handle from template
+        scope.imgElem    = elem.find('img');
+        scope.inputElem  = elem.find('input');
+        
+        // Image was ckick let's simulate an input (file) click
+        scope.imgClicked = function () {
+            scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
+        };
+        
+        // Slider control handle registration after creation
+        scope.SliderInitCB=function (slider) {
+           scope.slider= slider; 
+        };
+        
+        // Upload is delegated to a shared function
+        scope.UpLoadFile=function (files) {
+            var readerCB = function (upload) {
+                // scope.thumbnail = upload.target.result;
+                scope.imgElem[0].src = window.URL.createObjectURL(new Blob([upload.target.result], {type: "image"}));
+                scope.$apply('thumbnail');    // we short-circuit Angular resync image
+            };
+            var posturl = attrs.posturl + "?token=" + ConfigApp.session.token;
+            LoadFileSvc (scope, elem, posturl, files, readerCB);
+        };
+
+        // Initiallize default values from attributes values
+        scope.name= attrs.name || 'avatar';
+        scope.category= attrs.category  || 'image';
+        scope.mimetype= (attrs.accept || 'image') + '/*';
+        scope.maxsize= attrs.maxsize || 100; // default max size 100KB
+        scope.regexp = new RegExp (attrs.accept+ '.*','i');
+
+        if (attrs.thumbnail) scope.thumbnail= ConfigApp.paths[scope.category] +  attrs.thumbnail;
+        else  scope.thumbnail=ConfigApp.paths[scope.category] + 'tux-bzh.png';
+        
+        if (attrs.thumbnail) scope.isnotvalid= ConfigApp.paths[scope.category] +  attrs.isnotvalid;
+        else  scope.isnotvalid=ConfigApp.paths[scope.category] + 'isnotvalid.png';
+
+        if (attrs.istoobig) scope.istoobig= ConfigApp.paths[scope.category] +  attrs.istoobig;
+        else  scope.istoobig=ConfigApp.paths[scope.category] + 'istoobig.png';
+        scope.noslider = attrs.noslider || false;
+
+        if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs);            
+    }
+    return {
+        restrict: 'E',
+        template: tmpl,
+        link: mymethods,
+        scope: {
+            callback : '='
+        }
+    };
+})
+    
+.directive('uploadAudio', function(ConfigApp,  JQemu, Notification) {
+    function mymethods(scope, elem, attrs) {
+        
+        // get widget image handle from template
+        scope.imgElem    = elem.find('img');
+        scope.inputElem  = elem.find('input');
+        
+        // Image was ckick let's simulate an input (file) click
+        scope.imgClicked = function () {
+            scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
+        };
+        
+        // Slider control handle registration after creation
+        scope.SliderInitCB=function (slider) {
+           scope.slider= slider; 
+        };
+        
+        // Upload is delegated to a shared function
+        scope.UpLoadFile=function (files) {
+            var posturl = attrs.posturl + "?token=" + ConfigApp.session.token;
+            LoadFileSvc (scope, elem, posturl, files, false);
+        };
+
+        // Initiallize default values from attributes values
+        scope.name= attrs.name || 'audio';
+        scope.category= attrs.category  || 'audio';
+        scope.mimetype= (attrs.accept || 'audio') + '/*';
+        scope.maxsize= attrs.maxsize || 10000; // default max size 10MB
+        scope.regexp = new RegExp (attrs.accept+ '.*','i');
+
+        if (attrs.thumbnail) scope.thumbnail= ConfigApp.paths[scope.category] +  attrs.thumbnail;
+        else  scope.thumbnail=ConfigApp.paths[scope.category] + 'upload-music.png';
+        
+        if (attrs.thumbnail) scope.isnotvalid= ConfigApp.paths[scope.category] +  attrs.isnotvalid;
+        else  scope.isnotvalid=ConfigApp.paths[scope.category] + 'isnotvalid.png';
+
+        if (attrs.istoobig) scope.istoobig= ConfigApp.paths[scope.category] +  attrs.istoobig;
+        else  scope.istoobig=ConfigApp.paths[scope.category] + 'istoobig.png';
+        scope.noslider = attrs.noslider || false;
+
+        if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs);            
+    }
+    return {
+        restrict: 'E',
+        template: tmpl,
+        link: mymethods,
+        scope: {
+            callback : '='
+        }
+    };
+    
+})
+
+.directive('uploadAppli', function(ConfigApp,  JQemu, Notification) {
+    function mymethods(scope, elem, attrs) {
+        
+        // get widget image handle from template
+        scope.imgElem    = elem.find('img');
+        scope.inputElem  = elem.find('input');
+        
+        // Image was ckick let's simulate an input (file) click
+        scope.imgClicked = function () {
+            scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
+        };
+        
+        // Slider control handle registration after creation
+        scope.SliderInitCB=function (slider) {
+           scope.slider= slider; 
+        };
+        
+        // Upload is delegated to a shared function
+        scope.UpLoadFile=function (files) {
+                       
+            var readerCB = function (upload) {
+                var zipapp = new JSZip(upload.target.result);
+                var thumbnail = zipapp.file("afa-pkg/thumbnail.jpg");
+                
+                // Check is we have a thumbnail within loaded Zipfile
+                if (!thumbnail) {
+                    console.log ("This is not a valid Application Framework APP");
+                    scope.thumbnail=ConfigApp.paths[scope.category] + 'isnotvalid.png';
+                    scope.$apply('thumbnail');    // we short-circuit Angular resync image
+                    return;
+                } 
+                scope.imgElem[0].src = window.URL.createObjectURL(new Blob([thumbnail.asArrayBuffer()], {type: "image"}));
+                scope.$apply('thumbnail');    // we short-circuit Angular resync image
+            };
+                        
+            var posturl = attrs.posturl + "?token=" + ConfigApp.session.token;
+            LoadFileSvc (scope, elem, posturl, files, readerCB);
+        };
+
+        // Initiallize default values from attributes values
+        scope.name= attrs.name || 'appli';
+        scope.category= attrs.category  || 'appli';
+        scope.mimetype= (attrs.accept || '.zip');
+        scope.maxsize= attrs.maxsize || 100000; // default max size 100MB
+        scope.regexp = new RegExp (attrs.accept+ '.*','i');
+
+        if (attrs.thumbnail) scope.thumbnail= ConfigApp.paths[scope.category] +  attrs.thumbnail;
+        else  scope.thumbnail=ConfigApp.paths[scope.category] + 'upload-appli.png';
+        
+        if (attrs.thumbnail) scope.isnotvalid= ConfigApp.paths[scope.category] +  attrs.isnotvalid;
+        else  scope.isnotvalid=ConfigApp.paths[scope.category] + 'isnotvalid.png';
+
+        if (attrs.istoobig) scope.istoobig= ConfigApp.paths[scope.category] +  attrs.istoobig;
+        else  scope.istoobig=ConfigApp.paths[scope.category] + 'istoobig.png';
+        scope.noslider = attrs.noslider || false;
+
+        if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs);            
+    }
+    return {
+        restrict: 'E',
+        template: tmpl,
+        link: mymethods,
+        scope: {
+            callback : '='
+        }
+    };
+    
+});
+
+console.log ("UploadFile Loaded");
+})();
diff --git a/afb-client/app/Frontend/widgets/FormInput/newjavascript.js b/afb-client/app/Frontend/widgets/FormInput/newjavascript.js
new file mode 100644 (file)
index 0000000..10280c7
--- /dev/null
@@ -0,0 +1,55 @@
+/* 
+ * Copyright (C) 2015 fulup
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+ var reader = new FileReader();
+ // Closure to capture the file information.
+      reader.onload = (function(theFile) {
+        return function(e) {
+          var $title = $("<h4>", {
+            text : theFile.name
+          });
+          $result.append($title);
+          var $fileContent = $("<ul>");
+          try {
+
+            var dateBefore = new Date();
+            // read the content of the file with JSZip
+            var zip = new JSZip(e.target.result);
+            var dateAfter = new Date();
+
+            $title.append($("<span>", {
+              text:" (parsed in " + (dateAfter - dateBefore) + "ms)"
+            }));
+
+            // that, or a good ol' for(var entryName in zip.files)
+            $.each(zip.files, function (index, zipEntry) {
+              $fileContent.append($("<li>", {
+                text : zipEntry.name
+              }));
+              // the content is here : zipEntry.asText()
+            });
+            // end of the magic !
+
+          } catch(e) {
+            $fileContent = $("<div>", {
+              "class" : "alert alert-danger",
+              text : "Error reading " + theFile.name + " : " + e.message
+            });
+          }
+          $result.append($fileContent);
+        }
+      })(f);
\ No newline at end of file
index 2b1e9db..0d1cf38 100644 (file)
@@ -32,7 +32,7 @@
 
 
 // scope module is load statically before any route is cativated
-angular.module('TokenRefresh', [])
+angular.module('TokenRefresh', ['ConfigApp', 'ModalNotification'])
 
     .directive ('tokenRefresh', function($timeout, $http, $location, Notification, ConfigApp) {
 
diff --git a/afb-client/app/Frontend/widgets/RangeSliders/RangeSliderMod.js b/afb-client/app/Frontend/widgets/RangeSliders/RangeSliderMod.js
new file mode 100644 (file)
index 0000000..77f0fce
--- /dev/null
@@ -0,0 +1,631 @@
+/* 
+ * Copyright (C) 2015 "IoT.bzh"
+ * Author "Fulup Ar Foll"
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * Bugs: Input with Callback SHOULD BE get 'required' class
+ * 
+ * ref: https://developer.mozilla.org/en-US/docs/Web/Events/mouseover
+ * 
+ * usage: 
+Usage  <range-slider>
+---------------------
+   <range-slider
+      id="my-slider-name"                     // only use as an argument to callback
+      class="my-custom-class"                 // default class is ibz-range-slider
+      placeholder="Track Date Selection"      // place holder for date readonly input zone
+
+      <!-- Foundation classes -->
+      class="radius"                          // check Zurb foundation doc for further info.
+      class="ibz-handle-display"              // increase handle width to hold slider current value
+
+      <!-- Angular Scope Variables -->
+      callback="myCallBack"                    // $scope.myCallBack(sliderhandle) is called when ever slider handle blur
+      formatter="SliderFormatCB"               // $scope.myFormatter(value, sliderid) when exist is call when ever slider handle moves. Should return external form of slider value.
+      ng-model="xxxxxx"                        // xxx Must be defined, script will store a new RangerObject within provided ng-model variable.
+      start-at="ScopeVar"                      // Dynamic limitation when slider is constrains by an external componant [ex: check in/out]
+      stop-at="ScopeVar"                       // Idem but for end.
+
+      <!-- Angular Directive Attributes -->
+      not-less="integer"                       // Fixed starting value for slider [default 0]
+      not-more="integer"                       // Fixed end value for sliders [default 100]
+      by-step="+-integer"                      // If by-step is >0 then slider use it as step-value, when negative use it for decimal precision
+      display-target="handle"                  // display slider external formated value in the handle [requirer calss="ibz-handle-display"]
+      dual-handles='true'                      // add a second handle to slider for min/max range
+      initial='value|[start/stop]'             // slider initial value [dual-handles] may have initial values
+   /></range-slider>
+ */
+
+(function () {
+    'use strict';
+
+var RangeSlider = angular.module('RangeSlider',[]);
+
+function RangeSliderHandle (scope) {
+    var internals = [];
+    var externals = [];
+
+    this.getId = function() {
+        return scope.sliderid;
+    };
+
+    this.getCbHandle = function() {
+        return scope.cbhandle;
+    };
+
+    this.getView= function (handle) {
+        if (!handle) handle = 0;
+
+        // if value did not change return current external representation
+        if (scope.value[handle] === internals[handle]) return externals[handle];
+
+        // build external representation and save it for further requests
+        internals[handle] = scope.value[handle];
+        if (scope.formatter) externals[handle] = scope.formatter(scope.value[handle], scope.ctrlhandle);
+        else  externals[handle] = scope.value[handle];
+
+        return externals[handle];
+    };
+
+    this.updateClass = function (classe, status) {
+       scope.updateClass (classe, status);
+    };
+
+    this.forceRefresh = function (timer) {
+       scope.forceRefresh(timer);
+    };
+
+    this.getValue= function (handle) {
+        if (!handle) handle = 0;
+        return scope.value[handle];
+    };
+
+    this.getRelative= function (handle) {
+        if (!handle) handle = 0;
+        return scope.relative[handle];
+    };
+
+    this.setValue= function (value, handle) {
+        if (!handle) handle = 0;
+        scope.setValue (value, handle);
+    };
+
+    this.setDisable= function (flag) {
+        scope.setDisable(flag);
+    };
+}
+
+RangeSlider.directive('rangeSlider', function ($log, $document, $timeout) {
+
+    var template= '<div class="ibz-range-slider range-slider" title="{{title}}"data-slider>'+
+                  '<span class="range-slider-handle handle-min" ng-mousedown="handleCB($event,0)" ng-focus="focusCB(true)" ng-blur="focusCB(false)" role="slider" tabindex="0"></span>'+
+                  '<span class="handle-max" ng-mousedown="handleCB($event,1)" ng-focus="focusCB(true)" ng-blur="focusCB(false)" role="slider" tabindex="0"></span>'+
+                  '<span class="range-slider-active-segment"></span>'+
+                  '<span class="ibz-range-slider-start" ></span> '+
+                  '<span class="ibz-range-slider-stop"></span> '+
+                  '<input id={{sliderid}} type="hidden">'+
+                  '</div>';
+
+
+    function link (scope, element, attrs, model) {
+        // full initialisation of slider from a single object
+        scope.initWidget = function (initvalues) {
+
+            if (initvalues.byStep)  scope.byStep  = parseInt(initvalues.byStep);
+            if (initvalues.notMore) scope.notMore = parseInt(initvalues.notMore);
+            if (initvalues.notLess) scope.notLess = parseInt(initvalues.notLess);
+            if (initvalues.id)      scope.sliderid= initvalues.id;
+
+            // hugely but in some case DOM is not finish when we try to set values !!!
+            if (initvalues.value !== undefined)   {
+                scope.value = initvalues.value;
+                scope.forceRefresh (50); // wait 50ms for DOM to be ready
+            }
+        };
+
+        // this function recompute slide positioning
+        scope.forceRefresh = function (timer) {
+           var value = scope.value;
+           scope.value = [undefined,undefined];
+           $timeout (function() {
+               scope.setValue(value[0],0);
+               if (scope.dual)  scope.setValue(value[1],1);
+           }, timer);
+        };
+
+        // handler to change class from slider handle
+        scope.updateClass = function (classe, status) {
+
+            if (status) element.addClass (classe);
+            else  element.removeClass (classe);
+        };
+
+        scope.setDisable = function (disabled) {
+
+            if (disabled) {
+                element.addClass ("disable");
+                scope.handles[0].css ('visibility','hidden');
+                if (scope.dual) {
+                    scope.handles[1].css ('visibility','hidden');
+                }
+            } else {
+                element.removeClass ("disable");
+                scope.handles[0].css ('visibility','visible');
+                if (scope.dual) scope.handles[1].css ('visibility','visible');
+            }
+
+        };
+
+        scope.normalize = function (value) {
+            var result;
+            var range = scope.notMore - scope.notLess;
+            var point = value * range;
+
+            // if step is positive let's round step by step
+            if (scope.byStep >  0) {
+                var mod = (point - (point % scope.byStep)) / scope.byStep;
+                var rem = point % scope.byStep;
+
+                var round = (rem >= scope.byStep * 0.5 ? scope.byStep : 0);
+                result= (mod * scope.byStep + round) + scope.notLess;
+                //console.log ("range=%d value=%d point=%d mod=%d rem=%d round=%d result=%d", range, value, point, mod, rem, round, result)
+                return result;
+            }
+
+            // if step is negative return round to asked decimal
+            if (scope.byStep <  0) {
+                var power  =  Math.pow (10,(scope.byStep * -1));
+                result = scope.notLess + parseInt (point * power) / power;
+                return (result);
+            }
+
+            // if step is null return full value
+            return point;
+       };
+
+        // return current value
+        scope.getValue = function (offset, handle) {
+            if (scope.vertical) {
+                scope.relative[handle] = (offset - scope.bounds.handles[handle].getBoundingClientRect().height) / (scope.bounds.bar.getBoundingClientRect().height - scope.bounds.handles[handle].getBoundingClientRect().height);
+            } else {
+                scope.relative[handle] = offset /  (scope.bounds.bar.getBoundingClientRect().width - scope.bounds.handles[handle].getBoundingClientRect().width);
+            }
+
+            var newvalue = scope.normalize (scope.relative[handle]);
+
+
+            // if internal value change update or model
+            if (newvalue !== scope.value[handle]) {
+                if (newvalue < scope.startValue) newvalue=scope.startValue;
+                if (newvalue > scope.stopValue)  newvalue=scope.stopValue;
+
+
+                if (scope.formatter) {
+                    scope.viewValue = scope.formatter (newvalue, scope.ctrlhandle);
+                } else {
+                    scope.viewValue = newvalue;
+                }
+                if (scope.displays[handle]) {
+                    scope.displays[handle].html (scope.viewValue);
+                }
+
+                // update external representation of the model
+                scope.value[handle] = newvalue;
+                if (model) model.$setViewValue (scope.viewValue);
+                scope.$apply();
+                if (newvalue > scope.startValue && newvalue < scope.stopValue) scope.translate(offset, handle);
+            }
+        };
+
+
+        scope.setStart = function (value) {
+            var offset;
+            
+            if (value > scope.value[0]) {
+                if (!scope.dual) scope.setValue (value,0);
+                else scope.setValue (value,1);
+            }
+
+            if (scope.vertical) {
+                offset = scope.bounds.bar.getBoundingClientRect().height * (value - scope.notLess) / (scope.notMore - scope.notLess);
+                scope.start.css('height',offset + 'px');
+            } else {
+                offset = scope.bounds.bar.getBoundingClientRect().width * (value - scope.notLess) / (scope.notMore - scope.notLess);
+                scope.start.css('width',offset + 'px');
+            }
+
+            scope.startValue= value;
+        };
+
+        scope.setStop = function (value) {
+            var offset;
+            
+            if (value < scope.value[0]) {
+                if (!scope.dual) scope.setValue (value,0);
+                else scope.setValue (value,1);
+            }
+
+            if (scope.vertical) {
+                offset = scope.bounds.bar.getBoundingClientRect().height * (value - scope.notLess) / (scope.notMore - scope.notLess);
+                scope.start.css('height',offset + 'px');
+            } else {
+                offset = scope.bounds.bar.getBoundingClientRect().width * (value - scope.notLess) / (scope.notMore - scope.notLess);
+                scope.stop.css({'right': 0, 'width': (scope.bounds.bar.getBoundingClientRect().width  - offset) + 'px'});
+            }
+
+            scope.stopValue= value;
+        };
+
+        scope.translate = function (offset, handle) {
+            var start;
+            
+            if (scope.vertical) {
+                // take handle size in account to compute middle
+                var voffset = scope.bounds.bar.getBoundingClientRect().height - offset;
+
+                scope.handles[handle].css({
+                    '-webkit-transform': 'translateY(' + voffset + 'px)',
+                    '-moz-transform': 'translateY(' + voffset + 'px)',
+                    '-ms-transform': 'translateY(' + voffset + 'px)',
+                    '-o-transform': 'translateY(' + voffset + 'px)',
+                    'transform': 'translateY(' + voffset + 'px)'
+               });
+               if (!scope.dual) scope.slider.css('height', offset + 'px');
+               else if (scope.relative[1] && scope.relative[0]) {
+                   var height = (scope.relative[1] - scope.relative[0]) *  scope.bounds.bar.getBoundingClientRect().height;
+                   start  = (scope.relative[0] *  scope.bounds.bar.getBoundingClientRect().height);
+                   scope.slider.css ({'bottom': start+'px','height': height + 'px'});
+               }
+            } else {
+
+                scope.handles[handle].css({
+                    '-webkit-transform': 'translateX(' + offset + 'px)',
+                    '-moz-transform': 'translateX(' + offset + 'px)',
+                    '-ms-transform': 'translateX(' + offset + 'px)',
+                    '-o-transform': 'translateX(' + offset + 'px)',
+                    'transform': 'translateX(' + offset + 'px)'
+                });
+                if (!scope.dual) scope.slider.css('width',offset + 'px');
+                else if (scope.relative[1] && scope.relative[0]) {
+                    var width = (scope.relative[1] - scope.relative[0]) *  scope.bounds.bar.getBoundingClientRect().width;
+                    start = (scope.relative[0] *  scope.bounds.bar.getBoundingClientRect().width);
+                    scope.slider.css ({'left': start+'px','width': width + 'px'});
+                }
+            }
+        };
+
+        // position handle on the bar depending a given value
+        scope.setValue = function (value , handle) {
+            var offset;
+
+            // if value did not change ignore
+            if (value === scope.value[handle]) return;
+            if (value === undefined)   value=0;
+            if (value > scope.notMore) value=scope.notMore;
+            if (value < scope.notLess) value=scope.notLess;
+
+            if (scope.vertical) {
+                scope.relative[handle] = (value - scope.notLess) / (scope.notMore - scope.notLess);
+                if (handle === 0) offset = (scope.relative[handle] * scope.bounds.bar.getBoundingClientRect().height) + scope.bounds.handles[handle].getBoundingClientRect().height/2;
+                if (handle === 1) offset = scope.relative[handle] * scope.bounds.bar.getBoundingClientRect().height;
+
+            } else {
+                scope.relative[handle] = (value - scope.notLess) / (scope.notMore - scope.notLess);
+                offset = scope.relative[handle] *  (scope.bounds.bar.getBoundingClientRect().width - scope.bounds.handles[handle].getBoundingClientRect().width);
+            }
+
+            scope.translate (offset,handle);
+            scope.value[handle] = value;
+
+            if (scope.formatter) {
+                // when call through setValue we do not pass cbHandle
+                scope.viewValue = scope.formatter (value, undefined);
+            } else {
+                scope.viewValue = value;
+            }
+
+            if (model) model.$setViewValue( scope.viewValue);
+
+            if (scope.displays[handle]) {
+                scope.displays[handle].html (scope.viewValue);
+            }
+        };
+
+
+        // Minimal keystroke handling to close picker with ESC [scope.actif is current handle index]
+        scope.keydown=  function(e){
+
+            switch(e.keyCode){
+                case 39: // Right
+                case 38: // up
+                     if (scope.byStep > 0) scope.$apply(scope.setValue ((scope.value[scope.actif]+scope.byStep), scope.actif));
+                     if (scope.byStep < 0) scope.$apply(scope.setValue ((scope.value[scope.actif]+(1 / Math.pow(10, scope.byStep*-1))),scope.actif));
+                     if (scope.callback)  scope.callback (scope.value[scope.actif], scope.ctrlhandle);
+                     break;
+                case 37: // left
+                case 40: // down
+                    if (scope.byStep > 0) scope.$apply(scope.setValue ((scope.value[scope.actif] - scope.byStep), scope.actif));
+                    if (scope.byStep < 0) scope.$apply(scope.setValue ((scope.value[scope.actif] - (1 / Math.pow(10, scope.byStep*-1))),scope.actif));
+                    if (scope.callback)  scope.callback (scope.value[scope.actif], scope.ctrlhandle);
+                    break;
+                case 27: // esc
+                    scope.handles[scope.actif][0].blur();
+            }
+        };
+
+        scope.moveHandle = function (handle, clientX, clientY) {
+            var offset;
+            if (scope.vertical) {
+                offset = scope.bounds.bar.getBoundingClientRect().bottom - clientY;
+                if (offset > scope.bounds.bar.getBoundingClientRect().height) offset = scope.bounds.bar.getBoundingClientRect().height;
+                if (offset < scope.bounds.handles[handle].getBoundingClientRect().height) offset = scope.bounds.handles[handle].getBoundingClientRect().height;
+            } else {
+                offset = clientX - scope.bounds.bar.getBoundingClientRect().left;
+
+                if (offset < 0) offset = 0;
+                if ((clientX + scope.bounds.handles[handle].getBoundingClientRect().width) > scope.bounds.bar.getBoundingClientRect().right) {
+                    offset = scope.bounds.bar.getBoundingClientRect().width - scope.bounds.handles[handle].getBoundingClientRect().width;
+                }
+            }
+
+            scope.getValue  (offset, handle);
+
+            // prevent dual handle to cross
+            if (scope.dual && scope.value [0] > scope.value[1]) {
+                if (handle === 0) scope.setValue (scope.value[0] , 1);
+                else scope.setValue(scope.value[1],0);
+            }
+        };
+
+
+        scope.focusCB = function (inside) {
+            if (inside) {
+                $document.on('keydown',scope.keydown);
+            } else {
+                $document.unbind('keydown',scope.keydown);
+            }
+        };
+
+        // bar was touch let move handle to this point
+        scope.touchBarCB = function (event) {
+            var handle=0;
+            var relative;
+            var touches = event.changedTouches;
+            var oldvalue = scope.value[handle];
+
+            event.preventDefault();
+
+            // if we have two handles select closest one from touch point
+            if (scope.dual) {
+                if (scope.vertical) relative = (touches[0].pageY - scope.bounds.bar.getBoundingClientRect().bottom) / scope.bounds.bar.getBoundingClientRect().height;
+                else relative= (touches[0].pageX - scope.bounds.bar.getBoundingClientRect().left) / scope.bounds.bar.getBoundingClientRect().width;
+
+                var distance0 = Math.abs(relative - scope.relative[0]);
+                var distance1 = Math.abs(relative - scope.relative[1]);
+                if (distance1 < distance0) handle=1;
+            }
+
+            // move handle to new place
+            scope.moveHandle (handle,touches[0].pageX, touches[0].pageY);
+            if (scope.callback && oldvalue !== scope.value[handle]) scope.callback (scope.value[handle], scope.ctrlhandle);
+        };
+
+        // handle was touch and drag
+        scope.touchHandleCB = function (touchevt, handle) {
+            var oldvalue = scope.value[handle];
+
+            touchevt.preventDefault();
+            $document.on('touchmove',touchmove);
+            $document.on('touchend' ,touchend);
+            element.unbind('touchstart', scope.touchBarCB);
+
+            function touchmove(event) {
+                event.preventDefault();
+                var touches = event.changedTouches;
+                for (var idx = 0; idx < touches.length; idx++) {
+                    scope.moveHandle (handle,touches[idx].pageX, touches[idx].pageY);
+                }
+            }
+
+            function touchend(event) {
+               $document.unbind('touchmove',touchmove);
+               $document.unbind('touchend' ,touchend);
+               element.on('touchstart', scope.touchBarCB);
+
+                // if value change notify application callback
+                if (scope.callback && oldvalue !== scope.value[handle]) scope.callback (scope.value[handle], scope.ctrlhandle);
+            }
+        };
+
+        scope.handleCB = function (clickevent, handle) {
+
+            if (attrs.automatic) return;
+            
+            var oldvalue = scope.value[handle];
+            // register mouse event to track handle
+            clickevent.preventDefault();
+
+            $document.on('mousemove',mousemove);
+            $document.on('mouseup', mouseup);
+            scope.handles[handle][0].focus();
+            scope.actif=handle;
+
+            // slider handle is moving
+            function mousemove(event) {
+                scope.moveHandle (handle, event.clientX, event.clientY);
+            }
+
+            // mouse is up dans leave slider send resize events
+            function mouseup() {
+                $document.unbind('mousemove', mousemove);
+                $document.unbind('mouseup', mouseup);
+
+                // if value change notify application callback
+                if (scope.callback && oldvalue !== scope.value[handle]) scope.callback (scope.value[handle], scope.ctrlhandle);
+            }
+        };
+
+        // simulate jquery find by classes capabilities [warning only return 1st elements]
+        scope.find = function (select, elem) {
+            var domelem;
+
+            if (elem) domelem = elem[0].querySelector(select);
+            else domelem = element[0].querySelector(select);
+
+            var angelem = angular.element(domelem);
+            return (angelem);
+        };
+
+
+
+        scope.initialSettings = function (initial) {
+            var decimal_places_match_result;
+            scope.value=[];  // store low/height value when two handles
+            scope.relative=[];
+
+            if (scope.precision === null) {
+                decimal_places_match_result = ('' + scope.byStep).match(/\.([\d]*)/);
+                scope.precision = decimal_places_match_result && decimal_places_match_result[1] ? decimal_places_match_result[1].length : 0;
+            }
+
+            // position handle to initial value(s)
+            element.on('touchstart', scope.touchBarCB);
+            scope.handles[0].on('touchstart', function(evt){scope.touchHandleCB(evt,0);});
+
+            // this slider has two handles low/hight
+            if (scope.dual) {
+                scope.handles[1].addClass('range-slider-handle');
+                scope.handles[1].on('touchstart', function(evt){scope.touchHandleCB(evt,1);});
+                if (!scope.initvalues) scope.setValue (initial[1],1);
+            }
+
+            // if we have an initstate object apply it
+            if (scope.initvalues) scope.initWidget (scope.initvalues);
+            else   scope.setValue (initial[0],0);
+        };
+
+        scope.init = function () {
+            scope.sliderid   = attrs.id || "slider-" + parseInt (Math.random() * 1000);
+            scope.startValue = -Infinity;
+            scope.stopValue  = Infinity;
+            scope.byStep   = parseInt(attrs.byStep) || 1;
+            scope.vertical = attrs.vertical   || false;
+            scope.dual     = attrs.dualHandles|| false;
+            scope.trigger_input_change= false;
+            scope.notMore  = parseInt(attrs.notMore)   || 100;
+            scope.notLess  = parseInt(attrs.notLess)   || 0;
+
+            if (scope.vertical) element.addClass("vertical-range");
+
+            scope.handles= [scope.find('.handle-min'), scope.find('.handle-max')];
+            scope.bar    = element;
+            scope.slider = scope.find('.range-slider-active-segment');
+            scope.start  = scope.find('.ibz-range-slider-start');
+            scope.stop   = scope.find('.ibz-range-slider-stop');
+            scope.disable= attrs.disable || false;
+
+            scope.ctrlhandle = new RangeSliderHandle (scope);
+
+            // prepare DOM object pointer to compute size dynamically
+            scope.bounds = {
+                bar    : element[0],
+                handles: [scope.handles[0][0], scope.handles[1][0]]
+            };
+
+            if (attrs.disable === 'true') scope.setDisable(true);
+
+            if (attrs.displayTarget) {
+                switch (attrs.displayTarget) {
+                    case true :
+                    case 'handle' :
+                        scope.displays = scope.handles;
+                        scope.handles[0].addClass('ibz-range-slider-display');
+                        if (scope.dual) scope.handles[1].addClass('ibz-range-slider-display');
+                        break;
+                    default:
+                        scope.displays =  [$document.getElementById (attrs.displayTarget)];
+                }
+            } else scope.displays=[];
+
+            // extract initial values from attrs and parse into int
+            if (!attrs.initial) {
+                scope.initial  = [scope.ngModel, scope.ngModel]; // initialize to model values
+            } else {
+                var initial  = attrs.initial.split(',');
+                scope.initial = [
+                    initial[0] !== undefined ? parseInt (initial[0]) : scope.notLess,
+                    initial[1] !== undefined ? parseInt (initial[1]) : scope.notMore
+                ];
+            }
+
+            // Monitor any changes on start/stop dates.
+            scope.$watch('startAt', function() {
+                if (scope.value < scope.startAt ) {
+                    //scope.setValue (scope.startAt);
+                }
+                if (scope.startAt) scope.setStart (scope.startAt);
+            });
+
+            scope.$watch('stopAt' , function() {
+                if (scope.value > scope.stopAt) {
+                    //scope.setValue (scope.stopAt);
+                }
+                if (scope.stopAt) scope.setStop (scope.stopAt);
+            });
+
+            // finish widget initialisation
+            scope.initialSettings (scope.initial);
+
+        };
+
+        scope.init();
+        
+         // slider is ready provide control handle to application controller
+        scope.$watch ('inithook', function () {         // init Values may arrive late
+            if (scope.inithook) scope.inithook (scope.ctrlhandle);
+        });
+
+        scope.$watch ('initvalues', function () {      // init Values may arrive late
+            if (scope.initvalues) scope.initWidget(scope.initvalues);
+        });
+
+        // two-way binding if model value changes
+        scope.$watch ('ngModel', function (newValue) {
+          scope.setValue(newValue, 0);
+        });
+    }
+
+return {
+    restrict: "E",    // restrict to <range-slider> HTML element name
+    scope: {
+        startAt  :'=',  // First acceptable date
+        stopAt   :'=',  // Last acceptable date
+        callback :'=',  // Callback to actif when a date is selected
+        formatter:'=',  // Callback for drag event call each time internal value changes
+        inithook :'=',  // Hook point to control slider from API
+        cbhandle :'=',  // Argument added to every callback
+        initvalues:'=',   // Initial values as a single object
+        ngModel: '='    // the model value
+    },
+    require: '?ngModel',
+    template: template, // html template is build from JS
+    replace: true,      // replace current directive with template while inheriting of class
+    link: link          // pickadate object's methods
+};
+});
+
+console.log ("RangeSlider Loaded");
+
+})();
\ No newline at end of file
diff --git a/afb-client/app/Frontend/widgets/RangeSliders/Rangeslider.scss b/afb-client/app/Frontend/widgets/RangeSliders/Rangeslider.scss
new file mode 100644 (file)
index 0000000..6717d0e
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * ibz-Datepicker for Foundation
+ *
+ * Author: Fulup Ar Foll
+ * Date  : March-2015
+ * Object: SASS stylesheet, customized to Foundation
+ * References: https://css-tricks.com/stripes-css/
+ *
+ */
+@import "app/ibz-mixins";
+
+.range-slider-handle {
+  display: inline-block;
+  position: absolute;
+  z-index: 1;
+  top: -0.2rem;
+  width: 2rem;
+  height: 1.375rem;
+  border: 1px solid none;
+  cursor: pointer;
+  background: #008cba;
+}
+
+.range-slider.radius, .range-slider-handle {
+  background: #008cba;
+  -webkit-border-radius: 3px;
+  border-radius: 3px;
+}
+
+.range-slider-active-segment {
+  display: inline-block;
+  top: 0.07rem;
+  position: absolute;
+  height: 0.80rem;
+  background: #e5e5e5;
+}
+
+.ibz-range-slider {
+   background-color: rgba(154, 205, 50, 0.6) !important;
+   height: 1rem;
+   position: relative;
+   
+  .range-slider-active-segment {
+    background-color: rgba(82, 168, 200, 0.6);
+  }
+  
+  &-display {
+    background-color: rgba(82, 168, 200, 0.6) !important;
+    width : 4rem !important;
+    padding: .25rem;
+    text-align:center
+   }
+
+   &-start,&-stop {
+   display: inline-block;
+   position: absolute;
+   padding-top: 2px;
+   height: 95%;
+   background: repeating-linear-gradient(
+    45deg,
+    #606dbc,
+    #606dbc 10px,
+    #465298 10px,
+    #465298 20px
+  );}
+
+}
index 7b5326a..b0eb1a8 100644 (file)
@@ -29,6 +29,8 @@ config = {
     DEBUG   : 4001,          // Node Debug Port
     DBG_LVL : 5,             // Debug Trace Level 0=no trace.
     
+    UPLOAD_DIR: '/tmp/uploads',  // directory destination for uploaded files [/api/post/upload]
+    
     // EXPRESS WEB server config [note: URLBASE generate rewriting rules]
     EXPRESS_HOST    : 'localhost',         // HTTP will only listen on related Internet interface
     EXPRESS_PORT    : 4000,                // HTTP port
index 84dbe1e..c78c624 100644 (file)
@@ -8,11 +8,15 @@
     "angular-cookies": "~1.3.4",
     "foundation-apps": "~1.1.0",
     "angular-ui-notification": "~0.0.14",
-    "foundation-icon-fonts": "*"
+    "foundation-icon-fonts": "*",
+    "jszip": "Stuk/jszip#~2.5.0"
   },
   "overrides": {
     "foundation": {
-       "main": ["dist/js/foundation-apps-templates.js", "dist/js/foundation-apps.js"]
+      "main": [
+        "dist/js/foundation-apps-templates.js",
+        "dist/js/foundation-apps.js"
+      ]
     }
   }
 }
index f483431..3ec8774 100644 (file)
@@ -112,7 +112,7 @@ pipes.validatedDevServerScripts = function() {
 pipes.validatedPartials = function() {
     return gulp.src(paths.partials)
         .pipe(plugins.htmlhint({'doctype-first': false}))
-        .pipe(router({path: paths.application+'/tmp/routes.js', root: paths.application}))
+        .pipe(router({path: paths.application+'/etc/routes.js', root: paths.application}))
         .pipe(plugins.htmlhint.reporter());
 };