diff --git a/members/static/js/angular-recaptcha.js b/members/static/js/angular-recaptcha.js new file mode 100644 index 0000000..5b025ce --- /dev/null +++ b/members/static/js/angular-recaptcha.js @@ -0,0 +1,306 @@ +/** + * angular-recaptcha build:2016-04-05 + * https://github.com/vividcortex/angular-recaptcha + * Copyright (c) 2016 VividCortex +**/ + +/*global angular, Recaptcha */ +(function (ng) { + 'use strict'; + + ng.module('vcRecaptcha', []); + +}(angular)); + +/*global angular */ +(function (ng) { + 'use strict'; + + function throwNoKeyException() { + throw new Error('You need to set the "key" attribute to your public reCaptcha key. If you don\'t have a key, please get one from https://www.google.com/recaptcha/admin/create'); + } + + var app = ng.module('vcRecaptcha'); + + /** + * An angular service to wrap the reCaptcha API + */ + app.provider('vcRecaptchaService', function(){ + var provider = this; + var config = {}; + provider.onLoadFunctionName = 'vcRecaptchaApiLoaded'; + + /** + * Sets the reCaptcha configuration values which will be used by default is not specified in a specific directive instance. + * + * @since 2.5.0 + * @param defaults object which overrides the current defaults object. + */ + provider.setDefaults = function(defaults){ + angular.copy(config, defaults); + }; + + /** + * Sets the reCaptcha key which will be used by default is not specified in a specific directive instance. + * + * @since 2.5.0 + * @param siteKey the reCaptcha public key (refer to the README file if you don't know what this is). + */ + provider.setSiteKey = function(siteKey){ + config.key = siteKey; + }; + + /** + * Sets the reCaptcha theme which will be used by default is not specified in a specific directive instance. + * + * @since 2.5.0 + * @param theme The reCaptcha theme. + */ + provider.setTheme = function(theme){ + config.theme = theme; + }; + + /** + * Sets the reCaptcha stoken which will be used by default is not specified in a specific directive instance. + * + * @since 2.5.0 + * @param stoken The reCaptcha stoken. + */ + provider.setStoken = function(stoken){ + config.stoken = stoken; + }; + + /** + * Sets the reCaptcha size which will be used by default is not specified in a specific directive instance. + * + * @since 2.5.0 + * @param size The reCaptcha size. + */ + provider.setSize = function(size){ + config.size = size; + }; + + /** + * Sets the reCaptcha type which will be used by default is not specified in a specific directive instance. + * + * @since 2.5.0 + * @param type The reCaptcha type. + */ + provider.setType = function(type){ + config.type = type; + }; + + /** + * Sets the reCaptcha configuration values which will be used by default is not specified in a specific directive instance. + * + * @since 2.5.0 + * @param onLoadFunctionName string name which overrides the name of the onload function. Should match what is in the recaptcha script querystring onload value. + */ + provider.setOnLoadFunctionName = function(onLoadFunctionName){ + provider.onLoadFunctionName = onLoadFunctionName; + }; + + provider.$get = ['$rootScope','$window', '$q', function ($rootScope, $window, $q) { + var deferred = $q.defer(), promise = deferred.promise, recaptcha; + + $window.vcRecaptchaApiLoadedCallback = $window.vcRecaptchaApiLoadedCallback || []; + + var callback = function () { + recaptcha = $window.grecaptcha; + + deferred.resolve(recaptcha); + }; + + $window.vcRecaptchaApiLoadedCallback.push(callback); + + $window[provider.onLoadFunctionName] = function () { + $window.vcRecaptchaApiLoadedCallback.forEach(function(callback) { + callback(); + }); + }; + + + function getRecaptcha() { + if (!!recaptcha) { + return $q.when(recaptcha); + } + + return promise; + } + + function validateRecaptchaInstance() { + if (!recaptcha) { + throw new Error('reCaptcha has not been loaded yet.'); + } + } + + + // Check if grecaptcha is not defined already. + if (ng.isDefined($window.grecaptcha)) { + callback(); + } + + return { + + /** + * Creates a new reCaptcha object + * + * @param elm the DOM element where to put the captcha + * @param conf the captcha object configuration + * @throws NoKeyException if no key is provided in the provider config or the directive instance (via attribute) + */ + create: function (elm, conf) { + + conf.sitekey = conf.key || config.key; + conf.theme = conf.theme || config.theme; + conf.stoken = conf.stoken || config.stoken; + conf.size = conf.size || config.size; + conf.type = conf.type || config.type; + + if (!conf.sitekey || conf.sitekey.length !== 40) { + throwNoKeyException(); + } + return getRecaptcha().then(function (recaptcha) { + return recaptcha.render(elm, conf); + }); + }, + + /** + * Reloads the reCaptcha + */ + reload: function (widgetId) { + validateRecaptchaInstance(); + + // $log.info('Reloading captcha'); + recaptcha.reset(widgetId); + + // Let everyone know this widget has been reset. + $rootScope.$broadcast('reCaptchaReset', widgetId); + }, + + /** + * Gets the response from the reCaptcha widget. + * + * @see https://developers.google.com/recaptcha/docs/display#js_api + * + * @returns {String} + */ + getResponse: function (widgetId) { + validateRecaptchaInstance(); + + return recaptcha.getResponse(widgetId); + } + }; + + }]; + }); + +}(angular)); + +/*global angular, Recaptcha */ +(function (ng) { + 'use strict'; + + var app = ng.module('vcRecaptcha'); + + app.directive('vcRecaptcha', ['$document', '$timeout', 'vcRecaptchaService', function ($document, $timeout, vcRecaptcha) { + + return { + restrict: 'A', + require: "?^^form", + scope: { + response: '=?ngModel', + key: '=?', + stoken: '=?', + theme: '=?', + size: '=?', + type: '=?', + tabindex: '=?', + required: '=?', + onCreate: '&', + onSuccess: '&', + onExpire: '&' + }, + link: function (scope, elm, attrs, ctrl) { + scope.widgetId = null; + + if(ctrl && angular.isDefined(attrs.required)){ + scope.$watch('required', validate); + } + + var removeCreationListener = scope.$watch('key', function (key) { + var callback = function (gRecaptchaResponse) { + // Safe $apply + $timeout(function () { + scope.response = gRecaptchaResponse; + validate(); + + // Notify about the response availability + scope.onSuccess({response: gRecaptchaResponse, widgetId: scope.widgetId}); + }); + }; + + vcRecaptcha.create(elm[0], { + + callback: callback, + key: key, + stoken: scope.stoken || attrs.stoken || null, + theme: scope.theme || attrs.theme || null, + type: scope.type || attrs.type || null, + tabindex: scope.tabindex || attrs.tabindex || null, + size: scope.size || attrs.size || null, + 'expired-callback': expired + + }).then(function (widgetId) { + // The widget has been created + validate(); + scope.widgetId = widgetId; + scope.onCreate({widgetId: widgetId}); + + scope.$on('$destroy', destroy); + + scope.$on('reCaptchaReset', function(resetWidgetId){ + if(angular.isUndefined(resetWidgetId) || widgetId === resetWidgetId){ + scope.response = ""; + validate(); + } + }) + + }); + + // Remove this listener to avoid creating the widget more than once. + removeCreationListener(); + }); + + function destroy() { + if (ctrl) { + // reset the validity of the form if we were removed + ctrl.$setValidity('recaptcha', null); + } + + cleanup(); + } + + function expired(){ + scope.response = ""; + validate(); + + // Notify about the response availability + scope.onExpire({widgetId: scope.widgetId}); + } + + function validate(){ + if(ctrl){ + ctrl.$setValidity('recaptcha', scope.required === false ? null : Boolean(scope.response)); + } + } + + function cleanup(){ + // removes elements reCaptcha added. + angular.element($document[0].querySelectorAll('.pls-container')).parent().remove(); + } + } + }; + }]); + +}(angular)); diff --git a/members/static/js/application_controllers.js b/members/static/js/application_controllers.js index 9dd4655..615f82d 100644 --- a/members/static/js/application_controllers.js +++ b/members/static/js/application_controllers.js @@ -1,6 +1,6 @@ //app -app = angular.module('applicationApp', []); +app = angular.module('applicationApp', ['vcRecaptcha']); //tokens @@ -26,12 +26,37 @@ var notySuccess = notyfication('success',2500); //controllers -app.controller("applicationController", function($scope, $http, $location, $window) { +app.controller("applicationController", function($scope, $http, $location, $window, vcRecaptchaService) { $scope.member = {}; + $scope.response = null; + $scope.widgetId = null; + $scope.model = { + key: '6LevHAcUAAAAAA45B7c-7qja-2aSwHztr9xb4K2Z' + }; + $scope.setResponse = function(response) { + $scope.response = response; + }; + $scope.setWidgetId = function(widgetId) { + $scope.widgetId = widgetId; + }; + $scope.cbExpiration = function() { + vcRecaptchaService.reload($scope.widgetId); + $scope.response = null; + }; $scope.send = function() { - $http.post("/members/api/request", $scope.member).then(function(data){ - notySuccess("Hakemus lähetetty!"); - $window.location.href = "/application/"; - }); + var valid; + //server side validation + $scope.member.reCaptchaResponse = vcRecaptchaService.getResponse() + if(resp === "") { + alert("Ole hyvä ja täytä kuvavarmennus"); + } else { + $http.post("/members/api/request", $scope.member).then(function(data){ + notySuccess("Hakemus lähetetty!"); + $window.location.href = "/application/"; + }, function(data){ + notyError("Jokin meni vikaan. Yritä uudelleen."); + vcRecaptchaService.reload($scope.widgetId); + }); + } } }); \ No newline at end of file diff --git a/members/templates/application_index.html b/members/templates/application_index.html index b72ae74..cff91a2 100644 --- a/members/templates/application_index.html +++ b/members/templates/application_index.html @@ -18,7 +18,9 @@ {%load staticfiles %} - + + +