diff options
author | Fulup Ar Foll <fulup@iot.bzh> | 2015-12-20 21:04:34 +0100 |
---|---|---|
committer | Fulup Ar Foll <fulup@iot.bzh> | 2015-12-20 21:04:34 +0100 |
commit | 4136c1506e0c894e604ec069339313987a7e05e7 (patch) | |
tree | 1276966f93dce30949e78d4aef456223dea71b48 /afb-client/app | |
parent | 07eb8e102607da8d6a4c1cd9835e8465c9280161 (diff) |
Implemented client upload with rangeslider and zip open
Diffstat (limited to 'afb-client/app')
23 files changed, 1197 insertions, 185 deletions
diff --git a/afb-client/app/Backend/RestApis/PostMockApi.js b/afb-client/app/Backend/RestApis/PostMockApi.js index 6299f39..022f774 100644 --- a/afb-client/app/Backend/RestApis/PostMockApi.js +++ b/afb-client/app/Backend/RestApis/PostMockApi.js @@ -14,20 +14,36 @@ * * 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"}); }); diff --git a/afb-client/app/Frontend/app.js b/afb-client/app/Frontend/app.js index e539062..5e99d77 100644 --- a/afb-client/app/Frontend/app.js +++ b/afb-client/app/Frontend/app.js @@ -18,9 +18,10 @@ 'JQueryEmu', 'HomeModule', 'SampleModule', - 'UploadFile', + 'UploadFiles', 'LinkButton', 'TokenRefresh', + 'RangeSlider', 'ModalNotification' ]) .config(config) @@ -28,8 +29,10 @@ ; 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"); })(); diff --git a/afb-client/app/Frontend/services/ConfigApp.js b/afb-client/app/Frontend/etc/ConfigApp.js index 310ef88..22cf220 100644 --- a/afb-client/app/Frontend/services/ConfigApp.js +++ b/afb-client/app/Frontend/etc/ConfigApp.js @@ -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/tmp/routes.js b/afb-client/app/Frontend/etc/routes.js index c3c5ba4..c3c5ba4 100644 --- a/afb-client/app/Frontend/tmp/routes.js +++ b/afb-client/app/Frontend/etc/routes.js diff --git a/afb-client/app/Frontend/images/appli/isnotvalid.png b/afb-client/app/Frontend/images/appli/isnotvalid.png Binary files differnew file mode 100644 index 0000000..ee40d2a --- /dev/null +++ b/afb-client/app/Frontend/images/appli/isnotvalid.png diff --git a/afb-client/app/Frontend/images/appli/istoobig.png b/afb-client/app/Frontend/images/appli/istoobig.png Binary files differnew file mode 100644 index 0000000..5614073 --- /dev/null +++ b/afb-client/app/Frontend/images/appli/istoobig.png diff --git a/afb-client/app/Frontend/images/appli/upload-appli.png b/afb-client/app/Frontend/images/appli/upload-appli.png Binary files differnew file mode 100644 index 0000000..a35fd3a --- /dev/null +++ b/afb-client/app/Frontend/images/appli/upload-appli.png diff --git a/afb-client/app/Frontend/images/audio/istoobig.png b/afb-client/app/Frontend/images/audio/istoobig.png Binary files differnew file mode 100644 index 0000000..5614073 --- /dev/null +++ b/afb-client/app/Frontend/images/audio/istoobig.png diff --git a/afb-client/app/Frontend/images/audio/upload-music.png b/afb-client/app/Frontend/images/audio/upload-music.png Binary files differnew file mode 100644 index 0000000..2006ef0 --- /dev/null +++ b/afb-client/app/Frontend/images/audio/upload-music.png diff --git a/afb-client/app/Frontend/images/avatars/istoobig.png b/afb-client/app/Frontend/images/avatars/istoobig.png Binary files differnew file mode 100644 index 0000000..5614073 --- /dev/null +++ b/afb-client/app/Frontend/images/avatars/istoobig.png diff --git a/afb-client/app/Frontend/pages/Home/Home.html b/afb-client/app/Frontend/pages/Home/Home.html index 6eda66d..25bb983 100644 --- a/afb-client/app/Frontend/pages/Home/Home.html +++ b/afb-client/app/Frontend/pages/Home/Home.html @@ -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> ---> + diff --git a/afb-client/app/Frontend/pages/Home/HomeModule.js b/afb-client/app/Frontend/pages/Home/HomeModule.js index 30e796e..6ebaefc 100644 --- a/afb-client/app/Frontend/pages/Home/HomeModule.js +++ b/afb-client/app/Frontend/pages/Home/HomeModule.js @@ -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"; diff --git a/afb-client/app/Frontend/pages/Sample/Sample.html b/afb-client/app/Frontend/pages/Sample/Sample.html index e7e9164..35523a9 100644 --- a/afb-client/app/Frontend/pages/Sample/Sample.html +++ b/afb-client/app/Frontend/pages/Sample/Sample.html @@ -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> diff --git a/afb-client/app/Frontend/pages/Sample/SampleModule.js b/afb-client/app/Frontend/pages/Sample/SampleModule.js index 2a2e777..8ae82ea 100644 --- a/afb-client/app/Frontend/pages/Sample/SampleModule.js +++ b/afb-client/app/Frontend/pages/Sample/SampleModule.js @@ -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"); diff --git a/afb-client/app/Frontend/services/JQueryEmu.js b/afb-client/app/Frontend/services/JQueryEmu.js index 5112052..6d6e338 100644 --- a/afb-client/app/Frontend/services/JQueryEmu.js +++ b/afb-client/app/Frontend/services/JQueryEmu.js @@ -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]) { @@ -19,12 +44,23 @@ } }; - // 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; diff --git a/afb-client/app/Frontend/widgets/FormInput/FormInput.scss b/afb-client/app/Frontend/widgets/FormInput/FormInput.scss index 37519fd..77aed6e 100644 --- a/afb-client/app/Frontend/widgets/FormInput/FormInput.scss +++ b/afb-client/app/Frontend/widgets/FormInput/FormInput.scss @@ -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 index 9a2f031..0000000 --- a/afb-client/app/Frontend/widgets/FormInput/UploadFile.js +++ /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 index 0000000..6c68960 --- /dev/null +++ b/afb-client/app/Frontend/widgets/FormInput/UploadFiles.js @@ -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 index 0000000..10280c7 --- /dev/null +++ b/afb-client/app/Frontend/widgets/FormInput/newjavascript.js @@ -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 diff --git a/afb-client/app/Frontend/widgets/Notifications/TokenRefreshSvc.js b/afb-client/app/Frontend/widgets/Notifications/TokenRefreshSvc.js index 2b1e9db..0d1cf38 100644 --- a/afb-client/app/Frontend/widgets/Notifications/TokenRefreshSvc.js +++ b/afb-client/app/Frontend/widgets/Notifications/TokenRefreshSvc.js @@ -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 index 0000000..77f0fce --- /dev/null +++ b/afb-client/app/Frontend/widgets/RangeSliders/RangeSliderMod.js @@ -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 index 0000000..6717d0e --- /dev/null +++ b/afb-client/app/Frontend/widgets/RangeSliders/Rangeslider.scss @@ -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 + );} + +} diff --git a/afb-client/app/etc/AppDefaults.js b/afb-client/app/etc/AppDefaults.js index 7b5326a..b0eb1a8 100644 --- a/afb-client/app/etc/AppDefaults.js +++ b/afb-client/app/etc/AppDefaults.js @@ -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 |