pyc-website

main website for pyc inc.

git clone https://9o.is/git/pyc-website.git

commit 2e9df0efc99c581cad2bf342c187b168e076db84
parent d3f56ba818333d2c86174dd612207a3041fe9bef
Author: Jul <jul@9o.is>
Date:   Mon,  9 Jun 2014 21:25:32 -0400

separated js code in their own files and tried writing some angular mocks

Diffstat:
MGruntfile.js | 4+---
Msrc/main/webapp/app/App.js | 215++-----------------------------------------------------------------------------
Asrc/main/webapp/app/controllers/Forms.js | 147+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/webapp/app/controllers/ngAlert.js | 40++++++++++++++++++++++++++++++++++++++++
Asrc/main/webapp/app/directives/disabler.js | 29+++++++++++++++++++++++++++++
Asrc/main/webapp/app/directives/match.js | 26++++++++++++++++++++++++++
Asrc/main/webapp/app/directives/ngThumb.js | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/webapp/app/test/controllers/Forms.spec.js | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 392 insertions(+), 214 deletions(-)

diff --git a/Gruntfile.js b/Gruntfile.js @@ -97,9 +97,7 @@ module.exports = function(grunt) { }, jasmine: { - src: [ - '<%= dirs.src %>/app/App.js' - ], + src: '<%= app_files.js %>', options: { vendor: [ 'http://maps.googleapis.com/maps/api/js?sensor=false&language=en', diff --git a/src/main/webapp/app/App.js b/src/main/webapp/app/App.js @@ -1,4 +1,7 @@ -var app = angular.module("app", ['google-maps', 'ui.bootstrap', 'ui.router', 'ui.mask', 'angularFileUpload', 'angular-google-analytics']); +var app = angular.module("app", [ + 'google-maps', 'ui.bootstrap', 'ui.router', 'ui.mask', + 'angularFileUpload', 'angular-google-analytics', 'ngThumb', + 'match', 'disabler', 'ngAlert', 'Forms']); var ZIP_CODE_REGEXP = /^(\d{5}(-\d{4})?|[A-Z]\d[A-Z] *\d[A-Z]\d)$/; var PASSWORD_REGEXP = /^(?=.*[^a-zA-Z])\S{8,}$/; @@ -23,170 +26,6 @@ app.run(["$rootScope", "$window", "$state", "$stateParams", function ($rootScope $rootScope.$stateParams = $stateParams; }]); -app.directive('disabler', ['$compile', function($compile) { - return { - link: function(scope, elm, attrs) { - var btnContents = $compile(elm.contents())(scope); - scope.$watch(attrs.ngModel, function(value) { - if (value) { - elm.html("<i class='fa fa-spinner fa-spin'></i> Loading"); - elm.attr('disabled',true); - } else { - elm.html('').append(btnContents); - elm.attr('disabled',false); - } - }); - } - }; -}]); - -app.directive('match', function () { - return { - require: 'ngModel', - restrict: 'A', - scope: { - match: '=' - }, - link: function(scope, elem, attrs, ctrl) { - scope.$watch(function() { - return (ctrl.$pristine && angular.isUndefined(ctrl.$modelValue)) || scope.match === ctrl.$modelValue; - }, function(currentValue) { - ctrl.$setValidity('match', currentValue); - }); - } - }; -}); - -app.controller('AlertCtrl', ['$scope', function($scope) { - $scope.alerts = []; - - $scope.$on('alertDialog', function(event, alert) { - $scope.addAlert(alert); - }); - - $scope.$on('alertClear', function() { - $scope.clearAlerts(); - }); - - $scope.addAlert = function(alert) { - $scope.alerts = []; - $scope.alerts.push({type: alert.msg_type, msg: alert.msg}); - }; - - $scope.closeAlert = function(index) { - $scope.alerts.splice(index, 1); - }; - - $scope.clearAlerts = function() { - $scope.alerts = []; - }; -}]); - -app.controller('FormCtrl', ['$scope', function($scope) { - - /* Client-side data */ - $scope.model = {}; - - /* Success inputs for ng-class */ - $scope.stateSuccess = function(el) { - return "{'state-success':form."+el+".$valid && !form."+el+".$pristine}"; - }; - - /* Success and Failure inputs in ng-class */ - $scope.stateSuccessError = function(el) { - return "{'state-error':form."+el+".$invalid && !form."+el+".$pristine,'state-success':form."+el+".$valid && !form."+el+".$pristine}"; - }; - - /* Sends data to server. */ - $scope.submit = function(className, funcName, successFunc, failureFunc) { - $scope.loading = true; - window[className][funcName]($scope.model).then(function(alert) { - $scope.$apply(function() { - $scope.loading = false; - if(alert.msg_type === "success") { - if(typeof(successFunc) === "function") { - successFunc(alert); - } - } else { - if(typeof(failureFunc) === "function") { - failureFunc(alert); - } - } - }); - }); - }; - - /* Resets client-side data. */ - $scope.reset = function() { - $scope.model = {}; - $scope.form.$setPristine(); - }; -}]); - -/* Form controller that is initialized with server-side data. */ -app.controller('LoadedFormCtrl', ['$scope', '$controller', function($scope, $controller) { - $controller('FormCtrl', {$scope: $scope}); - - /* assumed server-side data */ - $scope.master = {}; - - /* Checks whether the client-side data is different from */ - $scope.diff = function(name) { - return $scope.master[name] !== $scope.model[name]; - }; - - /* Initiates the form with existing data from the server. */ - $scope.init = function(className, funcName) { - window[className][funcName]().then(function(data) { - $scope.$apply(function() { - $scope.model = angular.copy(data); - $scope.master = angular.copy(data); - }); - }); - }; -}]); - -/* Form controller to easily update (server-side data) inputs immediately. */ -app.controller('AutoUpdateFormCtrl', ['$scope', '$controller', '$rootScope', function($scope, $controller, $rootScope) { - $controller('LoadedFormCtrl', {$scope: $scope, $controller: $controller}); - - /* Updates server-side data with client-side's data corresponding to a change for 'name'. */ - $scope.update = function(className, funcName, successFunc, failureFunc) { - // if values are different, update - if($scope.diff(funcName)) { - $scope[funcName+"_loading"] = true; - window[className][funcName]($scope.model[funcName]).then(function(alert) { - $scope.$apply(function() { - $scope[funcName+"_loading"] = false; - if(alert.msg_type === "success") { - $scope.master= angular.copy($scope.model); - $scope.reset(); - - if(typeof(successFunc) === "function") { - successFunc(); - } - } else { - $rootScope.$broadcast('alertDialog', alert); - - if(typeof(failureFunc) === "function") { - failureFunc(); - } - } - }); - }); - } else { - $scope.reset(); - } - }; - - /* Resets sets form to pristine. */ - $scope.reset = function() { - $scope.model = {}; - $scope.form.$setPristine(); - $scope.model= angular.copy($scope.master); - }; -}]); - app.controller('NearAtmNotifyCtrl', ['$scope', '$controller', '$rootScope', function($scope, $controller, $rootScope) { $controller('FormCtrl', {$scope: $scope}); @@ -424,48 +263,3 @@ app.controller('GMapCtrl', ['$scope', function($scope) { }; }]); -app.directive('ngThumb', ['$window', function($window) { - var helper = { - support: !!($window.FileReader && $window.CanvasRenderingContext2D), - isFile: function(item) { - return angular.isObject(item) && item instanceof $window.File; - }, - isImage: function(file) { - var type = '|' + file.type.slice(file.type.lastIndexOf('/') + 1) + '|'; - return '|jpg|png|jpeg|bmp|gif|'.indexOf(type) !== -1; - } - }; - - return { - restrict: 'A', - template: '<canvas/>', - link: function(scope, element, attributes) { - - function onLoadFile(event) { - var img = new Image(); - img.onload = onLoadImage; - img.src = event.target.result; - } - - function onLoadImage() { - var width = params.width || this.width / this.height * params.height; - var height = params.height || this.height / this.width * params.width; - canvas.attr({ width: width, height: height }); - canvas[0].getContext('2d').drawImage(this, 0, 0, width, height); - } - - if (!helper.support) { return; } - - var params = scope.$eval(attributes.ngThumb); - - if (!helper.isFile(params.file)) { return; } - if (!helper.isImage(params.file)) { return; } - - var canvas = element.find('canvas'); - var reader = new FileReader(); - - reader.onload = onLoadFile; - reader.readAsDataURL(params.file); - } - }; - }]); -\ No newline at end of file diff --git a/src/main/webapp/app/controllers/Forms.js b/src/main/webapp/app/controllers/Forms.js @@ -0,0 +1,146 @@ +/** + * This modules simplifies the troubles of connecting with Liftweb 3.0's + * promises. + * + * Included ngAlert as dependency since Forms module checks the success + * of the message using ngAlert's message formatting. + */ +angular.module("Forms", ['ngAlert']) + + /** + * Simple form controller with helper functions like submit + * which handles the complexity of sending data and handling + * response from backend server. + */ + .controller('FormCtrl', ['$scope', function($scope) { + + /** + * Client-side data + */ + $scope.model = {}; + + /** + * Success inputs for ng-class + */ + $scope.stateSuccess = function(el) { + return "{'state-success':form."+el+".$valid && !form."+el+".$pristine}"; + }; + + /** + * Success and Failure inputs in ng-class + */ + $scope.stateSuccessError = function(el) { + return "{'state-error':form."+el+".$invalid && !form."+el+".$pristine,'state-success':form."+el+".$valid && !form."+el+".$pristine}"; + }; + + /** + * Sends data to server. (Useful for Liftweb's roundtrip with ngAlert at least) + */ + $scope.submit = function(className, funcName, successFunc, failureFunc) { + $scope.loading = true; + window[className][funcName]($scope.model).then(function(alert) { + $scope.$apply(function() { + $scope.loading = false; + if(alert.msg_type === "success") { + if(typeof(successFunc) === "function") { + successFunc(alert); + } + } else { + if(typeof(failureFunc) === "function") { + failureFunc(alert); + } + } + }); + }); + }; + + /** + * Resets client-side data and the entire form. + */ + $scope.reset = function() { + $scope.model = {}; + $scope.form.$setPristine(); + }; + }]) + + /** + * Form controller that is initialized with server-side data. + */ + .controller('LoadedFormCtrl', ['$scope', '$controller', function($scope, $controller) { + $controller('FormCtrl', {$scope: $scope}); + + /** + * Known server-side data + */ + $scope.master = {}; + + /** + * Checks whether the client-side data is different + * from known server-side data. + */ + $scope.diff = function(name) { + return $scope.master[name] !== $scope.model[name]; + }; + + /** + * Initiates the form with existing data from the server. + */ + $scope.init = function(className, funcName) { + window[className][funcName]().then(function(data) { + $scope.$apply(function() { + $scope.model = angular.copy(data); + $scope.master = angular.copy(data); + }); + }); + }; + }]) + + /** + * Form controller to easily update (server-side data) inputs immediately. + */ + .controller('AutoUpdateFormCtrl', ['$scope', '$controller', '$rootScope', function($scope, $controller, $rootScope) { + $controller('LoadedFormCtrl', {$scope: $scope, $controller: $controller}); + + /** + * Updates server-side data with client-side's data corresponding + * to a change of a data model 'name'. + * + * Sets funcName+"_loading" scoped variable to true (useful for Ajax animations) + */ + $scope.update = function(className, funcName, successFunc, failureFunc) { + // if values are different, update + if($scope.diff(funcName)) { + $scope[funcName+"_loading"] = true; + window[className][funcName]($scope.model[funcName]).then(function(alert) { + $scope.$apply(function() { + $scope[funcName+"_loading"] = false; + if(alert.msg_type === "success") { + $scope.master= angular.copy($scope.model); + $scope.reset(); + + if(typeof(successFunc) === "function") { + successFunc(); + } + } else { + $rootScope.$broadcast('alertDialog', alert); + + if(typeof(failureFunc) === "function") { + failureFunc(); + } + } + }); + }); + } else { + $scope.reset(); + } + }; + + /** + * Sets client-side data with server and resets entire form. + */ + $scope.reset = function() { + $scope.model = {}; + $scope.form.$setPristine(); + $scope.model= angular.copy($scope.master); + }; + }]); +\ No newline at end of file diff --git a/src/main/webapp/app/controllers/ngAlert.js b/src/main/webapp/app/controllers/ngAlert.js @@ -0,0 +1,39 @@ +/** + * Helper module for ui-bootstrap alert. + * Listens for 2 events: + * + * 1. alertClear: clears all alerts. + * 2. alertDialog: sets a message for Alert Box + * + * Message example: + * + * { + * type: "danger", + * msg: "<span>The world is going to explode!</span>" + * } + */ +angular.module("ngAlert", ['ui.bootstrap']) + .controller('AlertCtrl', ['$scope', function($scope) { + $scope.alerts = []; + + $scope.$on('alertDialog', function(event, alert) { + $scope.addAlert(alert); + }); + + $scope.$on('alertClear', function() { + $scope.clearAlerts(); + }); + + $scope.addAlert = function(alert) { + $scope.alerts = []; + $scope.alerts.push({type: alert.msg_type, msg: alert.msg}); + }; + + $scope.closeAlert = function(index) { + $scope.alerts.splice(index, 1); + }; + + $scope.clearAlerts = function() { + $scope.alerts = []; + }; + }]); +\ No newline at end of file diff --git a/src/main/webapp/app/directives/disabler.js b/src/main/webapp/app/directives/disabler.js @@ -0,0 +1,28 @@ +/** + * Disables button and displays loading. (Useful for Ajax) + * Depends on FontAwesome's spinning icon. + * + * Usage: + * + * <button type="submit" disabler ng-model="loading">Submit</button> + * + * Set $scope.loading to true in javascript code to display + * the button as loading. + */ +angular.module("disabler", []) + .directive('disabler', ['$compile', function($compile) { + return { + link: function(scope, elm, attrs) { + var btnContents = $compile(elm.contents())(scope); + scope.$watch(attrs.ngModel, function(value) { + if (value) { + elm.html("<i class='fa fa-spinner fa-spin'></i> Loading"); + elm.attr('disabled',true); + } else { + elm.html('').append(btnContents); + elm.attr('disabled',false); + } + }); + } + }; + }]); +\ No newline at end of file diff --git a/src/main/webapp/app/directives/match.js b/src/main/webapp/app/directives/match.js @@ -0,0 +1,25 @@ +/** + * Checks whether two inputs have matching values. + * + * Usage: + * + * <input name="input1" ng-model="model.password" type="password"> + * <input name="input2" ng-model="model.confirm" type="password" data-match="model.password"> + */ +angular.module("match", []) + .directive('match', function () { + return { + require: 'ngModel', + restrict: 'A', + scope: { + match: '=' + }, + link: function(scope, elem, attrs, ctrl) { + scope.$watch(function() { + return (ctrl.$pristine && angular.isUndefined(ctrl.$modelValue)) || scope.match === ctrl.$modelValue; + }, function(currentValue) { + ctrl.$setValidity('match', currentValue); + }); + } + }; + }); +\ No newline at end of file diff --git a/src/main/webapp/app/directives/ngThumb.js b/src/main/webapp/app/directives/ngThumb.js @@ -0,0 +1,50 @@ +/** + * ngThumb is an angular-file-upload directive to preview uploaded images. + * https://github.com/nervgh/angular-file-upload/blob/master/examples/image-preview/directives.js + */ +angular.module("ngThumb", ['angularFileUpload']) + .directive('ngThumb', ['$window', function($window) { + var helper = { + support: !!($window.FileReader && $window.CanvasRenderingContext2D), + isFile: function(item) { + return angular.isObject(item) && item instanceof $window.File; + }, + isImage: function(file) { + var type = '|' + file.type.slice(file.type.lastIndexOf('/') + 1) + '|'; + return '|jpg|png|jpeg|bmp|gif|'.indexOf(type) !== -1; + } + }; + + return { + restrict: 'A', + template: '<canvas/>', + link: function(scope, element, attributes) { + + function onLoadFile(event) { + var img = new Image(); + img.onload = onLoadImage; + img.src = event.target.result; + } + + function onLoadImage() { + var width = params.width || this.width / this.height * params.height; + var height = params.height || this.height / this.width * params.width; + canvas.attr({ width: width, height: height }); + canvas[0].getContext('2d').drawImage(this, 0, 0, width, height); + } + + if (!helper.support) { return; } + + var params = scope.$eval(attributes.ngThumb); + + if (!helper.isFile(params.file)) { return; } + if (!helper.isImage(params.file)) { return; } + + var canvas = element.find('canvas'); + var reader = new FileReader(); + + reader.onload = onLoadFile; + reader.readAsDataURL(params.file); + } + }; + }]); +\ No newline at end of file diff --git a/src/main/webapp/app/test/controllers/Forms.spec.js b/src/main/webapp/app/test/controllers/Forms.spec.js @@ -0,0 +1,93 @@ +// simulate Lift objects +window.lift_page = "F1002111409932BP3JRX"; +var ajaxhandler = "F1002111409938WP5FBR"; + +describe('Forms', function() { + var $httpBackend, $rootScope, createController; + + + + + + beforeEach(function() { + module('Forms'); + + inject(function($injector) { + $httpBackend = $injector.get('$httpBackend'); + + // if key is correct, responds with success (for Lift Promise requests) + $httpBackend.when('POST', '/ajax_request/'+lift_page).respond({ + msg_type : 'success', msg: '<span>You Got It!</span>'}); + }); + }); + + describe('FormCtrl', function() { + + beforeEach(inject(function($injector) { + + $rootScope = $injector.get('$rootScope'); + var $controller = $injector.get('$controller'); + + createController = function() { + return $controller('FormCtrl', { + '$scope' : $rootScope + }); + }; + })); + + it('works as is with stateSuccess', function() { + createController(); + expect($rootScope.stateSuccess('email')).toBe( + "{'state-success':form.email.$valid && !form.email.$pristine}" + ); + }); + + it('works as is with stateSuccessError', function() { + createController(); + expect($rootScope.stateSuccessError('email')).toBe( + "{'state-error':form.email.$invalid && !form.email.$pristine,"+ + "'state-success':form.email.$valid && !form.email.$pristine}" + ); + }); + + it('submit function sets loading variable', function() { + createController(); + expect($rootScope.loading !== true); + $rootScope.submit('TestPromise', 'testSubmit', function(){}, function(){}); + expect($rootScope.loading).toBe(true); + }); + + // TODO: not receiving response from test lift promise + it('submit function with Lift Promise sends and receives', function() { + createController(); + + $rootScope.key = 'secret'; + + $rootScope.submit('TestPromise', 'testSubmit', function(){ + expect(false); // success func is called, that's correct + }, function(){ + expect(false); // failure func is called, that's incorrect + }); + }); + }); + + afterEach(function() { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + // url: /ajax_request/{lift_page} + // form data: {ajaxhandler} : {guid, funcName, payload} + window.TestPromise = { + "_call_server": function(v) { + liftAjax.lift_ajaxHandler(ajaxhandler + '=' + + encodeURIComponent(JSON.stringify(v)), null, null, null) + }, + "testSubmit": function(param) { + var promise = new liftAjax.Promise(); + liftAjax.associate(promise); + this._call_server({guid: promise.guid, name: "testSubmit", payload: param}); + return promise; + } + }; +}); +\ No newline at end of file