diff --git a/sdk-html/pom.xml b/sdk-html/pom.xml index f8057eee55..3bb654a71c 100755 --- a/sdk-html/pom.xml +++ b/sdk-html/pom.xml @@ -13,6 +13,12 @@ + + org.keycloak + keycloak-social + ${project.version} + + org.jboss.resteasy jaxrs-api diff --git a/sdk-html/src/main/java/org/keycloak/sdk/resources/SdkApplication.java b/sdk-html/src/main/java/org/keycloak/sdk/resources/SdkApplication.java new file mode 100644 index 0000000000..30f0586c0b --- /dev/null +++ b/sdk-html/src/main/java/org/keycloak/sdk/resources/SdkApplication.java @@ -0,0 +1,9 @@ +package org.keycloak.sdk.resources; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +@ApplicationPath("sdk/api") +public class SdkApplication extends Application { + +} diff --git a/sdk-html/src/main/java/org/keycloak/sdk/resources/SdkResource.java b/sdk-html/src/main/java/org/keycloak/sdk/resources/SdkResource.java index a5decf401c..172b238a37 100644 --- a/sdk-html/src/main/java/org/keycloak/sdk/resources/SdkResource.java +++ b/sdk-html/src/main/java/org/keycloak/sdk/resources/SdkResource.java @@ -25,24 +25,28 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.UriInfo; import javax.xml.bind.annotation.XmlRootElement; +import org.keycloak.social.util.UriBuilder; + /** * @author Stian Thorgersen */ -@Path("sdk") +@Path("") public class SdkResource { @XmlRootElement public static class LoginConfig { private String callbackUrl; + + private String id; private String name; @@ -52,6 +56,10 @@ public class SdkResource { return callbackUrl; } + public String getId() { + return id; + } + public String getName() { return name; } @@ -64,6 +72,10 @@ public class SdkResource { this.callbackUrl = callbackUrl; } + public void setId(String id) { + this.id = id; + } + public void setName(String name) { this.name = name; } @@ -80,38 +92,40 @@ public class SdkResource { @Context private UriInfo uriInfo; + /** + * TODO Retrieve configuration for application from IDM + */ @GET - @Path("config/{application}") + @Path("{application}/login/config") @Produces(MediaType.APPLICATION_JSON) - public Response getConfig(@PathParam("application") String application) { - String applicationCallbackUrl = null; // TODO Get application callback url - String applicationJavaScriptOrigin = null; // TODO Get application javascript origin - + public LoginConfig getLoginConfig(@PathParam("application") String application) { LoginConfig loginConfig = new LoginConfig(); + loginConfig.setId(application); loginConfig.setName(application); - loginConfig.setCallbackUrl(applicationCallbackUrl); - loginConfig.setProviders(null); // TODO Get configured identity providers for application + loginConfig.setCallbackUrl("http://localhost:8080"); + loginConfig.setProviders(new String[] { "google", "twitter" }); + return loginConfig; + } - ResponseBuilder response = Response.ok(loginConfig); - - if (applicationJavaScriptOrigin != null) { - response.header("Access-Control-Allow-Origin", applicationJavaScriptOrigin); + @GET + @Path("{application}/login") + public Response login(@PathParam("application") String application, @QueryParam("error") String error) { + UriBuilder ub = new UriBuilder(headers, uriInfo, "sdk/login.html").setQueryParam("application", application); + if (error != null) { + ub.setQueryParam("error", error); } - - return response.build(); + return Response.seeOther(ub.build()).build(); } @GET - @Path("login/{application}") - @Produces(MediaType.TEXT_HTML) - public Response login(@PathParam("application") String application) { - return Response.ok(getClass().getResourceAsStream("login.html")).build(); + @Path("{application}/register") + public Response register(@PathParam("application") String application, @QueryParam("error") String error) { + UriBuilder ub = new UriBuilder(headers, uriInfo, "sdk/register.html").setQueryParam("application", application); + if (error != null) { + ub.setQueryParam("error", error); + } + return Response.seeOther(ub.build()).build(); } - @GET - @Path("register/{application}") - @Produces(MediaType.TEXT_HTML) - public Response register(@PathParam("application") String application) { - return Response.ok(getClass().getResourceAsStream("register.html")).build(); - } } + diff --git a/sdk-html/src/main/resources/META-INF/resources/sdk/css/bootstrap.css b/sdk-html/src/main/resources/META-INF/resources/sdk/css/bootstrap.css index 2f56af33f3..b725064aab 100644 --- a/sdk-html/src/main/resources/META-INF/resources/sdk/css/bootstrap.css +++ b/sdk-html/src/main/resources/META-INF/resources/sdk/css/bootstrap.css @@ -1,5 +1,5 @@ /*! - * Bootstrap v2.3.1 + * Bootstrap v2.3.2 * * Copyright 2012 Twitter, Inc * Licensed under the Apache License v2.0 @@ -3009,6 +3009,15 @@ table th[class*="span"], display: block; } +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} + .pull-right > .dropdown-menu { right: 0; left: auto; diff --git a/sdk-html/src/main/resources/META-INF/resources/sdk/example-config.json b/sdk-html/src/main/resources/META-INF/resources/sdk/example-config.json deleted file mode 100644 index 82e8513796..0000000000 --- a/sdk-html/src/main/resources/META-INF/resources/sdk/example-config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id" : "acme-corp", - "name" : "Acme Corp", - "providers" : [ "google", "twitter" ] -} \ No newline at end of file diff --git a/sdk-html/src/main/resources/META-INF/resources/sdk/js/angular-resource.js b/sdk-html/src/main/resources/META-INF/resources/sdk/js/angular-resource.js index 1af44ef0fd..d67f501c43 100644 --- a/sdk-html/src/main/resources/META-INF/resources/sdk/js/angular-resource.js +++ b/sdk-html/src/main/resources/META-INF/resources/sdk/js/angular-resource.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.0.6 + * @license AngularJS v1.0.7 * (c) 2010-2012 Google, Inc. http://angularjs.org * License: MIT */ diff --git a/sdk-html/src/main/resources/META-INF/resources/sdk/js/angular.js b/sdk-html/src/main/resources/META-INF/resources/sdk/js/angular.js index 5b431aebd9..a860c8594f 100644 --- a/sdk-html/src/main/resources/META-INF/resources/sdk/js/angular.js +++ b/sdk-html/src/main/resources/META-INF/resources/sdk/js/angular.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.0.6 + * @license AngularJS v1.0.7 * (c) 2010-2012 Google, Inc. http://angularjs.org * License: MIT */ @@ -67,6 +67,29 @@ var /** holds major version number for IE or NaN for real browsers */ nodeName_, uid = ['0', '0', '0']; + +/** + * @private + * @param {*} obj + * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...) + */ +function isArrayLike(obj) { + if (!obj || (typeof obj.length !== 'number')) return false; + + // We have on object which has length property. Should we treat it as array? + if (typeof obj.hasOwnProperty != 'function' && + typeof obj.constructor != 'function') { + // This is here for IE8: it is a bogus object treat it as array; + return true; + } else { + return obj instanceof JQLite || // JQLite + (jQuery && obj instanceof jQuery) || // jQuery + toString.call(obj) !== '[object Object]' || // some browser native object + typeof obj.callee === 'function'; // arguments (on IE8 looks like regular obj) + } +} + + /** * @ngdoc function * @name angular.forEach @@ -94,30 +117,6 @@ var /** holds major version number for IE or NaN for real browsers */ * @param {Object=} context Object to become context (`this`) for the iterator function. * @returns {Object|Array} Reference to `obj`. */ - - -/** - * @private - * @param {*} obj - * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...) - */ -function isArrayLike(obj) { - if (!obj || (typeof obj.length !== 'number')) return false; - - // We have on object which has length property. Should we treat it as array? - if (typeof obj.hasOwnProperty != 'function' && - typeof obj.constructor != 'function') { - // This is here for IE8: it is a bogus object treat it as array; - return true; - } else { - return obj instanceof JQLite || // JQLite - (jQuery && obj instanceof jQuery) || // jQuery - toString.call(obj) !== '[object Object]' || // some browser native object - typeof obj.callee === 'function'; // arguments (on IE8 looks like regular obj) - } -} - - function forEach(obj, iterator, context) { var key; if (obj) { @@ -201,6 +200,21 @@ function nextUid() { return uid.join(''); } + +/** + * Set or clear the hashkey for an object. + * @param obj object + * @param h the hashkey (!truthy to delete the hashkey) + */ +function setHashKey(obj, h) { + if (h) { + obj.$$hashKey = h; + } + else { + delete obj.$$hashKey; + } +} + /** * @ngdoc function * @name angular.extend @@ -212,8 +226,10 @@ function nextUid() { * * @param {Object} dst Destination object. * @param {...Object} src Source object(s). + * @returns {Object} Reference to `dst`. */ function extend(dst) { + var h = dst.$$hashKey; forEach(arguments, function(obj){ if (obj !== dst) { forEach(obj, function(value, key){ @@ -221,6 +237,8 @@ function extend(dst) { }); } }); + + setHashKey(dst,h); return dst; } @@ -575,12 +593,14 @@ function copy(source, destination){ destination.push(copy(source[i])); } } else { + var h = destination.$$hashKey; forEach(destination, function(value, key){ delete destination[key]; }); for ( var key in source) { destination[key] = copy(source[key]); } + setHashKey(destination,h); } } return destination; @@ -620,7 +640,7 @@ function shallowCopy(src, dst) { * During a property comparision, properties of `function` type and properties with names * that begin with `$` are ignored. * - * Scope and DOMWindow objects are being compared only be identify (`===`). + * Scope and DOMWindow objects are being compared only by identify (`===`). * * @param {*} o1 Object or value to compare. * @param {*} o2 Object or value to compare. @@ -680,7 +700,7 @@ function sliceArgs(args, startIndex) { * * @description * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for - * `fn`). You can supply optional `args` that are are prebound to the function. This feature is also + * `fn`). You can supply optional `args` that are prebound to the function. This feature is also * known as [function currying](http://en.wikipedia.org/wiki/Currying). * * @param {Object} self Context which `fn` should be evaluated in. @@ -873,7 +893,7 @@ function encodeUriQuery(val, pctEncodeSpaces) { * * @description * - * Use this directive to auto-bootstrap on application. Only + * Use this directive to auto-bootstrap an application. Only * one directive can be used per HTML document. The directive * designates the root of the application and is typically placed * at the root of the page. @@ -1012,7 +1032,7 @@ function bindJQuery() { } /** - * throw error of the argument is falsy. + * throw error if the argument is falsy. */ function assertArg(arg, name, reason) { if (!arg) { @@ -1293,11 +1313,11 @@ function setupModuleLoader(window) { * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { - full: '1.0.6', // all of these placeholder strings will be replaced by grunt's + full: '1.0.7', // all of these placeholder strings will be replaced by grunt's major: 1, // package task minor: 0, - dot: 6, - codeName: 'universal-irreversibility' + dot: 7, + codeName: 'monochromatic-rainbow' }; @@ -1442,18 +1462,18 @@ function publishExternalAPI(angular){ * - [after()](http://api.jquery.com/after/) * - [append()](http://api.jquery.com/append/) * - [attr()](http://api.jquery.com/attr/) - * - [bind()](http://api.jquery.com/bind/) - * - [children()](http://api.jquery.com/children/) + * - [bind()](http://api.jquery.com/bind/) - Does not support namespaces + * - [children()](http://api.jquery.com/children/) - Does not support selectors * - [clone()](http://api.jquery.com/clone/) * - [contents()](http://api.jquery.com/contents/) * - [css()](http://api.jquery.com/css/) * - [data()](http://api.jquery.com/data/) * - [eq()](http://api.jquery.com/eq/) - * - [find()](http://api.jquery.com/find/) - Limited to lookups by tag name. + * - [find()](http://api.jquery.com/find/) - Limited to lookups by tag name * - [hasClass()](http://api.jquery.com/hasClass/) * - [html()](http://api.jquery.com/html/) - * - [next()](http://api.jquery.com/next/) - * - [parent()](http://api.jquery.com/parent/) + * - [next()](http://api.jquery.com/next/) - Does not support selectors + * - [parent()](http://api.jquery.com/parent/) - Does not support selectors * - [prepend()](http://api.jquery.com/prepend/) * - [prop()](http://api.jquery.com/prop/) * - [ready()](http://api.jquery.com/ready/) @@ -1465,7 +1485,7 @@ function publishExternalAPI(angular){ * - [text()](http://api.jquery.com/text/) * - [toggleClass()](http://api.jquery.com/toggleClass/) * - [triggerHandler()](http://api.jquery.com/triggerHandler/) - Doesn't pass native event objects to handlers. - * - [unbind()](http://api.jquery.com/unbind/) + * - [unbind()](http://api.jquery.com/unbind/) - Does not support namespaces * - [val()](http://api.jquery.com/val/) * - [wrap()](http://api.jquery.com/wrap/) * @@ -2012,23 +2032,43 @@ forEach({ if (!eventFns) { if (type == 'mouseenter' || type == 'mouseleave') { - var counter = 0; + var contains = document.body.contains || document.body.compareDocumentPosition ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; - events.mouseenter = []; - events.mouseleave = []; + events[type] = []; + + // Refer to jQuery's implementation of mouseenter & mouseleave + // Read about mouseenter and mouseleave: + // http://www.quirksmode.org/js/events_mouse.html#link8 + var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"} + bindFn(element, eventmap[type], function(event) { + var ret, target = this, related = event.relatedTarget; + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !contains(target, related)) ){ + handle(event, type); + } - bindFn(element, 'mouseover', function(event) { - counter++; - if (counter == 1) { - handle(event, 'mouseenter'); - } - }); - bindFn(element, 'mouseout', function(event) { - counter --; - if (counter == 0) { - handle(event, 'mouseleave'); - } }); + } else { addEventListenerFn(element, type, handle); events[type] = []; @@ -2344,7 +2384,7 @@ function annotate(fn) { } } else if (isArray(fn)) { last = fn.length - 1; - assertArgFn(fn[last], 'fn') + assertArgFn(fn[last], 'fn'); $inject = fn.slice(0, last); } else { assertArgFn(fn, 'fn', true); @@ -2378,7 +2418,7 @@ function annotate(fn) { * # Injection Function Annotation * * JavaScript does not have annotations, and annotations are needed for dependency injection. The - * following ways are all valid way of annotating function with injection arguments and are equivalent. + * following are all valid ways of annotating function with injection arguments and are equivalent. * *
  *   // inferred (only works if code not minified/obfuscated)
@@ -2507,7 +2547,7 @@ function annotate(fn) {
  *     // ...
  *   };
  *   tmpFn.$inject = ['$compile', '$rootScope'];
- *   injector.invoke(tempFn);
+ *   injector.invoke(tmpFn);
  *
  *   // To better support inline function the inline annotation is supported
  *   injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
@@ -2560,7 +2600,7 @@ function annotate(fn) {
  *
  *     beforeEach(module(function($provide) {
  *       $provide.provider('greet', GreetProvider);
- *     });
+ *     }));
  *
  *     it('should greet', inject(function(greet) {
  *       expect(greet('angular')).toEqual('Hello angular!');
@@ -2573,9 +2613,7 @@ function annotate(fn) {
  *       inject(function(greet) {
  *         expect(greet('angular')).toEqual('Ahoj angular!');
  *       });
- *     )};
- *
- *   });
+ *     });
  * 
*/ @@ -2669,7 +2707,7 @@ function annotate(fn) { * * @param {string} name The name of the service to decorate. * @param {function()} decorator This function will be invoked when the service needs to be - * instanciated. The function is called using the {@link AUTO.$injector#invoke + * instantiated. The function is called using the {@link AUTO.$injector#invoke * injector.invoke} method and is therefore fully injectable. Local injection arguments: * * * `$delegate` - The original service instance, which can be monkey patched, configured, @@ -2869,6 +2907,8 @@ function createInjector(modulesToLoad) { var Constructor = function() {}, instance, returnedValue; + // Check if Type is annotated and use just the given function at n-1 as parameter + // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; instance = new Constructor(); returnedValue = invoke(Type, instance, locals); @@ -3249,7 +3289,13 @@ function Browser(window, document, $log, $sniffer) { cookie = cookieArray[i]; index = cookie.indexOf('='); if (index > 0) { //ignore nameless cookies - lastCookies[unescape(cookie.substring(0, index))] = unescape(cookie.substring(index + 1)); + var name = unescape(cookie.substring(0, index)); + // the first value that is seen for a cookie is the most + // specific one. values for the same cookie name that + // follow are for less specific paths. + if (lastCookies[name] === undefined) { + lastCookies[name] = unescape(cookie.substring(index + 1)); + } } } } @@ -4054,9 +4100,9 @@ function $CompileProvider($provide) { /** - * Once the directives have been collected their compile functions is executed. This method + * Once the directives have been collected, their compile functions are executed. This method * is responsible for inlining directive templates as well as terminating the application - * of the directives if the terminal directive has been reached.. + * of the directives if the terminal directive has been reached. * * @param {Array} directives Array of collected directives to execute their compile function. * this needs to be pre-sorted by priority order. @@ -4064,11 +4110,11 @@ function $CompileProvider($provide) { * @param {Object} templateAttrs The shared attribute function * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the * scope argument is auto-generated to the new child of the transcluded parent scope. - * @param {DOMElement} $rootElement If we are working on the root of the compile tree then this - * argument has the root jqLite array so that we can replace widgets on it. + * @param {JQLite} jqCollection If we are working on the root of the compile tree then this + * argument has the root jqLite array so that we can replace nodes on it. * @returns linkFn */ - function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, $rootElement) { + function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection) { var terminalPriority = -Number.MAX_VALUE, preLinkFns = [], postLinkFns = [], @@ -4122,7 +4168,7 @@ function $CompileProvider($provide) { $compileNode = templateAttrs.$$element = jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' ')); compileNode = $compileNode[0]; - replaceWith($rootElement, jqLite($template[0]), compileNode); + replaceWith(jqCollection, jqLite($template[0]), compileNode); childTranscludeFn = compile($template, transcludeFn, terminalPriority); } else { $template = jqLite(JQLiteClone(compileNode)).contents(); @@ -4146,7 +4192,7 @@ function $CompileProvider($provide) { throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue); } - replaceWith($rootElement, $compileNode, compileNode); + replaceWith(jqCollection, $compileNode, compileNode); var newTemplateAttrs = {$attr: {}}; @@ -4174,7 +4220,7 @@ function $CompileProvider($provide) { assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), - nodeLinkFn, $compileNode, templateAttrs, $rootElement, directive.replace, + nodeLinkFn, $compileNode, templateAttrs, jqCollection, directive.replace, childTranscludeFn); ii = directives.length; } else if (directive.compile) { @@ -4307,7 +4353,7 @@ function $CompileProvider($provide) { parentGet = $parse(attrs[attrName]); scope[scopeName] = function(locals) { return parentGet(parentScope, locals); - } + }; break; } @@ -4795,7 +4841,7 @@ function $DocumentProvider(){ * */ function $ExceptionHandlerProvider() { - this.$get = ['$log', function($log){ + this.$get = ['$log', function($log) { return function(exception, cause) { $log.error.apply($log, arguments); }; @@ -5560,6 +5606,10 @@ function $LocationProvider(){ // update $location when $browser url changes $browser.onUrlChange(function(newUrl) { if ($location.absUrl() != newUrl) { + if ($rootScope.$broadcast('$locationChangeStart', newUrl, $location.absUrl()).defaultPrevented) { + $browser.url($location.absUrl()); + return; + } $rootScope.$evalAsync(function() { var oldUrl = $location.absUrl(); @@ -5868,10 +5918,10 @@ function lex(text, csp){ function readIdent() { var ident = "", start = index, - lastDot, peekIndex, methodName; + lastDot, peekIndex, methodName, ch; while (index < text.length) { - var ch = text.charAt(index); + ch = text.charAt(index); if (ch == '.' || isIdent(ch) || isNumber(ch)) { if (ch == '.') lastDot = index; ident += ch; @@ -5885,7 +5935,7 @@ function lex(text, csp){ if (lastDot) { peekIndex = index; while(peekIndex < text.length) { - var ch = text.charAt(peekIndex); + ch = text.charAt(peekIndex); if (ch == '(') { methodName = ident.substr(lastDot - start + 1); ident = ident.substr(0, lastDot - start); @@ -6138,8 +6188,8 @@ function parser(text, json, $filter, csp){ text.substring(0, token.index) + "] can not be assigned to", token); } right = logicalOR(); - return function(self, locals){ - return left.assign(self, right(self, locals), locals); + return function(scope, locals){ + return left.assign(scope, right(scope, locals), locals); }; } else { return left; @@ -6256,12 +6306,12 @@ function parser(text, json, $filter, csp){ var field = expect().text; var getter = getterFn(field, csp); return extend( - function(self, locals) { - return getter(object(self, locals), locals); + function(scope, locals, self) { + return getter(self || object(scope, locals), locals); }, { - assign:function(self, value, locals) { - return setter(object(self, locals), field, value); + assign:function(scope, value, locals) { + return setter(object(scope, locals), field, value); } } ); @@ -6302,14 +6352,14 @@ function parser(text, json, $filter, csp){ } while (expect(',')); } consume(')'); - return function(self, locals){ + return function(scope, locals){ var args = [], - context = contextGetter ? contextGetter(self, locals) : self; + context = contextGetter ? contextGetter(scope, locals) : scope; for ( var i = 0; i < argsFn.length; i++) { - args.push(argsFn[i](self, locals)); + args.push(argsFn[i](scope, locals)); } - var fnPtr = fn(self, locals) || noop; + var fnPtr = fn(scope, locals, context) || noop; // IE stupidity! return fnPtr.apply ? fnPtr.apply(context, args) @@ -6351,8 +6401,7 @@ function parser(text, json, $filter, csp){ var object = {}; for ( var i = 0; i < keyValues.length; i++) { var keyValue = keyValues[i]; - var value = keyValue.value(self, locals); - object[keyValue.key] = value; + object[keyValue.key] = keyValue.value(self, locals); } return object; }; @@ -6474,7 +6523,7 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4) { } return pathVal; }; -}; +} function getterFn(path, csp) { if (getterFnCache.hasOwnProperty(path)) { @@ -6489,7 +6538,7 @@ function getterFn(path, csp) { fn = (pathKeysLength < 6) ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4]) : function(scope, locals) { - var i = 0, val + var i = 0, val; do { val = cspSafeGetterFn( pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++] @@ -6702,7 +6751,7 @@ function $ParseProvider() { * models and avoiding unnecessary browser repaints, which would result in flickering UI. * - $q promises are recognized by the templating engine in angular, which means that in templates * you can treat promises attached to a scope as if they were the resulting values. - * - Q has many more features that $q, but that comes at a cost of bytes. $q is tiny, but contains + * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains * all the important functionality needed for common async tasks. * * # Testing @@ -6897,10 +6946,7 @@ function qFactory(nextTick, exceptionHandler) { * the promise comes from a source that can't be trusted. * * @param {*} value Value or a promise - * @returns {Promise} Returns a single promise that will be resolved with an array of values, - * each value corresponding to the promise at the same index in the `promises` array. If any of - * the promises is resolved with a rejection, this resulting promise will be resolved with the - * same rejection. + * @returns {Promise} Returns a promise of the passed value or promise */ var when = function(value, callback, errback) { var result = defer(), @@ -7496,22 +7542,22 @@ function $RouteParamsProvider() { /** * DESIGN NOTES * - * The design decisions behind the scope ware heavily favored for speed and memory consumption. + * The design decisions behind the scope are heavily favored for speed and memory consumption. * * The typical use of scope is to watch the expressions, which most of the time return the same * value as last time so we optimize the operation. * - * Closures construction is expensive from speed as well as memory: - * - no closures, instead ups prototypical inheritance for API + * Closures construction is expensive in terms of speed as well as memory: + * - No closures, instead use prototypical inheritance for API * - Internal state needs to be stored on scope directly, which means that private state is * exposed as $$____ properties * * Loop operations are optimized by using while(count--) { ... } * - this means that in order to keep the same order of execution as addition we have to add - * items to the array at the begging (shift) instead of at the end (push) + * items to the array at the beginning (shift) instead of at the end (push) * * Child scopes are created and removed often - * - Using array would be slow since inserts in meddle are expensive so we use linked list + * - Using an array would be slow since inserts in middle are expensive so we use linked list * * There are few watches then a lot of observers. This is why you don't want the observer to be * implemented in the same way as watch. Watch requires return of initialization function which @@ -7533,7 +7579,7 @@ function $RouteParamsProvider() { * @methodOf ng.$rootScopeProvider * @description * - * Sets the number of digest iteration the scope should attempt to execute before giving up and + * Sets the number of digest iterations the scope should attempt to execute before giving up and * assuming that the model is unstable. * * The current default is 10 iterations. @@ -7813,7 +7859,7 @@ function $RootScopeProvider(){ * @function * * @description - * Process all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children. + * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children. * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are * firing. This means that it is possible to get into an infinite loop. This function will throw @@ -8155,7 +8201,7 @@ function $RootScopeProvider(){ * Afterwards, the event traverses upwards toward the root scope and calls all registered * listeners along the way. The event will stop propagating if one of the listeners cancels it. * - * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed + * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed * onto the {@link ng.$exceptionHandler $exceptionHandler} service. * * @param {string} name Event name to emit. @@ -8224,7 +8270,7 @@ function $RootScopeProvider(){ * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed * onto the {@link ng.$exceptionHandler $exceptionHandler} service. * - * @param {string} name Event name to emit. + * @param {string} name Event name to broadcast. * @param {...*} args Optional set of arguments which will be passed onto the event listeners. * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} */ @@ -8370,10 +8416,23 @@ function $SnifferProvider() { * @example - - + +
+ + +
+ it('should display the greeting in the input box', function() { + input('greeting').enter('Hello, E2E Tests'); + // If we click the button it will block the test runner + // element(':button').click(); + });
*/ @@ -8526,7 +8585,7 @@ function $HttpProvider() { * * @description * The `$http` service is a core Angular service that facilitates communication with the remote - * HTTP servers via browser's {@link https://developer.mozilla.org/en/xmlhttprequest + * HTTP servers via the browser's {@link https://developer.mozilla.org/en/xmlhttprequest * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}. * * For unit testing applications that use `$http` service, see @@ -8536,13 +8595,13 @@ function $HttpProvider() { * $resource} service. * * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by - * the $q service. While for simple usage patters this doesn't matter much, for advanced usage, - * it is important to familiarize yourself with these apis and guarantees they provide. + * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage + * it is important to familiarize yourself with these APIs and the guarantees they provide. * * * # General usage * The `$http` service is a function which takes a single argument — a configuration object — - * that is used to generate an http request and returns a {@link ng.$q promise} + * that is used to generate an HTTP request and returns a {@link ng.$q promise} * with two $http specific methods: `success` and `error`. * *
@@ -8557,21 +8616,21 @@ function $HttpProvider() {
      *     });
      * 
* - * Since the returned value of calling the $http function is a Promise object, you can also use + * Since the returned value of calling the $http function is a `promise`, you can also use * the `then` method to register callbacks, and these callbacks will receive a single argument – - * an object representing the response. See the api signature and type info below for more + * an object representing the response. See the API signature and type info below for more * details. * - * A response status code that falls in the [200, 300) range is considered a success status and + * A response status code between 200 and 299 is considered a success status and * will result in the success callback being called. Note that if the response is a redirect, * XMLHttpRequest will transparently follow it, meaning that the error callback will not be * called for such responses. * * # Shortcut methods * - * Since all invocation of the $http service require definition of the http method and url and - * POST and PUT requests require response body/data to be provided as well, shortcut methods - * were created to simplify using the api: + * Since all invocations of the $http service require passing in an HTTP method and URL, and + * POST/PUT requests require request data to be provided as well, shortcut methods + * were created: * *
      *   $http.get('/someUrl').success(successCallback);
@@ -8590,25 +8649,25 @@ function $HttpProvider() {
      *
      * # Setting HTTP Headers
      *
-     * The $http service will automatically add certain http headers to all requests. These defaults
+     * The $http service will automatically add certain HTTP headers to all requests. These defaults
      * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
      * object, which currently contains this default configuration:
      *
      * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
      *   - `Accept: application/json, text/plain, * / *`
      *   - `X-Requested-With: XMLHttpRequest`
-     * - `$httpProvider.defaults.headers.post`: (header defaults for HTTP POST requests)
+     * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
      *   - `Content-Type: application/json`
-     * - `$httpProvider.defaults.headers.put` (header defaults for HTTP PUT requests)
+     * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
      *   - `Content-Type: application/json`
      *
-     * To add or overwrite these defaults, simply add or remove a property from this configuration
+     * To add or overwrite these defaults, simply add or remove a property from these configuration
      * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
-     * with name equal to the lower-cased http method name, e.g.
+     * with the lowercased HTTP method name as the key, e.g.
      * `$httpProvider.defaults.headers.get['My-Header']='value'`.
      *
-     * Additionally, the defaults can be set at runtime via the `$http.defaults` object in a similar
-     * fassion as described above.
+     * Additionally, the defaults can be set at runtime via the `$http.defaults` object in the same
+     * fashion.
      *
      *
      * # Transforming Requests and Responses
@@ -8618,36 +8677,36 @@ function $HttpProvider() {
      *
      * Request transformations:
      *
-     * - if the `data` property of the request config object contains an object, serialize it into
+     * - If the `data` property of the request configuration object contains an object, serialize it into
      *   JSON format.
      *
      * Response transformations:
      *
-     *  - if XSRF prefix is detected, strip it (see Security Considerations section below)
-     *  - if json response is detected, deserialize it using a JSON parser
+     *  - If XSRF prefix is detected, strip it (see Security Considerations section below).
+     *  - If JSON response is detected, deserialize it using a JSON parser.
      *
      * To globally augment or override the default transforms, modify the `$httpProvider.defaults.transformRequest` and
-     * `$httpProvider.defaults.transformResponse` properties of the `$httpProvider`. These properties are by default an
+     * `$httpProvider.defaults.transformResponse` properties. These properties are by default an
      * array of transform functions, which allows you to `push` or `unshift` a new transformation function into the
      * transformation chain. You can also decide to completely override any default transformations by assigning your
      * transformation functions to these properties directly without the array wrapper.
      *
      * Similarly, to locally override the request/response transforms, augment the `transformRequest` and/or
-     * `transformResponse` properties of the config object passed into `$http`.
+     * `transformResponse` properties of the configuration object passed into `$http`.
      *
      *
      * # Caching
      *
-     * To enable caching set the configuration property `cache` to `true`. When the cache is
+     * To enable caching, set the configuration property `cache` to `true`. When the cache is
      * enabled, `$http` stores the response from the server in local cache. Next time the
      * response is served from the cache without sending a request to the server.
      *
      * Note that even if the response is served from cache, delivery of the data is asynchronous in
      * the same way that real requests are.
      *
-     * If there are multiple GET requests for the same url that should be cached using the same
+     * If there are multiple GET requests for the same URL that should be cached using the same
      * cache, but the cache is not populated yet, only one request to the server will be made and
-     * the remaining requests will be fulfilled using the response for the first request.
+     * the remaining requests will be fulfilled using the response from the first request.
      *
      *
      * # Response interceptors
@@ -8699,7 +8758,7 @@ function $HttpProvider() {
      * When designing web applications, consider security threats from:
      *
      * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
-     *   JSON Vulnerability}
+     *   JSON vulnerability}
      * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF}
      *
      * Both server and the client must cooperate in order to eliminate these threats. Angular comes
@@ -8709,8 +8768,8 @@ function $HttpProvider() {
      * ## JSON Vulnerability Protection
      *
      * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
-     * JSON Vulnerability} allows third party web-site to turn your JSON resource URL into
-     * {@link http://en.wikipedia.org/wiki/JSON#JSONP JSONP} request under some conditions. To
+     * JSON vulnerability} allows third party website to turn your JSON resource URL into
+     * {@link http://en.wikipedia.org/wiki/JSONP JSONP} request under some conditions. To
      * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
      * Angular will automatically strip the prefix before processing it as JSON.
      *
@@ -8731,19 +8790,19 @@ function $HttpProvider() {
      * ## Cross Site Request Forgery (XSRF) Protection
      *
      * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which
-     * an unauthorized site can gain your user's private data. Angular provides following mechanism
+     * an unauthorized site can gain your user's private data. Angular provides a mechanism
      * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
      * called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that
      * runs on your domain could read the cookie, your server can be assured that the XHR came from
      * JavaScript running on your domain.
      *
      * To take advantage of this, your server needs to set a token in a JavaScript readable session
-     * cookie called `XSRF-TOKEN` on first HTTP GET request. On subsequent non-GET requests the
+     * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
      * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
-     * that only JavaScript running on your domain could have read the token. The token must be
-     * unique for each user and must be verifiable by the server (to prevent the JavaScript making
+     * that only JavaScript running on your domain could have sent the request. The token must be
+     * unique for each user and must be verifiable by the server (to prevent the JavaScript from making
      * up its own tokens). We recommend that the token is a digest of your site's authentication
-     * cookie with {@link http://en.wikipedia.org/wiki/Rainbow_table salt for added security}.
+     * cookie with a {@link https://en.wikipedia.org/wiki/Salt_(cryptography) salt} for added security.
      *
      *
      * @param {object} config Object describing the request to be made and how it should be
@@ -8921,7 +8980,7 @@ function $HttpProvider() {
      * @methodOf ng.$http
      *
      * @description
-     * Shortcut method to perform `GET` request
+     * Shortcut method to perform `GET` request.
      *
      * @param {string} url Relative or absolute URL specifying the destination of the request
      * @param {Object=} config Optional configuration object
@@ -8934,7 +8993,7 @@ function $HttpProvider() {
      * @methodOf ng.$http
      *
      * @description
-     * Shortcut method to perform `DELETE` request
+     * Shortcut method to perform `DELETE` request.
      *
      * @param {string} url Relative or absolute URL specifying the destination of the request
      * @param {Object=} config Optional configuration object
@@ -8947,7 +9006,7 @@ function $HttpProvider() {
      * @methodOf ng.$http
      *
      * @description
-     * Shortcut method to perform `HEAD` request
+     * Shortcut method to perform `HEAD` request.
      *
      * @param {string} url Relative or absolute URL specifying the destination of the request
      * @param {Object=} config Optional configuration object
@@ -8960,7 +9019,7 @@ function $HttpProvider() {
      * @methodOf ng.$http
      *
      * @description
-     * Shortcut method to perform `JSONP` request
+     * Shortcut method to perform `JSONP` request.
      *
      * @param {string} url Relative or absolute URL specifying the destination of the request.
      *                     Should contain `JSON_CALLBACK` string.
@@ -8975,7 +9034,7 @@ function $HttpProvider() {
      * @methodOf ng.$http
      *
      * @description
-     * Shortcut method to perform `POST` request
+     * Shortcut method to perform `POST` request.
      *
      * @param {string} url Relative or absolute URL specifying the destination of the request
      * @param {*} data Request content
@@ -8989,7 +9048,7 @@ function $HttpProvider() {
      * @methodOf ng.$http
      *
      * @description
-     * Shortcut method to perform `PUT` request
+     * Shortcut method to perform `PUT` request.
      *
      * @param {string} url Relative or absolute URL specifying the destination of the request
      * @param {*} data Request content
@@ -9041,7 +9100,7 @@ function $HttpProvider() {
 
 
     /**
-     * Makes the request
+     * Makes the request.
      *
      * !!! ACCESSES CLOSURE VARS:
      * $httpBackend, $config, $log, $rootScope, defaultCache, $http.pendingRequests
@@ -9388,17 +9447,17 @@ function $TimeoutProvider() {
       * block and delegates any exceptions to
       * {@link ng.$exceptionHandler $exceptionHandler} service.
       *
-      * The return value of registering a timeout function is a promise which will be resolved when
+      * The return value of registering a timeout function is a promise, which will be resolved when
       * the timeout is reached and the timeout function is executed.
       *
-      * To cancel a the timeout request, call `$timeout.cancel(promise)`.
+      * To cancel a timeout request, call `$timeout.cancel(promise)`.
       *
       * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
       * synchronously flush the queue of deferred functions.
       *
-      * @param {function()} fn A function, who's execution should be delayed.
+      * @param {function()} fn A function, whose execution should be delayed.
       * @param {number=} [delay=0] Delay in milliseconds.
-      * @param {boolean=} [invokeApply=true] If set to false skips model dirty checking, otherwise
+      * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
       *   will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
       * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
       *   promise will be resolved with is the return value of the `fn` function.
@@ -9438,7 +9497,7 @@ function $TimeoutProvider() {
       * @methodOf ng.$timeout
       *
       * @description
-      * Cancels a task associated with the `promise`. As a result of this the promise will be
+      * Cancels a task associated with the `promise`. As a result of this, the promise will be
       * resolved with a rejection.
       *
       * @param {Promise=} promise Promise returned by the `$timeout` function.
@@ -9526,7 +9585,7 @@ function $TimeoutProvider() {
  *
  * The general syntax in templates is as follows:
  *
- *         {{ expression | [ filter_name ] }}
+ *         {{ expression [| filter_name[:parameter_value] ... ] }}
  *
  * @param {String} name Name of the filter function to retrieve
  * @return {Function} the filter function
@@ -9611,7 +9670,7 @@ function $FilterProvider($provide) {
        
Any:
Name only
- Phone only
+ Phone only
@@ -9914,6 +9973,7 @@ function padNumber(num, digits, trim) { function dateGetter(name, size, offset, trim) { + offset = offset || 0; return function(date) { var value = date['get' + name](); if (offset > 0 || value > -offset) @@ -10024,7 +10084,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+ * (e.g. `"h o''clock"`). * * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or - * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and it's + * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is * specified in the string input, the time is considered to be in the local timezone. * @param {string=} format Formatting rules (see Description). If not specified, @@ -11142,8 +11202,8 @@ var inputType = { * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less then `min`. - * @param {string=} max Sets the `max` validation error key if the value entered is greater then `min`. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of @@ -11455,6 +11515,15 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { } else { var timeout; + var deferListener = function() { + if (!timeout) { + timeout = $browser.defer(function() { + listener(); + timeout = null; + }); + } + }; + element.bind('keydown', function(event) { var key = event.keyCode; @@ -11462,16 +11531,16 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { // command modifiers arrows if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; - if (!timeout) { - timeout = $browser.defer(function() { - listener(); - timeout = null; - }); - } + deferListener(); }); // if user paste into input using mouse, we need "change" event to catch it element.bind('change', listener); + + // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it + if ($sniffer.hasEvent('paste')) { + element.bind('paste cut', deferListener); + } } @@ -11770,7 +11839,7 @@ function checkboxInputType(scope, element, attr, ctrl) { myForm.userName.$valid = {{myForm.userName.$valid}}
myForm.userName.$error = {{myForm.userName.$error}}
myForm.lastName.$valid = {{myForm.lastName.$valid}}
- myForm.userName.$error = {{myForm.lastName.$error}}
+ myForm.lastName.$error = {{myForm.lastName.$error}}
myForm.$valid = {{myForm.$valid}}
myForm.$error.required = {{!!myForm.$error.required}}
myForm.$error.minlength = {{!!myForm.$error.minlength}}
@@ -12033,7 +12102,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * For example {@link ng.directive:input input} or * {@link ng.directive:select select} directives call it. * - * It internally calls all `formatters` and if resulted value is valid, updates the model and + * It internally calls all `parsers` and if resulted value is valid, updates the model and * calls all registered change listeners. * * @param {string} value Value from the view. @@ -12339,7 +12408,7 @@ var ngValueDirective = function() { * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like * `{{ expression }}` which is similar but less verbose. * - * Once scenario in which the use of `ngBind` is prefered over `{{ expression }}` binding is when + * One scenario in which the use of `ngBind` is preferred over `{{ expression }}` binding is when * it's desirable to put bindings into template that is momentarily displayed by the browser in its * raw state before Angular compiles it. Since `ngBind` is an element attribute, it makes the * bindings invisible to the user while the page is loading. @@ -12480,9 +12549,9 @@ function classDirective(name, selector) { if (name !== 'ngClass') { scope.$watch('$index', function($index, old$index) { - var mod = $index % 2; - if (mod !== old$index % 2) { - if (mod == selector) { + var mod = $index & 1; + if (mod !== old$index & 1) { + if (mod === selector) { addClass(scope.$eval(attr[name])); } else { removeClass(scope.$eval(attr[name])); @@ -12494,12 +12563,12 @@ function classDirective(name, selector) { function ngClassWatchAction(newVal) { if (selector === true || scope.$index % 2 === selector) { - if (oldVal && (newVal !== oldVal)) { + if (oldVal && !equals(newVal,oldVal)) { removeClass(oldVal); } addClass(newVal); } - oldVal = newVal; + oldVal = copy(newVal); } @@ -12625,7 +12694,7 @@ var ngClassOddDirective = classDirective('Odd', 0); * @name ng.directive:ngClassEven * * @description - * The `ngClassOdd` and `ngClassEven` works exactly as + * The `ngClassOdd` and `ngClassEven` directives work exactly as * {@link ng.directive:ngClass ngClass}, except it works in * conjunction with `ngRepeat` and takes affect only on odd (even) rows. * @@ -12742,8 +12811,7 @@ var ngCloakDirective = ngDirective({ * * Controller — The `ngController` directive specifies a Controller class; the class has * methods that typically express the business logic behind the application. * - * Note that an alternative way to define controllers is via the `{@link ng.$route}` - * service. + * Note that an alternative way to define controllers is via the {@link ng.$route $route} service. * * @element ANY * @scope @@ -12834,16 +12902,32 @@ var ngControllerDirective = [function() { * @name ng.directive:ngCsp * @priority 1000 * + * @element html * @description * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. - * This directive should be used on the root element of the application (typically the `` - * element or other element with the {@link ng.directive:ngApp ngApp} - * directive). - * - * If enabled the performance of template expression evaluator will suffer slightly, so don't enable - * this mode unless you need it. - * - * @element html + * + * This is necessary when developing things like Google Chrome Extensions. + * + * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). + * For us to be compatible, we just need to implement the "getterFn" in $parse without violating + * any of these restrictions. + * + * AngularJS uses `Function(string)` generated functions as a speed optimization. By applying `ngCsp` + * it is be possible to opt into the CSP compatible mode. When this mode is on AngularJS will + * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will + * be raised. + * + * In order to use this feature put `ngCsp` directive on the root element of the application. + * + * @example + * This example shows how to apply the `ngCsp` directive to the `html` tag. +
+     
+     
+     ...
+     ...
+     
+   
*/ var ngCspDirective = ['$sniffer', function($sniffer) { @@ -13468,7 +13552,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp if (!isNaN(value)) { //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, //check it against pluralization rules in $locale service - if (!whens[value]) value = $locale.pluralCat(value - offset); + if (!(value in whens)) value = $locale.pluralCat(value - offset); return whensExpFns[value](scope, element, true); } else { return ''; @@ -14201,7 +14285,8 @@ var scriptDirective = ['$templateCache', function($templateCache) { * `select` model to be bound to a non-string value. This is because an option element can currently * be bound to string values only. * - * @param {string} name assignable expression to data-bind to. + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. * @param {string=} required The control is considered valid only if value is entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of @@ -14568,10 +14653,6 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) { if (multiple) { selectedSet = new HashMap(modelValue); - } else if (modelValue === null || nullOption) { - // if we are not multiselect, and we are null then we have to add the nullOption - optionGroups[''].push({selected:modelValue === null, id:'', label:''}); - selectedSet = true; } // We now build up the list of options we need (we merge later) @@ -14596,9 +14677,14 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) { selected: selected // determine if we should be selected }); } - if (!multiple && !selectedSet) { - // nothing was selected, we have to insert the undefined item - optionGroups[''].unshift({id:'?', label:'', selected:true}); + if (!multiple) { + if (nullOption || modelValue === null) { + // insert null option if we have a placeholder, or the model is null + optionGroups[''].unshift({id:'', label:'', selected:!selectedSet}); + } else if (!selectedSet) { + // option could not be found, we have to insert the undefined item + optionGroups[''].unshift({id:'?', label:'', selected:true}); + } } // Now we need to update the list of DOM nodes to match the optionGroups we computed above @@ -14642,7 +14728,8 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) { if (existingOption.id !== option.id) { lastElement.val(existingOption.id = option.id); } - if (existingOption.element.selected !== option.selected) { + // lastElement.prop('selected') provided by jQuery has side-effects + if (lastElement[0].selected !== option.selected) { lastElement.prop('selected', (existingOption.selected = option.selected)); } } else { diff --git a/sdk-html/src/main/resources/META-INF/resources/sdk/js/app.js b/sdk-html/src/main/resources/META-INF/resources/sdk/js/app.js index fdbed27bb8..50d7f5b436 100644 --- a/sdk-html/src/main/resources/META-INF/resources/sdk/js/app.js +++ b/sdk-html/src/main/resources/META-INF/resources/sdk/js/app.js @@ -1,5 +1,25 @@ var keycloakModule = angular.module('keycloak', [ 'ngResource' ]); -keycloakModule.controller('LoginCtrl', function($scope, $resource) { - $scope.config = $resource("example-config.json").get(); +keycloakModule.factory('messages', function() { + var messages = {}; + messages['user_registered'] = "User registered"; + messages['invalid_provider'] = "Social provider not found"; + messages['provider_error'] = "Failed to login with social provider"; + return messages }); + +keycloakModule.factory('queryParams', function($location) { + var queryParams = {}; + var locationParameters = window.location.search.substring(1).split("&"); + for ( var i = 0; i < locationParameters.length; i++) { + var param = locationParameters[i].split("="); + queryParams[decodeURIComponent(param[0])] = decodeURIComponent(param[1]); + } + return queryParams; +}); + +keycloakModule.controller('GlobalCtrl', function($scope, $resource, queryParams, messages) { + $scope.config = $resource("/keycloak-server/sdk/api/" + queryParams.application + "/login/config").get(); + $scope.info = queryParams.info && (messages[queryParams.info] || queryParams.info); + $scope.error = queryParams.error && (messages[queryParams.error] || queryParams.error); +}); \ No newline at end of file diff --git a/sdk-html/src/main/resources/META-INF/resources/sdk/keycloak.js b/sdk-html/src/main/resources/META-INF/resources/sdk/keycloak.js deleted file mode 100644 index 55f8d62971..0000000000 --- a/sdk-html/src/main/resources/META-INF/resources/sdk/keycloak.js +++ /dev/null @@ -1,248 +0,0 @@ -window.Keycloak = (function () { - var queryParameters = function (name) { - var parameters = window.location.search.substring(1).split("&"); - for (var i = 0; i < parameters.length; i++) { - var param = parameters[i].split("="); - if (decodeURIComponent(param[0]) == name) { - return decodeURIComponent(param[1]); - } - } - }; - - var messageError = queryParameters("error"); - var messageInfo = queryParameters("info"); - - var keycloak = { - appKey: queryParameters("app"), - token: queryParameters("token"), - baseUrl: "/ejs-identity", - get loginUrl() { - return this.baseUrl + "/api/login/" + this.appKey; - }, - get registerUrl() { - return this.baseUrl + "/api/register/" + this.appKey; - }, - get userInfoUrl() { - return this.baseUrl + "/api/auth/userinfo?appKey=" + this.appKey; - } - }; - - keycloak.getConfig = function (success, error) { - var req = new XMLHttpRequest(); - req.open("GET", keycloak.loginUrl); - req.setRequestHeader("Accept", "application/json"); - req.onreadystatechange = function () { - if (req.readyState == 4) { - if (req.status == 200) { - var config = JSON.parse(req.responseText); - if (success) { - success(config); - } - } else { - if (error) { - error(req.status); - } - } - } - }; - req.send(); - }; - - keycloak.getUser = function (success, error) { - var req = new XMLHttpRequest(); - req.open("GET", keycloak.userInfoUrl + "&token=" + keycloak.token); - req.setRequestHeader("Accept", "application/json"); - req.onreadystatechange = function () { - if (req.readyState == 4) { - if (req.status == 200) { - var user = JSON.parse(req.responseText); - if (success) { - success(user); - } - } else { - if (error) { - error(req.status); - } - } - } - }; - req.send(); - }; - - var createLogin = function(containerId) { - var login = document.createElement("div"); - login.setAttribute("class", "keycloak-login"); - - var container = document.getElementById(containerId); - container.setAttribute("class", "keycloak-login-container"); - container.innerHTML = null; - container.appendChild(login); - - return login; - }; - - var createHeader = function(text) { - var div = document.createElement("div"); - div.setAttribute("class", "keycloak-login-header"); - - var h = document.createElement("h1"); - h.textContent = text; - div.appendChild(h); - - return div; - }; - - var createInput = function(group, name, labelText, type) { - var div = document.createElement("div"); - div.setAttribute("class", "keycloak-login-" + group + "-" + name); - - var label = document.createElement("label"); - label.setAttribute("for", name); - label.textContent = labelText; - div.appendChild(label); - - var input = document.createElement("input"); - input.setAttribute("name", name); - if (type) { - input.setAttribute("type", type); - } else { - input.setAttribute("type", "text"); - } - div.appendChild(input); - - return div; - }; - - var createMessage = function(message, type) { - var div = document.createElement("div"); - div.setAttribute("class", "keycloak-login-message-" + type); - - if (message == "login_failed") { - div.textContent = "Failed to login"; - } else if (message == "register_failed") { - div.textContent = "Failed to register user"; - } else if (message = "register_created") { - div.textContent = "Created user"; - } else { - div.textContent = message; - } - - return div; - }; - - keycloak.renderLoginForm = function (containerId) { - var success = function (config) { - var login = createLogin(containerId); - - login.appendChild(createHeader("Login to " + config.name)); - - if (messageError) { - login.appendChild(createMessage(messageError, "warn")); - } - - if (messageInfo) { - login.appendChild(createMessage(messageInfo, "info")); - } - - var standardLogin = document.createElement("div"); - standardLogin.setAttribute("class", "keycloak-login-standard"); - login.appendChild(standardLogin); - - var form = document.createElement("form"); - form.setAttribute("action", keycloak.loginUrl); - form.setAttribute("method", "post"); - standardLogin.appendChild(form); - - form.appendChild(createInput("standard", "username", "Username")); - form.appendChild(createInput("standard", "password", "Password", "password")); - - var buttonsDiv = document.createElement("div"); - buttonsDiv.setAttribute("class", "keycloak-login-buttons"); - form.appendChild(buttonsDiv); - - var submitButton = document.createElement("button"); - submitButton.setAttribute("type", "submit"); - submitButton.textContent = "Login"; - buttonsDiv.appendChild(submitButton); - - var registerButton = document.createElement("button"); - registerButton.setAttribute("type", "button"); - registerButton.setAttribute("onclick", "location.href='" + keycloak.registerUrl + "'"); - registerButton.textContent = "Register"; - buttonsDiv.appendChild(registerButton); - - var cancelButton = document.createElement("button"); - cancelButton.setAttribute("type", "button"); - cancelButton.setAttribute("onclick", "location.href='" + config.callbackUrl + "'"); - cancelButton.textContent = "Cancel"; - buttonsDiv.appendChild(cancelButton); - - var socialLogin = document.createElement("div"); - socialLogin.setAttribute("class", "keycloak-login-social"); - login.appendChild(socialLogin); - - for (var i = 0; i < config.providerConfigs.length; i++) { - var provider = config.providerConfigs[i]; - - var providerLink = document.createElement("a"); - providerLink.setAttribute("href", provider.loginUri); - socialLogin.appendChild(providerLink); - - var providerImage = document.createElement("img"); - providerImage.setAttribute("src", provider.icon); - providerLink.appendChild(providerImage); - } - }; - - var error = function() { - var login = createLogin(containerId); - - login.appendChild(createHeader("Invalid")); - login.appendChild(createMessage("Invalid application key", "warn")); - }; - - keycloak.getConfig(success, error); - }; - - keycloak.renderRegistrationForm = function (containerId) { - var login = createLogin(containerId); - - var success = function (config) { - login.appendChild(createHeader("Register with " + config.name)); - - if (messageError) { - login.appendChild(createMessage(messageError, "warn")); - } - - var form = document.createElement("form"); - form.setAttribute("action", keycloak.registerUrl); - form.setAttribute("method", "post"); - login.appendChild(form); - - form.appendChild(createInput("register", "username", "Username")); - form.appendChild(createInput("register", "email", "Email", "email")); - form.appendChild(createInput("register", "firstName", "First name")); - form.appendChild(createInput("register", "lastName", "Last name")); - form.appendChild(createInput("register", "password", "Password", "password")); - - var buttonsDiv = document.createElement("div"); - buttonsDiv.setAttribute("class", "keycloak-login-buttons"); - form.appendChild(buttonsDiv); - - var submitButton = document.createElement("button"); - submitButton.setAttribute("type", "submit"); - submitButton.textContent = "Register"; - buttonsDiv.appendChild(submitButton); - - var cancelButton = document.createElement("button"); - cancelButton.setAttribute("type", "button"); - cancelButton.setAttribute("onclick", "location.href='" + keycloak.loginUrl + "'"); - cancelButton.textContent = "Cancel"; - buttonsDiv.appendChild(cancelButton); - }; - - keycloak.getConfig(success); - }; - - return keycloak; -}()); \ No newline at end of file diff --git a/sdk-html/src/main/resources/META-INF/resources/sdk/login.html b/sdk-html/src/main/resources/META-INF/resources/sdk/login.html index 723634dd5c..43a9b3a6b5 100644 --- a/sdk-html/src/main/resources/META-INF/resources/sdk/login.html +++ b/sdk-html/src/main/resources/META-INF/resources/sdk/login.html @@ -11,10 +11,13 @@ -
+

Login to {{config.name}}

- + +
{{info}}
+
{{error}}
+
@@ -22,7 +25,11 @@ -
+
+ + Register + Cancel +
@@ -30,7 +37,7 @@

Login with

- +
diff --git a/sdk-html/src/main/resources/META-INF/resources/sdk/register.html b/sdk-html/src/main/resources/META-INF/resources/sdk/register.html index b19126383f..cc3aebd9b2 100644 --- a/sdk-html/src/main/resources/META-INF/resources/sdk/register.html +++ b/sdk-html/src/main/resources/META-INF/resources/sdk/register.html @@ -11,9 +11,12 @@ -
+

Register with {{config.name}}

+ +
{{info}}
+
{{error}}
@@ -34,7 +37,10 @@ -
+
+ + Cancel +
NamePhone