diff options
Diffstat (limited to 'afb-client/app/Frontend/widgets/FormInput')
4 files changed, 440 insertions, 0 deletions
diff --git a/afb-client/app/Frontend/widgets/FormInput/FormInput.scss b/afb-client/app/Frontend/widgets/FormInput/FormInput.scss new file mode 100644 index 0000000..37519fd --- /dev/null +++ b/afb-client/app/Frontend/widgets/FormInput/FormInput.scss @@ -0,0 +1,69 @@ +/* + Sample of style overload for a specific widget + + Note: this SCSS is injected with main HTML page, it scope should be reduce + to a specific widget or it value will be propagated at a global level. +*/ + +@import "app/ibz-mixins"; + +upload-file { + height: 5rem; + display: inline-block; + float: right; + img { height: inherit;} +} + +input-text { + + alert {@include ibz-input-alert(darkblue, rgba(200, 200, 200, 0.6))}; + + input { + margin-bottom: .5rem !important; + } + + label { + margin-top: 1rem !important; + } + + .required { + color: blue; + float: right; + color: lightskyblue; + } + .required.valid { + color: green; + } + + .required.invalid { + color: plum; + } + + .status-untouch { + border-color: rgba(200, 200, 200, 0.6) !important; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(200, 200, 200, 0.6) !important; + color: #696969 !important; + } + + input:focus { + border-color: rgba(82,168,236,0.8) ; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(82,168,236,0.8) !important; + transition: border 0.2s linear 0s, box-shadow 0.2s linear 0s; + color: darkslateblue !important; + @extend shadow-transition; + } + + .status-valid { + border-color: rgba(154, 205, 50, 0.6)!important; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(154, 205, 50, 0.6)!important; + @extend shadow-transition; + } + + .status-invalid { + border-color: rgba(154, 17, 69, 0.6); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(154, 17, 69, 0.6)!important; + color: rgb(154, 17, 69); + @extend shadow-transition; + } + +} diff --git a/afb-client/app/Frontend/widgets/FormInput/InputPassword.js b/afb-client/app/Frontend/widgets/FormInput/InputPassword.js new file mode 100644 index 0000000..157009c --- /dev/null +++ b/afb-client/app/Frontend/widgets/FormInput/InputPassword.js @@ -0,0 +1,79 @@ +/* + * 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. + */ + +(function() { +'use strict'; + +var tmpl = '<input-text class="password" tip="{{tip1}}" placeholder="{{place1}}"' + + 'label="{{label1}}" callback="valid1" name="{{name}}-1" value="pass1" required minlen="{{minlen}}" type="password" >' + + '</input-text>' + + '<input-text class="password" tip="tip2" placeholder="{{place2}}"' + + 'label="{{label2}}" callback="valid2" name="{{name}}-2" value="pass2" required minlen="{{minlen}}" type="password" > '+ + '</input-text>'; + +angular.module('InputPassword',[]) + +.directive('inputPassword', function() { + function mymethods(scope, elem, attrs) { + + scope.valid1 = function (name, value) { + console.log ("Clicked InputPassword1 name=%s value=%s", name, value); + scope.firstpwd = value; + }; + + scope.valid2 = function (name, value, done) { + console.log ("Clicked InputPassword2 name=%s value=%s", name, value); + + // if both passwd equal then call form CB + if (scope.firstpwd !== value) { + done({valid: false, status: 'invalid', errmsg: "both password should match"}); + } else { + scope.callback (attrs.name, value); + } + + }; + + // this method can be called from controller to update widget status + scope.done=function (data) { + console.log ("Text-Input Callback ID="+ attrs.name + " data=", data); + for (var i in data) scope[i] = data[i]; + }; + + // Export some attributes within directive scope for template + scope.name = attrs.name; + scope.label1 = attrs.label || 'Password'; + scope.label2 = attrs.label || 'Password Verification'; + scope.place1 = attrs.placeholder1 || 'User Password'; + scope.tip1 = attrs.tip || 'Choose a Password'; + scope.place2 = attrs.placeholder1 || 'Password Verification'; + scope.tip2 = attrs.tip || 'Confirme your Password'; + scope.minlen = attrs.minlen || 10; + + if ("required" in attrs) scope.required = 'required'; + + } + + return { + restrict: 'E', + template: tmpl, + link: mymethods, + scope: { + callback : '=', + } + }; +}); + +console.log ("InputPassword Loaded"); +})(); diff --git a/afb-client/app/Frontend/widgets/FormInput/InputText.js b/afb-client/app/Frontend/widgets/FormInput/InputText.js new file mode 100644 index 0000000..2653175 --- /dev/null +++ b/afb-client/app/Frontend/widgets/FormInput/InputText.js @@ -0,0 +1,179 @@ + +/* + * 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.. + */ + + + +(function() { +'use strict'; + +var tmpl = '<tip-modal tip="tip"></tip-modal>' + + '<label for="{{name}}-intext">{{label}} <i ng-show="required" ng-click="ToBeDefined" ' + + 'class="required {{status}} fi-checkbox" title="Free Value But Mandatory Argument" alt="?"> </i></label>'+ + '<input '+ + ' type="{{type}}" id="{{name}}-intext" placeholder="{{placeholder}}" class="status-{{status}}"'+ + ' ng-model="value" ng-blur="validate()" ng-focus="selected()" '+ + ' ng-model-options="{ updateOn: \'default blur\', debounce: {default: 500, blur: 0} }"' + + '><alert data-ng-show="!valid&&errmsg">{{errmsg}}</alert>'; + +var emailpatern = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; + +angular.module('InputText',['JQueryEmu']) + +.directive('inputText', function(JQemu) { + function mymethods(scope, elem, attrs) { + + // default value at 1st rendering + scope.error = false; + scope.valid = false; + scope.status = 'untouch'; + + scope.input = elem.find ("input"); + scope.required = 0; + + // requirer is use to increment requested counter + if ("required" in attrs) { + scope.required = 1; + elem.addClass ("required"); + } + + // user enter input reset error status + scope.selected = function () { + scope.error=false; + scope.errmsg=false; + scope.status = 'touch'; + }; + + scope.validate = function () { + + // get value from input field bypassing Angular ng-model + console.log ("Clicked InputText name=%s value=%s valid=%s", scope.name, scope.value, scope.valid); + + // form is not untouched anymore + scope.parent.removeClass ("ng-pristine"); + + // if value not null clean up string + if (scope.value) { + scope.error=false; + // remove leading and trailling space + scope.value = scope.value.trim(); + + // remove any space is not allowed + if ('nospace' in attrs) { + scope.value=scope.value.replace(/\s/g, ''); + } + + if ('lowercase' in attrs) { + scope.value = scope.value.toLowerCase(); + } + + // check minimum lenght + if ("minlen" in attrs) { + if (scope.value.length < attrs.minlen) { + scope.status='invalid'; + scope.errmsg=scope.name + ': Mininum Lengh= ' + attrs.minlen + ' Characters'; + scope.error=true; + } + } + + if ('email' in attrs) { + if (!emailpatern.test (scope.value)) { + scope.status='invalid'; + scope.errmsg='invalid email address'; + scope.error=true; + } + } + + } else { + if (scope.required) { + scope.status='invalid'; + scope.errmsg=scope.name + ': Required Attribute'; + scope.error=true; + } + } + + // If local control fail let's refuse input + if (scope.error) { + if (scope.required && scope.valid) { + scope.valid = false; + if (scope.l4acounter.validated > 0) scope.l4acounter.validated --; + } + // use call to update form scope on form completeness + scope.callback (attrs.name, null, scope.done); + } else { + // localcheck is OK backup may nevertheless change status to false + if (scope.required && !scope.valid) scope.l4acounter.validated ++; + scope.status='valid'; + scope.valid=true; + scope.callback (attrs.name, scope.value, scope.done); + } + + }; + + // this method can be called from controller to update widget status + scope.done=function (data) { + console.log ("Text-Input Callback ID="+ attrs.name + " data=", data); + for (var i in data) scope[i] = data[i]; + }; + + // Export some attributes within directive scope for template + scope.label = attrs.label; + scope.name = attrs.name; + scope.placeholder = attrs.placeholder; + scope.type = attrs.type || "text"; + scope.tip = attrs.tip; + + // search for form within parent elemnts + scope.parent = JQemu.parent (elem, "FORM"); + + // email enforce lowercase and nospace + if ("email" in attrs) { + attrs.lowercase=true; + attrs.nospace=true; + attrs.minlen=6; + } + + if (scope.required) { + scope.l4acounter = scope.parent.data ("l4acounter"); + if (!scope.l4acounter) { + scope.l4acounter = {required:1, validated:0}; + console.log("Field "+scope.name+" is required (1st)"); + scope.parent.data ("l4acounter", scope.l4acounter); + } else { + console.log("Field "+scope.name+" is required"); + scope.l4acounter.required ++; + } + } + + // refresh validation each time controler update value + scope.$watch ('value', function(){ + if(scope.value) scope.validate(); } + ); + + } + + return { + restrict: 'E', + template: tmpl, + link: mymethods, + scope: { + callback : '=', + value: '=' + } + }; +}); + +console.log ("InputText Loaded"); +})(); diff --git a/afb-client/app/Frontend/widgets/FormInput/UploadFile.js b/afb-client/app/Frontend/widgets/FormInput/UploadFile.js new file mode 100644 index 0000000..9a2f031 --- /dev/null +++ b/afb-client/app/Frontend/widgets/FormInput/UploadFile.js @@ -0,0 +1,113 @@ + +/* + * 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"); +})(); |